ProjectQ-Framework / ProjectQ

ProjectQ: An open source software framework for quantum computing
https://projectq.ch
Apache License 2.0
880 stars 271 forks source link

How to pass commands between commandprinter() and another engine #320

Closed jason-jk-kang closed 3 years ago

jason-jk-kang commented 5 years ago

Hi,

I am wondering how it may be possible to pass commands between engines. I have a commandprinter() and I want to interface it with an IBM Backend. The documentation indicates that IBM Backend can receive commands, but how do I access the command list that is printed previously by commandprinter()? Thank you.

Takishima commented 5 years ago

Could you post an example code?

If you specify your commandprinter as part of the engine_list parameter of the MainEngine in your program, the commands should be transferred automatically to the next one (which would be in your case the IBM backend)

jason-jk-kang commented 5 years ago

For example, in this block of code, I am computing the expectation value of a hamiltonian using ProjectQ's simulator. I would like to use IBM's simulator, or IBM's machine. However, if I simply replace the compiler_engine (line 82) with uccsd_trotter_engine(ibm_backend()) then it will not work.

Thank you.

import os
from numpy import array, concatenate, zeros
from numpy.random import randn
from scipy.optimize import minimize
from openfermion.config import *
from openfermionprojectq import *
from openfermion.hamiltonians import MolecularData
from openfermion.transforms import jordan_wigner, get_fermion_operator, get_sparse_operator
from openfermion.utils import uccsd_singlet_paramsize
from projectq.ops import X, All, Measure
from projectq.backends import CommandPrinter, CircuitDrawer, IBMBackend
from pyscf import mp, fci

from openfermionpyscf import run_pyscf

def energy_objective(packed_amplitudes):
    Evaluate the energy of a UCCSD singlet wavefunction with packed_amplitudes
    Args:
        packed_amplitudes(ndarray): Compact array that stores the unique
            amplitudes for a UCCSD singlet wavefunction.
    Returns:
        energy(float): Energy corresponding to the given amplitudes

    os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
    # Set Jordan-Wigner initial state with correct number of electrons
    wavefunction = compiler_engine.allocate_qureg(molecule.n_qubits)
    for i in range(molecule.n_electrons):
        X | wavefunction[i]

    # Build the circuit and act it on the wavefunction
    evolution_operator = uccsd_singlet_evolution(packed_amplitudes,
                                                 molecule.n_qubits,
                                                 molecule.n_electrons)
    evolution_operator | wavefunction
    compiler_engine.flush()
    # Evaluate the energy and reset wavefunction
    energy = compiler_engine.backend.get_expectation_value(qubit_hamiltonian, wavefunction)
    All(Measure) | wavefunction
    compiler_engine.flush()
    return energy

# Load saved file for H3.
basis = 'sto-3g'
spin = 2

# Set calculation parameters.
run_scf = 1
run_mp2 = 1
run_cisd = 0
run_ccsd = 0
run_fci = 1
delete_input = True
delete_output = True

# Set Hamiltonian parameters.
active_space_start = 1
active_space_stop = 3
geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414)), ('H', (0., 0., 3.3))]

# Generate and populate instance of MolecularData.
molecule = MolecularData(geometry, basis, spin, description="h3")

molecule = run_pyscf(molecule,
                     run_scf=run_scf,
                     run_mp2=run_mp2,
                     run_cisd=run_cisd,
                     run_ccsd=run_ccsd,
                     run_fci=run_fci)

# Use a Jordan-Wigner encoding, and compress to remove 0 imaginary components
molecular_hamiltonian = molecule.get_molecular_hamiltonian(
    occupied_indices=range(active_space_start),
    active_indices=range(active_space_start, active_space_stop))

fermion_hamiltonian = get_fermion_operator(molecular_hamiltonian)
qubit_hamiltonian = jordan_wigner(fermion_hamiltonian)
qubit_hamiltonian.compress()
compiler_engine = uccsd_trotter_engine()

n_amplitudes = uccsd_singlet_paramsize(molecule.n_qubits, molecule.n_electrons)
initial_amplitudes = [0.01] * n_amplitudes
initial_energy = energy_objective(initial_amplitudes)

# Run VQE Optimization to find new CCSD parameters
opt_result = minimize(energy_objective, initial_amplitudes,
                      method="CG", options={'disp':True})

opt_energy, opt_amplitudes = opt_result.fun, opt_result.x

print("\n Results for {}:".format(molecule.name))
print("Optimal UCCSD Singlet Energy: {}".format(opt_energy))
print("Optimal UCCSD Singlet Amplitudes: {}".format(opt_amplitudes))
print("Classical CCSD Energy: {} Hartrees".format(molecule.ccsd_energy))
print("Exact FCI Energy: {} Hartrees".format(molecule.fci_energy))
print("Initial Energy of UCCSD with CCSD amplitudes: {} Hartrees".format(initial_energy))

compiler_engine = uccsd_trotter_engine(CommandPrinter())

wavefunction = compiler_engine.allocate_qureg(molecule.n_qubits)
for i in range(molecule.n_electrons):
    X | wavefunction[i]

# Build the circuit and act it on the wavefunction
evolution_operator = uccsd_singlet_evolution(opt_amplitudes,
                                             molecule.n_qubits,
                                             molecule.n_electrons)
evolution_operator | wavefunction
compiler_engine.flush()

`

Takishima commented 4 years ago

Sorry I missed following through with this issue.

Actually, the uccsd_trotter_engine function does not allow you to add arbitrary compiler engines in the engine list passed to the MainEngine constructor.

What you could do is use the insert_engine function from the projectq.meta module:

backend = IBMBackend()
compiler_engine = uccsd_trotter_engine(backend)
cmd_printer = CommandPrinter()
insert_engine(backend, cmd_printer)
jason-jk-kang commented 4 years ago

Thank you for following up! However, now when I run it by specifying the backend as such, I receive the error

File "/home/jasonkang/anaconda3/lib/python3.7/site-packages/projectq/cengines/_main.py", line 286, in send "\n" + repr(last_line[-2])) TypeError: __init__() missing 2 required positional arguments: 'doc' and 'pos'

Are you sure ProjectQ's uccsd_trotter_engine is still compatible with the IBM backend?

jason-jk-kang commented 4 years ago

Bumping - any help is appreciated!

Takishima commented 4 years ago

The way ProjectQ communicates with the IBM quantum experience indeed needs to be patched. We will probably need to use the qiskit-ibmq-provider Python module which will require that people install at least qiskit-terra(and some other dependencies) to make it work.

I will try to get something out soon, although I cannot make any promises right now. If you are willing to help fix this issue, I am more than happy to help you along the way though.

Takishima commented 3 years ago

The issues with the Qiskit QE API should be fixed in the latest version. Re-open this issue if that is not the case.