Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow postselecting on mid-circuit measurements #4604

Merged
merged 154 commits into from
Oct 23, 2023
Merged

Allow postselecting on mid-circuit measurements #4604

merged 154 commits into from
Oct 23, 2023

Conversation

mudit2812
Copy link
Contributor

@mudit2812 mudit2812 commented Sep 15, 2023

Context:
This PR adds support for postselecting on mid-circuit measurements with default.qubit. The various changes to cover the edge cases of postselection are described below.

Description of the Change:

Changes to measurements, math, and transforms:

  • Add postselect kwarg to qml.measure.
  • Add logic to defer_measurements to support postselection:
    • Apply qml.Projector as an operation when postselection is requested.
    • Call broadcast_expand when postselection is requested and batch_size is not None. This is done because there is a possibility of ending up with NaN values in the state, and we only want the state vector of the "bad" parameter to have NaN values, not the entire state vector.
    • Raise an error if the device used for executing the deferred tape is not default.qubit.
  • Added tensorflow single dispatch to qml.math for isnan, full, and any. Why must tensorflow name their functions slightly differently than everyone else? The functions I needed to add dispatches for are called tf.math.is_nan, tf.fill, and tf.reduce_any respectively 😢

Changes to default.qubit:

  • Added logic for applying qml.Projector and renormalizing state in simulate.py:
    • If the probability of the postselected state being measured is zero or extremely close to zero, we make it exactly zero and let execution continue so that the results become invalid rather than incorrect.
    • Use np.random.binomial to update the number of shots on the tape such that only a subset of the shots are then used. This is done to simulate behaviour on real hardware, where invalid samples would be discarded.
  • Added _FlexShots class which inherits Shots to simulate.py that allows shot vectors to contain 0 shots. We need this in cases when the probability of measuring the postselected state is close to 0 but not 0. In these cases, it is possible to end up with 0 samples that are valid, and we need to be able to handle that.
  • Added logic to sampling.py for catching errors raised by the rng if attempting to generate samples from nan probabilities. When this happens, samples of shape (shots, len(wires)) with NaN values are returned.

The following is done to measurements in cases where the state is invalid or the number of shots is 0:

  • Measurement processes return NaN values within their expected shape. Eg, qml.probs returns a 2**n size array with all NaNs, and qml.expval returns a single NaN.

  • QInfo measurements don't work for analytic execution, and probs and counts don't work for finite shots if state has NaN values.

Other changes:

  • Removed application of defer_measurements in QNode construction if the device is default.qubit. This is done so that multiple tapes are not created by the transform in case there is postselection and broadcasting.
  • Added error to drawer for circuits with mid-circuit measurements.

TODOs:

  • Update documentation and examples to include info about possible invalid results. This should include info about:
    • when invalid results can occur: when the postselection probability is zero, or when the postselection probability is so low that updating the number of shots per the binomial distribution leads to zero shots.
    • Which measurements are invalid,
  • Add integration tests.
  • Fix sampling tests. In an integrated setting, postselection works with finite shots measurements except for probs and counts, but the unit tests just fail. I haven't figured out yet what needs to change. All tests related to postselection are in tests.devices.qubit.test_sampling.TestInvalidStateSamples.

Benefits:
Postselection is available on default.qubit.

Possible Drawbacks:

  • Postselection with some measurements causes errors that aren't necessarily easy to understand.
  • I had to remove the use of defer_measurements from qnode construction if the new device was being used as postselection would split tapes if the tape was broadcasted. This causes the drawer to not work with default.qubit for circuits with mid-circuit measurements unless defer_measurements is explicitly applied to the QNode.

Related GitHub Issues:

Here's a gist with the notebook I've been using to run examples locally. It has flags to test analytic vs finite shots, integer shots vs shot vector, exactly zero vs close to zero postselected state probability, and unit vs integration execution

doc/introduction/measurements.rst Outdated Show resolved Hide resolved
doc/introduction/measurements.rst Outdated Show resolved Hide resolved
doc/introduction/measurements.rst Outdated Show resolved Hide resolved
doc/introduction/measurements.rst Outdated Show resolved Hide resolved
Copy link
Contributor

@timmysilv timmysilv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mostly just little things, but this is looking pretty great

doc/introduction/measurements.rst Outdated Show resolved Hide resolved
doc/releases/changelog-dev.md Show resolved Hide resolved
pennylane/devices/qubit/sampling.py Show resolved Hide resolved
pennylane/measurements/mid_measure.py Outdated Show resolved Hide resolved
pennylane/ops/qubit/observables.py Outdated Show resolved Hide resolved
pennylane/devices/qubit/simulate.py Outdated Show resolved Hide resolved
tests/devices/default_qubit/test_default_qubit.py Outdated Show resolved Hide resolved
tests/devices/qubit/test_sampling.py Outdated Show resolved Hide resolved
tests/devices/qubit/test_sampling.py Outdated Show resolved Hide resolved
tests/transforms/test_defer_measurements.py Show resolved Hide resolved
Copy link
Contributor

@albi3ro albi3ro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impressive amount of work 🎉 Quite an accomplishment

Copy link
Contributor

@timmysilv timmysilv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just following up on a few more things

tests/devices/qubit/test_sampling.py Outdated Show resolved Hide resolved
@mudit2812 mudit2812 added the merge-ready ✔️ All tests pass and the PR is ready to be merged. label Oct 20, 2023
@mudit2812 mudit2812 enabled auto-merge (squash) October 23, 2023 18:04
@mudit2812 mudit2812 merged commit 00a06dd into master Oct 23, 2023
33 checks passed
@mudit2812 mudit2812 deleted the mcm-post branch October 23, 2023 18:30
mudit2812 added a commit that referenced this pull request Nov 24, 2023
**Context:**
Adding support for `qml.cond` for mid-circuit measurement drawing with
`qml.draw`. This PR adds support for using one mid-circuit measurement
with multiple `qml.cond`s, where each `qml.cond` uses the **same**
mid-circuit measurement. An example of the drawing is shown below:

```python
import pennylane as qml

def circuit():
    qml.RX(0.5, 0)
    qml.RX(0.5, 1)
    m0 = qml.measure(0, reset=True, postselect=1)
    qml.cond(m0, qml.MultiControlledX)(wires=[1, 2, 3, 0], control_values="110")

    qml.CNOT([3, 2])
    qml.cond(m0, qml.ctrl(qml.MultiRZ, control=[1, 2], control_values=[True, False]))(0.5, wires=[0, 3])

    return qml.expval(qml.PauliZ(0))

print(qml.draw(circuit)())
```
```
0: ──RX(0.50)──┤↗₁│  │0⟩─╭X────╭MultiRZ(0.50)─┤  <Z>
1: ──RX(0.50)───║────────├●────├●─────────────┤     
2: ─────────────║────────├●─╭X─├○─────────────┤     
3: ─────────────║────────╰○─╰●─╰MultiRZ(0.50)─┤     
                ╚═════════╩═════╝                   
```

**Note:** This PR contains work that is foundational for follow up PRs
but isn't used for drawing the cases mentioned above. I'll add arbitrary
testing to get full coverage, but the aforementioned cases will be the
only ones considered to currently be complete.

**Description of the Change:**

`drawable_layers`:
* `drawable_layers` no longer stacks mid-circuit measurements used by a
`qml.cond` in the same layer.
* `drawable layers` now considers all wires under a mid-circuit
measurement used for conditioning to be used.
* `drawable_layers` now considers all wires under a `qml.cond` to be
used.

`tape_text`:
* Added helper functions to `tape_text.py` for correctly adding labels
for mid-circuit measurements and `qml.cond`s, as well as for correctly
padding the width of the layers for unused wires.
* Added helper function to `tape_text.py` for getting information about
connectivity between mid-circuit measurements and conditional operations
to keep track of when to start and stop drawing classical bits.
* `tape_text()` now tracks a `bit_map`, which stores a mapping between
mid-circuit measurements and classical bits that are drawn for them.
Classical bits are drawn using double lines `═`, and are placed under
the wires in the drawing. Each mid-circuit measurement used for
conditioning uses a classical bit. Measurements not used for
conditioning are not allocated a bit.
* `tape_text()` now pads the classical bits between each layer of the
drawing along with the wires.
* This PR also addresses #4809. The changes pertaining to that are
contained in `tape_text.py::_add_measurements()`.

Other changes:
* Added `label` to `qml.transforms.Conditional`.
* Adjusted `qml.equal` to consider postselection when comparing
`MidMeasureMP`s. This was missed in #4604.

**Benefits:**
Drawing support for mid-circuit measurements is better :)

**Possible Drawbacks:**
Code in `tape_text.py` is less readable. After a discussion with
@trbromley , we decided that the best course of action is to leave
refactoring as technical debt for the end of the release cycle. Reviews
pertaining to major refactors are still appreciated :)

**Related GitHub Issues:**
#4809

---------

Co-authored-by: Christina Lee <christina@xanadu.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merge-ready ✔️ All tests pass and the PR is ready to be merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.