Skip to main content

Circuit Factory: Time evolution

In this tutorial, you will learn how to use QURI Algo to obtain time evolution circuits and their controlled conterpart. This includes:

  • Understanding the HamiltonianInput object and how to create one
  • Understanding the (Controlled)TimeEvolutionCircuitFactory object
  • Evaluate the (controlled) time evolution circuit with QURI VM

Hamiltonian

In QURI Algo, we provide the HamiltionianInput interface representing any problen that involves a Hamiltonian. Here, we first set up a molecular Hamiltonian as the example of this tutorial.

import numpy as np
from pyscf import gto, scf
from quri_parts.pyscf.mol import get_spin_mo_integrals_from_mole
from quri_parts.openfermion.mol import get_qubit_mapped_hamiltonian
from quri_parts.core.operator import Operator, PAULI_IDENTITY

mole = gto.M(atom="H 0 0 0; H 0 0 1")
mf = scf.RHF(mole).run()
hamiltonian, mapping = get_qubit_mapped_hamiltonian(
*get_spin_mo_integrals_from_mole(mole, mf.mo_coeff)
)

eff_hamiltonian = hamiltonian - Operator({PAULI_IDENTITY: hamiltonian.constant})
converged SCF energy = -1.06610864931794

The Hamiltonian generated above is a qubit Hamiltonian represented by a QURI Parts Operator. We create a QubitHamiltonianInput object, which is a variant of HamiltonianInput that represents a Hamiltonian in its qubit mapped form.

from quri_algo.problem import QubitHamiltonianInput

hamiltonian_input = QubitHamiltonianInput(mapping.n_qubits, eff_hamiltonian)

Time evolution circuit

Having constructed the Hamiltonian, we can construct a time evolution circuit that performs the time evolution with the Hamiltonian. In QURI Algo, we provide TimeEvolutionCircuitFactory, which allows you to create a time evolution circuit at time step tt. We provide a variant of the TimeEvolutionCircuitFactory, theTrotterTimeEvolutionCircuitFactory, that generates a time evolution circuit based on Trotterization. To create one, you need to pass in the hamiltonian_input we created above as well as the Trotter steps.

from quri_algo.circuit.time_evolution.trotter_time_evo import TrotterTimeEvolutionCircuitFactory

trotter_10_circuit_factory = TrotterTimeEvolutionCircuitFactory(hamiltonian_input, n_trotter=10)

With the factory, you can generate a time evolution circuit by passing an evolution time to it.

# Evolution circuit at t = 5.0

circuit = trotter_10_circuit_factory(5.0)
print(circuit)
<quri_parts.rust.circuit.circuit_parametric.ImmutableBoundParametricQuantumCircuit object at 0x7f34be26fd80>

Controlled time evolution circuits

In phase-estimation-like algorithms, controlled time evolution circuits are used ubiquitously. To facilitate them, we also provide ControlledTimeEvolutionCircuitFactory for generating controlled time evolution circuits at time step tt. We provide 2 variants of the ControlledTimeEvolutionCircuitFactory:

  • ControlledTrotterTimeEvolutionCircuitFactory: generates a time evolution circuit based on Trotterization
  • ControlledExactUnitaryTimeEvolutionCircuitFactory: generates a time evolution circuit based on the exact unitary matrix generated by the QubitHamiltonianInput.
from quri_algo.circuit.time_evolution.trotter_time_evo import TrotterControlledTimeEvolutionCircuitFactory
from quri_algo.circuit.time_evolution.exact_unitary import ExactUnitaryControlledTimeEvolutionCircuitFactory

trotter_controlled_factory = TrotterControlledTimeEvolutionCircuitFactory(hamiltonian_input, n_trotter=10)
exact_controlled_factory = ExactUnitaryControlledTimeEvolutionCircuitFactory(hamiltonian_input)

They can be used in the same way as TimeEvolutionCircuitFactorys.

evolution_time = 10

trotter_controlled_circuit = trotter_controlled_factory(evolution_time)
exact_controlled_circuit = exact_controlled_factory(evolution_time)

Evolution circuit on error corrected devices

On an error corrected device, we need to transpile the circuit according to the native gates of the device or architecture.

from quri_parts.backend.devices import nisq_spcond_lattice, star_device
from quri_parts.circuit.topology import SquareLattice
from quri_parts.backend.units import TimeValue, TimeUnit
from quri_vm import VM

nisq_vm = VM.from_device_prop(
nisq_spcond_lattice.generate_device_property(
lattice=SquareLattice(4, 4),
native_gates=("RZ", "SqrtX", "X", "CNOT"),
gate_error_1q=1e-3,
gate_error_2q=1e-2,
gate_error_meas=1e-2,
gate_time_1q=TimeValue(60, TimeUnit.NANOSECOND),
gate_time_2q=TimeValue(660, TimeUnit.NANOSECOND),
gate_time_meas=TimeValue(1.4, TimeUnit.MICROSECOND),
)
)

star_vm = VM.from_device_prop(
star_device.generate_device_property(
qubit_count=16,
code_distance=9,
qec_cycle=TimeValue(1, TimeUnit.MICROSECOND),
physical_error_rate=1e-4
)
)

Each VM has its own transpiler, which ensures that any time a logical circuit is simulated, it first is transpiled to a gate-set that is supported by the device in question. Below we first define the logical circuit factory that we will use for time-evolution.

trotter_factory = TrotterTimeEvolutionCircuitFactory(
hamiltonian_input, n_trotter=10
)
from quri_parts.circuit import NonParametricQuantumCircuit

def get_gate_names(circuit: NonParametricQuantumCircuit) -> set[str]:
return set([g.name for g in circuit.gates])
print("Gates that compose the time evolution circuit on superconducting NISQ device:")
print(get_gate_names(nisq_vm.transpile(trotter_factory(evolution_time))))
print("Gates that compose the time evolution circuit on STAR device:")
print(get_gate_names(star_vm.transpile(trotter_factory(evolution_time))))
Gates that compose the time evolution circuit on superconducting NISQ device:
{'X', 'CNOT', 'RZ', 'SqrtX'}
Gates that compose the time evolution circuit on STAR device:
{'H', 'CNOT', 'RZ', 'S'}

We can also perform an analysis of the circuit based on each architecture:

import pprint

print("Evaluation result on NISQ device:")
pprint.pprint(nisq_vm.analyze(trotter_factory(evolution_time)))
print("\n")
print("Evaluation result on STAR device:")
pprint.pprint(star_vm.analyze(trotter_factory(evolution_time)))
Evaluation result on NISQ device:
AnalyzeResult(lowering_level=<LoweringLevel.ArchLogicalCircuit: 1>,
qubit_count=4,
gate_count=1860,
depth=853,
latency=TimeValue(value=239160.0, unit=<TimeUnit.NANOSECOND>),
fidelity=0.005982771196137667)


Evaluation result on STAR device:
AnalyzeResult(lowering_level=<LoweringLevel.ArchLogicalCircuit: 1>,
qubit_count=4,
gate_count=1460,
depth=770,
latency=TimeValue(value=16380000.0, unit=<TimeUnit.NANOSECOND>),
fidelity=0.9958925186936065)

We can see that the fidelity of the time evolution citcuit improves from 0.50.5% to 99.5% at the cost of about 68 times execution time on the error corrected STAR architecture.