mirror of https://github.com/Qiskit/qiskit.git
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:
parent
588f2df46b
commit
43ea026bab
|
@ -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,
|
||||
]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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}"):
|
||||
|
|
Loading…
Reference in New Issue