Add AerStatevector (#1590)

Add a new class AerStatevector that supports q.i.s.Statevector interface.
This class uses an internal class AerState that directly access to Aer's
native runtime.
This commit is contained in:
Hiroshi Horii 2022-09-14 10:34:28 +09:00 committed by GitHub
parent 329169f80c
commit 3babb7fcc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1467 additions and 39 deletions

View File

@ -14,4 +14,5 @@ Qiskit Aer API Reference
aer_primitives
aer_pulse
aer_utils
aer_quantum_info
parallel

View File

@ -1,6 +1,6 @@
.. _aer-primitives:
.. automodule:: qiskit.providers.aer.primitives
.. automodule:: qiskit_aer.primitives
:no-members:
:no-inherited-members:
:no-special-members:

View File

@ -0,0 +1,6 @@
.. _aer-quantum_info:
.. automodule:: qiskit_aer.quantum_info
:no-members:
:no-inherited-members:
:no-special-members:

View File

@ -68,6 +68,7 @@ from .aererror import AerError
from .backends import *
from . import library
from . import pulse
from . import quantum_info
from . import noise
from . import utils
from .version import __version__

View File

@ -148,7 +148,7 @@ PYBIND11_MODULE(controller_wrappers, m) {
else
return state.probabilities(qubits);
}, py::arg("qubits") = reg_t());
aer_state.def("sample_measure", &AER::AerState::sample_measure);
aer_state.def("expval_pauli", &AER::AerState::expval_pauli);
aer_state.def("sample_memory", &AER::AerState::sample_memory);
aer_state.def("sample_counts", &AER::AerState::sample_counts);
}

View File

@ -14,4 +14,17 @@
=================================================
Aer Quantum Info (:mod:`qiskit_aer.quantum_info`)
=================================================
.. currentmodule:: qiskit_aer.quantum_info
States
======
.. autosummary::
:toctree: ../stubs/
AerStatevector
"""
from .states import AerStatevector

View File

@ -10,8 +10,6 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
=============================================
State (:mod:`qiskit_aer.quantum_info.states`)
=============================================
"""
"""Aer Quantum States."""
from .aer_statevector import AerStatevector

View File

@ -47,14 +47,17 @@ class AerState:
"""State that handles cpp quantum state safely"""
self._state = _STATE.INITIALIZING
self._native_state = AerStateWrapper()
self._method = 'statevector'
self._init_data = None
self._moved_data = None
self._last_qubit = -1
self._configs = {}
for key, value in kwargs.items():
self.configure(key, value)
if 'method' not in kwargs:
self.configure('method', 'statevector')
def _assert_initializing(self):
if self._state != _STATE.INITIALIZING:
raise AerError('AerState was already initialized.')
@ -108,8 +111,13 @@ class AerState:
raise AerError('AerState is configured with a str key')
if not isinstance(value, str):
value = str(value)
self._configs[key] = value
self._native_state.configure(key, value)
def configuration(self):
"""return configuration"""
return self._configs.copy()
def initialize(self, data=None, copy=True):
"""initialize state."""
self._assert_initializing()
@ -120,7 +128,7 @@ class AerState:
elif isinstance(data, np.ndarray):
self._initialize_with_ndarray(data, copy)
else:
raise AerError('unsupported init data.')
raise AerError(f'unsupported init data: {data.__class__}')
def _initialize_with_ndarray(self, data, copy):
if AerState._is_in_use(data) and not copy:
@ -131,7 +139,7 @@ class AerState:
raise AerError('length of init data must be power of two')
if (isinstance(data, np.ndarray) and
self._method == 'statevector' and
self._configs['method'] == 'statevector' and
self._native_state.initialize_statevector(num_of_qubits, data, copy)):
if not copy:
self._init_data = data
@ -186,7 +194,7 @@ class AerState:
self._last_qubit = allocated[len(allocated) - 1]
def _assert_in_allocated_qubits(self, qubit):
if isinstance(qubit, list):
if hasattr(qubit, '__iter__'):
for q in qubit:
self._assert_in_allocated_qubits(q)
elif qubit < 0 or qubit > self._last_qubit:
@ -195,7 +203,6 @@ class AerState:
@property
def num_qubits(self):
"""return a number of allocate qubits."""
self._assert_initializing()
return self._last_qubit + 1
def flush(self):
@ -212,8 +219,8 @@ class AerState:
return self._native_state.last_result()
def apply_global_phase(self, phase):
"""apply global phase."""
self._assert_allocated_or_mapped_or_moved()
"""apply global phase"""
self._assert_allocated_or_mapped()
self._native_state.apply_global_phase(phase)
def apply_unitary(self, qubits, data):
@ -353,11 +360,20 @@ class AerState:
# retrieve probability
return self._native_state.probabilities(qubits)
def sample_measure(self, qubits=None, shots=1024):
def sample_counts(self, qubits=None, shots=1024):
"""samples all the qubits."""
self._assert_allocated_or_mapped()
if qubits is None:
qubits = range(self._last_qubit + 1)
else:
self._assert_in_allocated_qubits(qubits)
return self._native_state.sample_measure(qubits, shots)
return self._native_state.sample_counts(qubits, shots)
def sample_memory(self, qubits=None, shots=1024):
"""samples all the qubits."""
self._assert_allocated_or_mapped()
if qubits is None:
qubits = range(self._last_qubit + 1)
else:
self._assert_in_allocated_qubits(qubits)
return self._native_state.sample_memory(qubits, shots)

View File

@ -0,0 +1,219 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2019, 2020, 2021, 2022.
#
# 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.
"""
Statevector quantum state class.
"""
import copy
import numpy as np
from qiskit.circuit import QuantumCircuit, Instruction
from qiskit.quantum_info.states import Statevector
from qiskit_aer import AerSimulator
from .aer_state import AerState
from ...backends.aerbackend import AerError
class AerStatevector(Statevector):
"""AerStatevector class
This class inherits :class:`Statevector`, which stores probability amplitudes
in its `ndarray`. class:`AerStatevector` generates this `ndarray` by using the
same runtime with :class:`AerSimulator`.
"""
def __init__(self, data, dims=None, **configs):
"""
Args:
data (np.array or list or Statevector or QuantumCircuit or qiskit.circuit.Instruction):
Data from which the statevector can be constructed. This can be either a complex
vector, another statevector or a ``QuantumCircuit`` or ``Instruction``
(``Operator`` is not supportted in the current implementation). If the data is
a circuit or instruction, the statevector is constructed by assuming that all
qubits are initialized to the zero state.
dims (int or tuple or list): Optional. The subsystem dimension of
the state (See additional information).
configs (kwargs): configurations of :class:`AerSimulator`. `method` configuration must
be `statevector` or `matrix_product_state`.
Raises:
AerError: if input data is not valid.
Additional Information:
The ``dims`` kwarg is used to ``Statevector`` constructor.
"""
if '_aer_state' in configs:
self._aer_state = configs.pop('_aer_state')
else:
if 'method' not in configs:
configs['method'] = 'statevector'
elif configs['method'] not in ('statevector', 'matrix_product_state'):
method = configs['method']
raise AerError(f'Method {method} is not supported')
if isinstance(data, (QuantumCircuit, Instruction)):
data, aer_state = AerStatevector._from_instruction(data, None, configs)
elif isinstance(data, list):
data, aer_state = AerStatevector._from_ndarray(np.array(data, dtype=complex),
configs)
elif isinstance(data, np.ndarray):
data, aer_state = AerStatevector._from_ndarray(data, configs)
elif isinstance(data, AerStatevector):
aer_state = data._aer_state
if dims is None:
dims = data._op_shape._dims_l
data = data._data.copy()
else:
raise AerError(f'Input data is not supported: type={data.__class__}, data={data}')
self._aer_state = aer_state
super().__init__(data, dims=dims)
self._result = None
self._configs = configs
def _last_result(self):
if self._result is None:
self._result = self._aer_state.last_result()
return self._result
def metadata(self):
"""Return result metadata of an operation that executed lastly."""
if self._last_result() is None:
raise AerError('AerState was not used and metdata does not exist.')
return self._last_result()['metadata']
def __copy__(self):
return copy.deepcopy(self)
def __deepcopy__(self, _memo=None):
ret = AerStatevector(self._data.copy(), **self._configs)
ret._op_shape = copy.deepcopy(self._op_shape)
ret._rng_generator = copy.deepcopy(self._rng_generator)
return ret
def conjugate(self):
return AerStatevector(np.conj(self._data), dims=self.dims())
def sample_memory(self, shots, qargs=None):
if qargs is None:
qubits = np.arange(self._aer_state.num_qubits)
else:
qubits = np.array(qargs)
self._aer_state.close()
self._aer_state = AerState(**self._aer_state.configuration())
self._aer_state.initialize(self._data, copy=False)
samples = self._aer_state.sample_memory(qubits, shots)
self._data = self._aer_state.move_to_ndarray()
return samples
@staticmethod
def _from_ndarray(init_data, configs):
aer_state = AerState()
options = AerSimulator._default_options()
for config_key, config_value in configs.items():
if options.get(config_key):
aer_state.configure(config_key, config_value)
if len(init_data) == 0:
raise AerError('initial data must be larger than 0')
num_qubits = int(np.log2(len(init_data)))
aer_state.allocate_qubits(num_qubits)
aer_state.initialize(data=init_data)
return aer_state.move_to_ndarray(), aer_state
@classmethod
def from_instruction(cls, instruction):
return AerStatevector(instruction)
@staticmethod
def _from_instruction(inst, init_data, configs):
aer_state = AerState()
for config_key, config_value in configs.items():
aer_state.configure(config_key, config_value)
aer_state.allocate_qubits(inst.num_qubits)
num_qubits = inst.num_qubits
if init_data is not None:
aer_state.initialize(data=init_data, copy=True)
else:
aer_state.initialize()
if isinstance(inst, QuantumCircuit) and inst.global_phase != 0:
aer_state.apply_global_phase(inst.global_phase)
if isinstance(inst, QuantumCircuit):
AerStatevector._aer_evolve_circuit(aer_state, inst, range(num_qubits))
else:
AerStatevector._aer_evolve_instruction(aer_state, inst, range(num_qubits))
return aer_state.move_to_ndarray(), aer_state
@staticmethod
def _aer_evolve_circuit(aer_state, circuit, qubits):
"""Apply circuit into aer_state"""
for instruction in circuit.data:
if instruction.clbits:
raise AerError(
f"Cannot apply instruction with classical bits: {instruction.operation.name}"
)
inst = instruction.operation
qargs = instruction.qubits
AerStatevector._aer_evolve_instruction(aer_state, inst,
[qubits[circuit.find_bit(qarg).index]
for qarg in qargs])
@staticmethod
def _aer_evolve_instruction(aer_state, inst, qubits):
"""Apply instruction into aer_state"""
params = inst.params
if inst.name in ['u', 'u3']:
aer_state.apply_mcu(qubits[0:len(qubits) - 1], qubits[len(qubits) - 1],
params[0], params[1], params[2])
elif inst.name in ['x', 'cx', 'ccx']:
aer_state.apply_mcx(qubits[0:len(qubits) - 1], qubits[len(qubits) - 1])
elif inst.name in ['y', 'cy']:
aer_state.apply_mcy(qubits[0:len(qubits) - 1], qubits[len(qubits) - 1])
elif inst.name in ['z', 'cz']:
aer_state.apply_mcz(qubits[0:len(qubits) - 1], qubits[len(qubits) - 1])
elif inst.name == 'unitary':
aer_state.apply_unitary(qubits, inst.params[0])
elif inst.name == 'diagonal':
aer_state.apply_diagonal(qubits, inst.params[0])
elif inst.name == 'reset':
aer_state.apply_reset(qubits)
else:
definition = inst.definition
if definition is inst:
raise AerError('cannot decompose ' + inst.name)
if not definition:
raise AerError('cannot decompose ' + inst.name)
AerStatevector._aer_evolve_circuit(aer_state, definition, qubits)
@classmethod
def from_label(cls, label):
return AerStatevector(Statevector.from_label(label)._data)
@staticmethod
def from_int(i, dims):
size = np.product(dims)
state = np.zeros(size, dtype=complex)
state[i] = 1.0
return AerStatevector(state, dims=dims)

View File

@ -292,15 +292,14 @@ public:
// Return M sampled outcomes for Z-basis measurement of specified qubits
// The input is a length M list of random reals between [0, 1) used for
// generating samples.
virtual std::unordered_map<uint_t, uint_t> sample_measure(const reg_t &qubits, uint_t shots);
// The returned value is unordered sampled outcomes
virtual std::vector<std::string> sample_memory(const reg_t &qubits, uint_t shots);
//-----------------------------------------------------------------------
// Expectation Values
//-----------------------------------------------------------------------
// Return the expectation value of an N-qubit Pauli matrix.
// The Pauli is input as a length N string of I,X,Y,Z characters.
virtual double expval_pauli(const reg_t &qubits, const std::string &pauli);
// Return M sampled outcomes for Z-basis measurement of specified qubits
// The input is a length M list of random reals between [0, 1) used for
// generating samples.
// The returned value is a map from outcome to its number of samples.
virtual std::unordered_map<uint_t, uint_t> sample_counts(const reg_t &qubits, uint_t shots);
//-----------------------------------------------------------------------
// Operation management
@ -866,7 +865,7 @@ void AerState::apply_mcu(const reg_t &qubits, const double theta, const double p
op.type = Operations::OpType::gate;
op.name = "mcu";
op.qubits = qubits;
op.params = {theta, phi, lambda};
op.params = {theta, phi, lambda, 0.0};
buffer_op(std::move(op));
}
@ -1037,7 +1036,21 @@ std::vector<double> AerState::probabilities(const reg_t &qubits) {
return ((DataMap<ListData, rvector_t>)last_result_.data).value()["s"].value()[0];
}
std::unordered_map<uint_t, uint_t> AerState::sample_measure(const reg_t &qubits, uint_t shots) {
std::vector<std::string> AerState::sample_memory(const reg_t &qubits, uint_t shots) {
assert_initialized();
flush_ops();
std::vector<std::string> ret;
ret.reserve(shots);
std::vector<reg_t> samples = state_->sample_measure(qubits, shots, rng_);
for (auto& sample : samples) {
ret.push_back(Utils::int2string(Utils::reg2int(sample, 2), 2, qubits.size()));
}
return ret;
}
std::unordered_map<uint_t, uint_t> AerState::sample_counts(const reg_t &qubits, uint_t shots) {
assert_initialized();
flush_ops();
@ -1059,15 +1072,6 @@ std::unordered_map<uint_t, uint_t> AerState::sample_measure(const reg_t &qubits,
return ret;
}
double AerState::expval_pauli(const reg_t &qubits, const std::string &pauli) {
assert_initialized();
flush_ops();
return state_->expval_pauli(qubits, pauli);
}
//-----------------------------------------------------------------------
// Operation management
//-----------------------------------------------------------------------

View File

@ -85,12 +85,12 @@ class TestAerState(common.QiskitAerTestCase):
state1 = AerState(seed_simulator=2222)
state1.allocate_qubits(4)
state1.initialize(init_state.data, copy=True)
sample1 = state1.sample_measure()
sample1 = state1.sample_counts()
sv1 = state1.move_to_ndarray()
state2 = AerState(seed_simulator=2222)
state2.initialize(sv1, copy=False)
sample2 = state2.sample_measure()
sample2 = state2.sample_counts()
sv2 = state2.move_to_ndarray()
state2.close()
@ -447,7 +447,7 @@ class TestAerState(common.QiskitAerTestCase):
state = AerState(seed_simulator=11111)
state.allocate_qubits(5)
state.initialize(init_state.data)
actual = state.sample_measure()
actual = state.sample_counts()
for key, value in actual.items():
key_str = f"{key:05b}"

File diff suppressed because it is too large Load Diff