Hamiltonians and Time Evolution

MIMIQ provides tools to define quantum Hamiltonians and simulate their time evolution using Trotterization methods. This page explains how to construct Hamiltonians, compute their expectation values, and apply Lie-Trotter, Suzuki-Trotter, and Yoshida decompositions.

This workflow allows you to:

Build realistic Hamiltonians from Pauli terms

Simulate time evolution efficiently using Trotter expansions

Measure physical observables like energy

Hamiltonian

In quantum computing, Hamiltonians play a central role in algorithms such as the Variational Quantum Eigensolver (VQE), Quantum Phase Estimation, and the Quantum Approximate Optimization Algorithm (QAOA). They are used to encode the energy landscape of a physical system, generate dynamics, or define cost functions for optimization.

A typical Hamiltonian is expressed as a sum of weighted Pauli strings:

\[H = \sum_j c_j \cdot P_j\]

Each term consists of a real coefficient \(c_j\) and a Pauli string \(P_j\), such as X, ZZ, or XYZ, which acts on a specific subset of qubits.

In quantum circuit frameworks, these Hamiltonians are often represented programmatically by associating each Pauli string with a set of target qubit indices and a coefficient, allowing users to construct and simulate physical models or optimization problems.

Simulating the Ising Model

A fundamental use case for Hamiltonians in quantum computing is to estimate physical quantities like the ground state energy of a system. This is at the heart of quantum algorithms such as the Variational Quantum Eigensolver (VQE), quantum simulation of materials, and quantum optimization.

Here, we demonstrate how to use MIMIQ to:

  • Build the Hamiltonian for the 1D transverse-field Ising model

  • Apply Trotterized time evolution (first and second order)

  • Measure the energy (expectation value of the Hamiltonian)

This provides a concrete template for simulating many-body quantum systems.

Ising Model

The 1D transverse-field Ising model is defined by the Hamiltonian:

\[H = -J \sum_{j=0}^{N-1} Z_j Z_{j+1} - h \sum_{j=0}^{N} X_j\]

This models a chain of spins with:

  • nearest-neighbor interaction (ZZ terms),

  • and a transverse magnetic field (X terms).

It’s widely used in condensed matter physics and quantum algorithm benchmarks.

Building the Hamiltonian

To construct this model in MIMIQ you can use Hamiltonian class to easily build your hamiltonian:

>>> N = 4  # number of qubits (spins)
>>> J = 1.0  # interaction strength
>>> h = 0.5  # field strength

>>> hamiltonian = Hamiltonian()

>>> for j in range(N - 1):
...     _ = hamiltonian.push(-J, PauliString("ZZ"), j, j + 1)


>>> for j in range(N):
...     _ = hamiltonian.push(-h, PauliString("X"), j)

>>> print(hamiltonian)
4-qubit Hamiltonian with 7 terms:
├── -1.0 * ZZ @ q[0,1]
├── -1.0 * ZZ @ q[1,2]
├── -1.0 * ZZ @ q[2,3]
├── -0.5 * X @ q[0]
├── -0.5 * X @ q[1]
├── -0.5 * X @ q[2]
└── -0.5 * X @ q[3]

Simulating Time Evolution

Suppose we want to apply \(e^{-iHt}\) to a quantum state. This is useful for preparing ground states via imaginary-time evolution (approximate cooling), or evolving an initial state in real time.

Because H has non-commuting terms, we use a Trotter approximation.

First-order Trotterization (Lie) (push_lietrotter())

>>> c = Circuit()
>>> c.push_lietrotter(hamiltonian, tuple(range(N)), t = 0.1, steps = 1)
4-qubit circuit with 1 instructions:
└── trotter(0.1) @ q[0,1,2,3]


>>> c.decompose()
4-qubit circuit with 7 instructions:
├── RZZ(-0.2) @ q[0,1]
├── RZZ(-0.2) @ q[1,2]
├── RZZ(-0.2) @ q[2,3]
├── RX(-0.1) @ q[0]
├── RX(-0.1) @ q[1]
├── RX(-0.1) @ q[2]
└── RX(-0.1) @ q[3]

Second-order Trotterization (Suzuki) (push_suzukitrotter())

>>> c = Circuit()
>>> c.push_suzukitrotter(hamiltonian, tuple(range(N)), t = 0.1, steps = 1, order = 2)
4-qubit circuit with 1 instructions:
└── suzukitrotter_2(0.1) @ q[0,1,2,3]


>>> c.decompose()
4-qubit circuit with 14 instructions:
├── RZZ(-0.1) @ q[0,1]
├── RZZ(-0.1) @ q[1,2]
├── RZZ(-0.1) @ q[2,3]
├── RX(-0.05) @ q[0]
├── RX(-0.05) @ q[1]
├── RX(-0.05) @ q[2]
├── RX(-0.05) @ q[3]
├── RX(-0.05) @ q[3]
├── RX(-0.05) @ q[2]
├── RX(-0.05) @ q[1]
├── RX(-0.05) @ q[0]
├── RZZ(-0.1) @ q[2,3]
├── RZZ(-0.1) @ q[1,2]
└── RZZ(-0.1) @ q[0,1]

Measuring the Energy

Once the circuit has prepared the desired quantum state such as by applying time evolution with Trotterization we can measure the energy by evaluating the expectation value (push_expval()) of the Hamiltonian.

>>> c.push_expval(hamiltonian, *range(N))
4-qubit, 7-zvar circuit with 16 instructions:
├── suzukitrotter_2(0.1) @ q[0,1,2,3]
├── ⟨ZZ⟩ @ q[0,1], z[0]
├── z[0] *= -1.0
├── ⟨ZZ⟩ @ q[1,2], z[1]
├── z[1] *= -1.0
├── ⟨ZZ⟩ @ q[2,3], z[2]
├── z[2] *= -1.0
├── ⟨X⟩ @ q[0], z[3]
├── z[3] *= -0.5
├── ⟨X⟩ @ q[1], z[4]
├── z[4] *= -0.5
├── ⟨X⟩ @ q[2], z[5]
├── z[5] *= -0.5
├── ⟨X⟩ @ q[3], z[6]
├── z[6] *= -0.5
└── z[0] += 0.0 + z[1] + z[2] + z[3] + z[4] + z[5] + z[6]

The result is stored in the Z-register — the first index contains the total expectation value (energy). You can access it from the simulation result.

Reference

class mimiqcircuits.Hamiltonian(terms=None)[source]

Bases: object

A quantum Hamiltonian composed of multiple Pauli terms.

This class models a Hamiltonian of the form:

\[H = \sum_j c_j \cdot P_j\]

where each term is a HamiltonianTerm consisting of a coefficient and a Pauli string.

- `push(...)`

Add a term by specifying its components.

- `add_terms(...)`

Add a HamiltonianTerm object.

- `num_qubits()`

Total number of qubits this Hamiltonian acts on.

- `saveproto(...)`

Save to protobuf format.

- `loadproto(...)`

Load from protobuf.

Examples

>>> from mimiqcircuits import *
>>> h = Hamiltonian()
>>> h.push(1.0, PauliString("XX"), 0, 1)
2-qubit Hamiltonian with 1 terms:
└── 1.0 * XX @ q[0,1]
>>> h.push(1.0, PauliString("YY"), 0, 1)
2-qubit Hamiltonian with 2 terms:
├── 1.0 * XX @ q[0,1]
└── 1.0 * YY @ q[0,1]
>>> print(h)
2-qubit Hamiltonian with 2 terms:
├── 1.0 * XX @ q[0,1]
└── 1.0 * YY @ q[0,1]
__init__(terms=None)[source]
add_terms(term)[source]
push(coefficient, pauli, *qubits)[source]
num_terms()[source]
num_qubits()[source]
saveproto(file)[source]
static loadproto(file)[source]
matrix()[source]

Return the symbolic matrix representation of the Hamiltonian using symengine.

evaluate(d)[source]

Evaluate the symbolic coefficients of each term using the substitution dictionary d.

Parameters:

d (dict) – Dictionary mapping symbols (e.g., {‘theta’: 0.5}) to values.

Returns:

A new Hamiltonian with evaluated (numerical or partially symbolic) coefficients.

Return type:

Hamiltonian

mimiqcircuits.Circuit.push_lietrotter(self, h, qubits, t, steps)

Apply a Lie-Trotter expansion of the Hamiltonian h to the circuit self for the qubits qubits over total time t with steps steps.

The Lie-Trotter expansion is a first-order approximation of the time evolution operator for a Hamiltonian composed of non-commuting terms. It decomposes the exponential of a sum of operators into a product of exponentials:

\[e^{-i H t} \approx \left[ \prod_{j=1}^m e^{-i c_j P_j \Delta t} \right]^n\]
where:
  • \(H = \sum_{j=1}^m c_j P_j\) is the Hamiltonian

  • \(P_j\) are Pauli strings

  • \(\Delta t = t / n\) is the time step size

  • \(n\) is the number of steps

This method is particularly useful for simulating quantum systems and time-evolving quantum states in quantum algorithms such as VQE or QAOA.

See also

push_suzukitrotter(), GateDecl

Examples

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> h = Hamiltonian()
>>> h.push(1.0, PauliString("ZZ"), 0, 1)
2-qubit Hamiltonian with 1 terms:
└── 1.0 * ZZ @ q[0,1]
>>> c.push_lietrotter(h, (0, 1), t=1.0, steps=3)
2-qubit circuit with 3 instructions:
├── trotter(0.3333333333333333) @ q[0:1]
├── trotter(0.3333333333333333) @ q[0:1]
└── trotter(0.3333333333333333) @ q[0:1]
mimiqcircuits.Circuit.push_suzukitrotter(self, h, qubits, t, steps, order=2)

Apply Suzuki-Trotter expansion of the Hamiltonian h to the circuit self for the qubits qubits over time t with steps steps.

The Suzuki-Trotter expansion approximates the time evolution operator of a quantum Hamiltonian using a sequence of exponentiated subterms. This is particularly useful for simulating quantum systems where the Hamiltonian is composed of non-commuting parts.

The expansion performed is a 2k-th order expansion according to the Suzuki construction.

The second-order expansion is given by:

\[e^{-i H t} \approx \left[ \prod_{j=1}^{m} e^{-i \frac{\Delta t}{2} H_j} \prod_{j=m}^{1} e^{-i \frac{\Delta t}{2} H_j} \right]^n\]

where the Hamiltonian \(H\) is a sum of terms:

\[H = \sum_{j=1}^{m} H_j\]

and the Trotter step size is:

\[\Delta t = \frac{t}{n}\]

Higher-order expansions follow the Suzuki recursion relation:

\[S_{2k}(\lambda) = [S_{2k-2}(p_k \lambda)]^2 \cdot S_{2k-2}((1 - 4p_k)\lambda) \cdot [S_{2k-2}(p_k \lambda)]^2\]

with:

\[p_k = \left(4 - 4^{1/(2k - 1)}\right)^{-1}\]

See also

push_lietrotter(), GateDecl

Examples

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> h = Hamiltonian()
>>> h.push(1.0, PauliString("XX"), 0, 1)
2-qubit Hamiltonian with 1 terms:
└── 1.0 * XX @ q[0,1]
>>> c.push_suzukitrotter(h, (0, 1), t=1.0, steps=5, order=2)
2-qubit circuit with 5 instructions:
├── suzukitrotter_2(0.2) @ q[0:1]
├── suzukitrotter_2(0.2) @ q[0:1]
├── suzukitrotter_2(0.2) @ q[0:1]
├── suzukitrotter_2(0.2) @ q[0:1]
└── suzukitrotter_2(0.2) @ q[0:1]
mimiqcircuits.Circuit.push_expval(self, hamiltonian, *qubits, firstzvar=None)

Push an expectation value estimation circuit for a given Hamiltonian.

This operation measures the expectation value of a Hamiltonian and stores the result in a Z-register, combining individual Pauli term evaluations.

For each term \(c_j P_j\), the circuit performs:

\[\langle \psi | c_j P_j | \psi \rangle\]

and sums the contributions in a new z-register.

Parameters:
  • hamiltonian (Hamiltonian) – The Hamiltonian to evaluate.

  • qubits (int) – The qubit mapping to use.

  • firstzvar (int, optional) – Index of the first Z-register to use.

Returns:

The modified circuit.

Return type:

Circuit

Examples

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> h = Hamiltonian()
>>> h.push(1.0, PauliString("ZZ"), 0, 1)
2-qubit Hamiltonian with 1 terms:
└── 1.0 * ZZ @ q[0,1]
>>> c.push_expval(h, 1, 2)
3-qubit, 1-zvar circuit with 3 instructions:
├── ⟨ZZ⟩ @ q[1:2], z[0]
├── z[0] *= 1.0
└── z[0] += 0.0

See also

ExpectationValue, Multiply, Add