# -*- coding: utf-8 -*-
# This code is part of Qiskit.
#
# (C) Copyright IBM 2018, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
""" Hartree-Fock initial state."""
from typing import Optional, Union, List
import logging
import numpy as np
from qiskit import QuantumRegister, QuantumCircuit
from qiskit.aqua.utils.validation import validate_min, validate_in_set
from qiskit.aqua.components.initial_states import InitialState
logger = logging.getLogger(__name__)
[docs]class HartreeFock(InitialState):
"""A Hartree-Fock initial state."""
def __init__(self,
num_orbitals: int,
num_particles: Union[List[int], int],
qubit_mapping: str = 'parity',
two_qubit_reduction: bool = True,
sq_list: Optional[List[int]] = None) -> None:
"""
Args:
num_orbitals: number of spin orbitals, has a min. value of 1.
num_particles: number of particles, if it is a list, the first number
is alpha and the second number if beta.
qubit_mapping: mapping type for qubit operator
two_qubit_reduction: flag indicating whether or not two qubit is reduced
sq_list: position of the single-qubit operators that
anticommute with the cliffords
Raises:
ValueError: wrong setting in num_particles and num_orbitals.
ValueError: wrong setting for computed num_qubits and supplied num_qubits.
"""
validate_min('num_orbitals', num_orbitals, 1)
if isinstance(num_particles, list) and len(num_particles) != 2:
raise ValueError('Num particles value {}. Number of values allowed is 2'.format(
num_particles))
validate_in_set('qubit_mapping', qubit_mapping,
{'jordan_wigner', 'parity', 'bravyi_kitaev'})
super().__init__()
self._sq_list = sq_list
self._qubit_tapering = bool(self._sq_list)
self._qubit_mapping = qubit_mapping.lower()
self._two_qubit_reduction = two_qubit_reduction
if self._qubit_mapping != 'parity':
if self._two_qubit_reduction:
logger.warning("two_qubit_reduction only works with parity qubit mapping "
"but you have %s. We switch two_qubit_reduction "
"to False.", self._qubit_mapping)
self._two_qubit_reduction = False
self._num_orbitals = num_orbitals
if isinstance(num_particles, list):
self._num_alpha = num_particles[0]
self._num_beta = num_particles[1]
else:
logger.info("We assume that the number of alphas and betas are the same.")
self._num_alpha = num_particles // 2
self._num_beta = num_particles // 2
self._num_particles = self._num_alpha + self._num_beta
if self._num_particles > self._num_orbitals:
raise ValueError("# of particles must be less than or equal to # of orbitals.")
self._num_qubits = num_orbitals - 2 if self._two_qubit_reduction else self._num_orbitals
self._num_qubits = self._num_qubits \
if not self._qubit_tapering else self._num_qubits - len(sq_list)
self._bitstr = None
def _build_bitstr(self):
half_orbitals = self._num_orbitals // 2
bitstr = np.zeros(self._num_orbitals, np.bool)
bitstr[-self._num_alpha:] = True
bitstr[-(half_orbitals + self._num_beta):-half_orbitals] = True
if self._qubit_mapping == 'parity':
new_bitstr = bitstr.copy()
t_r = np.triu(np.ones((self._num_orbitals, self._num_orbitals)))
new_bitstr = t_r.dot(new_bitstr.astype(np.int)) % 2 # pylint: disable=no-member
bitstr = np.append(new_bitstr[1:half_orbitals], new_bitstr[half_orbitals + 1:]) \
if self._two_qubit_reduction else new_bitstr
elif self._qubit_mapping == 'bravyi_kitaev':
binary_superset_size = int(np.ceil(np.log2(self._num_orbitals)))
beta = 1
basis = np.asarray([[1, 0], [0, 1]])
for _ in range(binary_superset_size):
beta = np.kron(basis, beta)
beta[0, :] = 1
start_idx = beta.shape[0] - self._num_orbitals
beta = beta[start_idx:, start_idx:]
new_bitstr = beta.dot(bitstr.astype(int)) % 2
bitstr = new_bitstr.astype(np.bool)
if self._qubit_tapering:
sq_list = (len(bitstr) - 1) - np.asarray(self._sq_list)
bitstr = np.delete(bitstr, sq_list)
self._bitstr = bitstr.astype(np.bool)
[docs] def construct_circuit(self, mode='circuit', register=None):
"""
Construct the statevector of desired initial state.
Args:
mode (string): `vector` or `circuit`. The `vector` mode produces the vector.
While the `circuit` constructs the quantum circuit corresponding that
vector.
register (QuantumRegister): register for circuit construction.
Returns:
QuantumCircuit or numpy.ndarray: statevector.
Raises:
ValueError: when mode is not 'vector' or 'circuit'.
"""
if self._bitstr is None:
self._build_bitstr()
if mode == 'vector':
state = 1.0
one = np.asarray([0.0, 1.0])
zero = np.asarray([1.0, 0.0])
for k in self._bitstr[::-1]:
state = np.kron(one if k else zero, state)
return state
elif mode == 'circuit':
if register is None:
register = QuantumRegister(self._num_qubits, name='q')
quantum_circuit = QuantumCircuit(register)
for qubit_idx, bit in enumerate(self._bitstr[::-1]):
if bit:
quantum_circuit.u3(np.pi, 0.0, np.pi, register[qubit_idx])
return quantum_circuit
else:
raise ValueError('Mode should be either "vector" or "circuit"')
@property
def bitstr(self):
"""Getter of the bit string represented the statevector."""
if self._bitstr is None:
self._build_bitstr()
return self._bitstr