Skip to content

Exponentiation and Hamiltonian Simulation

View on GitHub Experiment in the IDE

This tutorial demonstrates how to use the Classiq platform exponentiation function to solve Hamiltonian simulation problems, thereby demonstrating the strength of the Classiq exponentiation module.

1. Chemical Simulation

Chemical simulation is one of the most exciting applications for quantum computers. When precise simulations of electron-electron interactions are necessary, it is sometimes possible to use a classical computer, but classical computers struggle to simulate more complex molecular interactions. It is best to simulate these particle interactions at the quantum level, and an excellent way to do this is with a quantum computer.

The ability to accurately simulate molecular interactions will have extensive applications. When used for drug discovery, it will allow for the rapid development of vaccines and new cures for diseases. In materials research, we can hope to discover materials with higher strength-to-weight ratios and environmentally friendly building materials.

2. The H2O Hamiltonian Simulation Problem

Generate a circuit that approximates the unitary \(e^{-iH}\) where \(H\) is the qubit Hamiltonian of a H2O (water) molecule. The H2O Hamiltonian is composed of 551 Pauli strings on twelve qubits.

from openfermion.chem import MolecularData
from openfermionpyscf import run_pyscf

from classiq import *
from classiq.applications.chemistry.mapping import FermionToQubitMapper
from classiq.applications.chemistry.op_utils import qubit_op_to_pauli_terms
from classiq.applications.chemistry.problems import FermionHamiltonianProblem

molecule_H2O_geometry = [
    ("O", (0.0, 0.0, 0.0)),
    ("H", (0, 0.586, 0.757)),
    ("H", (0, 0.586, -0.757)),
]
molecule = MolecularData(molecule_H2O_geometry, "sto-3g", 1, 0)

molecule = run_pyscf(molecule)

problem = FermionHamiltonianProblem.from_molecule(molecule, first_active_index=1)
mapper = FermionToQubitMapper()
hamiltonian = qubit_op_to_pauli_terms(mapper.map(problem.fermion_hamiltonian))
@qfunc
def main() -> None:
    state = QArray()
    allocate(hamiltonian.num_qubits, state)
    suzuki_trotter(
        hamiltonian,
        evolution_coefficient=1,
        order=1,
        repetitions=1,
        qbv=state,
    )


preferences = Preferences(
    custom_hardware_settings=CustomHardwareSettings(basis_gates=["cx", "u"])
)

write_qmod(main, "example_exponentiation")

qprog = synthesize(main, preferences=preferences)

print(f"Classiq's exponentiation depth is {qprog.transpiled_circuit.depth}")
print(
    f"Classiq's exponentiation CX-count is {qprog.transpiled_circuit.count_ops['cx']}"
)
show(qprog)
Classiq's exponentiation depth is 1475
Classiq's exponentiation CX-count is 1586
Quantum program link: https://platform.classiq.io/circuit/31BPhXbGYtDrlhhJh8nWFwbcP2N

These impressive results can be compared to the naive exponentiation modules often found in the literature, see comprehensive comparison in the Hamiltonian Evolution notebook.

3. Automatic Error Reduction

The Classiq exponentiation module provides error management, automatically minimizes the error, and determines the best Trotter-Suzuki order and repetitions for any provided depth. Try this with an arbitrarily input Pauli list on eight qubits.

def sparse_pauli_to_list(operator: SparsePauliOp) -> list[PauliTerm]:
    terms_list = []
    for term in operator.terms:
        pauli_list = [Pauli.I for i in range(operator.num_qubits)]
        for p in term.paulis:  # type:ignore[attr-defined]
            pauli_list[p.index] = p.pauli
        terms_list.append(
            PauliTerm(coefficient=term.coefficient, pauli=list(reversed(pauli_list)))
        )
    return terms_list
pauli_sum = (
    0.1 * Pauli.X(2) * Pauli.X(3) * Pauli.X(4) * Pauli.Z(5)
    + 0.2 * Pauli.Y(2) * Pauli.Y(3) * Pauli.X(4) * Pauli.X(5)
    + 0.3 * Pauli.X(0) * Pauli.Y(1) * Pauli.Z(2) * Pauli.Z(3)
    + 0.4 * Pauli.X(0) * Pauli.Z(6) * Pauli.X(7)
    + 0.5 * Pauli.X(1) * Pauli.Z(2)
    + 0.6 * Pauli.Y(0) * Pauli.Z(1)
    + 0.7 * Pauli.Y(0) * Pauli.X(1)
    + 0.8 * Pauli.Z(2) * Pauli.Y(3) * Pauli.X(4) * Pauli.Y(5)
    + 0.9 * Pauli.Z(0) * Pauli.X(1)
    + 1.0 * Pauli.Y(3) * Pauli.Z(4) * Pauli.Y(5)
)


@qfunc
def main() -> None:
    state = QArray()
    allocate(pauli_sum.num_qubits, state)
    exponentiation_with_depth_constraint(
        pauli_operator=sparse_pauli_to_list(pauli_sum),
        evolution_coefficient=0.05,
        max_depth=400,
        qbv=state,
    )


write_qmod(main, "example_exponentiation_minimize_error")

qprog_minimize_error = synthesize(main)
show(qprog_minimize_error)
Quantum program link: https://platform.classiq.io/circuit/31BPi0Jl7KUoDLA8LOF6DeerEgX

The Classiq engine automatically opts for six second-order Suzuki-Trotter layers instead of 12 first-order layers, to minimize the error of the exponentiation within the depth constraints.

4. Conclusion

Classiq packages the domain expertise of dozens of scientists and quantum software engineers into the software platform. The result: a system that can automatically generate efficient quantum circuits for complex problems, making it faster and easier than ever to solve real-life problems with quantum computing. When the circuits are of manageable size, Classiq creates solutions that are on par with the best manually created circuits. When the circuits are larger than those a human can reasonably create, Classiq allows you to progress farther because of its powerful capabilities.