Remove `vals` from `BindingsArray` (#11642)

* Remove vals from BindingsArray

* rename kwvals to data

* Update tests

* stash num_parameters

* Fix BindingsArrayLike

* fix tests of StatevectorSampler

* be more careful about support for 0-d arrays

* constrain dtype to float

* fix StatevectorEstimator tests

* added as_array back in

* set a convention on the last axis of arrays for 1 parameter

---------

Co-authored-by: Takashi Imamichi <imamichi@jp.ibm.com>
Co-authored-by: Christopher J. Wood <cjwood@us.ibm.com>
This commit is contained in:
Ian Hincks 2024-01-31 11:24:44 -05:00 committed by GitHub
parent 588f2df46b
commit 43ea026bab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 345 additions and 369 deletions

View File

@ -15,7 +15,7 @@ Bindings array class
"""
from __future__ import annotations
from collections.abc import Iterable, Mapping, Sequence
from collections.abc import Iterable, Mapping
from itertools import chain, islice
from typing import Union
@ -23,7 +23,6 @@ import numpy as np
from numpy.typing import ArrayLike
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.circuit.parameterexpression import ParameterValueType
from .shape import ShapedMixin, ShapeInput, shape_tuple
@ -34,66 +33,54 @@ class BindingsArray(ShapedMixin):
r"""Stores parameter binding value sets for a :class:`qiskit.QuantumCircuit`.
A single parameter binding set provides numeric values to bind to a circuit with free
:class:`qiskit.circuit.Parameter`\s. An instance of this class stores an array-valued
collection of such sets. The simplest example is a 0-d array consisting of a single
parameter binding set, whereas an n-d array of parameter binding sets represents an
n-d sweep over values.
:class:`qiskit.circuit.Parameter`\s. An instance of this class stores an array-valued collection
of such sets. The simplest example is a 0-d array consisting of a single parameter binding set,
whereas an n-d array of parameter binding sets represents an n-d sweep over values.
The storage format is a list of arrays, ``[vals0, vals1, ...]``, as well as a dictionary of
arrays attached to parameters, ``{params0: kwvals0, ...}``. A convention is used
where the last dimension of each array indexes (a subset of) circuit parameters. For
example, if the last dimension of ``vals1`` is 25, then it represents an array of
possible binding values for 25 distinct parameters, where its leading shape is the
array :attr:`~.shape` of its binding array. This implies a degeneracy of the storage
format: ``[vals, vals1[..., :10], vals1[..., 10:], ...]`` is exactly equivalent to
``[vals0, vals1, ...]`` in the bindings it specifies. This allows flexibility about whether
values for different parameters are stored in one big array, or across several smaller
arrays. It also allows different parameters to use different dtypes.
The storage format is a dictionary of arrays attached to parameters,
``{params_0: values_0,...}``. A convention is used where the last dimension of each array
indexes (a subset of) circuit parameters. For example, if the last dimension of ``values_0`` is
25, then it represents an array of possible binding values for the 25 distinct parameters
``params_0``, where its leading shape is the array :attr:`~.shape` of its binding array. This
allows flexibility about whether values for different parameters are stored in one big array, or
across several smaller arrays.
.. code-block:: python
# 0-d array (i.e. only one binding)
BindingsArray([1, 2, 3], {"a": 4, ("b", "c"): [5, 6]})
BindingsArray({"a": 4, ("b", "c"): [5, 6]})
# single array, last index is parameters
BindingsArray(np.empty((10, 10, 100)))
parameters = tuple(f"a{idx}" for idx in range(100))
BindingsArray({parameters: np.ones((10, 10, 100))})
# multiple arrays, where each last index is parameters. notice that it's smart enough to
# figure out that a missing last dimension corresponds to a single parameter.
BindingsArray(
[np.empty((10, 10, 100)), np.empty((10, 10)), np.empty((10, 10, 20), dtype=complex)],
{("c", "a"): np.empty((10, 10, 2)), "b": np.empty((10, 10))}
{("c", "a"): np.zeros((10, 10, 2)), "b": np.ones((10, 10))}
)
"""
__slots__ = ("_vals", "_kwvals")
def __init__(
self,
vals: ArrayLike | Iterable[ArrayLike] | None = None,
kwvals: Mapping[ParameterLike, Iterable[ParameterValueType]] | ArrayLike | None = None,
data: Mapping[ParameterLike, ArrayLike | float] | None = None,
shape: ShapeInput | None = None,
):
r"""
Initialize a ``BindingsArray``. It can take parameter vectors and dictionaries.
Initialize a :class:`~.BindingsArray`.
The ``shape`` argument does not need to be provided whenever it can unambiguously
be inferred from the provided arrays. Ambiguity arises because an array provided to the
constructor might represent values for either a single parameter, with an implicit missing
last dimension of size ``1``, or for many parameters, where the size of the last dimension
is the number of parameters it is providing values to. This ambiguity can be broken in the
following common ways:
be inferred from the provided arrays. Ambiguity arises whenever the key of an entry of
``data`` contains only one parameter and the corresponding array's shape ends in a one.
In this case, it can't be decided whether that one is an index over parameters, or whether
it should be encorporated in :attr:`~shape`.
* Only a single array is provided to ``vals``, and no arrays to ``kwvals``, in which case
it is assumed that the last dimension is over many parameters.
* Multiple arrays are given whose shapes differ only in the last dimension size.
* Some array is given in ``kwvals`` where the key contains multiple
:class:`~.Parameter`\s, whose length the last dimension of the array must therefore match.
Since :class:`~.Parameter` objects are only allowed to represent float values, this
class casts all given values to float. If an incompatible dtype is given, such as complex
numbers, a ``TypeError`` will be raised.
Args:
vals: One or more arrays, where the last index of each corresponds to
distinct parameters. If their dtypes allow it, concatenating these
arrays over the last axis is equivalent to providing them separately.
kwvals: A mapping from one or more parameters to arrays of values to bind
data: A mapping from one or more parameters to arrays of values to bind
them to, where the last axis is over parameters.
shape: The leading shape of every array in these bindings.
@ -101,33 +88,22 @@ class BindingsArray(ShapedMixin):
ValueError: If all inputs are ``None``.
ValueError: If the shape cannot be automatically inferred from the arrays, or if there
is some inconsistency in the shape of the given arrays.
TypeError: If some of the vaules can't be cast to a float type.
"""
super().__init__()
if vals is None:
vals = []
if kwvals is None:
kwvals = {}
if data is None:
self._data = {}
else:
self._data = {
_format_key((key,))
if isinstance(key, (Parameter, str))
else _format_key(key): np.asarray(val, dtype=float)
for key, val in data.items()
}
vals = [vals] if isinstance(vals, np.ndarray) else [np.array(v, copy=False) for v in vals]
kwvals = {
_format_key((p,))
if isinstance(p, Parameter)
else _format_key(p): np.array(val, copy=False)
for p, val in kwvals.items()
}
if shape is None:
# jump through hoops to find out user's intended shape
shape = _infer_shape(vals, kwvals)
# shape checking, and normalization so that each last index must be over parameters
self._shape = shape_tuple(shape)
for idx, val in enumerate(vals):
vals[idx] = _standardize_shape(val, self._shape)
self._vals: list[np.ndarray] = vals
self._kwvals = kwvals
self._shape = _infer_shape(self._data) if shape is None else shape_tuple(shape)
self._num_parameters = None
self.validate()
@ -137,37 +113,79 @@ class BindingsArray(ShapedMixin):
# on all unspecified trailing dimensions
# separately, we choose to not disallow args which touch the last dimension, even though it
# would not be a particularly friendly way to chop parameters
vals = [val[args] for val in self._vals]
kwvals = {params: val[args] for params, val in self._kwvals.items()}
data = {params: val[args] for params, val in self._data.items()}
try:
shape = next(chain(vals, kwvals.values())).shape[:-1]
shape = next(data.values()).shape[:-1]
except StopIteration:
shape = ()
return BindingsArray(vals, kwvals, shape)
return BindingsArray(data, shape)
def __repr__(self):
descriptions = [f"shape={self.shape}", f"num_parameters={self.num_parameters}"]
if num_kwval_params := sum(val.shape[-1] for val in self._kwvals.values()):
names = list(islice(map(repr, chain.from_iterable(map(_format_key, self._kwvals))), 5))
if len(names) < num_kwval_params:
if self.num_parameters:
names = list(islice(map(repr, chain.from_iterable(map(_format_key, self._data))), 5))
if len(names) < self.num_parameters:
names.append("...")
descriptions.append(f"parameters=[{', '.join(names)}]")
return f"{type(self).__name__}(<{', '.join(descriptions)}>)"
@property
def kwvals(self) -> dict[tuple[str, ...], np.ndarray]:
def data(self) -> dict[tuple[str, ...], np.ndarray]:
"""The keyword values of this array."""
return self._kwvals
return self._data
@property
def num_parameters(self) -> int:
"""The total number of parameters."""
return sum(val.shape[-1] for val in chain(self.vals, self._kwvals.values()))
if self._num_parameters is None:
self._num_parameters = sum(val.shape[-1] for val in self._data.values())
return self._num_parameters
@property
def vals(self) -> list[np.ndarray]:
"""The non-keyword values of this array."""
return self._vals
def as_array(self, parameters: Iterable[ParameterLike] | None = None) -> np.ndarray:
"""Return the contents of this bindings array as a single NumPy array.
The parameters are indexed along the last dimension of the returned array.
Parameters:
parameters: Optional parameters that determine the order of the output.
Returns:
This bindings array as a single NumPy array.
Raises:
ValueError: If ``parameters`` are provided, but do not match those found in ``data``.
"""
position = 0
ret = np.empty(shape_tuple(self.shape, self.num_parameters))
if parameters is None:
# preserve the order of both the dict and the parameters in the keys
for arr in self.data.values():
size = arr.shape[-1]
ret[..., position : position + size] = arr
position += size
else:
# use the order of the provided parameters
parameters = list(parameters)
if len(parameters) != self.num_parameters:
raise ValueError(
f"Expected {self.num_parameters} parameters but {len(parameters)} received."
)
# If we make it through the following loop without a KeyError, we will know that the
# data parameters are a subset of the given parameters. However, the above check
# ensures there are at least as many of them as parameters. Thus we will know that
# set(parameters) == set(chain(*data.values())).
idx_lookup = {_param_name(parameter): idx for idx, parameter in enumerate(parameters)}
for arr_params, arr in self.data.items():
try:
idxs = [idx_lookup[_param_name(param)] + position for param in arr_params]
except KeyError as ex:
missing = next(p for p in map(_param_name, arr_params) if p not in idx_lookup)
raise ValueError(f"Could not find placement for parameter '{missing}'.") from ex
ret[..., idxs] = arr
return ret
def bind(self, circuit: QuantumCircuit, loc: tuple[int, ...]) -> QuantumCircuit:
"""Return a new circuit bound to the values at the provided index.
@ -183,20 +201,13 @@ class BindingsArray(ShapedMixin):
ValueError: If the index doesn't have the right number of values.
"""
if len(loc) != self.ndim:
raise ValueError(f"Expected {loc} to index all dimensions of {self.shape}")
raise ValueError(f"Expected {loc} to index all dimensions of {self.shape}.")
flat_vals = (val for vals in self.vals for val in vals[loc])
if not self._kwvals:
# special case to avoid constructing a dictionary input
return circuit.assign_parameters(list(flat_vals))
parameters = dict(zip(circuit.parameters, flat_vals))
parameters.update(
(param, val)
for params, vals in self._kwvals.items()
parameters = {
param: val
for params, vals in self._data.items()
for param, val in zip(params, vals[loc])
)
}
return circuit.assign_parameters(parameters)
def bind_all(self, circuit: QuantumCircuit) -> np.ndarray:
@ -248,9 +259,8 @@ class BindingsArray(ShapedMixin):
if np.prod(shape, dtype=int) != self.size:
raise ValueError("Reshaping cannot change the total number of elements.")
vals = [val.reshape(shape + val.shape[-1:]) for val in self._vals]
kwvals = {ps: val.reshape(shape + val.shape[-1:]) for ps, val in self._kwvals.items()}
return BindingsArray(vals, kwvals, shape=shape)
data = {ps: val.reshape(shape + val.shape[-1:]) for ps, val in self._data.items()}
return BindingsArray(data, shape=shape)
@classmethod
def coerce(cls, bindings_array: BindingsArrayLike) -> BindingsArray:
@ -262,14 +272,10 @@ class BindingsArray(ShapedMixin):
Returns:
A new bindings array.
"""
if isinstance(bindings_array, Sequence):
bindings_array = np.array(bindings_array)
if bindings_array is None:
bindings_array = cls()
elif isinstance(bindings_array, np.ndarray):
bindings_array = cls(bindings_array)
elif isinstance(bindings_array, Mapping):
bindings_array = cls(kwvals=bindings_array)
bindings_array = cls(data=bindings_array)
elif isinstance(bindings_array, BindingsArray):
return bindings_array
else:
@ -278,8 +284,8 @@ class BindingsArray(ShapedMixin):
def validate(self):
"""Validate the consistency in bindings_array."""
for parameters, val in self._kwvals.items():
val = self._kwvals[parameters] = _standardize_shape(val, self._shape)
for parameters, val in self._data.items():
val = self._data[parameters] = _standardize_shape(val, self._shape)
if len(parameters) != val.shape[-1]:
raise ValueError(
f"Length of {parameters} inconsistent with last dimension of {val}"
@ -307,14 +313,11 @@ def _standardize_shape(val: np.ndarray, shape: tuple[int, ...]) -> np.ndarray:
return val
def _infer_shape(
vals: list[np.ndarray], kwvals: dict[tuple[Parameter, ...], np.ndarray]
) -> tuple[int, ...]:
def _infer_shape(data: dict[tuple[Parameter, ...], np.ndarray]) -> tuple[int, ...]:
"""Return a shape tuple that consistently defines the leading dimensions of all arrays.
Args:
vals: A list of arrays.
kwvals: A mapping from tuples to arrays, where the length of each tuple should match the
data: A mapping from tuples to arrays, where the length of each tuple should match the
last dimension of the corresponding array.
Returns:
@ -332,31 +335,28 @@ def _infer_shape(
else:
only_possible_shapes.intersection_update(possible_shapes)
for parameters, val in kwvals.items():
if len(parameters) > 1:
# here, the last dimension _has_ to be over parameters
for parameters, val in data.items():
if len(parameters) != 1:
# the last dimension _has_ to be over parameters
examine_array(val.shape[:-1])
elif val.shape and val.shape[-1] == 1:
# this case is a convention, and separated from the previous case for clarity:
# if the last axis is 1-d, make an assumption that it is for our 1 parameter
examine_array(val.shape[:-1])
elif val.shape == () or val.shape == (1,) or val.shape[-1] != 1:
# here, if the last dimension is not 1 or shape is () or (1,) then the shape is the shape
examine_array(val.shape)
else:
# here, the last dimension could be over parameters or not
examine_array(val.shape, val.shape[:-1])
if len(vals) == 1 and len(kwvals) == 0:
examine_array(vals[0].shape[:-1])
elif len(vals) == 0 and len(kwvals) == 0:
examine_array(())
else:
for val in vals:
# here, the last dimension could be over parameters or not
examine_array(val.shape, val.shape[:-1])
# otherwise, the user has left off the last axis and we'll be nice to them
examine_array(val.shape)
if only_possible_shapes is None:
return ()
if len(only_possible_shapes) == 1:
return next(iter(only_possible_shapes))
elif len(only_possible_shapes) == 0:
raise ValueError("Could not find any consistent shape.")
raise ValueError("Could not unambiguously determine the intended shape; specify shape manually")
raise ValueError(
"Could not unambiguously determine the intended shape, all shapes in "
f"{only_possible_shapes} are consistent with the input; specify shape manually."
)
def _format_key(key: tuple[Parameter | str, ...]):
@ -369,8 +369,6 @@ def _param_name(param: Parameter | str) -> str:
BindingsArrayLike = Union[
BindingsArray,
ArrayLike,
"Mapping[Parameter, ArrayLike]",
"Sequence[ArrayLike]",
"Mapping[Union[Parameter, str, Iterable[Union[Parameter, str]]], ArrayLike]",
None,
]

View File

@ -18,6 +18,7 @@ Estimator Pub class
from __future__ import annotations
from numbers import Real
from collections.abc import Mapping
from typing import Tuple, Union
import numpy as np
@ -134,9 +135,18 @@ class EstimatorPub(ShapedMixin):
)
circuit = pub[0]
observables = ObservablesArray.coerce(pub[1])
parameter_values = BindingsArray.coerce(pub[2]) if len(pub) > 2 else None
if len(pub) > 2 and pub[2] is not None:
values = pub[2]
if not isinstance(values, Mapping):
values = {tuple(circuit.parameters): values}
parameter_values = BindingsArray.coerce(values)
else:
parameter_values = None
if len(pub) > 3 and pub[3] is not None:
precision = pub[3]
return cls(
circuit=circuit,
observables=observables,

View File

@ -17,6 +17,7 @@ Sampler Pub class
from __future__ import annotations
from collections.abc import Mapping
from typing import Tuple, Union
from numbers import Integral
@ -114,7 +115,15 @@ class SamplerPub(ShapedMixin):
f"The length of pub must be 1, 2 or 3, but length {len(pub)} is given."
)
circuit = pub[0]
parameter_values = BindingsArray.coerce(pub[1]) if len(pub) > 1 else None
if len(pub) > 1 and pub[1] is not None:
values = pub[1]
if not isinstance(values, Mapping):
values = {tuple(circuit.parameters): values}
parameter_values = BindingsArray.coerce(values)
else:
parameter_values = None
if len(pub) > 2 and pub[2] is not None:
shots = pub[2]
return cls(circuit=circuit, parameter_values=parameter_values, shots=shots, validate=True)

View File

@ -39,29 +39,25 @@ class BindingsArrayTestCase(QiskitTestCase):
def test_construction_failures(self):
"""Test all the possible construction failures"""
with self.assertRaisesRegex(ValueError, "inconsistent with last dimension of"):
BindingsArray(kwvals={Parameter("a"): [0, 1]}, shape=())
BindingsArray(data={Parameter("a"): [0, 1]}, shape=())
with self.assertRaisesRegex(ValueError, r"Array with shape \(\) inconsistent with \(1,\)"):
BindingsArray(kwvals={Parameter("a"): 0}, shape=(1,))
with self.assertRaisesRegex(ValueError, "ambiguous"):
# could have shape (1,) or (1, 1)
BindingsArray(kwvals={Parameter("a"): [[1]]})
BindingsArray(data={Parameter("a"): 0}, shape=(1,))
with self.assertRaisesRegex(ValueError, r"\(3, 5\) inconsistent with \(2,\)"):
BindingsArray(np.empty((3, 5)), shape=2)
with self.assertRaisesRegex(ValueError, "ambiguous"):
# could have shape (2,) or ()
BindingsArray([np.empty(2), np.empty(2)])
BindingsArray({"a": np.empty((3, 5))}, shape=2)
with self.assertRaisesRegex(ValueError, "Could not find any consistent shape"):
BindingsArray([np.empty((5, 8, 3)), np.empty((4, 7, 2))])
BindingsArray({"a": np.empty((5, 8, 3)), "b": np.empty((4, 7, 2))})
with self.assertRaisesRegex(ValueError, "inconsistent with last dimension of"):
BindingsArray(
vals=np.empty((5, 10)),
kwvals={(Parameter("a"), Parameter("b")): np.empty((5, 10, 3))},
data={(Parameter("a"), Parameter("b")): np.empty((5, 10, 3))},
)
with self.assertRaisesRegex(TypeError, "complex"):
BindingsArray(
data={"a": 1j},
)
def test_repr(self):
@ -70,33 +66,36 @@ class BindingsArrayTestCase(QiskitTestCase):
# raise an error. it is more sensible for humans to detect a deficiency in the formatting
# itself, should one be uncovered
self.assertTrue(repr(BindingsArray()).startswith("BindingsArray"))
self.assertTrue(repr(BindingsArray(np.empty((1, 2, 3)))).startswith("BindingsArray"))
self.assertTrue(
repr(
BindingsArray([1], {"p": 2, "q": 5, ("a", "b", "c", "d"): [1, 22, 4, 5]})
).startswith("BindingsArray")
repr(BindingsArray({"p": 2, "q": 5, ("a", "b", "c", "d"): [1, 22, 4, 5]})).startswith(
"BindingsArray"
)
)
def test_bind_at_idx(self):
def test_bind(self):
"""Test binding at a specified index"""
vals = np.linspace(0, 1, 1000).reshape((5, 4, 50))
expected_circuit = self.circuit.assign_parameters(vals[2, 3])
parameters = tuple(self.circuit.parameters)
ba = BindingsArray(vals)
ba = BindingsArray({parameters: vals})
self.assertEqual(ba.bind(self.circuit, (2, 3)), expected_circuit)
ba = BindingsArray([vals[:, :, :20], vals[:, :, 20:27], vals[:, :, 27:]])
self.assertEqual(ba.bind(self.circuit, (2, 3)), expected_circuit)
ba = BindingsArray(vals[:, :, :20], {tuple(self.params[20:]): vals[:, :, 20:]})
ba = BindingsArray(
{
parameters[:20]: vals[:, :, :20],
parameters[20:27]: vals[:, :, 20:27],
parameters[27:]: vals[:, :, 27:],
}
)
self.assertEqual(ba.bind(self.circuit, (2, 3)), expected_circuit)
order = np.arange(30, 50, dtype=int)
np.random.default_rng().shuffle(order)
ba = BindingsArray(
[vals[:, :, :20], vals[:, :, 20:25]],
{
tuple(self.params[25:30]): vals[:, :, 25:30],
parameters[0:25]: vals[:, :, :25],
parameters[25:30]: vals[:, :, 25:30],
tuple(self.params[i] for i in order): vals[:, :, order],
},
)
@ -107,59 +106,25 @@ class BindingsArrayTestCase(QiskitTestCase):
# this test assumes bind_all() is implemented via bind_at_idx(), which we have already
# tested. so here, we just test that it gets the order right
vals = np.linspace(0, 1, 300).reshape((2, 3, 50))
bound_circuits = BindingsArray(vals).bind_all(self.circuit)
bound_circuits = BindingsArray({tuple(self.circuit.parameters): vals}).bind_all(
self.circuit
)
self.assertIsInstance(bound_circuits, np.ndarray)
self.assertEqual(bound_circuits.shape, (2, 3))
for idx in np.ndindex((2, 3)):
self.assertEqual(bound_circuits[idx], self.circuit.assign_parameters(vals[idx]))
def test_properties(self):
"""Test properties"""
with self.subTest("binding a list"):
vals = np.linspace(0, 1, 50).tolist()
ba = BindingsArray(vals)
self.assertEqual(ba.num_parameters, 50)
self.assertEqual(ba.ndim, 0)
self.assertEqual(ba.shape, ())
self.assertEqual(ba.size, 1)
self.assertEqual(ba.kwvals, {})
np.testing.assert_allclose(ba.vals, np.array(vals)[:, np.newaxis])
with self.subTest("binding a single array"):
vals = np.linspace(0, 1, 300).reshape((2, 3, 50))
ba = BindingsArray(vals)
self.assertEqual(ba.num_parameters, 50)
self.assertEqual(ba.ndim, 2)
self.assertEqual(ba.shape, (2, 3))
self.assertEqual(ba.size, 6)
self.assertEqual(ba.kwvals, {})
np.testing.assert_allclose(ba.vals, vals.reshape((1, 2, 3, 50)))
with self.subTest("binding multiple arrays"):
vals = np.linspace(0, 1, 300).reshape((2, 3, 50))
ba = BindingsArray([vals[:, :, :20], vals[:, :, 20:]])
self.assertEqual(ba.num_parameters, 50)
self.assertEqual(ba.ndim, 2)
self.assertEqual(ba.shape, (2, 3))
self.assertEqual(ba.size, 6)
self.assertEqual(ba.kwvals, {})
self.assertEqual(len(ba.vals), 2)
np.testing.assert_allclose(ba.vals[0], vals[:, :, :20])
np.testing.assert_allclose(ba.vals[1], vals[:, :, 20:])
def test_ravel(self):
"""Test ravel"""
vals = np.linspace(0, 1, 300).reshape((2, 3, 50))
ba = BindingsArray(vals)
ba = BindingsArray({tuple(self.circuit.parameters): vals})
flat = ba.ravel()
self.assertEqual(flat.num_parameters, 50)
self.assertEqual(flat.ndim, 1)
self.assertEqual(flat.shape, (6,))
self.assertEqual(flat.size, 6)
self.assertEqual(flat.kwvals, {})
flat_vals = vals.reshape(-1, 50)
np.testing.assert_allclose(flat.vals, flat_vals.reshape((1, 6, 50)))
bound_circuits = list(flat.bind_all(self.circuit).reshape(6))
self.assertEqual(len(bound_circuits), 6)
@ -170,17 +135,18 @@ class BindingsArrayTestCase(QiskitTestCase):
"""Test reshape"""
with self.subTest("reshape"):
vals = np.linspace(0, 1, 300).reshape((2, 3, 50))
ba = BindingsArray(vals)
ba = BindingsArray({tuple(self.circuit.parameters): vals})
reshape_ba = ba.reshape((3, 2))
self.assertEqual(reshape_ba.num_parameters, 50)
self.assertEqual(reshape_ba.ndim, 2)
self.assertEqual(reshape_ba.shape, (3, 2))
self.assertEqual(reshape_ba.size, 6)
self.assertEqual(reshape_ba.kwvals, {})
reshape_vals = vals.reshape((3, 2, 50))
np.testing.assert_allclose(reshape_ba.vals, reshape_vals.reshape((1, 3, 2, 50)))
np.testing.assert_allclose(
next(iter(reshape_ba.data.values())), vals.reshape((3, 2, 50))
)
bound_circuits = list(reshape_ba.bind_all(self.circuit).ravel())
reshape_vals = vals.reshape((3, 2, -1))
self.assertEqual(len(bound_circuits), 6)
self.assertEqual(bound_circuits[0], self.circuit.assign_parameters(reshape_vals[0, 0]))
self.assertEqual(bound_circuits[1], self.circuit.assign_parameters(reshape_vals[0, 1]))
@ -191,16 +157,15 @@ class BindingsArrayTestCase(QiskitTestCase):
with self.subTest("flatten"):
vals = np.linspace(0, 1, 300).reshape((2, 3, 50))
ba = BindingsArray(vals)
ba = BindingsArray({tuple(self.circuit.parameters): vals})
reshape_ba = ba.reshape(6)
self.assertEqual(reshape_ba.num_parameters, 50)
self.assertEqual(reshape_ba.ndim, 1)
self.assertEqual(reshape_ba.shape, (6,))
self.assertEqual(reshape_ba.size, 6)
self.assertEqual(reshape_ba.kwvals, {})
reshape_vals = vals.reshape(-1, 50)
np.testing.assert_allclose(reshape_ba.vals, reshape_vals.reshape((1, 6, 50)))
np.testing.assert_allclose(next(iter(reshape_ba.data.values())), vals.reshape(6, 50))
reshape_vals = vals.reshape(-1, 50)
bound_circuits = list(reshape_ba.bind_all(self.circuit).ravel())
self.assertEqual(len(bound_circuits), 6)
for i in range(6):
@ -208,7 +173,6 @@ class BindingsArrayTestCase(QiskitTestCase):
with self.subTest("various_formats"):
ba = BindingsArray(
[np.empty((16, 7)), np.empty((16, 3))],
{Parameter("a"): np.empty(16), (Parameter("b"), Parameter("c")): np.empty((16, 2))},
)
@ -233,32 +197,30 @@ class BindingsArrayTestCase(QiskitTestCase):
reshaped_ba = ba.reshape(input_shape)
self.assertEqual(reshaped_ba.shape, shape)
def test_kwvals(self):
"""Test constructor with kwvals"""
def test_data(self):
"""Test constructor with data"""
with self.subTest("binding a single value"):
vals = np.linspace(0, 1, 50)
kwvals = {self.params: vals}
ba = BindingsArray(kwvals=kwvals)
data = {self.params: vals}
ba = BindingsArray(data=data)
self.assertEqual(ba.num_parameters, 50)
self.assertEqual(ba.ndim, 0)
self.assertEqual(ba.shape, ())
self.assertEqual(ba.size, 1)
self.assertEqual(ba.vals, [])
self.assertEqual(ba.kwvals, {tuple(param.name for param in self.params): vals})
self.assertEqual(ba.data, {tuple(param.name for param in self.params): vals})
bound_circuit = ba.bind(self.circuit, ())
self.assertEqual(bound_circuit, self.circuit.assign_parameters(vals))
with self.subTest("binding an array"):
vals = np.linspace(0, 1, 300).reshape((2, 3, 50))
kwvals = {self.params: vals}
ba = BindingsArray(kwvals=kwvals)
data = {self.params: vals}
ba = BindingsArray(data=data)
self.assertEqual(ba.num_parameters, 50)
self.assertEqual(ba.ndim, 2)
self.assertEqual(ba.shape, (2, 3))
self.assertEqual(ba.size, 6)
self.assertEqual(ba.vals, [])
self.assertEqual(ba.kwvals, {tuple(param.name for param in self.params): vals})
self.assertEqual(ba.data, {tuple(param.name for param in self.params): vals})
bound_circuits = ba.bind_all(self.circuit)
self.assertEqual(bound_circuits.shape, (2, 3))
@ -271,106 +233,59 @@ class BindingsArrayTestCase(QiskitTestCase):
with self.subTest("binding a single param"):
vals = np.linspace(0, 1, 50)
kwvals = {self.params[0]: vals}
ba = BindingsArray(kwvals=kwvals)
data = {self.params[0]: vals}
ba = BindingsArray(data=data)
self.assertEqual(ba.num_parameters, 1)
self.assertEqual(ba.ndim, 1)
self.assertEqual(ba.shape, (50,))
self.assertEqual(ba.size, 50)
self.assertEqual(ba.vals, [])
self.assertEqual(list(ba.kwvals.keys()), [(self.params[0].name,)])
np.testing.assert_allclose(list(ba.kwvals.values()), [vals[..., np.newaxis]])
self.assertEqual(list(ba.data.keys()), [(self.params[0].name,)])
np.testing.assert_allclose(list(ba.data.values()), [vals[..., np.newaxis]])
def test_vals_kwvals(self):
"""Test constructor with vals and kwvals"""
with self.subTest("binding a single value"):
vals = np.linspace(0, 1, 50)
kwvals = {tuple(self.params[20:]): vals[20:]}
ba = BindingsArray(vals=vals[:20], kwvals=kwvals)
self.assertEqual(ba.num_parameters, 50)
self.assertEqual(ba.ndim, 0)
self.assertEqual(ba.shape, ())
self.assertEqual(ba.size, 1)
np.testing.assert_allclose(ba.vals, vals[np.newaxis, :20])
self.assertEqual(ba.kwvals, {tuple(p.name for p in k): v for k, v in kwvals.items()})
bound_circuit = ba.bind(self.circuit, ())
self.assertEqual(bound_circuit, self.circuit.assign_parameters(vals))
with self.subTest("binding an array"):
vals = np.linspace(0, 1, 300).reshape((2, 3, 50))
kwvals = {tuple(self.params[20:]): vals[:, :, 20:]}
ba = BindingsArray(vals=vals[:, :, :20], kwvals=kwvals)
self.assertEqual(ba.num_parameters, 50)
self.assertEqual(ba.ndim, 2)
self.assertEqual(ba.shape, (2, 3))
self.assertEqual(ba.size, 6)
np.testing.assert_allclose(ba.vals, vals[np.newaxis, :, :, :20])
self.assertEqual(ba.kwvals, {tuple(p.name for p in k): v for k, v in kwvals.items()})
bound_circuits = ba.bind_all(self.circuit)
self.assertEqual(bound_circuits.shape, (2, 3))
self.assertEqual(bound_circuits[0, 0], self.circuit.assign_parameters(vals[0, 0]))
self.assertEqual(bound_circuits[0, 1], self.circuit.assign_parameters(vals[0, 1]))
self.assertEqual(bound_circuits[0, 2], self.circuit.assign_parameters(vals[0, 2]))
self.assertEqual(bound_circuits[1, 0], self.circuit.assign_parameters(vals[1, 0]))
self.assertEqual(bound_circuits[1, 1], self.circuit.assign_parameters(vals[1, 1]))
self.assertEqual(bound_circuits[1, 2], self.circuit.assign_parameters(vals[1, 2]))
with self.subTest("len(val) == 1 and len(kwvals) > 0"):
ba = BindingsArray(
vals=np.empty((5, 10)),
kwvals={(Parameter("a"), Parameter("b")): np.empty((5, 10, 2))},
)
self.assertEqual(ba.num_parameters, 3)
self.assertEqual(ba.ndim, 2)
self.assertEqual(ba.shape, (5, 10))
self.assertEqual(ba.size, 50)
def test_simple_kwvals(self):
"""Test simple constructions of BindingsArrays using kwvals."""
def test_simple_data(self):
"""Test simple constructions of BindingsArrays using data."""
with self.subTest("Single number kwval 1"):
ba = BindingsArray(kwvals={Parameter("a"): 1.0})
ba = BindingsArray({Parameter("a"): 1.0})
self.assertEqual(ba.shape, ())
with self.subTest("Single number kwval 1 with shape"):
ba = BindingsArray(kwvals={Parameter("a"): 1.0}, shape=())
ba = BindingsArray(data={Parameter("a"): 1.0}, shape=())
self.assertEqual(ba.shape, ())
with self.subTest("Single number kwval 1 ndarray"):
ba = BindingsArray(kwvals={Parameter("a"): np.array(1.0)})
ba = BindingsArray(data={Parameter("a"): np.array(1.0)})
self.assertEqual(ba.shape, ())
with self.subTest("Single number kwval 2"):
ba = BindingsArray(kwvals={Parameter("a"): 1.0, Parameter("b"): 0.0})
ba = BindingsArray(data={Parameter("a"): 1.0, Parameter("b"): 0.0})
self.assertEqual(ba.shape, ())
with self.subTest("Empty kwval"):
ba = BindingsArray(kwvals={Parameter("a"): []})
ba = BindingsArray(data={Parameter("a"): []})
self.assertEqual(ba.shape, (0,))
with self.subTest("Single kwval"):
ba = BindingsArray(kwvals={Parameter("a"): [0.0]})
self.assertEqual(ba.shape, (1,))
ba = BindingsArray(data={Parameter("a"): [0.0]})
self.assertEqual(ba.shape, ())
with self.subTest("Single kwval ndarray"):
ba = BindingsArray(kwvals={Parameter("a"): np.array([0.0])})
self.assertEqual(ba.shape, (1,))
ba = BindingsArray(data={Parameter("a"): np.array([0.0])})
self.assertEqual(ba.shape, ())
with self.subTest("Multi kwval"):
ba = BindingsArray(kwvals={Parameter("a"): [0.0, 1.0]})
ba = BindingsArray(data={Parameter("a"): [0.0, 1.0]})
self.assertEqual(ba.shape, (2,))
with self.subTest("Multiple kwvals empty"):
ba = BindingsArray(kwvals={Parameter("a"): [], Parameter("b"): []})
with self.subTest("Multiple data empty"):
ba = BindingsArray(data={Parameter("a"): [], Parameter("b"): []})
self.assertEqual(ba.shape, (0,))
with self.subTest("Multiple kwvals single"):
ba = BindingsArray(kwvals={Parameter("a"): [0.0], Parameter("b"): [1.0]})
self.assertEqual(ba.shape, (1,))
with self.subTest("Multiple data single"):
ba = BindingsArray(data={Parameter("a"): [0.0], Parameter("b"): [1.0]})
self.assertEqual(ba.shape, ())
with self.subTest("Multiple kwvals multi"):
ba = BindingsArray(kwvals={Parameter("a"): [0.0, 1.0], Parameter("b"): [1.0, 0.0]})
with self.subTest("Multiple data multi"):
ba = BindingsArray(data={Parameter("a"): [0.0, 1.0], Parameter("b"): [1.0, 0.0]})
self.assertEqual(ba.shape, (2,))
def test_empty(self):
@ -380,58 +295,17 @@ class BindingsArrayTestCase(QiskitTestCase):
self.assertEqual(ba.shape, ())
with self.subTest("Empty 2"):
ba = BindingsArray([], shape=())
ba = BindingsArray({}, shape=())
self.assertEqual(ba.shape, ())
with self.subTest("Empty 3"):
ba = BindingsArray([], {}, shape=())
self.assertEqual(ba.shape, ())
with self.subTest("Empty 4"):
ba = BindingsArray(shape=())
self.assertEqual(ba.shape, ())
with self.subTest("Empty 5"):
ba = BindingsArray(kwvals={}, shape=())
ba = BindingsArray(data={}, shape=())
self.assertEqual(ba.shape, ())
def test_simple_vals(self):
"""Test simple constructions of BindingsArrays using vals."""
with self.subTest("0-d vals"):
ba = BindingsArray([1, 2, 3])
self.assertEqual(ba.shape, ())
# ba.vals => [array([1]), array([2]), array([3])]
self.assertEqual(len(ba.vals), 3)
self.assertEqual(ba.vals[0], 1)
self.assertEqual(ba.vals[1], 2)
self.assertEqual(ba.vals[2], 3)
with self.subTest("1-d vals"):
ba = BindingsArray([[1, 2, 3]])
self.assertEqual(ba.shape, ())
# ba.vals => [array([1, 2, 3])]
self.assertEqual(len(ba.vals), 1)
np.testing.assert_allclose(ba.vals[0], [1, 2, 3])
with self.subTest("1-d vals ndarray"):
ba = BindingsArray(np.array([1, 2, 3]))
self.assertEqual(ba.shape, ())
# ba.vals => [array([1, 2, 3])]
self.assertEqual(len(ba.vals), 1)
np.testing.assert_allclose(ba.vals[0], [1, 2, 3])
with self.subTest("2-d vals"):
ba = BindingsArray([[[1, 2, 3]]])
self.assertEqual(ba.shape, (1,))
self.assertEqual(len(ba.vals), 1)
np.testing.assert_allclose(ba.vals[0], [[1, 2, 3]])
with self.subTest("2-d vals ndarray"):
ba = BindingsArray(np.array([[1, 2, 3]]))
self.assertEqual(ba.shape, (1,))
self.assertEqual(len(ba.vals), 1)
np.testing.assert_allclose(ba.vals[0], [[1, 2, 3]])
def test_coerce(self):
"""Test the coerce() method."""
# BindingsArray passthrough
@ -448,3 +322,94 @@ class BindingsArrayTestCase(QiskitTestCase):
arg = None
ba = BindingsArray.coerce(None)
self.assertEqual(ba.num_parameters, 0)
@ddt.data(
((0,), 0, True),
((), 0, True),
((0,), 1, True), # this shouldn't work because we don't know if shape is (0,) or (0, 1)
((0,), 2, True),
((1,), 0, True),
((0,), 0, False),
((2, 3), 0, True),
((), 0, False),
((0,), 1, False),
((0,), 2, False),
((1,), 0, False),
((2, 3), 0, False),
)
@ddt.unpack
def test_shape_with_0(self, shape, num_params, do_inference):
"""Tests various combinations of inputs that include 0-d axes."""
ba = BindingsArray(
{tuple(f"a{idx}" for idx in range(num_params)): np.empty(shape + (num_params,))},
shape=(None if do_inference else shape),
)
self.assertEqual(ba.shape, shape)
self.assertEqual(ba.num_parameters, num_params)
if num_params == 1:
# if there is 1 parameter, we should be allowed to leave it off as an axis
ba = BindingsArray(
{tuple(f"a{idx}" for idx in range(num_params)): np.empty(shape)},
shape=(None if do_inference else shape),
)
self.assertEqual(ba.shape, shape)
self.assertEqual(ba.num_parameters, num_params)
@ddt.idata([(True, True), (True, False), (False, True), (False, False)])
@ddt.unpack
def test_as_array_bad_param_raises(self, kwvals_str, args_str):
"""Test as_array() raises when a parameter key is missing."""
kwval_param = lambda param: Parameter(param) if kwvals_str else param
args_param = lambda param: Parameter(param) if args_str else param
ba = BindingsArray({(kwval_param("a"), kwval_param("b")): np.empty((5, 2))})
with self.assertRaisesRegex(ValueError, "Expected 2 parameters but 1 received"):
ba.as_array([args_param("b")])
ba = BindingsArray({(kwval_param("a"), kwval_param("b")): np.empty((5, 2))})
with self.assertRaisesRegex(ValueError, "Expected 2 parameters but 3 received"):
ba.as_array([args_param("b"), args_param("a"), args_param("b")])
with self.assertRaisesRegex(ValueError, "Could not find placement for parameter 'a'"):
ba.as_array([args_param("b"), args_param("c")])
@ddt.idata([(True, True), (True, False), (False, True), (False, False)])
@ddt.unpack
def test_as_array(self, kwvals_str, args_str):
"""Test as_array() works for various combinations of string/Parameter inputs."""
kwval_param = lambda param: Parameter(param) if kwvals_str else param
args_param = lambda param: Parameter(param) if args_str else param
arr_a = np.linspace(0, 20, 250).reshape((25, 5, 2))
arr_b = np.linspace(0, 5, 375).reshape((25, 5, 3))
ba = BindingsArray(
{
(kwval_param("a"), kwval_param("b")): arr_a,
(kwval_param("c"), kwval_param("d"), kwval_param("e")): arr_b,
}
)
np.testing.assert_allclose(ba.as_array(), np.concatenate([arr_a, arr_b], axis=2))
params = map(args_param, "dabec")
expected = [arr_b[..., 1], arr_a[..., 0], arr_a[..., 1], arr_b[..., 2], arr_b[..., 0]]
expected = np.concatenate([arr[..., None] for arr in expected], axis=2)
np.testing.assert_allclose(ba.as_array(params), expected)
def test_as_array_cases(self):
"""Test as_array() works in various edge cases."""
ba = BindingsArray({(): np.ones((1, 2, 0))})
arr = ba.as_array()
self.assertEqual(arr.shape, (1, 2, 0))
ba = BindingsArray({(): np.ones((0,))})
arr = ba.as_array()
self.assertEqual(arr.shape, (0,))
ba = BindingsArray({("a", "b", "c"): np.ones((0, 3))})
arr = ba.as_array()
self.assertEqual(arr.shape, (0, 3))
ba = BindingsArray({"a": np.ones((0, 1))}, shape=(0,))
arr = ba.as_array()
self.assertEqual(arr.shape, (0, 1))

View File

@ -30,7 +30,7 @@ class EstimatorPubTestCase(QiskitTestCase):
circuit = QuantumCircuit(2)
circuit.rx(params[0], 0)
circuit.ry(params[1], 1)
parameter_values = BindingsArray(kwvals={params: np.ones((10, 2))})
parameter_values = BindingsArray(data={params: np.ones((10, 2))})
observables = ObservablesArray([{"XX": 0.1}])
precision = 0.05
@ -79,7 +79,9 @@ class EstimatorPubTestCase(QiskitTestCase):
"""Test unparameterized circuit raises for parameter values"""
circuit = QuantumCircuit(2)
obs = ObservablesArray([{"XY": 1}])
parameter_values = BindingsArray(np.zeros((2, num_params)), shape=2)
parameter_values = BindingsArray(
{(f"a{idx}" for idx in range(num_params)): np.zeros((2, num_params))}, shape=2
)
if num_params == 0:
EstimatorPub(circuit, obs, parameter_values=parameter_values)
return
@ -104,7 +106,9 @@ class EstimatorPubTestCase(QiskitTestCase):
circuit.ry(params[1], 1)
obs = ObservablesArray([{"XY": 1}])
parameter_values = BindingsArray(np.zeros((2, num_params)), shape=2)
parameter_values = BindingsArray(
{(f"a{idx}" for idx in range(num_params)): np.zeros((2, num_params))}, shape=2
)
if num_params == len(params):
EstimatorPub(circuit, obs, parameter_values=parameter_values)
@ -118,7 +122,7 @@ class EstimatorPubTestCase(QiskitTestCase):
"""Test Passing in a shaped array with no parameters works"""
circuit = QuantumCircuit(2)
obs = ObservablesArray({"XZ": 1})
parameter_values = BindingsArray(np.zeros((*shape, 0)), shape=shape)
parameter_values = BindingsArray({(): np.zeros((*shape, 0))}, shape=shape)
pub = EstimatorPub(circuit, obs, parameter_values=parameter_values)
self.assertEqual(pub.shape, shape)
@ -170,7 +174,7 @@ class EstimatorPubTestCase(QiskitTestCase):
pub1 = EstimatorPub(
circuit,
obs,
parameter_values=BindingsArray(kwvals={params: np.ones((10, 2))}),
parameter_values=BindingsArray(data={params: np.ones((10, 2))}),
precision=0.01,
)
pub2 = EstimatorPub.coerce(pub1, precision=precision)
@ -187,7 +191,7 @@ class EstimatorPubTestCase(QiskitTestCase):
pub1 = EstimatorPub(
circuit,
obs,
parameter_values=BindingsArray(kwvals={params: np.ones((10, 2))}),
parameter_values=BindingsArray(data={params: np.ones((10, 2))}),
precision=None,
)
pub2 = EstimatorPub.coerce(pub1, precision=precision)
@ -382,7 +386,7 @@ class EstimatorPubTestCase(QiskitTestCase):
circuit.rz(params[2 * idx + 1], 1)
obs = ObservablesArray([{"XX": 1}] * np.prod(obs_shape, dtype=int)).reshape(obs_shape)
params = BindingsArray(np.empty(params_shape + (6,)))
params = BindingsArray({tuple(params): np.empty(params_shape + (6,))})
pub = EstimatorPub(circuit, obs, params)
self.assertEqual(obs.shape, obs_shape)
@ -409,7 +413,7 @@ class EstimatorPubTestCase(QiskitTestCase):
circuit.rz(params[2 * idx + 1], 1)
obs = ObservablesArray([{"XX": 1}] * np.prod(obs_shape, dtype=int)).reshape(obs_shape)
params = BindingsArray(np.empty(params_shape + (6,)))
params = BindingsArray({tuple(params): np.empty(params_shape + (6,))})
self.assertEqual(obs.shape, obs_shape)
self.assertEqual(params.shape, params_shape)

View File

@ -31,7 +31,7 @@ class SamplerPubTestCase(QiskitTestCase):
circuit.rx(params[0], 0)
circuit.ry(params[1], 1)
circuit.measure_all()
parameter_values = BindingsArray(kwvals={params: np.ones((10, 2))})
parameter_values = BindingsArray(data={params: np.ones((10, 2))})
shots = 1000
pub = SamplerPub(
@ -70,7 +70,9 @@ class SamplerPubTestCase(QiskitTestCase):
def test_validate_no_parameters(self, num_params):
"""Test unparameterized circuit raises for parameter values"""
circuit = QuantumCircuit(2)
parameter_values = BindingsArray(np.zeros((2, num_params)), shape=2)
parameter_values = BindingsArray(
{(f"a{idx}" for idx in range(num_params)): np.zeros((2, num_params))}, shape=2
)
if num_params == 0:
SamplerPub(circuit, parameter_values=parameter_values)
return
@ -86,7 +88,9 @@ class SamplerPubTestCase(QiskitTestCase):
circuit.rx(params[0], 0)
circuit.ry(params[1], 1)
circuit.measure_all()
parameter_values = BindingsArray(np.zeros((2, num_params)), shape=2)
parameter_values = BindingsArray(
{(f"a{idx}" for idx in range(num_params)): np.zeros((2, num_params))}, shape=2
)
if num_params == len(params):
SamplerPub(circuit, parameter_values=parameter_values)
return
@ -98,7 +102,7 @@ class SamplerPubTestCase(QiskitTestCase):
def test_shaped_zero_parameter_values(self, shape):
"""Test Passing in a shaped array with no parameters works"""
circuit = QuantumCircuit(2)
parameter_values = BindingsArray(np.zeros((*shape, 0)), shape=shape)
parameter_values = BindingsArray({(): np.zeros((*shape, 0))}, shape=shape)
pub = SamplerPub(circuit, parameter_values=parameter_values)
self.assertEqual(pub.shape, shape)
@ -145,7 +149,7 @@ class SamplerPubTestCase(QiskitTestCase):
circuit.measure_all()
pub1 = SamplerPub(
circuit=circuit,
parameter_values=BindingsArray(kwvals={params: np.ones((10, 2))}),
parameter_values=BindingsArray(data={params: np.ones((10, 2))}),
shots=1000,
)
pub2 = SamplerPub.coerce(pub1, shots=shots)
@ -161,7 +165,7 @@ class SamplerPubTestCase(QiskitTestCase):
circuit.measure_all()
pub1 = SamplerPub(
circuit=circuit,
parameter_values=BindingsArray(kwvals={params: np.ones((10, 2))}),
parameter_values=BindingsArray(data={params: np.ones((10, 2))}),
shots=None,
)
pub2 = SamplerPub.coerce(pub1, shots=shots)

View File

@ -94,10 +94,10 @@ class TestStatevectorEstimator(QiskitTestCase):
theta1, theta2, theta3 = self.theta
obs1 = ObservablesArray.coerce([hamiltonian1, hamiltonian3])
bind1 = BindingsArray.coerce([theta1, theta3])
bind1 = BindingsArray.coerce({tuple(psi1.parameters): [theta1, theta3]})
pub1 = EstimatorPub(psi1, obs1, bind1)
obs2 = ObservablesArray.coerce(hamiltonian2)
bind2 = BindingsArray.coerce(theta2)
bind2 = BindingsArray.coerce({tuple(psi2.parameters): theta2})
pub2 = EstimatorPub(psi2, obs2, bind2)
estimator = StatevectorEstimator()
@ -135,10 +135,7 @@ class TestStatevectorEstimator(QiskitTestCase):
op = SparsePauliOp("Z")
param_vals = [
[np.pi],
[[np.pi]],
np.array([np.pi]),
np.array([[np.pi]]),
[np.array([np.pi])],
]
target = [-1]
for val in param_vals:

View File

@ -78,9 +78,9 @@ class TestStatevectorSampler(QiskitTestCase):
def test_sampler_run(self):
"""Test run()."""
bell, _, target = self._cases[1]
with self.subTest("single"):
bell, _, target = self._cases[1]
sampler = StatevectorSampler(seed=self._seed)
job = sampler.run([bell], shots=self._shots)
result = job.result()
@ -93,8 +93,10 @@ class TestStatevectorSampler(QiskitTestCase):
self._assert_allclose(result[0].data.meas, np.array(target))
with self.subTest("single with param"):
pqc, param_vals, target = self._cases[2]
sampler = StatevectorSampler(seed=self._seed)
job = sampler.run([(bell, ())], shots=self._shots)
params = (param.name for param in pqc.parameters)
job = sampler.run([(pqc, {params: param_vals})], shots=self._shots)
result = job.result()
self.assertIsInstance(result, PrimitiveResult)
self.assertIsInstance(result.metadata, dict)
@ -104,21 +106,13 @@ class TestStatevectorSampler(QiskitTestCase):
self.assertIsInstance(result[0].data.meas, BitArray)
self._assert_allclose(result[0].data.meas, np.array(target))
with self.subTest("single array"):
sampler = StatevectorSampler(seed=self._seed)
job = sampler.run([(bell, [()])], shots=self._shots)
result = job.result()
self.assertIsInstance(result, PrimitiveResult)
self.assertIsInstance(result.metadata, dict)
self.assertEqual(len(result), 1)
self.assertIsInstance(result[0], PubResult)
self.assertIsInstance(result[0].data, DataBin)
self.assertIsInstance(result[0].data.meas, BitArray)
self._assert_allclose(result[0].data.meas, np.array([target]))
with self.subTest("multiple"):
pqc, param_vals, target = self._cases[2]
sampler = StatevectorSampler(seed=self._seed)
job = sampler.run([(bell, [(), (), ()])], shots=self._shots)
params = (param.name for param in pqc.parameters)
job = sampler.run(
[(pqc, {params: [param_vals, param_vals, param_vals]})], shots=self._shots
)
result = job.result()
self.assertIsInstance(result, PrimitiveResult)
self.assertIsInstance(result.metadata, dict)
@ -206,14 +200,7 @@ class TestStatevectorSampler(QiskitTestCase):
circuit, _, target = self._cases[1]
param_target = [
(None, np.array(target)),
((), np.array(target)),
([], np.array(target)),
(np.array([]), np.array(target)),
(((),), np.array([target])),
(([],), np.array([target])),
([[]], np.array([target])),
([()], np.array([target])),
(np.array([[]]), np.array([target])),
({}, np.array(target)),
]
for param, target in param_target:
with self.subTest(f"{circuit.name} w/ {param}"):
@ -228,12 +215,14 @@ class TestStatevectorSampler(QiskitTestCase):
circuit.ry(param, 0)
circuit.measure(0, 0)
param_target = [
([np.pi], np.array({1: self._shots})),
((np.pi,), np.array({1: self._shots})),
(np.array([np.pi]), np.array({1: self._shots})),
([[np.pi]], np.array([{1: self._shots}])),
(((np.pi,),), np.array([{1: self._shots}])),
(np.array([[np.pi]]), np.array([{1: self._shots}])),
({"x": np.pi}, np.array({1: self._shots})),
({param: np.pi}, np.array({1: self._shots})),
({"x": np.array(np.pi)}, np.array({1: self._shots})),
({param: np.array(np.pi)}, np.array({1: self._shots})),
({"x": [np.pi]}, np.array({1: self._shots})),
({param: [np.pi]}, np.array({1: self._shots})),
({"x": np.array([np.pi])}, np.array({1: self._shots})),
({param: np.array([np.pi])}, np.array({1: self._shots})),
]
for param, target in param_target:
with self.subTest(f"{circuit.name} w/ {param}"):