QCQMC

This notebook demonstrates the high-level code interface used to run the quantum part of https://arxiv.org/pdf/2106.16235.pdf

The code is organized into five steps, where the input of each step is specified by one or more [Name]Params dataclasses and the output of each step is an attrs dataclass named [Name]Data with a factory method build_.... Each of the steps are demonstrated below.

Setup

First install recirq:

try:
    import recirq
except ImportError:
    %pip install git+https://github.com/quantumlib/ReCirq
import attrs
# Helper function to inspect the classes. 
def print_fields(x):
    """Helper function to inspect returned objects' fields."""
    for k, v in attrs.asdict(x).items():
        print(f'{k}: {type(v)}')

Hamiltonian

The first step is to pick a molecule and generate or load its relevant physical properties, namely the Hamiltonian. Here we specify a 2-electron Fermi-Hubbard Hamiltonian in the sto-3g basis. Integral data is stored in the data/ file in this repository.

The resulting data consists of the one- and two-body integrals and some energy quantities.

from recirq.qcqmc.hamiltonian import HamiltonianFileParams
hamiltonian_params = HamiltonianFileParams(
    name='4q_pp',
    integral_key='fh_sto3g',
    n_orb=2,
    n_elec=2,
)
hamiltonian_params
HamiltonianFileParams(name='4q_pp', integral_key='fh_sto3g', n_orb=2, n_elec=2, do_eri_restore=False, path_prefix='.')
from recirq.qcqmc.hamiltonian import HamiltonianData
hamiltonian_data = HamiltonianData.build_hamiltonian_from_file(hamiltonian_params)
print_fields(hamiltonian_data)
params: <class 'dict'>
e_core: <class 'float'>
one_body_integrals: <class 'numpy.ndarray'>
two_body_integrals_pqrs: <class 'numpy.ndarray'>
e_hf: <class 'float'>
e_fci: <class 'float'>

Trial Wavefunction

Next, we specify a trial wavefunction. Here we request a perfect pairing trial (using the specialized Params class) and don't include any heuristic layers (to keep the example simple and the runtime short).

The output data includes parameterized circuits and their parameters.

from recirq.qcqmc.trial_wf import PerfectPairingPlusTrialWavefunctionParams
trial_wf_params = PerfectPairingPlusTrialWavefunctionParams(
    name='4q_pp',
    hamiltonian_params=hamiltonian_params,
    heuristic_layers=(),
)
trial_wf_params
PerfectPairingPlusTrialWavefunctionParams(name='4q_pp', hamiltonian_params=HamiltonianFileParams(name='4q_pp', integral_key='fh_sto3g', n_orb=2, n_elec=2, do_eri_restore=False, path_prefix='.'))
from recirq.qcqmc.trial_wf import TrialWavefunctionData 
trial_wf_data = TrialWavefunctionData.build_pp_plus_trial_from_dependencies(
    trial_wf_params, 
    dependencies={
        hamiltonian_params: hamiltonian_data
    }
)
print('--'*20)
print_fields(trial_wf_data)
----------------------------------------
params: <class 'dict'>
ansatz_circuit: <class 'cirq.circuits.circuit.Circuit'>
superposition_circuit: <class 'cirq.circuits.circuit.Circuit'>
hf_energy: <class 'numpy.complex128'>
ansatz_energy: <class 'float'>
fci_energy: <class 'float'>
one_body_basis_change_mat: <class 'numpy.ndarray'>
one_body_params: <class 'numpy.ndarray'>
two_body_params: <class 'numpy.ndarray'>

Blueprint

Next, we configure the shadow tomography strategy for measuring the trial wavefunction. We specify how many cliffords and how to generate them, i.e. the qubit partition.

The returned data is a compiled circuit with parameterized clifford suffixes and Cirq resolvers for efficient execution on a device.

qubits = trial_wf_params.qubits_linearly_connected
from recirq.qcqmc.blueprint import BlueprintParamsTrialWf

blueprint_params = BlueprintParamsTrialWf(
    name='4q_pp',
    trial_wf_params=trial_wf_params,
    n_cliffords=100,
    qubit_partition=tuple((q,) for q in qubits),
)
blueprint_params
BlueprintParamsTrialWf(name='4q_pp', trial_wf_params=PerfectPairingPlusTrialWavefunctionParams(name='4q_pp', hamiltonian_params=HamiltonianFileParams(name='4q_pp', integral_key='fh_sto3g', n_orb=2, n_elec=2, do_eri_restore=False, path_prefix='.')), n_cliffords=100, qubit_partition=((cirq.GridQubit(0, 0),), (cirq.GridQubit(0, 1),), (cirq.GridQubit(1, 1),), (cirq.GridQubit(1, 0),)), seed=0, optimizer_suite=0, path_prefix='.')
from recirq.qcqmc.blueprint import BlueprintData 
blueprint_data = BlueprintData.build_blueprint_from_dependencies(
    blueprint_params,
    dependencies={
        trial_wf_params: trial_wf_data
    })
print_fields(blueprint_data)
params: <class 'dict'>
compiled_circuit: <class 'cirq.circuits.circuit.Circuit'>
parameterized_clifford_circuits: <class 'tuple'>
resolvers: <class 'list'>

Experiment

Now, we're ready to execute circuits and gather samples. The experiment step has two versions: simulated or on a real device. In either case, we configure how many samples to collect and any runtime-specific parameters.

The returned data includes the experimental samples.

from recirq.qcqmc.experiment import SimulatedExperimentParams
expt_params = SimulatedExperimentParams(
    name='4q_pp',
    blueprint_params=blueprint_params,
    n_samples_per_clifford=1_000,
    noise_model_name='None',
)
expt_params
SimulatedExperimentParams(name=4q_pp, blueprint_params=BlueprintParamsTrialWf(name='4q_pp', trial_wf_params=PerfectPairingPlusTrialWavefunctionParams(name='4q_pp', hamiltonian_params=HamiltonianFileParams(name='4q_pp', integral_key='fh_sto3g', n_orb=2, n_elec=2, do_eri_restore=False, path_prefix='.')), n_cliffords=100, qubit_partition=((cirq.GridQubit(0, 0),), (cirq.GridQubit(0, 1),), (cirq.GridQubit(1, 1),), (cirq.GridQubit(1, 0),)), seed=0, optimizer_suite=0, path_prefix='.'), n_samples_per_clifford=1000, noise_model_name=None)
from recirq.qcqmc.experiment import ExperimentData 
expt_data = ExperimentData.build_experiment_from_dependencies(
    expt_params,
    dependencies={
        blueprint_params: blueprint_data
})
print_fields(expt_data)
params: <class 'dict'>
raw_samples: <class 'numpy.ndarray'>
metadata: <class 'dict'>

Overlap Analysis

Finally, we reconstruct the wavefunction from our experiment data.

The returned data here are reconstructed wavefunctions suitable for exporting to iPie for classical QCQMC walker updates.

from recirq.qcqmc.analysis import OverlapAnalysisParams
analysis_params = OverlapAnalysisParams(
    name='4q_pp',
    experiment_params=expt_params,
    k_to_calculate=(1, 2),
)
analysis_params
OverlapAnalysisParams(name=4q_pp, experiment_params=SimulatedExperimentParams(name=4q_pp, blueprint_params=BlueprintParamsTrialWf(name='4q_pp', trial_wf_params=PerfectPairingPlusTrialWavefunctionParams(name='4q_pp', hamiltonian_params=HamiltonianFileParams(name='4q_pp', integral_key='fh_sto3g', n_orb=2, n_elec=2, do_eri_restore=False, path_prefix='.')), n_cliffords=100, qubit_partition=((cirq.GridQubit(0, 0),), (cirq.GridQubit(0, 1),), (cirq.GridQubit(1, 1),), (cirq.GridQubit(1, 0),)), seed=0, optimizer_suite=0, path_prefix='.'), n_samples_per_clifford=1000, noise_model_name=None), k_to_calculate=(1, 2))
from recirq.qcqmc.analysis import OverlapAnalysisData
analysis_data = OverlapAnalysisData.build_analysis_from_dependencies(analysis_params, dependencies={
    expt_params: expt_data,
    blueprint_params: blueprint_data,
    trial_wf_params: trial_wf_data,
})
print_fields(analysis_data)
params: <class 'dict'>
reconstructed_wf_for_k: <class 'dict'>