Orbital rotations

The orbital rotation is a fundamental operation in the simulation of fermions. This page discusses orbital rotations and their properties.

Definition of orbital rotation

Description via a unitary matrix

An orbital rotation is described by an N×N unitary matrix U (here N is the number spatial orbitals), and we denote the corresponding operator as U. This operator has the following action on the fermionic creation operators {aσ,i}:

Uaσ,iU=jUjiaσ,j.

That is, aσ,i is mapped to a new operator bσ,i where bσ,i is a linear combination of the operators {aσ,i} with coefficients given by the i-th column of U. The fact that U is unitary can be used, together with the fermionic anticommutation relations, to show that the {bσ,i} also satisfy the fermionic anticommutation relations. Thus, the bσ,i are creation operators in a rotated basis of orbitals.

Explicit expression

It is a theorem that U can be written explicitly as

U=σexp[ijlog(U)ijaσ,iaσ,j].

Sometimes, it is useful to specify the orbital rotation via the antihermitian matrix K=log(U) rather than U itself. However, ffsim generally represents orbital rotations using the unitary matrix U.

Homomorphism property

The mapping UU satisfies the important homomorphism property:

UU,U1U2U1U2

for any unitary matrices U, U1, and U2.

Independent orbital rotations for spin-up and spin-down

The definitions given above assume that the same orbital rotation applies to both the spin-up and spin-down orbitals. However, it is possible for each spin sector to have an independent orbital rotation, in which case a pair of unitary matrices (Uα,Uβ) needs to be specified. Letting Kσ=log(Uσ), the definitions above then become

Uaσ,iU=jUjiσaσ,j,U=σexp[ijlog(Uσ)ijaσ,iaσ,j].

Application to time evolution by a quadratic Hamiltonian

A quadratic Hamiltonian is an operator of the form (here we restrict the definition to Hamiltonians with particle number and spin Z symmetry)

M=σ,ijMijaσ,iaσ,j

where M is a Hermitian matrix. Time evolution by this Hamiltonian is given by the operator eiMt, where t is the evolution time. Orbital rotations can be used to implement time evolution by a quadratic Hamiltonian using two different methods.

Method 1: Diagonalize the quadratic Hamiltonian

A quadratic Hamiltonian can always be rewritten as

M=U(σ,iλinσ,i)U

where the {λi} are real numbers called orbital energies, U is an orbital rotation, and nσ,i=aσ,iaσ,i is the occupation number operator. The {λi} and the unitary matrix U describing the orbital rotation are obtained from an eigendecomposition of M:

Mij=kλkUikUjk.

Time evolution by M can be implemented with the following steps:

  • Compute the orbital energies {λi} and the orbital rotation matrix U by performing an eigendecomposition of M.

  • Perform the orbital rotation U, which corresponds to the matrix U.

  • Perform time evolution by the operator σ,iλinσ,i.

  • Perform the orbital rotation U, which corresponds to the matrix U.

Method 2: Implement directly as an orbital rotation

Reviewing the definition of an orbital rotation, we can see that the time evolution operator eiMt is itself an orbital rotation defined by K=iMt, or U=eiMt. Thus, the time evolution operator can be implemented directly as an orbital rotation.

The following code cell demonstrates the equivalence of the two methods by applying them to a randomly generated state vector and checking that the result is the same in both cases.

[1]:
import numpy as np
import scipy.linalg

import ffsim

rng = np.random.default_rng(12345)
norb = 6
nelec = (3, 3)

# Generate a random Hermitian matrix M to represent the quadratic Hamiltonian
mat = ffsim.random.random_hermitian(norb, seed=rng)

# Generate a random state vector and set the evolution time
vec = ffsim.random.random_state_vector(ffsim.dim(norb, nelec), seed=rng)
time = 1.0

# Apply time evolution via diagonalization
eigs, orbital_rotation = scipy.linalg.eigh(mat)
result1 = ffsim.apply_num_op_sum_evolution(
    vec, eigs, time=time, norb=norb, nelec=nelec, orbital_rotation=orbital_rotation
)

# Apply time evolution directly as an orbital rotation
evolution_mat = scipy.linalg.expm(-1j * time * mat)
result2 = ffsim.apply_orbital_rotation(vec, evolution_mat, norb=norb, nelec=nelec)

# Check that the results match
np.testing.assert_allclose(result1, result2)

Quantum circuit implementation

An orbital rotation can be implemented as a quantum circuit by decomposing it into Givens rotations. Under the Jordan-Wigner transformation, the circuit can be implemented as a dense brickwork pattern of two-qubit rotations, followed by a single layer of single-qubit phase gates. The following code cell constructs and displays a Qiskit circuit for implementing an orbital rotation. Notice that the circuit consists of two independent sub-circuits acting on each spin sector.

[2]:
from qiskit.circuit import QuantumCircuit, QuantumRegister

norb = 6
nelec = (3, 3)

# Generate a random orbital rotation
orbital_rotation = ffsim.random.random_unitary(norb, seed=rng)

# Create a quantum circuit that implements this orbital rotation
qubits = QuantumRegister(2 * norb, name="q")
circuit = QuantumCircuit(qubits)
circuit.append(ffsim.qiskit.OrbitalRotationJW(norb, orbital_rotation), qubits)

# Decompose the orbital rotation into basic gates before drawing
circuit.decompose().draw("mpl", scale=0.6)
[2]:
../_images/explanations_orbital-rotation_3_0.png

When an orbital rotation is applied to a single electronic configuration (in the qubit picture, a computational basis state), the quantum circuit implementation can be optimized to use fewer gates, resulting in a “diamond” pattern of gates rather than a dense “brickwork” pattern. For additional information, see this paper.

The following code cell constructs and displays an example of the optimized circuit.

[3]:
# Create a quantum circuit that applies an orbital rotation to the Hartree-Fock state
circuit = QuantumCircuit(qubits)
circuit.append(ffsim.qiskit.PrepareHartreeFockJW(norb, nelec), qubits)
circuit.append(ffsim.qiskit.OrbitalRotationJW(norb, orbital_rotation), qubits)

# Optimize the orbital rotation circuit
circuit = ffsim.qiskit.PRE_INIT.run(circuit)

# Decompose the orbital rotation into basic gates before drawing
circuit.decompose().draw("mpl", scale=0.6)
[3]:
../_images/explanations_orbital-rotation_5_0.png