In [1]:
# import common packages
from collections import OrderedDict
import itertools
import logging

import numpy as np
import scipy

from qiskit_aqua import (get_algorithm_instance, get_optimizer_instance, 
                        get_variational_form_instance, get_initial_state_instance, Operator)
from qiskit_aqua._logging import build_logging_config, set_logging_config
from qiskit_aqua_chemistry.drivers import ConfigurationManager
from qiskit_aqua_chemistry.core import get_chemistry_operator_instance

# set_logging_config(build_logging_config(logging.INFO))

In [2]:
# using driver to get fermionic Hamiltonian
cfg_mgr = ConfigurationManager()
pyscf_cfg = OrderedDict([('atom', 'Li .0 .0 .0; H .0 .0 1.6'), 
                         ('unit', 'Angstrom'), ('charge', 0), 
                         ('spin', 0), ('basis', 'sto3g')])
section = {}
section['properties'] = pyscf_cfg
driver = cfg_mgr.get_driver_instance('PYSCF')
molecule = driver.run(section)

In [3]:
core = get_chemistry_operator_instance('hamiltonian')
hamiltonian_cfg = OrderedDict([
    ('name', 'hamiltonian'),
    ('transformation', 'full'),
    ('qubit_mapping', 'parity'),
    ('two_qubit_reduction', True),
    ('freeze_core', True),
    ('orbital_reduction', [])
])
core.init_params(hamiltonian_cfg)
algo_input = core.run(molecule)
qubit_op = algo_input.qubit_op

print("Originally requires {} qubits".format(qubit_op.num_qubits))
print(qubit_op)


Originally requires 8 qubits
Representation: paulis, qubits: 8, size: 276

Find the symmetries of the qubit operator


In [4]:
[symmetries, sq_paulis, cliffords, sq_list] = qubit_op.find_Z2_symmetries()
print('Z2 symmetries found:')
for symm in symmetries:
    print(symm.to_label())
print('single qubit operators found:')
for sq in sq_paulis:
    print(sq.to_label())
print('cliffords found:')
for clifford in cliffords:
    print(clifford.print_operators())
print('single-qubit list: {}'.format(sq_list))


Z2 symmetries found:
IZIZIZIZ
IIZZIIZZ
single qubit operators found:
IXIIIIII
IIXIIIII
cliffords found:
IZIZIZIZ	0.7071067811865475
IXIIIIII	0.7071067811865475

IIZZIIZZ	0.7071067811865475
IIXIIIII	0.7071067811865475

single-qubit list: [1, 2]

Use the found symmetries, single qubit operators, and cliffords to taper qubits from the original qubit operator. For each Z2 symmetry one can taper one qubit. However, different tapered operators can be built, corresponding to different symmetry sectors.


In [5]:
tapered_ops = []
for coeff in itertools.product([1, -1], repeat=len(sq_list)):
    tapered_op = Operator.qubit_tapering(qubit_op, cliffords, sq_list, list(coeff))
    tapered_ops.append((list(coeff), tapered_op))
    print("Number of qubits of tapered qubit operator: {}".format(tapered_op.num_qubits))


Number of qubits of tapered qubit operator: 6
Number of qubits of tapered qubit operator: 6
Number of qubits of tapered qubit operator: 6
Number of qubits of tapered qubit operator: 6

The user has to specify the symmetry sector he is interested in. Since we are interested in finding the ground state here, let us get the original ground state energy as a reference.


In [6]:
ee = get_algorithm_instance('ExactEigensolver')
ee.init_args(qubit_op, k=1)
result = core.process_algorithm_result(ee.run())
for line in result[0]:
    print(line)


=== GROUND STATE ENERGY ===
 
* Electronic ground state energy (Hartree): -8.874303870396
  - computed part:      -1.078084301625
  - frozen energy part: -7.796219568771
  - particle hole part: 0.0
~ Nuclear repulsion energy (Hartree): 0.992207270475
> Total ground state energy (Hartree): -7.882096599921

Now, let us iterate through all tapered qubit operators to find out the one whose ground state energy matches the original (un-tapered) one.


In [7]:
smallest_eig_value = 99999999999999
smallest_idx = -1
for idx in range(len(tapered_ops)):
    ee.init_args(tapered_ops[idx][1], k=1)
    curr_value = ee.run()['energy']
    if curr_value < smallest_eig_value:
        smallest_eig_value = curr_value
        smallest_idx = idx
    print("Lowest eigenvalue of the {}-th tapered operator (computed part) is {:.12f}".format(idx, curr_value))
    
the_tapered_op = tapered_ops[smallest_idx][1]
the_coeff = tapered_ops[smallest_idx][0]
print("The {}-th tapered operator matches original ground state energy, with corresponding symmetry sector of {}".format(smallest_idx, the_coeff))


Lowest eigenvalue of the 0-th tapered operator (computed part) is -1.078084301625
Lowest eigenvalue of the 1-th tapered operator (computed part) is -0.509523578167
Lowest eigenvalue of the 2-th tapered operator (computed part) is -0.912078232998
Lowest eigenvalue of the 3-th tapered operator (computed part) is -0.912078232998
The 0-th tapered operator matches original ground state energy, with corresponding symmetry sector of [1, 1]

Alternatively, one can run multiple VQE instances to find the lowest eigenvalue sector. Here we just validate that the_tapered_op reach the smallest eigenvalue in one VQE execution with the UCCSD variational form, modified to take into account of the tapered symmetries.


In [8]:
# setup initial state
init_state = get_initial_state_instance('HartreeFock')
init_state.init_args(num_qubits=the_tapered_op.num_qubits, num_orbitals=core._molecule_info['num_orbitals'],
                    qubit_mapping=core._qubit_mapping, two_qubit_reduction=core._two_qubit_reduction,
                    num_particles=core._molecule_info['num_particles'], sq_list=sq_list)

# setup variationl form
var_form = get_variational_form_instance('UCCSD')
var_form.init_args(num_qubits=the_tapered_op.num_qubits, depth=1,
                   num_orbitals=core._molecule_info['num_orbitals'], 
                   num_particles=core._molecule_info['num_particles'],
                   active_occupied=None, active_unoccupied=None, initial_state=init_state,
                   qubit_mapping=core._qubit_mapping, two_qubit_reduction=core._two_qubit_reduction, 
                   num_time_slices=1,
                   cliffords=cliffords, sq_list=sq_list, tapering_values=the_coeff, symmetries=symmetries)

# setup optimizer
optimizer = get_optimizer_instance('COBYLA')
optimizer.init_args()
optimizer.set_options(maxiter=1000)

# set vqe
algo = get_algorithm_instance('VQE')
algo.setup_quantum_backend(backend='statevector_simulator')
algo.init_args(the_tapered_op, 'matrix', var_form, optimizer)

In [9]:
algo_result = algo.run()

In [11]:
result = core.process_algorithm_result(algo_result)
for line in result[0]:
    print(line)

print("The parameters for UCCSD are:\n{}".format(algo_result['opt_params']))


=== GROUND STATE ENERGY ===
 
* Electronic ground state energy (Hartree): -8.874303866224
  - computed part:      -1.078084297452
  - frozen energy part: -7.796219568771
  - particle hole part: 0.0
~ Nuclear repulsion energy (Hartree): 0.992207270475
> Total ground state energy (Hartree): -7.882096595749
The parameters for UCCSD are:
[ 0.03841346  0.0541097  -0.00571893  0.00369552  0.03835424 -0.00823223
 -0.00473582  0.00365347 -0.03613082  0.05948699 -0.02738111 -0.02744031
  0.05961706 -0.11502287 -0.00593335  0.00937726  0.01207211  0.06069824
 -0.09090369 -0.04738047 -0.00678526 -0.10049275 -0.02625539 -0.00075635]

In [ ]: