diff --git a/qiskit/primitives/containers/bindings_array.py b/qiskit/primitives/containers/bindings_array.py index 036465369ba0..195340202c52 100644 --- a/qiskit/primitives/containers/bindings_array.py +++ b/qiskit/primitives/containers/bindings_array.py @@ -15,7 +15,7 @@ """ 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 @@ 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. - - 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. + :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 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: - - * 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. + 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`. + + 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 @@ def __init__( 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 = {} - - 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) + 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() + } - 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 @@ def __getitem__(self, args) -> BindingsArray: # 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 @@ def bind(self, circuit: QuantumCircuit, loc: tuple[int, ...]) -> QuantumCircuit: 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}") - - flat_vals = (val for vals in self.vals for val in vals[loc]) + raise ValueError(f"Expected {loc} to index all dimensions of {self.shape}.") - 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 @@ def reshape(self, *shape: int | Iterable[int]) -> BindingsArray: 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 @@ def coerce(cls, bindings_array: BindingsArrayLike) -> BindingsArray: 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 @@ def coerce(cls, bindings_array: BindingsArrayLike) -> BindingsArray: 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 examine_array(*possible_shapes): 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, ] diff --git a/qiskit/primitives/containers/estimator_pub.py b/qiskit/primitives/containers/estimator_pub.py index 82b50a9b755e..7f040fa77fc9 100644 --- a/qiskit/primitives/containers/estimator_pub.py +++ b/qiskit/primitives/containers/estimator_pub.py @@ -18,6 +18,7 @@ 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 @@ def coerce(cls, pub: EstimatorPubLike, precision: float | None = None) -> Estima ) 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, diff --git a/qiskit/primitives/containers/sampler_pub.py b/qiskit/primitives/containers/sampler_pub.py index 4011b9c217d8..049dab4c5524 100644 --- a/qiskit/primitives/containers/sampler_pub.py +++ b/qiskit/primitives/containers/sampler_pub.py @@ -17,6 +17,7 @@ from __future__ import annotations +from collections.abc import Mapping from typing import Tuple, Union from numbers import Integral @@ -114,7 +115,15 @@ def coerce(cls, pub: SamplerPubLike, shots: int | None = None) -> SamplerPub: 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) diff --git a/test/python/primitives/containers/test_bindings_array.py b/test/python/primitives/containers/test_bindings_array.py index b15e16b214c7..200c5bdf2b31 100644 --- a/test/python/primitives/containers/test_bindings_array.py +++ b/test/python/primitives/containers/test_bindings_array.py @@ -39,29 +39,25 @@ def setUp(self): 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 @@ def test_repr(self): # 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 @@ def test_bind_all(self): # 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 @@ def test_reshape(self): """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 @@ def test_reshape(self): 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 @@ def test_reshape(self): 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 @@ def various_formats(shape): 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 @@ def test_kwvals(self): 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]]) - - 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) + self.assertEqual(list(ba.data.keys()), [(self.params[0].name,)]) + np.testing.assert_allclose(list(ba.data.values()), [vals[..., np.newaxis]]) - 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,57 +295,16 @@ def test_empty(self): 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=()) - 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])) + ba = BindingsArray(data={}, shape=()) 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.""" @@ -448,3 +322,94 @@ def test_coerce(self): 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)) diff --git a/test/python/primitives/containers/test_estimator_pub.py b/test/python/primitives/containers/test_estimator_pub.py index dcd2114f35cf..17d7b9498b19 100644 --- a/test/python/primitives/containers/test_estimator_pub.py +++ b/test/python/primitives/containers/test_estimator_pub.py @@ -30,7 +30,7 @@ def test_properties(self): 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 @@ def test_validate_no_parameters(self, num_params): """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 @@ def test_validate_num_parameters(self, num_params): 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 @@ def test_shaped_zero_parameter_values(self, shape): """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 @@ def test_coerce_pub_with_precision(self, precision): 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 @@ def test_coerce_pub_without_shots(self, precision): 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 @@ def test_broadcasting(self, obs_shape, params_shape, pub_shape): 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 @@ def test_broadcasting_fails(self, obs_shape, params_shape): 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) diff --git a/test/python/primitives/containers/test_sampler_pub.py b/test/python/primitives/containers/test_sampler_pub.py index 42fb04afc560..bf76ab8b91bb 100644 --- a/test/python/primitives/containers/test_sampler_pub.py +++ b/test/python/primitives/containers/test_sampler_pub.py @@ -31,7 +31,7 @@ def test_properties(self): 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 @@ def test_invalidate_shots_value(self, shots): 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 @@ def test_validate_num_parameters(self, num_params): 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 @@ def test_validate_num_parameters(self, num_params): 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 @@ def test_coerce_pub_with_shots(self, shots): 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 @@ def test_coerce_pub_without_shots(self, shots): 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) diff --git a/test/python/primitives/test_statevector_estimator.py b/test/python/primitives/test_statevector_estimator.py index a3fc52bbc887..0c1db3cbcce0 100644 --- a/test/python/primitives/test_statevector_estimator.py +++ b/test/python/primitives/test_statevector_estimator.py @@ -94,10 +94,10 @@ def test_estimator_with_pub(self): 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 @@ def test_run_single_circuit_observable(self): 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: diff --git a/test/python/primitives/test_statevector_sampler.py b/test/python/primitives/test_statevector_sampler.py index fa6ba20a2622..082a6d65910d 100644 --- a/test/python/primitives/test_statevector_sampler.py +++ b/test/python/primitives/test_statevector_sampler.py @@ -78,9 +78,9 @@ def _assert_allclose(self, bitarray: BitArray, target: NDArray | BitArray, rtol= 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 @@ def test_sampler_run(self): 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 @@ def test_sampler_run(self): 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 @@ def test_run_single_circuit(self): 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 @@ def test_run_single_circuit(self): 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}"):