Skip to content
Merged
Show file tree
Hide file tree
Changes from 68 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
27 changes: 25 additions & 2 deletions doc/introduction/measurements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,31 @@ And the result is:
>>> circuit()
{'00': 495, '11': 505}

Per default, only observed outcomes are included in the dictionary. The kwarg ``all_outcomes=True`` can
be used to display all possible outcomes, including those that were observed ``0`` times in sampling.

For example, we could run the previous circuit with ``all_outcomes=True``:

.. code-block:: python

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

@qml.qnode(dev)
def circuit():
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
return qml.counts(all_outcomes=True)

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

Note: For complicated Hamiltonians, this can add considerable overhead time (due to the cost of calculating
eigenvalues to determine possible outcomes), and as the number of qubits increases, the length of the output
dictionary showing possible computational basis states grows rapidly.

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.
a tuple is returned to provide differentiability for the outputs of QNodes.

.. code-block:: python

Expand All @@ -183,7 +206,7 @@ a tensor of tensors is returned to provide differentiability for the outputs of
return qml.expval(qml.PauliZ(0)),qml.expval(qml.PauliZ(1)), qml.counts()

>>> circuit()
(-0.036, 0.036, {'01': 482, '10': 518})
(-0.036, 0.036, {'01': 482, '10': 518})

Probability
-----------
Expand Down
17 changes: 17 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,22 @@
False
```

* Per default, counts returns only the outcomes observed in sampling. Optionally, specifying `qml.counts(all_outcomes=True)`
will return a dictionary containing all possible outcomes. [(#2889)](https://github.com/PennyLaneAI/pennylane/pull/2889)

```pycon
>>> dev = qml.device("default.qubit", wires=2, shots=1000)
>>>
>>> @qml.qnode(dev)
>>> def circuit():
... qml.Hadamard(wires=0)
... qml.CNOT(wires=[0, 1])
... return qml.counts(all_outcomes=True)
>>> result = circuit()
>>> print(result)
{'00': 495, '01': 0, '10': 0, '11': 505}
```

<h3>Breaking changes</h3>

* Measuring an operator that might not be hermitian as an observable now raises a warning instead of an
Expand All @@ -151,6 +167,7 @@ Josh Izaac,
Edward Jiang,
Ankit Khandelwal,
Korbinian Kottmann,
Lillian Marie Austin Frederiksen,
Albert Mitjans Coma,
Rashid N H M,
Zeyue Niu,
Expand Down
12 changes: 9 additions & 3 deletions pennylane/_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,9 +727,15 @@ def batch_transform(self, circuit):
elif (
len(circuit._obs_sharing_wires) > 0
and not hamiltonian_in_obs
and qml.measurements.Sample not in return_types
and qml.measurements.Probability not in return_types
and qml.measurements.Counts not in return_types
and all(
t not in return_types
for t in [
qml.measurements.Sample,
qml.measurements.Probability,
qml.measurements.Counts,
qml.measurements.AllCounts,
]
)
):
# Check for case of non-commuting terms and that there are no Hamiltonians
# TODO: allow for Hamiltonians in list of observables as well.
Expand Down
112 changes: 82 additions & 30 deletions pennylane/_qubit_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from pennylane.math import sum as qmlsum
from pennylane.measurements import (
Counts,
AllCounts,
Expectation,
MeasurementProcess,
MutualInfo,
Expand Down Expand Up @@ -316,7 +317,9 @@ def execute(self, circuit, **kwargs):
self._samples = self.generate_samples()

ret_types = [m.return_type for m in circuit.measurements]
counts_exist = any(ret is qml.measurements.Counts for ret in ret_types)
counts_exist = any(
ret in (qml.measurements.Counts, qml.measurements.AllCounts) for ret in ret_types
)

# compute the required statistics
if not self.analytic and self._shot_vector is not None:
Expand Down Expand Up @@ -445,7 +448,9 @@ def shot_vec_statistics(self, circuit):
s1 = 0

ret_types = [m.return_type for m in circuit.measurements]
counts_exist = any(ret is qml.measurements.Counts for ret in ret_types)
counts_exist = any(
ret in (qml.measurements.Counts, qml.measurements.AllCounts) for ret in ret_types
)
single_measurement = len(circuit.measurements) == 1

for shot_tuple in self._shot_vector:
Expand Down Expand Up @@ -542,7 +547,7 @@ def _multi_meas_with_counts_shot_vec(self, circuit, shot_tuple, r):
else:
result = r_[idx]

if not circuit.observables[idx2].return_type is Counts:
if not circuit.observables[idx2].return_type in (Counts, AllCounts):
result = self._asarray(result.T)

result_group.append(result)
Expand Down Expand Up @@ -741,7 +746,7 @@ def statistics(self, observables, shot_range=None, bin_size=None, circuit=None):
self.sample(obs, shot_range=shot_range, bin_size=bin_size, counts=False)
)

elif obs.return_type is Counts:
elif obs.return_type in (Counts, AllCounts):
results.append(
self.sample(obs, shot_range=shot_range, bin_size=bin_size, counts=True)
)
Expand Down Expand Up @@ -886,7 +891,7 @@ def statistics_new(self, observables, shot_range=None, bin_size=None):
samples = self.sample(obs, shot_range=shot_range, bin_size=bin_size, counts=False)
result = qml.math.squeeze(samples)

elif obs.return_type is Counts:
elif obs.return_type in (Counts, AllCounts):
result = self.sample(obs, shot_range=shot_range, bin_size=bin_size, counts=True)

elif obs.return_type is Probability:
Expand Down Expand Up @@ -1571,6 +1576,75 @@ 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.

The format of the dictionary depends on obs.return_type, which is set when
calling measurements.counts by setting the kwarg all_outcomes (bool). Per default,
the dictionary will only contain the observed outcomes. Optionally (all_outcomes=True)
the dictionary will instead contain all possible outcomes, with a count of 0
for those not observed. See example.


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)

Per default, this will return:
>>> self._samples_to_counts(samples, obs, num_wires)
{'00': 2, '10': 1}

However, if obs.return_type is AllCounts, this will return:
>>> self._samples_to_counts(samples, obs, num_wires)
{'00': 2, '01': 0, '10': 1, '11': 0}

The variable all_outcomes can be set when running measurements.counts, i.e.:

.. code-block:: python3

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

@qml.qnode(dev)
def circuit(x):
qml.RX(x, wires=0)
return qml.counts(all_outcomes=True)


"""

outcomes = []

if isinstance(obs, MeasurementProcess):
# convert samples and outcomes (if using) from arrays to str for dict keys
samples = ["".join([str(s.item()) for s in sample]) for sample in samples]

if obs.return_type is AllCounts:
outcomes = self.generate_basis_states(num_wires)
outcomes = ["".join([str(o.item()) for o in outcome]) for outcome in outcomes]
elif obs.return_type is AllCounts:
outcomes = qml.eigvals(obs)

# 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 @@ -1593,28 +1667,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 @@ -1657,16 +1709,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
4 changes: 2 additions & 2 deletions pennylane/devices/default_mixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
Snapshot,
)
from pennylane import numpy as np
from pennylane.measurements import Counts, MutualInfo, Sample, State, VnEntropy
from pennylane.measurements import Counts, AllCounts, MutualInfo, Sample, State, VnEntropy
from pennylane.operation import Channel
from pennylane.ops.qubit.attributes import diagonal_in_z_basis
from pennylane.wires import Wires
Expand Down Expand Up @@ -579,7 +579,7 @@ def execute(self, circuit, **kwargs):
# Assumed to only be allowed if it's the only measurement.
self.measured_wires = []
return super().execute(circuit, **kwargs)
if obs.return_type in (Sample, Counts):
if obs.return_type in (Sample, Counts, AllCounts):
if obs.name == "Identity" and obs.wires in (qml.wires.Wires([]), self.wires):
# Sample, Counts: Readout error applied to all device wires when wires
# not specified or all wires specified.
Expand Down
5 changes: 4 additions & 1 deletion pennylane/interfaces/autograd.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ def _execute(

for i, r in enumerate(res):

if any(m.return_type is qml.measurements.Counts for m in tapes[i].measurements):
if any(
m.return_type in (qml.measurements.Counts, qml.measurements.AllCounts)
for m in tapes[i].measurements
):
continue

if isinstance(r, np.ndarray):
Expand Down
5 changes: 4 additions & 1 deletion pennylane/interfaces/jax.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,10 @@ def array_if_not_counts(tape, r):
dictionaries. JAX NumPy arrays don't support dictionaries."""
return (
jnp.array(r)
if not any(m.return_type is qml.measurements.Counts for m in tape.measurements)
if not any(
m.return_type in (qml.measurements.Counts, qml.measurements.AllCounts)
for m in tape.measurements
)
else r
)

Expand Down
5 changes: 4 additions & 1 deletion pennylane/interfaces/tensorflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ def execute(tapes, device, execute_fn, gradient_fn, gradient_kwargs, _n=1, max_d
for i, tape in enumerate(tapes):
# convert output to TensorFlow tensors

if any(m.return_type is qml.measurements.Counts for m in tape.measurements):
if any(
m.return_type in (qml.measurements.Counts, qml.measurements.AllCounts)
for m in tape.measurements
):
continue

if isinstance(res[i], np.ndarray):
Expand Down
5 changes: 4 additions & 1 deletion pennylane/interfaces/torch.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ def forward(ctx, kwargs, *parameters): # pylint: disable=arguments-differ
# For backwards compatibility, we flatten ragged tape outputs
r = np.hstack(r)

if any(m.return_type is qml.measurements.Counts for m in ctx.tapes[i].measurements):
if any(
m.return_type in (qml.measurements.Counts, qml.measurements.AllCounts)
for m in ctx.tapes[i].measurements
):
continue

if isinstance(r, (list, tuple)):
Expand Down
Loading