Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
f69b1bd
generate list of possible sampling outcomes
Aug 3, 2022
a75c41b
add all outcomes as dict keys
Aug 3, 2022
c66b859
update _samples_to_counts docstring
Aug 3, 2022
47e04f1
move _samples_to_counts up a layer
Aug 3, 2022
77a0756
pre-commit checks
Aug 3, 2022
34e7706
pre-commit checks
Aug 3, 2022
1801db0
minor doc string edit
Aug 3, 2022
363b207
update measurements.rst doc
Aug 3, 2022
1fe391e
update measurements.rst doc
Aug 3, 2022
ba840e4
fix bug in wire numbering in example code block
Aug 3, 2022
200cbdf
update tests accept additional dict keys in outcome
Aug 3, 2022
eb880cb
Merge branch 'PennyLaneAI:master' into add_zero_count_outcomes
lillian542 Aug 3, 2022
a7a2936
update changelog-dev
Aug 3, 2022
7de6b84
Merge branch 'add_zero_count_outcomes' of https://github.com/lillian5…
Aug 3, 2022
c3c4999
revert pre-commit config file
Aug 3, 2022
355070b
make 0 count int64 data type
Aug 3, 2022
9324cc9
update test_measurements.py for new dict format
Aug 4, 2022
f67345a
add EigvalsUndefinedError exception to _samples_to_counts
Aug 4, 2022
ad45002
add unit tests
Aug 4, 2022
d091e4e
add pytest xfail to test_scalar_counts_with_obs
Aug 4, 2022
d9f5469
Update doc/introduction/measurements.rst
lillian542 Aug 5, 2022
11dd3b5
Update tests/test_new_return_types.py
lillian542 Aug 5, 2022
5d2d773
Update tests/test_measurements.py
lillian542 Aug 5, 2022
207ecbd
Update docstring _samples_to_counts
lillian542 Aug 5, 2022
2c3b88a
Update tests/test_measurements.py
lillian542 Aug 5, 2022
9faf6c2
remove ToDo
Aug 5, 2022
6cdd3c4
qml.eigvals(obs) instead of obs.compute_eigvals()
lillian542 Aug 5, 2022
acee677
add all_outcomes=False kwarg to counts fn
Aug 6, 2022
7d62989
update docstrings to reflect new kwarg
Aug 6, 2022
6d6ce84
update measurements.rst
Aug 6, 2022
52f0750
update changelog
Aug 6, 2022
9a11148
revert tests for standard (all_outcomes=False) counts fn to previous …
Aug 6, 2022
5e5bb5a
display default (all_outcomes=False) behaviour with warning for Eigva…
Aug 6, 2022
3ce246a
tests default (all_outcomes=False) counts behaviour, test passes
Aug 6, 2022
f29d3b4
update test for counts(all_outcomes=True)
Aug 6, 2022
b493794
fix typo
Aug 6, 2022
764fbc3
add missing all_outcomes=True kwarg in test
Aug 6, 2022
435d3fc
modify ppre-commit file to circumvent error on pc
Aug 6, 2022
0a4e5e3
small formatting changes
Aug 6, 2022
41204b0
revert change to pre-commit config
Aug 6, 2022
06e0126
Update doc/introduction/measurements.rst
lillian542 Aug 9, 2022
db0aec6
Update doc/introduction/measurements.rst
lillian542 Aug 9, 2022
c24a0f6
Update doc/introduction/measurements.rst
lillian542 Aug 9, 2022
9e5014f
Update doc/introduction/measurements.rst
lillian542 Aug 9, 2022
3b03370
Update pennylane/measurements.py
lillian542 Aug 9, 2022
fecf125
Update pennylane/measurements.py
lillian542 Aug 9, 2022
b30c7b1
Update doc/introduction/measurements.rst
lillian542 Aug 9, 2022
6952341
Update doc/introduction/measurements.rst
lillian542 Aug 9, 2022
3093020
update counts docstring
Aug 9, 2022
5a744ed
Merge branch 'add_zero_count_outcomes' of https://github.com/lillian5…
Aug 11, 2022
5b3eb1d
Update doc/releases/changelog-dev.md
lillian542 Aug 14, 2022
0ff4690
temp modification to pre-commit yaml
Aug 14, 2022
4ff4bbb
Switch to AllCounts implementation
Aug 14, 2022
f7f8bdb
Update measurements.rst
Aug 15, 2022
703081a
Add test for multiple measurement types with counts(all_outcomes=True)
Aug 15, 2022
34ffc31
remove completed todo
Aug 15, 2022
e778b1d
Revert "temp modification to pre-commit yaml"
Aug 15, 2022
2eaeaf4
Merge branch 'add_zero_count_outcomes' of https://github.com/lillian5…
Aug 15, 2022
99325c7
Merge branch 'master' into add_zero_count_outcomes
AlbertMitjans Aug 15, 2022
2c6acaf
indent codeblock in measurements.rst
Aug 19, 2022
ae86d4c
Add AllCounts to updates from master
Aug 19, 2022
e6f59a5
Remove irrelevant exception handling in _samples_to_counts
Aug 19, 2022
cb6df88
Fix too-many-boolean-expressions CodeFactor issue
Aug 19, 2022
8f3e9b7
Merge branch 'master' into add_zero_count_outcomes
AlbertMitjans Aug 22, 2022
28b8f02
indent code block
Aug 29, 2022
f7b7940
tidy up
Aug 29, 2022
ffadf35
Update tests/test_measurements.py
lillian542 Aug 29, 2022
4a20847
Merge branch 'add_zero_count_outcomes' of https://github.com/lillian5…
Aug 29, 2022
e128d91
Merge branch 'master' into add_zero_count_outcomes
AlbertMitjans Aug 30, 2022
1b6f0ef
indent docstring code-block
Sep 6, 2022
a1cc24e
fix indentation
Sep 6, 2022
c7d23c8
Merge branch 'master' into add_zero_count_outcomes
antalszava Sep 7, 2022
ae9d8e8
black reformatting
Sep 8, 2022
d2bd1ef
Merge branch 'master' into add_zero_count_outcomes
AlbertMitjans Sep 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions doc/introduction/measurements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,11 @@ and :func:`~.pennylane.sample`.
Counts
------

To avoid dealing with long arrays for the larger numbers of shots, one can pass an argument counts=True
to :func:`~pennylane.sample`. In this case, the result will be a dictionary containing the number of occurrences for each
unique sample. The previous example will be modified as follows:
To avoid dealing with long arrays for the larger numbers of shots, one can run :func:`~pennylane.counts` rather than
:func:`~pennylane.sample`. This performs the same measurement as sampling, but returns a dictionary containing the
possible measurement outcomes and the number of occurrences for each, rather than a list of all outcomes.

The previous example will be modified as follows:

.. code-block:: python

Expand All @@ -141,14 +143,13 @@ unique sample. The previous example will be modified as follows:
def circuit():
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
# passing the counts flag
return qml.sample(qml.PauliZ(0), counts=True), qml.sample(qml.PauliZ(1), counts=True)
return qml.counts(qml.PauliZ(0)), qml.counts(qml.PauliZ(1))

After executing the circuit, we can directly see how many times each measurement outcome occurred:

>>> result = circuit()
>>> print(result)
[{-1: 526, 1: 474} {-1: 526, 1: 474}]
({1: 475, -1: 525}, {1: 475, -1: 525})

Similarly, if the observable is not provided, the count of each computational basis state is returned.

Expand All @@ -160,14 +161,13 @@ Similarly, if the observable is not provided, the count of each computational ba
def circuit():
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
# passing the counts flag
return qml.sample(counts=True)
return qml.counts()

And the result is:

>>> result = circuit()
>>> print(result)
{'00': 495, '11': 505}
{'00': 495, '01': 0, '10': 0, '11': 505}

If counts are obtained along with a measurement function other than :func:`~.pennylane.sample`,
a tensor of tensors is returned to provide differentiability for the outputs of QNodes.
Expand All @@ -178,8 +178,8 @@ a tensor of tensors is returned to provide differentiability for the outputs of
def circuit():
qml.Hadamard(wires=0)
qml.CNOT(wires=[0,1])
qml.PauliX(wires=2)
return qml.expval(qml.PauliZ(0)),qml.expval(qml.PauliZ(1)), qml.sample(counts=True)
qml.PauliX(wires=1)
return qml.expval(qml.PauliZ(0)),qml.expval(qml.PauliZ(1)), qml.counts()

>>> result = circuit()
>>> print(result)
Expand Down
12 changes: 7 additions & 5 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ of operators. [(#2622)](https://github.com/PennyLaneAI/pennylane/pull/2622)
[(#2686)](https://github.com/PennyLaneAI/pennylane/pull/2686)
[(#2839)](https://github.com/PennyLaneAI/pennylane/pull/2839)
[(#2876)](https://github.com/PennyLaneAI/pennylane/pull/2876)
[(#2889)](https://github.com/PennyLaneAI/pennylane/pull/2889)

Note that the change included creating a new `Counts` measurement type in `measurements.py`.

Expand All @@ -419,7 +420,7 @@ of operators. [(#2622)](https://github.com/PennyLaneAI/pennylane/pull/2622)
... return qml.counts()
>>> result = circuit()
>>> print(result)
{'00': 495, '11': 505}
{'00': 495, '01': 0, '10': 0, '11': 505}
```

Counts can also be obtained when sampling the eigenstates of an observable:
Expand Down Expand Up @@ -563,7 +564,8 @@ of operators. [(#2622)](https://github.com/PennyLaneAI/pennylane/pull/2622)

This release contains contributions from (in alphabetical order):

Samuel Banning, Juan Miguel Arrazola, Utkarsh Azad, David Ittah, Soran Jahangiri, Edward Jiang,
Ankit Khandelwal, Christina Lee, Sergio Martínez-Losa, Albert Mitjans Coma, Ixchel Meza Chavez,
Romain Moyard, Lee James O'Riordan, Mudit Pandey, Bogdan Reznychenko, Shuli Shu, Jay Soni,
Modjtaba Shokrian-Zini, Antal Száva, David Wierichs, Moritz Willmann
Samuel Banning, Juan Miguel Arrazola, Utkarsh Azad, Lillian Marie Austin Frederiksen, David Ittah,
Soran Jahangiri, Edward Jiang, Ankit Khandelwal, Christina Lee, Sergio Martínez-Losa,
Albert Mitjans Coma, Ixchel Meza Chavez,Romain Moyard, Lee James O'Riordan, Mudit Pandey,
Bogdan Reznychenko, Shuli Shu, Jay Soni, Modjtaba Shokrian-Zini, Antal Száva, David Wierichs,
Moritz Willmann
75 changes: 50 additions & 25 deletions pennylane/_qubit_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -1436,6 +1436,53 @@ def var(self, observable, shot_range=None, bin_size=None):
# TODO: do we need to squeeze here? Maybe remove with new return types
return np.squeeze(np.var(samples, axis=axis))

def _samples_to_counts(self, samples, obs, num_wires):
"""Groups the samples into a dictionary showing number of occurences for
each possible outcome.

Args:
samples: samples in an array of dimension ``(shots,len(wires))``
obs (Observable): the observable sampled
num_wires (int): number of wires the sampled observable was performed on

Returns:
dict: dictionary with format {'outcome': num_occurences}, including all
outcomes for the sampled observable

**Example**

>>> samples
tensor([[0, 0],
[0, 0],
[1, 0]], requires_grad=True)
>>> self._samples_to_counts(samples, obs, num_wires)
{'00': 2, '01': 0, '10': 1, '11': 0}
"""

if isinstance(obs, MeasurementProcess):
outcomes = self.generate_basis_states(num_wires)

# ToDo: converting to str means code does not work with JAX
# convert samples and outcomes from arrays to str for dict keys
outcomes = ["".join([str(o.item()) for o in outcome]) for outcome in outcomes]
samples = ["".join([str(s.item()) for s in sample]) for sample in samples]
else:
try:
outcomes = obs.compute_eigvals()
# if observable has no info on eigenvalues, we cannot return this measurement
except qml.operation.EigvalsUndefinedError as e:
raise qml.operation.EigvalsUndefinedError(
f"Cannot find outcomes for {obs.name}."
) from e

# generate empty outcome dict, populate values with state counts
outcome_dict = {k: np.int64(0) for k in outcomes}
states, counts = np.unique(samples, return_counts=True)
for s, c in zip(states, counts):
outcome_dict[s] = c

return outcome_dict

def sample(self, observable, shot_range=None, bin_size=None, counts=False):
"""Return samples of an observable.

Expand All @@ -1458,28 +1505,6 @@ def sample(self, observable, shot_range=None, bin_size=None, counts=False):
dimension ``(shots,)`` or counts
"""

def _samples_to_counts(samples, no_observable_provided):
"""Group the obtained samples into a dictionary.

**Example**

>>> samples
tensor([[0, 0, 1],
[0, 0, 1],
[1, 1, 1]], requires_grad=True)
>>> self._samples_to_counts(samples)
{'111':1, '001':2}
"""
if no_observable_provided:
# If we describe a state vector, we need to convert its list representation
# into string (it's hashable and good-looking).
# Before converting to str, we need to extract elements from arrays
# to satisfy the case of jax interface, as jax arrays do not support str.
samples = ["".join([str(s.item()) for s in sample]) for sample in samples]

states, counts = np.unique(samples, return_counts=True)
return dict(zip(states, counts))

# translate to wire labels used by device
device_wires = self.map_wires(observable.wires)
name = observable.name
Expand Down Expand Up @@ -1522,16 +1547,16 @@ def _samples_to_counts(samples, no_observable_provided):
f"Cannot compute samples of {observable.name}."
) from e

num_wires = len(device_wires) if len(device_wires) > 0 else self.num_wires
if bin_size is None:
if counts:
return _samples_to_counts(samples, no_observable_provided)
return self._samples_to_counts(samples, observable, num_wires)
return samples

num_wires = len(device_wires) if len(device_wires) > 0 else self.num_wires
if counts:
shape = (-1, bin_size, num_wires) if no_observable_provided else (-1, bin_size)
return [
_samples_to_counts(bin_sample, no_observable_provided)
self._samples_to_counts(bin_sample, observable, num_wires)
for bin_sample in samples.reshape(shape)
]

Expand Down
73 changes: 60 additions & 13 deletions tests/test_measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ def circuit():
return qml.counts(wires=wires)

res = circuit()
assert res == {basis_state: n_shots}
assert res[basis_state] == n_shots

@pytest.mark.all_interfaces
@pytest.mark.parametrize("interface", ["autograd", "jax", "tensorflow", "torch"])
Expand Down Expand Up @@ -757,9 +757,9 @@ def circuit():
res = circuit()

assert isinstance(res, tuple)
assert res[0] == {basis_state: shot_vec[0]}
assert res[1] == {basis_state: shot_vec[1]}
assert res[2] == {basis_state: shot_vec[2]}
assert res[0][basis_state] == shot_vec[0]
assert res[1][basis_state] == shot_vec[1]
assert res[2][basis_state] == shot_vec[2]
assert len(res) == len(shot_vec)
assert sum(sum(v for v in res_bin.values()) for res_bin in res) == sum(shot_vec)

Expand All @@ -778,9 +778,9 @@ def circuit():
res = circuit()

assert isinstance(res, tuple)
assert res[0] == {1: shot_vec[0]}
assert res[1] == {1: shot_vec[1]}
assert res[2] == {1: shot_vec[2]}
assert res[0][1] == shot_vec[0]
assert res[1][1] == shot_vec[1]
assert res[2][1] == shot_vec[2]
assert len(res) == len(shot_vec)
assert sum(sum(v for v in res_bin.values()) for res_bin in res) == sum(shot_vec)

Expand All @@ -801,9 +801,10 @@ def circuit():
basis_state = "0111"

assert isinstance(res, tuple)
assert res[0] == {basis_state: shot_vec[0]}
assert res[1] == {basis_state: shot_vec[1]}
assert res[2] == {basis_state: shot_vec[2]}
print(res[0])
assert res[0][basis_state] == shot_vec[0]
assert res[1][basis_state] == shot_vec[1]
assert res[2][basis_state] == shot_vec[2]
assert len(res) == len(shot_vec)
assert sum(sum(v for v in res_bin.values()) for res_bin in res) == sum(shot_vec)

Expand All @@ -824,9 +825,9 @@ def circuit():
sample = 1

assert isinstance(res, tuple)
assert res[0] == {sample: shot_vec[0]}
assert res[1] == {sample: shot_vec[1]}
assert res[2] == {sample: shot_vec[2]}
assert res[0][sample] == shot_vec[0]
assert res[1][sample] == shot_vec[1]
assert res[2][sample] == shot_vec[2]
assert len(res) == len(shot_vec)
assert sum(sum(v for v in res_bin.values()) for res_bin in res) == sum(shot_vec)

Expand Down Expand Up @@ -867,6 +868,52 @@ def circuit():
for ind in counts_term_indices:
assert isinstance(res[ind], dict)

def test_outcome_dict_keys_providing_observable(self):
"""Test that the dictionary keys are the eigenvalues of the
observable if observable is given"""

n_shots = 10
dev = qml.device("default.qubit", wires=1, shots=n_shots)

@qml.qnode(dev)
def circuit():
res = qml.counts(qml.PauliZ(0))
return res

res = circuit()

assert set(res.keys()) == set(qml.PauliZ.compute_eigvals())

def test_outcome_dict_keys_providing_no_observable_no_wires(self):
"""Test that the dictionary keys are the possible combinations of
basis states for the device if no wire count and no observable are given"""

n_shots = 10
dev = qml.device("default.qubit", wires=2, shots=n_shots)

@qml.qnode(dev)
def circuit():
return qml.counts()

res = circuit()

assert set(res.keys()) == {"00", "01", "10", "11"}

def test_outcome_keys_providing_no_observable_and_wires(self):
"""Test that the dictionary keys are the possible combinations
of basis states for the specified wires if wire count is given"""

n_shots = 10
dev = qml.device("default.qubit", wires=4, shots=n_shots)

@qml.qnode(dev)
def circuit():
return qml.counts(wires=[0, 2])

res = circuit()

assert set(res.keys()) == {"00", "01", "10", "11"}


class TestMeasure:
"""Tests for the measure function"""
Expand Down
1 change: 1 addition & 0 deletions tests/test_new_return_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ def circuit(x):
else:
assert r.shape == (shot_tuple.shots,)

@pytest.mark.xfail
@pytest.mark.parametrize("meas1,meas2", scalar_counts_multi)
def test_scalar_counts_with_obs(self, shot_vector, meas1, meas2, device):
"""Test scalar-valued and counts measurements where counts takes an
Expand Down