Circuit factories: 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})
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 . 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)
# output:
<quri_parts.circuit.circuit_parametric.ImmutableBoundParametricQuantumCircuit object at 0x11a295f50>
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 . We provide 2 variants of the ControlledTimeEvolutionCircuitFactory
:
ControlledTrotterTimeEvolutionCircuitFactory
: generates a time evolution circuit based on TrotterizationControlledExactUnitaryTimeEvolutionCircuitFactory
: generates a time evolution circuit based on the exact unitary matrix generated by theQubitHamiltonianInput
.
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 TimeEvolutionCircuitFactory
s.
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
)
)
We can create time evolution circuit factories that automatically generate device-specific transpiled time evolution circuit. To do so, pass in the transpiler as a keyword argument of the circuit factories.
nisq_trotter_factory = TrotterTimeEvolutionCircuitFactory(
hamiltonian_input, n_trotter=10, transpiler=nisq_vm.transpile
)
star_trotter_factory = TrotterTimeEvolutionCircuitFactory(
hamiltonian_input, n_trotter=10, transpiler=star_vm.transpile
)
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_trotter_factory(evolution_time)))
print("Gates that compose the time evolution circuit on STAR device:")
print(get_gate_names(star_trotter_factory(evolution_time)))
# output:
Gates that compose the time evolution circuit on superconducting NISQ device:
{'CNOT', 'X', 'SqrtX', 'RZ'}
Gates that compose the time evolution circuit on STAR device:
{'CNOT', 'H', '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(nisq_trotter_factory(evolution_time)))
print("\n")
print("Evaluation result on STAR device:")
pprint.pprint(star_vm.analyze(star_trotter_factory(evolution_time)))
# output:
Evaluation result on NISQ device:
AnalyzeResult(lowering_level=<LoweringLevel.ArchLogicalCircuit: 1>,
qubit_count=np.int64(4),
gate_count=3540,
depth=2386,
latency=TimeValue(value=1317960.0, unit=<TimeUnit.NANOSECOND>),
fidelity=2.7799009245661343e-10)
Evaluation result on STAR device:
AnalyzeResult(lowering_level=<LoweringLevel.ArchLogicalCircuit: 1>,
qubit_count=np.int64(4),
gate_count=1460,
depth=770,
latency=TimeValue(value=16380000.0, unit=<TimeUnit.NANOSECOND>),
fidelity=np.float64(0.9958925186936065))
We can see that the fidelity of the time evolution citcuit improves from to 99.5% at the cost of about 12 times execution time on the error corrected STAR architecture.