mirror of https://github.com/Qiskit/qiskit-aer.git
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:
parent
329169f80c
commit
3babb7fcc2
|
@ -14,4 +14,5 @@ Qiskit Aer API Reference
|
|||
aer_primitives
|
||||
aer_pulse
|
||||
aer_utils
|
||||
aer_quantum_info
|
||||
parallel
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.. _aer-primitives:
|
||||
|
||||
.. automodule:: qiskit.providers.aer.primitives
|
||||
.. automodule:: qiskit_aer.primitives
|
||||
:no-members:
|
||||
:no-inherited-members:
|
||||
:no-special-members:
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
.. _aer-quantum_info:
|
||||
|
||||
.. automodule:: qiskit_aer.quantum_info
|
||||
:no-members:
|
||||
:no-inherited-members:
|
||||
:no-special-members:
|
|
@ -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__
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
//-----------------------------------------------------------------------
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue