Skip to content

Commit

Permalink
Remove vals from BindingsArray (#11642)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people authored Jan 31, 2024
1 parent 588f2df commit 43ea026
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 371 deletions.
248 changes: 123 additions & 125 deletions qiskit/primitives/containers/bindings_array.py

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion qiskit/primitives/containers/estimator_pub.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
11 changes: 10 additions & 1 deletion qiskit/primitives/containers/sampler_pub.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from __future__ import annotations

from collections.abc import Mapping
from typing import Tuple, Union
from numbers import Integral

Expand Down Expand Up @@ -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)
Expand Down
357 changes: 161 additions & 196 deletions test/python/primitives/containers/test_bindings_array.py

Large diffs are not rendered by default.

20 changes: 12 additions & 8 deletions test/python/primitives/containers/test_estimator_pub.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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)

Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand Down
16 changes: 10 additions & 6 deletions test/python/primitives/containers/test_sampler_pub.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
7 changes: 2 additions & 5 deletions test/python/primitives/test_statevector_estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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:
Expand Down
47 changes: 18 additions & 29 deletions test/python/primitives/test_statevector_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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}"):
Expand All @@ -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}"):
Expand Down

0 comments on commit 43ea026

Please sign in to comment.