From 081ee5c9c4a237c3dfb6f1c94533ca7e86073f26 Mon Sep 17 00:00:00 2001 From: Mudit Pandey Date: Tue, 15 Aug 2023 19:54:44 -0400 Subject: [PATCH] Allow reusing and reseting wires after a mid-circuit measurement (#4402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added qubit reuse and reset * Fixed `defer_measurements` * Removed `MeasurementValue` * Update type hint * Removed `MeasurementValue` * Add VJP/JVP capabilities to `DefaultQubit2` (#4374) * Add vjp/jvp capabilities * pylint * some tests for simulate * Fix syntax error * Split simulate * Add some tests * pylint * more device tests * pylint and fix tests * change tracker keys * Update pennylane/devices/qubit/adjoint_jacobian.py Co-authored-by: Christina Lee * other places * Add docs for simulate funcs * revert suggestion * Remove validation * Add validation for use_device_gradient * remove None case * use device gradient in validation * changelog --------- Co-authored-by: Christina Lee * Initialize `DefaultQubit2` local rng from global rng if no seed provided (#4394) * initialize dev rng from global state * change default value to global str * Apply suggestions from code review Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> * Update tests/devices/experimental/test_default_qubit_2.py * fix indentation --------- Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> * Changes to remove use of `Operator.__eq__` (#4398) * Testing changes to operator __eq__ * Updated until `nodes_between` * Updated metric tensor, circuit graph * Updating `qml.equal` * Linters * Updated tape tests * Added coverage * Updates changelog * Removed changes to __eq__ and __hash__ * Update pennylane/operation.py Co-authored-by: Matthew Silverman * Update pennylane/ops/functions/equal.py Co-authored-by: Christina Lee --------- Co-authored-by: Matthew Silverman Co-authored-by: Christina Lee * Make autoray changes backwards compatible (#4397) * Fixes for new autoray version * unused import * Use if instead of try-except * Fix case of two torch tensors * pin autoray * dispatch * test with new version * Raise warning in Operator `__eq__` and `__hash__` about change in behaviour (#4144) * Updated Operator * Added tests * Updated warning * black * Updated warning text * black * Update doc/development/deprecations.rst * Updated docs, tests * Updated deprecations.rst * Fixing tests * Updated tests and warnings * Pylint * Testing warnings * Testing changes * Updated tests * testing what breaks * Checking tests * Testing changes * Testing changes to operator __eq__ * Updated until `nodes_between` * Updated metric tensor, circuit graph * Updating `qml.equal` * Linters * Updated tape tests * Added coverage * Updates changelog * Removed changes to __eq__ and __hash__ * Update pennylane/operation.py Co-authored-by: Matthew Silverman * Testing changes to operator __eq__ * Updated until `nodes_between` * Updated metric tensor, circuit graph * Updating `qml.equal` * Linters * Fixes for new autoray version (#4396) * Fixes for new autoray version * unused import * Use if instead of try-except * Fix case of two torch tensors * Updated tape tests * Add ability to apply `TransformProgram` to batch of tapes (#4364) * Draft structure * draf exec * Simple execute * Update * More tests * Update * Update exec * Pylint and black * Update tests * Update more tests * More tests * changelog * Coverage * Cover fix * pylint * Pylint * Pylint tests * proposed changes to transform program integration * oops * add to legacy, remove cotransform support * just transform program call component * just transform program call component * no longer support cotransforms, fix _batch_postprocessing * some more testing * test null postprocessing function * docstring, rename batch_slices to slices, black * Apply suggestions from code review Co-authored-by: Matthew Silverman --------- Co-authored-by: rmoyard Co-authored-by: Matthew Silverman * torch qnode integration with DefaultQubit2 (#4392) * copy-paste torch file; replace device in most places * make tests work; little changes in pennylane * pylint fix * changelog * update test name and docstring for hamiltonian_expand * minimal test for single-shot bugfix * Added coverage * Updates changelog * Removed changes to __eq__ and __hash__ * Update pennylane/operation.py Co-authored-by: Matthew Silverman * Updated deprecations to 0.32 * Added warnings * Trigger CI * Removed docstrings * Added correctness test * Trigger CI --------- Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> Co-authored-by: Matthew Silverman Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> Co-authored-by: Christina Lee Co-authored-by: rmoyard * Tensorflow qnode integration with DefaultQubit2 (#4393) * copy-paste tf files * make tests work; little changes in pennylane * changes coming from torch PR first * update remaining test files * support tf.function by adding empty kwargs * update copyright year in test files * changelog * put all qnode integrations together * remove turning warning into error (#4409) * Fix jax.ad deprecation. (#4403) * Fix jax.ad deprecation. * Update changelog. * Print name of requested device in DeviceError. * Force push. * Fix pytest.raises in test_device.py * fix `has_decomposition` for ControlledQubitUnitary (#4407) * fix has_decomposition for ControlledQubitUnitary * changelog * add comment; add test when super returns False * [sc-36527]: Add new robots.txt to doc build to hide latest build from search engine (#4423) * Adding a `wire_order` kwarg to `Tensor.sparse_matrix()` (#4424) * `wire_order` kwarg for `Tensor.sparse_matrix()` * update changelog * swap kwarg order Co-authored-by: Matthew Silverman * adapt docstring to the kwarg order swap * add comment about using over --------- Co-authored-by: Matthew Silverman * Updated defer_measurements post-merge * Fixed defer_measurements * Updated test * Updated class signature * Updating keys * Updated id process * Updated changelog * Updated changelog * Adds shots to experimental device interface and integrate with QNode (#4388) * default shots on new device interface * make fewer changes and clean stuff up later * fix tests, lint, and sphinx * Update doc/releases/changelog-dev.md * Update tests/interfaces/test_set_shots.py Co-authored-by: Matthew Silverman * Update tests/devices/experimental/test_default_qubit_2.py * make set_shots error with new device, shots type hinting * Update doc/releases/changelog-dev.md Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> * Update pennylane/interfaces/set_shots.py Co-authored-by: Matthew Silverman * Apply suggestions from code review * Update tests/devices/experimental/test_default_qubit_2.py * merge problem * black * Update doc/releases/changelog-dev.md * Update pennylane/qnode.py Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> --------- Co-authored-by: Matthew Silverman Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> * QNSPSA bugfix (#4421) * qnspsa bugfix * Update doc/releases/changelog-dev.md * Update tests/optimize/test_qnspsa.py Co-authored-by: Matthew Silverman --------- Co-authored-by: Matthew Silverman * Update changelog * Updated defer_measurements for device wires * Removed error from drawable layers * Update changelog * Updated drawable layers test * Updated signatures * Updated signatures * Merge `v0.31.1-rc0` branch into master (#4428) * Support `HardwareHamiltonian` pulses in `stoch_pulse_grad` (#4215) * single out gradient transform checks * rename stochastic pulse gradient file * unify gradient_analysis and grad_method_validation * continue restructure of analysis+validation * CV * black * modularize more * more modularizing * black * tiny [skip ci] * [skip ci] lint * remove dummy test * test fix * add test file to linting test file * test fixes, docstrings * code review * docstring gradient_analysis_and_grad_method_validation * move first fun * code review:move functions * test regex * regexs * move and promote reorder_grads * tmp * more tmp * test cases, contractions * lint * docstring * even more tmp * cleanup * black * tmp * lint * move stoch_pulse_gradient.. files back to pulse_gradient... * move stoch_pulse_gradient.. files back to pulse_gradient... * lint * rename * extend functions and tests * lint and black * changelog * improve * update example to include non-Pauli word generator * add jit test with pauli sentence * tmp * debugging, docstring, extend test * review * optimize for Pauli words * Apply suggestions from code review Co-authored-by: Romain Moyard * test cases code review * fix parametrization * drafting * working prototype * finish merge; cleanup * changelog * comments * [skip ci] * raising an error; cleanup [skip ci] * Apply suggestions from code review Co-authored-by: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> * change contraction idea * typo in docs * tests * remove prints * fix test * test descriptions * fix merge * format * code review; test coverage * coverage reordering * fix * trigger CI * trigger * clear caches * trigger * trigger --------- Co-authored-by: Romain Moyard Co-authored-by: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> Co-authored-by: Korbinian Kottmann * Fix batching of derivative tapes in autograd (#4245) * Fix `expval` of `Sum` with broadcasting (#4275) * fix bug and add test * changelog addition * Various doc fixes (#4268) * Various things while validating changes for 0.31 (#4279) * change link in setup.py * fix qchem docs * fix qcut docs * rename string function (#4278) * Update docs (#4269) * Incrementing the version number to `v0.32.0-dev` (#4266) * pre-release for v0.31.0 * Update doc/releases/changelog-0.31.0.md * Incrementing the version number to v0.32.0-dev * Update installation.rst (#4247) Update Python version to 3.8 Co-authored-by: Romain Moyard * docs typos * don't include updating versioning from master * don't include python requirements from master * update example output for shot_vector * Update pennylane/operation.py Co-authored-by: Matthew Silverman * fix entirely unrelated rendering issue --------- Co-authored-by: Matthew Silverman Co-authored-by: Catalina Albornoz Co-authored-by: Romain Moyard * Minor fixes for v0.31 (#4280) * Added fixes * Update pennylane/ops/qubit/matrix_ops.py Co-authored-by: Matthew Silverman * Update pennylane/ops/qubit/matrix_ops.py --------- Co-authored-by: Matthew Silverman * add graphviz to RTD apt packages (#4284) * Pulse gradient methods docs polish (#4282) * stoch pulse docs update * generator method docs update * small fixes / typos * Update pennylane/gradients/pulse_generator_gradient.py Co-authored-by: David Wierichs * Update pennylane/gradients/pulse_generator_gradient.py Co-authored-by: David Wierichs * Update pennylane/gradients/pulse_generator_gradient.py Co-authored-by: David Wierichs * code review * Update pennylane/gradients/pulse_gradient.py Co-authored-by: David Wierichs * code review * Update pennylane/gradients/pulse_generator_gradient.py Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> * large | * add equal to --------- Co-authored-by: David Wierichs Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> * 0.31 release notes (#4218) * Add categories * First pass at categorization * More categorization * More categorization * Typo * Add first half of resource estimation text * Improvements * Add custom ops resource estimation * Update wording * Improve entry * Move * Move * Move * Update * Add details about Torch and Keras * Add entry * Add contributor * Add to Keras and Torch layer section * Add saving and loading details * Add entry * Add contributor * Add contributor * Add qutrit basis state example * first round of edits * Remove * Add entry for one_qubit_decompostition * more changes * Reorder sections * fermi blurb * minor * Update * Add * Update doc/releases/changelog-dev.md Co-authored-by: Isaac De Vlugt <34751083+isaacdevlugt@users.noreply.github.com> * Update doc/releases/changelog-dev.md Co-authored-by: Isaac De Vlugt <34751083+isaacdevlugt@users.noreply.github.com> * update fermionic code examples * additions * minor * restructure sections * minor * minor * reorder fermi section * fixed fermi section * various small changes * Tweaks to fermionic * Tweaks to resource and UH sections * Improvements * Move * Update doc/releases/changelog-0.31.0.md * minior * minor * minor * minor * minor * Update * minor * use from_string for fermi ops * Add link * Update doc/releases/changelog-0.31.0.md * added links and other minor things * Update doc/releases/changelog-0.31.0.md * Update doc/releases/changelog-0.31.0.md * minor * minor * deleted extraneous notebook * Apply suggestions from code review Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> --------- Co-authored-by: Isaac De Vlugt Co-authored-by: Isaac De Vlugt <34751083+isaacdevlugt@users.noreply.github.com> Co-authored-by: soranjh * Allow updating dense kwarg in call of ParametrizedEvolution (#4285) * add dense kwarg to call * update changelog * update changelog better * changelog formatting --------- Co-authored-by: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> * Remove line in code example to make docs render properly. (#4290) * Pin autograd to <= 1.5 (#4289) * pin autograd to 1.5 or less * allow autograd 1.4 as well * pin for doc build as well * Add dm_from_state_vector function to qml.math docs page. (#4291) * Fix `ShotAdaptiveOptimizer` to prevent removing shot vector axes (#4240) * Updated `ShotAdaptiveOpt` per suggestion in issue * Reformatting * Testing changes * Fixed bug * Updated min to max * Allow classical shadow measurements in new device (#4162) * Support classical shadow measurements * Add shot vector tests * Address comments * Add comment for diag_list * Apply suggestions from code review Co-authored-by: Christina Lee * Fix * Add unit tests for process_state_with_shots * Update preprocessing * Apply suggestions from code review Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> * Address PR comments * set rng for test --------- Co-authored-by: Christina Lee Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> * Add the `fermi` module docs (#4250) * support trainable Sum observables (#4251) * support trainable Sum observables (analytic only) * just use pre-rotated state; use super with finite shots * fix tests; add test for trainable Sum coeffs * changelog * use overlapping wires to prove they work * add hacky gradient support * Revert "add hacky gradient support" This reverts commit 09009c8d40d6c10e695fe40aa8e08b8e29cd1a9d. * set interface=None to keep test behaviour * Integrate experimental device with the `QNode` (#4196) * integrate qnode with new device * some diff method improvements * repr methods * add tests, always pass config to device * add tests, always pass config to device * final test * pylint * autograd integration tests * pylint * Update pennylane/interfaces/execution.py Co-authored-by: Matthew Silverman * pass shots through methods * changelog * revert set shots change * Apply suggestions from code review Co-authored-by: Matthew Silverman * revert executionc hange, pylint: * pylint again --------- Co-authored-by: Matthew Silverman Co-authored-by: Romain Moyard * pre-release for v0.31.0 (#4265) * pre-release for v0.31.0 * Update doc/releases/changelog-0.31.0.md * Support `HardwareHamiltonian` pulses in `stoch_pulse_grad` (#4215) * single out gradient transform checks * rename stochastic pulse gradient file * unify gradient_analysis and grad_method_validation * continue restructure of analysis+validation * CV * black * modularize more * more modularizing * black * tiny [skip ci] * [skip ci] lint * remove dummy test * test fix * add test file to linting test file * test fixes, docstrings * code review * docstring gradient_analysis_and_grad_method_validation * move first fun * code review:move functions * test regex * regexs * move and promote reorder_grads * tmp * more tmp * test cases, contractions * lint * docstring * even more tmp * cleanup * black * tmp * lint * move stoch_pulse_gradient.. files back to pulse_gradient... * move stoch_pulse_gradient.. files back to pulse_gradient... * lint * rename * extend functions and tests * lint and black * changelog * improve * update example to include non-Pauli word generator * add jit test with pauli sentence * tmp * debugging, docstring, extend test * review * optimize for Pauli words * Apply suggestions from code review Co-authored-by: Romain Moyard * test cases code review * fix parametrization * drafting * working prototype * finish merge; cleanup * changelog * comments * [skip ci] * raising an error; cleanup [skip ci] * Apply suggestions from code review Co-authored-by: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> * change contraction idea * typo in docs * tests * remove prints * fix test * test descriptions * fix merge * format * code review; test coverage * coverage reordering * fix * trigger CI * trigger * clear caches * trigger * trigger --------- Co-authored-by: Romain Moyard Co-authored-by: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> Co-authored-by: Korbinian Kottmann * Fix batching of derivative tapes in autograd (#4245) * Fix `expval` of `Sum` with broadcasting (#4275) * fix bug and add test * changelog addition * Various doc fixes (#4268) * Various things while validating changes for 0.31 (#4279) * change link in setup.py * fix qchem docs * fix qcut docs * rename string function (#4278) * Update docs (#4269) * Incrementing the version number to `v0.32.0-dev` (#4266) * pre-release for v0.31.0 * Update doc/releases/changelog-0.31.0.md * Incrementing the version number to v0.32.0-dev * Update installation.rst (#4247) Update Python version to 3.8 Co-authored-by: Romain Moyard * docs typos * don't include updating versioning from master * don't include python requirements from master * update example output for shot_vector * Update pennylane/operation.py Co-authored-by: Matthew Silverman * fix entirely unrelated rendering issue --------- Co-authored-by: Matthew Silverman Co-authored-by: Catalina Albornoz Co-authored-by: Romain Moyard * Minor fixes for v0.31 (#4280) * Added fixes * Update pennylane/ops/qubit/matrix_ops.py Co-authored-by: Matthew Silverman * Update pennylane/ops/qubit/matrix_ops.py --------- Co-authored-by: Matthew Silverman * add graphviz to RTD apt packages (#4284) * Updated test --------- Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> Co-authored-by: Christina Lee Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> Co-authored-by: Matthew Silverman Co-authored-by: Romain Moyard Co-authored-by: David Wierichs Co-authored-by: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> Co-authored-by: Korbinian Kottmann Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> Co-authored-by: Catalina Albornoz * Make links to qml.eigvals and the eigvals method appear distinctly. (#4292) * Make links to qml.eigvals and the eigvals method appear distinctly in the docstring of compute_method. * Make links more informative. --------- Co-authored-by: Matthew Silverman * Device API review (#4283) * fix example * minor docstring change in `_zyz_decomposition` * update decomposition examples * update counts & samples docstrings * Shots class link * Fix code block * Change note Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> --------- Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> * remove duplicate controlled operation entries (#4298) * Refer to class QNode in ml layers (#4299) * Specs dict fix (#4286) * Add copy function to SpecsDict. * black * Adjust docs to not throw warnings from deprecated specs keys. * Formatting fix. * update doc string * update doc string --------- Co-authored-by: Jay Soni Co-authored-by: Matthew Silverman * Fermionic ops in changelog (#4302) * bump lightning to 0.31 in setup.py, pin scipy to [1.8,1.10] (#4304) * bump lightning to 0.31 in setup.py * trigger ci * pin scipy * Revert "pin scipy" This reverts commit 492ad40315a7b1c1a669d2b1d833da7c3aea4952. * take Soran's forward fix for scipy fac2 * revert the scipy un-pinning and forward fix - just pin scipy * remove lower bound in files that didn't have them * remove autograd lower bound * Implement new Dataset class using HDF5 (#4369) * rename changelog * test * rename changelog back * changes from 9f5939e * changes from b6dc487 * changes until 01d9141 * changes until 935832e * changes until 76c4ae8 * trigger ci * newline * revert * changes until 3b59d71 * trigger ci * Make qchem compatible with scipy factorial2 (#4387) * pull diff from #4321 * unpin scipy * Release notes and version bump (#4385) * changelog and version bump * Add qchem changes * Apply suggestions from code review Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> --------- Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> * pin autoray (#4399) * raise error with actual identifier name (#4405) * Escape special characters in Dataset URL (#4412) * url escape * remove debug * update changelog * pylint * Update doc/releases/changelog-0.31.1.md Co-authored-by: Matthew Silverman --------- Co-authored-by: Matthew Silverman * BUGFIX: ensure that operators are not queued upon deserialization (#4414) * ensure that operators are not queued upon deserialization * Update tests/data/attributes/operator/test_operator.py * Updated the datasets docs (#4400) * Updated the datasets docs * Update doc/Makefile * a few more * Apply suggestions from code review Co-authored-by: Utkarsh * Apply suggestions from code review Co-authored-by: Diego <67476785+DSGuala@users.noreply.github.com> * Update pennylane/data/data_manager/__init__.py Co-authored-by: Josh Izaac * fix typo * fix typo --------- Co-authored-by: Utkarsh Co-authored-by: Diego <67476785+DSGuala@users.noreply.github.com> Co-authored-by: Diego * Fix error message when successively loading the same dataset (#4422) * Fix Attribute Error when accessing identifiers on partial dataset (#4413) * no exception if identifiers are not downloaded * always download identifiers * Update tests/data/test_dataset.py Co-authored-by: Matthew Silverman * add error message --------- Co-authored-by: Matthew Silverman * Update tests/tape/test_qscript.py * Update setup.py * Update requirements.txt Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> --------- Co-authored-by: David Wierichs Co-authored-by: Romain Moyard Co-authored-by: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> Co-authored-by: Korbinian Kottmann Co-authored-by: Christina Lee Co-authored-by: Matthew Silverman Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> Co-authored-by: Catalina Albornoz Co-authored-by: Mudit Pandey Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> Co-authored-by: Isaac De Vlugt Co-authored-by: Isaac De Vlugt <34751083+isaacdevlugt@users.noreply.github.com> Co-authored-by: soranjh Co-authored-by: BorjaRequena <59647767+BorjaRequena@users.noreply.github.com> Co-authored-by: Jay Soni Co-authored-by: Jack Brown Co-authored-by: Josh Izaac Co-authored-by: Utkarsh Co-authored-by: Diego <67476785+DSGuala@users.noreply.github.com> Co-authored-by: Diego Co-authored-by: albi3ro * Integrate `TransformProgram` with `QNode` (#4404) * Draft structure * draf exec * Simple execute * Update * More tests * Update * Update exec * Pylint and black * Update tests * Update more tests * More tests * changelog * Coverage * Cover fix * pylint * Pylint * Pylint tests * proposed changes to transform program integration * oops * add to legacy, remove cotransform support * just transform program call component * just transform program call component * no longer support cotransforms, fix _batch_postprocessing * some more testing * test null postprocessing function * docstring, rename batch_slices to slices, black * Apply suggestions from code review Co-authored-by: Matthew Silverman * integrate transform program with qnode * adding integration tests * test modifications * [skip ci] fiddling * more testing * changelog entry * add to execute, start on testing * add qml.execute tests * Update doc/releases/changelog-dev.md Co-authored-by: Matthew Silverman * fix test --------- Co-authored-by: rmoyard Co-authored-by: Matthew Silverman * move multiprocessing pre-processing to preprocess (#4425) * move multiprocessing pre-processing to preprocess * add test for None case * you knew it was a bad idea... 🙃 * changelog * Remove `parametric_ops.py` (#4434) * Removed `parametric_ops.py` * Fixed test --------- Co-authored-by: Christina Lee * Updated signature * Removed `dev_wires` arg * Fixed defer_measurements * Fixed tests * Fixed tests * Update mid_measure tests * Updated how queuing for `MidMeasureMP` works * Updated tests * Added tests * Add converter function for PySCF's CISD to PennyLane statevector (#4427) * Updated init * Reverted queuing change * Trigger CI * Added documentation * Trigger CI * Trigger CI * Update docs * Updated measurement value * Updated changelog * Update doc/releases/changelog-dev.md * Updated tests * Fix transmon Hamiltonians (#4418) * Fix transmon Hamiltonians * black * docs update * docs * update changelog * small updates * Update pennylane/pulse/transmon.py Co-authored-by: David Wierichs * Update pennylane/pulse/transmon.py Co-authored-by: David Wierichs * black * code review * formatting * Update pennylane/pulse/transmon.py Co-authored-by: David Wierichs * Update tests/pulse/test_transmon.py Co-authored-by: David Wierichs --------- Co-authored-by: David Wierichs * Updated measurement value * Updated changelog * Update doc/releases/changelog-dev.md * Updated tests * Added qubit reuse and reset * Removed `MeasurementValue` * Integrate `TransformProgram` with `QNode` (#4404) * Draft structure * draf exec * Simple execute * Update * More tests * Update * Update exec * Pylint and black * Update tests * Update more tests * More tests * changelog * Coverage * Cover fix * pylint * Pylint * Pylint tests * proposed changes to transform program integration * oops * add to legacy, remove cotransform support * just transform program call component * just transform program call component * no longer support cotransforms, fix _batch_postprocessing * some more testing * test null postprocessing function * docstring, rename batch_slices to slices, black * Apply suggestions from code review Co-authored-by: Matthew Silverman * integrate transform program with qnode * adding integration tests * test modifications * [skip ci] fiddling * more testing * changelog entry * add to execute, start on testing * add qml.execute tests * Update doc/releases/changelog-dev.md Co-authored-by: Matthew Silverman * fix test --------- Co-authored-by: rmoyard Co-authored-by: Matthew Silverman * Removed `dev_wires` arg * Fixed tests * Update mid_measure tests * Trigger CI * Trigger CI * Trigger CI * Updated tests * Updated tests * Remove references to measurement_ids * Removed weird code * Add converter function for PySCF's RCISD and CCSD/UCCSD to PennyLane statevector (#4433) * Remove references to measurement_ids * Updated init * linting * Removed merge artifacts * Fixing tests * Fixed tests * Linting * Testing to see if doc build fails * Check doc build * Progressing docs * Updated doc * doc test * Testing doc * Updated docstring * Changed docstring (hopefully the last time) * Apply suggestions from code review Co-authored-by: Christina Lee * Updating docs * linting * Removed old comment * Added optimization * Fixed preprocess test * linting * Cleaning up * Adding tests * Finish adding tests * Apply suggestions from code review Co-authored-by: Matthew Silverman * Updated docstring per review --------- Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> Co-authored-by: Christina Lee Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> Co-authored-by: Matthew Silverman Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> Co-authored-by: rmoyard Co-authored-by: Vincent Michaud-Rioux Co-authored-by: Rashid N H M <95639609+rashidnhm@users.noreply.github.com> Co-authored-by: BorjaRequena <59647767+BorjaRequena@users.noreply.github.com> Co-authored-by: David Wierichs Co-authored-by: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> Co-authored-by: Korbinian Kottmann Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> Co-authored-by: Catalina Albornoz Co-authored-by: Isaac De Vlugt Co-authored-by: Isaac De Vlugt <34751083+isaacdevlugt@users.noreply.github.com> Co-authored-by: soranjh Co-authored-by: Jay Soni Co-authored-by: Jack Brown Co-authored-by: Josh Izaac Co-authored-by: Utkarsh Co-authored-by: Diego <67476785+DSGuala@users.noreply.github.com> Co-authored-by: Diego Co-authored-by: albi3ro Co-authored-by: Stepan Fomichev --- doc/releases/changelog-dev.md | 7 + pennylane/drawer/drawable_layers.py | 5 - pennylane/measurements/mid_measure.py | 50 ++- pennylane/transforms/condition.py | 4 +- pennylane/transforms/defer_measurements.py | 83 ++++- tests/devices/qubit/test_preprocess.py | 6 +- tests/drawer/test_drawable_layers.py | 16 - tests/measurements/test_mid_measure.py | 16 +- tests/test_qnode.py | 24 +- tests/transforms/test_defer_measurements.py | 370 ++++++++++++++++---- 10 files changed, 431 insertions(+), 150 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index f586c36c900..7be85112af2 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -4,6 +4,10 @@

New features since last release

+* `qml.measure` now includes a boolean keyword argument `reset` to reset a wire to the + $|0\rangle$ computational basis state after measurement. + [(#4402)](https://github.com/PennyLaneAI/pennylane/pull/4402/) + * Python-native logging can now be enabled with `qml.logging.enable_logging()`. [(#4383)](https://github.com/PennyLaneAI/pennylane/pull/4383) @@ -66,6 +70,9 @@ array([False, False])

Improvements 🛠

+* Wires can now be reused after making a mid-circuit measurement on them. + [(#4402)](https://github.com/PennyLaneAI/pennylane/pull/4402/) + * Transform Programs, `qml.transforms.core.TransformProgram`, can now be called on a batch of circuits and return a new batch of circuits and a single post processing function. [(#4364)](https://github.com/PennyLaneAI/pennylane/pull/4364) diff --git a/pennylane/drawer/drawable_layers.py b/pennylane/drawer/drawable_layers.py index 0042b9e3122..9a29b90e320 100644 --- a/pennylane/drawer/drawable_layers.py +++ b/pennylane/drawer/drawable_layers.py @@ -97,11 +97,6 @@ def drawable_layers(ops, wire_map=None): # loop over operations for op in ops: is_mid_measure = is_conditional = False - if set(measured_wires.values()).intersection({wire_map[w] for w in op.wires}): - raise ValueError( - f"Cannot apply operations on {op.wires} as some wires have been measured already." - ) - if isinstance(op, MidMeasureMP): if len(op.wires) > 1: raise ValueError("Cannot draw mid-circuit measurements with more than one wire.") diff --git a/pennylane/measurements/mid_measure.py b/pennylane/measurements/mid_measure.py index 2de61e178a8..d27a2e9a4fd 100644 --- a/pennylane/measurements/mid_measure.py +++ b/pennylane/measurements/mid_measure.py @@ -24,8 +24,8 @@ from .measurements import MeasurementProcess, MidMeasure -def measure(wires): # TODO: Change name to mid_measure - """Perform a mid-circuit measurement in the computational basis on the +def measure(wires: Wires, reset: Optional[bool] = False): + r"""Perform a mid-circuit measurement in the computational basis on the supplied qubit. Measurement outcomes can be obtained and used to conditionally apply @@ -38,7 +38,7 @@ def measure(wires): # TODO: Change name to mid_measure .. code-block:: python3 - dev = qml.device("default.qubit", wires=2) + dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def func(x, y): @@ -55,16 +55,37 @@ def func(x, y): >>> func(*pars) tensor([0.90165331, 0.09834669], requires_grad=True) + Wires can be reused after measurement. Moreover, measured wires can be reset + to the :math:`|0 \rangle` by setting ``reset=True``. + + .. code-block:: python3 + + dev = qml.device("default.qubit", wires=3) + + @qml.qnode(dev) + def func(): + qml.PauliX(1) + m_0 = qml.measure(1, reset=True) + return qml.probs(wires=[1]) + + Executing this QNode: + + >>> func() + tensor([1., 0.], requires_grad=True) + Mid circuit measurements can be manipulated using the following dunder methods ``+``, ``-``, ``*``, ``/``, ``~`` (not), ``&`` (and), ``|`` (or), ``==``, ``<=``, ``>=``, ``<``, ``>`` with other mid-circuit measurements or scalars. - Note: - python ``not``, ``and``, ``or``, do not work since these do not have dunder - methods. Instead use ``~``, ``&``, ``|``. + .. Note :: + + Python ``not``, ``and``, ``or``, do not work since these do not have dunder methods. + Instead use ``~``, ``&``, ``|``. Args: wires (Wires): The wire of the qubit the measurement process applies to. + reset (Optional[bool]): Whether to reset the wire to the :math:`|0 \rangle` + state after measurement. Returns: MidMeasureMP: measurement process instance @@ -72,6 +93,7 @@ def func(x, y): Raises: QuantumFunctionError: if multiple wires were specified """ + wire = Wires(wires) if len(wire) > 1: raise qml.QuantumFunctionError( @@ -80,7 +102,7 @@ def func(x, y): # Create a UUID and a map between MP and MV to support serialization measurement_id = str(uuid.uuid4())[:8] - mp = MidMeasureMP(wires=wire, id=measurement_id) + mp = MidMeasureMP(wires=wire, reset=reset, id=measurement_id) return MeasurementValue([mp], processing_fn=lambda v: v) @@ -90,17 +112,23 @@ def func(x, y): class MidMeasureMP(MeasurementProcess): """Mid-circuit measurement. + This class additionally stores information about unknown measurement outcomes in the qubit model. + Measurements on a single qubit in the computational basis are assumed. + Please refer to :func:`measure` for detailed documentation. Args: wires (.Wires): The wires the measurement process applies to. This can only be specified if an observable was not provided. - id (str): custom label given to a measurement instance, can be useful for some applications - where the instance has to be identified + reset (bool): Whether to reset the wire after measurement. + id (str): Custom label given to a measurement instance. """ - def __init__(self, wires: Optional[Wires] = None, id: Optional[str] = None): - super().__init__(wires=wires, id=id) + def __init__( + self, wires: Optional[Wires] = None, reset: Optional[bool] = False, id: Optional[str] = None + ): + super().__init__(wires=Wires(wires), id=id) + self.reset = reset @property def return_type(self): diff --git a/pennylane/transforms/condition.py b/pennylane/transforms/condition.py index 1c3530b0eff..d5dcd4ea6e7 100644 --- a/pennylane/transforms/condition.py +++ b/pennylane/transforms/condition.py @@ -67,7 +67,7 @@ def cond(condition, true_fn, false_fn=None): :func:`defer_measurements` transform. Args: - condition (.MeasurementValue[bool]): a conditional expression involving a mid-circuit + condition (.MeasurementValue): a conditional expression involving a mid-circuit measurement value (see :func:`.pennylane.measure`) true_fn (callable): The quantum function of PennyLane operation to apply if ``condition`` is ``True`` @@ -114,6 +114,8 @@ def qnode(x, y): Expressions with boolean logic flow using operators like ``and``, ``or`` and ``not`` are not supported as the ``condition`` argument. + While such statements may not result in errors, they may result in + incorrect behaviour. .. details:: :title: Usage Details diff --git a/pennylane/transforms/defer_measurements.py b/pennylane/transforms/defer_measurements.py index 29c13cdfe27..3bf6ffa355e 100644 --- a/pennylane/transforms/defer_measurements.py +++ b/pennylane/transforms/defer_measurements.py @@ -16,12 +16,15 @@ from pennylane.measurements import MidMeasureMP from pennylane.ops.op_math import ctrl from pennylane.queuing import apply +from pennylane.tape import QuantumTape from pennylane.transforms import qfunc_transform from pennylane.wires import Wires +# pylint: disable=too-many-branches + @qfunc_transform -def defer_measurements(tape): +def defer_measurements(tape: QuantumTape): """Quantum function transform that substitutes operations conditioned on measurement outcomes to controlled operations. @@ -40,6 +43,14 @@ def defer_measurements(tape): that can be controlled as such depends on the set of operations supported by the chosen device. + .. note:: + + Devices that inherit from :class:`~pennylane.QubitDevice` **must** be initialized + with an additional wire for each mid-circuit measurement after which the measured + wire is reused or reset for ``defer_measurements`` to transform the quantum tape + correctly. Hence, devices and quantum tapes must also be initialized without custom + wire labels for correct behaviour. + .. note:: This transform does not change the list of terminal measurements returned by @@ -54,7 +65,7 @@ def defer_measurements(tape): post-measurement states are considered. Args: - qfunc (function): a quantum function + tape (.QuantumTape): a quantum tape **Example** @@ -84,8 +95,30 @@ def qfunc(par): >>> qml.grad(qnode)(par) tensor(-0.49622252, requires_grad=True) + + Reusing and reseting measured wires will work as expected with the + ``defer_measurements`` transform: + + .. code-block:: python3 + + dev = qml.device("default.qubit", wires=3) + + @qml.qnode(dev) + def func(x, y): + qml.RY(x, wires=0) + qml.CNOT(wires=[0, 1]) + m_0 = qml.measure(1, reset=True) + + qml.cond(m_0, qml.RY)(y, wires=0) + qml.RX(np.pi/4, wires=1) + return qml.probs(wires=[0, 1]) + + Executing this QNode: + + >>> pars = np.array([0.643, 0.246], requires_grad=True) + >>> func(*pars) + tensor([0.76960924, 0.13204407, 0.08394415, 0.01440254], requires_grad=True) """ - measured_wires = {} cv_types = (qml.operation.CVOperation, qml.operation.CVObservable) ops_cv = any(isinstance(op, cv_types) for op in tape.operations) @@ -93,25 +126,51 @@ def qfunc(par): if ops_cv or obs_cv: raise ValueError("Continuous variable operations and observables are not supported.") - for op in tape: - op_wires_measured = set(wire for wire in op.wires if wire in measured_wires.values()) - if len(op_wires_measured) > 0: - raise ValueError( - f"Cannot apply operations on {op.wires} as the following wires have been measured already: {op_wires_measured}." + # Find wires that are reused after measurement + measured_wires = set() + reused_measurement_wires = set() + + for op in tape.operations: + if isinstance(op, MidMeasureMP): + if op.wires[0] in measured_wires or op.reset is True: + reused_measurement_wires.add(op.wires[0]) + measured_wires.add(op.wires[0]) + + else: + reused_measurement_wires = reused_measurement_wires.union( + measured_wires.intersection(op.wires.toset()) ) + # Apply controlled operations to store measurement outcomes and replace + # classically controlled operations + control_wires = {} + cur_wire = max(tape.wires) + 1 + + for op in tape: if isinstance(op, MidMeasureMP): - measured_wires[op.id] = op.wires[0] + # Only store measurement outcome in new wire if wire gets reused + if op.wires[0] in reused_measurement_wires: + control_wires[op.id] = cur_wire + + qml.CNOT([op.wires[0], cur_wire]) + if op.reset: + qml.CNOT([cur_wire, op.wires[0]]) + + cur_wire += 1 + else: + control_wires[op.id] = op.wires[0] elif op.__class__.__name__ == "Conditional": - _add_control_gate(op, measured_wires) + _add_control_gate(op, control_wires) else: apply(op) + return tape._qfunc_output # pylint: disable=protected-access + -def _add_control_gate(op, measured_wires): +def _add_control_gate(op, control_wires): """Helper function to add control gates""" - control = [measured_wires[m.id] for m in op.meas_val.measurements] + control = [control_wires[m.id] for m in op.meas_val.measurements] for branch, value in op.meas_val._items(): # pylint: disable=protected-access if value: ctrl( diff --git a/tests/devices/qubit/test_preprocess.py b/tests/devices/qubit/test_preprocess.py index 9c1ad571a73..83d17f709e0 100644 --- a/tests/devices/qubit/test_preprocess.py +++ b/tests/devices/qubit/test_preprocess.py @@ -217,7 +217,7 @@ def test_expand_fn_expand_unsupported_op(self, shots): # pylint: disable=no-member def test_expand_fn_defer_measurement(self): """Test that expand_fn defers mid-circuit measurements.""" - mp = MidMeasureMP(wires=[0], id="test_id") + mp = MidMeasureMP(wires=[0], reset=True, id="test_id") mv = MeasurementValue([mp], processing_fn=lambda v: v) ops = [ qml.Hadamard(0), @@ -230,7 +230,9 @@ def test_expand_fn_defer_measurement(self): expanded_tape = expand_fn(tape) expected = [ qml.Hadamard(0), - qml.ops.Controlled(qml.RX(0.123, wires=1), 0), + qml.CNOT([0, 2]), + qml.CNOT([2, 0]), + qml.ops.Controlled(qml.RX(0.123, wires=1), 2), ] for op, exp in zip(expanded_tape, expected + measurements): diff --git a/tests/drawer/test_drawable_layers.py b/tests/drawer/test_drawable_layers.py index a842a3c915a..5bac4268d6a 100644 --- a/tests/drawer/test_drawable_layers.py +++ b/tests/drawer/test_drawable_layers.py @@ -173,23 +173,7 @@ def test_empty_layers_are_pruned(self): layers = drawable_layers(ops, wire_map={i: i for i in range(3)}) assert layers == [[ops[1]], [ops[2], ops[0]], [ops[3]]] - def test_cannot_reuse_wire_after_conditional(self): - """Tests that a wire cannot be re-used after using a mid-circuit measurement.""" - with AnnotatedQueue() as q: - m0 = qml.measure(0) - qml.cond(m0, qml.PauliX)(1) - qml.Hadamard(0) - - with pytest.raises(ValueError, match="some wires have been measured already"): - drawable_layers(q.queue) - def test_cannot_draw_multi_wire_MidMeasureMP(self): """Tests that MidMeasureMP is only supported with one wire.""" with pytest.raises(ValueError, match="mid-circuit measurements with more than one wire."): drawable_layers([MidMeasureMP([0, 1])]) - - def test_cannot_use_measured_wire(self): - """Tests error is raised when trying to use a measured wire.""" - ops = [MidMeasureMP([0]), qml.PauliX(0)] - with pytest.raises(ValueError, match="some wires have been measured already"): - drawable_layers(ops) diff --git a/tests/measurements/test_mid_measure.py b/tests/measurements/test_mid_measure.py index 06352b0a1de..6aa70a21457 100644 --- a/tests/measurements/test_mid_measure.py +++ b/tests/measurements/test_mid_measure.py @@ -26,7 +26,7 @@ def test_samples_computational_basis(): """Test that samples_computational_basis is always false for mid circuit measurements.""" - m = qml.measurements.MidMeasureMP(Wires(0)) + m = MidMeasureMP(Wires(0)) assert not m.samples_computational_basis @@ -44,19 +44,19 @@ def test_many_wires_error(self): def test_hash(self): """Test that the hash for `MidMeasureMP` is defined correctly.""" - m1 = MidMeasureMP(Wires(0), "m1") - m2 = MidMeasureMP(Wires(0), "m2") - m3 = MidMeasureMP(Wires(1), "m1") - m4 = MidMeasureMP(Wires(0), "m1") + m1 = MidMeasureMP(Wires(0), id="m1") + m2 = MidMeasureMP(Wires(0), id="m2") + m3 = MidMeasureMP(Wires(1), id="m1") + m4 = MidMeasureMP(Wires(0), id="m1") assert m1.hash != m2.hash assert m1.hash != m3.hash assert m1.hash == m4.hash -mp1 = MidMeasureMP(Wires(0), "m0") -mp2 = MidMeasureMP(Wires(1), "m1") -mp3 = MidMeasureMP(Wires(2), "m2") +mp1 = MidMeasureMP(Wires(0), id="m0") +mp2 = MidMeasureMP(Wires(1), id="m1") +mp3 = MidMeasureMP(Wires(2), id="m2") class TestMeasurementValueManipulation: diff --git a/tests/test_qnode.py b/tests/test_qnode.py index 32c4333e104..65c9011e92f 100644 --- a/tests/test_qnode.py +++ b/tests/test_qnode.py @@ -1077,7 +1077,7 @@ def test_defer_meas_if_mcm_unsupported(self, first_par, sec_par, return_type): """Tests that the transform using the deferred measurement principle is applied if the device doesn't support mid-circuit measurements natively.""" - dev = qml.device("default.qubit", wires=2) + dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def cry_qnode(x, y): @@ -1105,7 +1105,7 @@ def conditional_ry_qnode(x, y): def test_sampling_with_mcm(self, basis_state): """Tests that a QNode with qml.sample and mid-circuit measurements returns the expected results.""" - dev = qml.device("default.qubit", wires=2, shots=1000) + dev = qml.device("default.qubit", wires=3, shots=1000) first_par = np.pi @@ -1135,7 +1135,7 @@ def test_conditional_ops_tensorflow(self, interface): """Test conditional operations with TensorFlow.""" import tensorflow as tf - dev = qml.device("default.qubit", wires=2) + dev = qml.device("default.qubit", wires=3) @qml.qnode(dev, interface=interface, diff_method="parameter-shift") def cry_qnode(x): @@ -1178,7 +1178,7 @@ def test_conditional_ops_torch(self, interface): """Test conditional operations with Torch.""" import torch - dev = qml.device("default.qubit", wires=2) + dev = qml.device("default.qubit", wires=3) @qml.qnode(dev, interface=interface, diff_method="parameter-shift") def cry_qnode(x): @@ -1217,7 +1217,7 @@ def test_conditional_ops_jax(self, jax_interface): import jax jnp = jax.numpy - dev = qml.device("default.qubit", wires=2) + dev = qml.device("default.qubit", wires=3) @qml.qnode(dev, interface=jax_interface, diff_method="parameter-shift") def cry_qnode(x): @@ -1246,20 +1246,6 @@ def conditional_ry_qnode(x): assert np.allclose(r1, r2) assert np.allclose(jax.grad(cry_qnode)(x1), jax.grad(conditional_ry_qnode)(x2)) - def test_already_measured_error_operation(self): - """Test that attempting to apply an operation on a wires that has been - measured raises an error.""" - dev = qml.device("default.qubit", wires=3) - - @qml.qnode(dev) - def circuit(): - qml.measure(1) - qml.PauliX(1) - return qml.expval(qml.PauliZ(0)) - - with pytest.raises(ValueError, match="wires have been measured already: {1}"): - circuit() - def test_qnode_does_not_support_nested_queuing(self): """Test that operators in QNodes are not queued to surrounding contexts.""" dev = qml.device("default.qubit", wires=1) diff --git a/tests/transforms/test_defer_measurements.py b/tests/transforms/test_defer_measurements.py index 6e247e1c8f2..87ccf6a244c 100644 --- a/tests/transforms/test_defer_measurements.py +++ b/tests/transforms/test_defer_measurements.py @@ -47,19 +47,98 @@ def qnode2(): assert isinstance(res1, type(res2)) assert res1.shape == res2.shape - assert len(qnode1.qtape.operations) == len(qnode2.qtape.operations) + assert len(qnode2.qtape.operations) == 0 assert len(qnode1.qtape.measurements) == len(qnode2.qtape.measurements) - # Check the operations - for op1, op2 in zip(qnode1.qtape.operations, qnode2.qtape.operations): - assert isinstance(op1, type(op2)) - assert op1.data == op2.data - # Check the measurements for op1, op2 in zip(qnode1.qtape.measurements, qnode2.qtape.measurements): assert isinstance(op1, type(op2)) assert op1.data == op2.data + def test_reuse_wire_after_measurement(self): + """Test that wires can be reused after measurement.""" + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + @qml.defer_measurements + def qnode(): + qml.Hadamard(0) + qml.measure(0) + qml.PauliZ(0) + return qml.expval(qml.PauliX(0)) + + _ = qnode() + + def test_no_new_wires_without_reuse(self): + """Test that new wires are not added if a measured wire is not reused.""" + dev = qml.device("default.qubit", wires=3) + + # Quantum teleportation + @qml.qnode(dev) + def qnode1(phi): + qml.RX(phi, 0) + qml.Hadamard(1) + qml.CNOT([1, 2]) + qml.CNOT([0, 1]) + qml.Hadamard(0) + + m0 = qml.measure(0) + qml.cond(m0, qml.PauliZ)(2) + m1 = qml.measure(1) + qml.cond(m1, qml.PauliX)(2) + return qml.expval(qml.PauliZ(2)) + + # Prepare wire 0 in arbitrary state + @qml.qnode(dev) + def qnode2(phi): + qml.RX(phi, 0) + return qml.expval(qml.PauliZ(0)) + + # Outputs should match + assert np.isclose(qnode1(np.pi / 4), qnode2(np.pi / 4)) + + assert isinstance(qnode1.qtape.operations[5], qml.ops.Controlled) + assert qml.equal(qnode1.qtape.operations[5].base, qml.PauliZ(2)) + assert qnode1.qtape.operations[5].hyperparameters["control_wires"] == qml.wires.Wires(0) + + assert qml.equal(qnode1.qtape.operations[6], qml.CNOT([1, 2])) + + def test_new_wires_after_reuse(self): + """Test that a new wire is added for every measurement after which + the wire is reused.""" + dev = qml.device("default.qubit", wires=4) + + @qml.qnode(dev) + def qnode1(phi, theta): + qml.RX(phi, 0) + m0 = qml.measure(0, reset=True) # Reused measurement, one new wire added + qml.cond(m0, qml.Hadamard)(1) + m1 = qml.measure(1) # No reuse + qml.RY(theta, 2) + qml.cond(m1, qml.RY)(-theta, 2) + return qml.expval(qml.PauliZ(2)) + + res1 = qnode1(np.pi / 4, 3 * np.pi / 4) + + assert len(qnode1.qtape.wires) == 4 + assert len(qnode1.qtape.operations) == 6 + + @qml.qnode(dev) + def qnode2(phi, theta): + qml.RX(phi, 0) + m0 = qml.measure(0) # No reuse + qml.cond(m0, qml.Hadamard)(1) + m1 = qml.measure(1) # No reuse + qml.RY(theta, 2) + qml.cond(m1, qml.RY)(-theta, 2) + return qml.expval(qml.PauliZ(2)) + + res2 = qnode2(np.pi / 4, 3 * np.pi / 4) + assert np.allclose(res1, res2) + + assert len(qnode2.qtape.wires) == 3 + assert len(qnode2.qtape.operations) == 4 + def test_measure_between_ops(self): """Test that a quantum function that contains one operation before and after a mid-circuit measurement yields the correct results and is @@ -87,7 +166,7 @@ def func2(): assert isinstance(res1, type(res2)) assert res1.shape == res2.shape - assert len(qnode1.qtape.operations) == len(qnode2.qtape.operations) + assert len(qnode2.qtape.operations) == len(qnode1.qtape.operations) assert len(qnode1.qtape.measurements) == len(qnode2.qtape.measurements) # Check the operations @@ -100,13 +179,12 @@ def func2(): assert isinstance(op1, type(op2)) assert op1.data == op2.data - @pytest.mark.parametrize( - "mid_measure_wire, tp_wires", [(0, [1, 2, 3]), (0, [3, 1, 2]), ("a", ["b", "c", "d"])] - ) + @pytest.mark.parametrize("mid_measure_wire, tp_wires", [(0, [1, 2, 3]), (0, [3, 1, 2])]) def test_measure_with_tensor_obs(self, mid_measure_wire, tp_wires): """Test that the defer_measurements transform works well even with tensor observables in the tape.""" # pylint: disable=protected-access + with qml.queuing.AnnotatedQueue() as q: qml.measure(mid_measure_wire) qml.expval(qml.operation.Tensor(*[qml.PauliZ(w) for w in tp_wires])) @@ -115,7 +193,6 @@ def test_measure_with_tensor_obs(self, mid_measure_wire, tp_wires): tape = qml.defer_measurements(tape) # Check the operations and measurements in the tape - assert tape._ops == [] assert len(tape.measurements) == 1 measurement = tape.measurements[0] @@ -128,37 +205,6 @@ def test_measure_with_tensor_obs(self, mid_measure_wire, tp_wires): assert isinstance(ob, qml.PauliZ) assert ob.wires == qml.wires.Wires(tp_wires[idx]) - def test_already_measured_error_operation(self): - """Test that attempting to apply an operation on a wires that has been - measured raises an error.""" - dev = qml.device("default.qubit", wires=3) - - def qfunc(): - qml.measure(1) - qml.PauliX(1) - return qml.expval(qml.PauliZ(0)) - - tape_deferred_func = qml.defer_measurements(qfunc) - qnode = qml.QNode(tape_deferred_func, dev) - - with pytest.raises(ValueError, match="wires have been measured already: {1}"): - qnode() - - def test_already_measured_error_terminal_measurement(self): - """Test that attempting to measure a wire at the end of the circuit - that has been measured in the middle of the circuit raises an error.""" - dev = qml.device("default.qubit", wires=3) - - def qfunc(): - qml.measure(1) - return qml.expval(qml.PauliZ(1)) - - tape_deferred_func = qml.defer_measurements(qfunc) - qnode = qml.QNode(tape_deferred_func, dev) - - with pytest.raises(ValueError, match="Cannot apply operations"): - qnode() - def test_cv_op_error(self): """Test that CV operations are not supported.""" dev = qml.device("default.gaussian", wires=3) @@ -206,7 +252,7 @@ def test_correct_ops_in_tape(self, terminal_measurement): sec_par = 0.3 with qml.queuing.AnnotatedQueue() as q: - m_0 = qml.measure(4) + m_0 = qml.measure(4, reset=True) qml.cond(m_0, qml.RY)(first_par, wires=1) m_1 = qml.measure(3) @@ -216,15 +262,15 @@ def test_correct_ops_in_tape(self, terminal_measurement): tape = qml.tape.QuantumScript.from_queue(q) tape = qml.defer_measurements(tape) - assert len(tape.operations) == 2 + assert len(tape.operations) == 4 assert len(tape.measurements) == 1 # Check the two underlying Controlled instances - first_ctrl_op = tape.operations[0] + first_ctrl_op = tape.operations[2] assert isinstance(first_ctrl_op, qml.ops.op_math.Controlled) assert qml.equal(first_ctrl_op.base, qml.RY(first_par, 1)) - sec_ctrl_op = tape.operations[1] + sec_ctrl_op = tape.operations[3] assert isinstance(sec_ctrl_op, qml.ops.op_math.Controlled) assert qml.equal(sec_ctrl_op.base, qml.RZ(sec_par, 1)) @@ -301,7 +347,7 @@ def test_quantum_teleportation(self, rads): # Alice measures her qubits, obtaining one of four results, and sends this information to Bob. m_0 = qml.measure(0) - m_1 = qml.measure(1) + m_1 = qml.measure(1, reset=True) # Given Alice's measurements, Bob performs one of four operations on his half of the EPR pair and # recovers the original quantum state. @@ -312,7 +358,9 @@ def test_quantum_teleportation(self, rads): tape = qml.tape.QuantumScript.from_queue(q) tape = qml.defer_measurements(tape) - assert len(tape.operations) == 5 + 2 # 5 regular ops + 2 conditional ops + assert ( + len(tape.operations) == 5 + 1 + 1 + 2 + ) # 5 regular ops + 1 measurement op + 1 reset op + 2 conditional ops assert len(tape.measurements) == 1 # Check the each operation @@ -337,12 +385,22 @@ def test_quantum_teleportation(self, rads): assert isinstance(op5, qml.Hadamard) assert op5.wires == qml.wires.Wires([0]) - # Check the two underlying Controlled instances - ctrl_op1 = tape.operations[5] + # Check the two underlying CNOTs for storing measurement state + meas_op1 = tape.operations[5] + assert isinstance(meas_op1, qml.CNOT) + assert meas_op1.wires == qml.wires.Wires([1, 3]) + + meas_op2 = tape.operations[6] + assert isinstance(meas_op2, qml.CNOT) + assert meas_op2.wires == qml.wires.Wires([3, 1]) + + # Check the two underlying Controlled instances + ctrl_op1 = tape.operations[7] assert isinstance(ctrl_op1, qml.ops.op_math.Controlled) assert qml.equal(ctrl_op1.base, qml.RX(math.pi, 2)) + assert ctrl_op1.wires == qml.wires.Wires([3, 2]) - ctrl_op2 = tape.operations[6] + ctrl_op2 = tape.operations[8] assert isinstance(ctrl_op2, qml.ops.op_math.Controlled) assert qml.equal(ctrl_op2.base, qml.RZ(math.pi, 2)) assert ctrl_op2.wires == qml.wires.Wires([0, 2]) @@ -388,18 +446,28 @@ def test_hermitian_queued(self): measurement = qml.expval(qml.Hermitian(mat, wires=[3, 1, 2])) with qml.queuing.AnnotatedQueue() as q: - m_0 = qml.measure(0) + m_0 = qml.measure(0, reset=True) qml.cond(m_0, qml.RY)(rads, wires=4) qml.apply(measurement) tape = qml.tape.QuantumScript.from_queue(q) tape = qml.defer_measurements(tape) - assert len(tape.operations) == 1 + assert len(tape.operations) == 3 assert len(tape.measurements) == 1 + # Check the underlying CNOT for storing measurement state + meas_op1 = tape.operations[0] + assert isinstance(meas_op1, qml.CNOT) + assert meas_op1.wires == qml.wires.Wires([0, 5]) + + # Check the underlying CNOT for reseting measured wire + meas_op1 = tape.operations[1] + assert isinstance(meas_op1, qml.CNOT) + assert meas_op1.wires == qml.wires.Wires([5, 0]) + # Check the underlying Controlled instances - first_ctrl_op = tape.operations[0] + first_ctrl_op = tape.operations[2] assert isinstance(first_ctrl_op, qml.ops.op_math.Controlled) assert qml.equal(first_ctrl_op.base, qml.RY(rads, 4)) @@ -435,7 +503,7 @@ def test_hamiltonian_queued(self): assert qml.equal(first_ctrl_op.base, qml.RY(rads, 4)) assert len(tape.measurements) == 1 assert isinstance(tape.measurements[0], qml.measurements.MeasurementProcess) - assert tape.measurements[0].obs == H + assert qml.equal(tape.measurements[0].obs, H) @pytest.mark.parametrize("device", ["default.qubit", "default.mixed", "lightning.qubit"]) @pytest.mark.parametrize("ops", [(qml.RX, qml.CRX), (qml.RY, qml.CRY), (qml.RZ, qml.CRZ)]) @@ -470,7 +538,7 @@ def quantum_control_circuit(rads): @pytest.mark.parametrize("device", ["default.qubit", "default.mixed", "lightning.qubit"]) def test_conditional_rotations_with_else(self, device): """Test that an else operation can also defined using qml.cond.""" - dev = qml.device(device, wires=2) + dev = qml.device(device, wires=3) r = 2.345 op1, controlled_op1 = qml.RY, qml.CRY @@ -504,7 +572,7 @@ def test_keyword_syntax(self): keyword syntax works.""" op = qml.RY - dev = qml.device("default.qubit", wires=2) + dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def qnode1(par): @@ -528,7 +596,7 @@ def qnode2(par): def test_condition_using_measurement_outcome(self, control_val, expected): """Apply a conditional bitflip by selecting the measurement outcome.""" - dev = qml.device("default.qubit", wires=2) + dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def qnode(): @@ -541,7 +609,7 @@ def qnode(): @pytest.mark.parametrize("device", ["default.qubit", "default.mixed", "lightning.qubit"]) def test_cond_qfunc(self, device): """Test that a qfunc can also used with qml.cond.""" - dev = qml.device(device, wires=2) + dev = qml.device(device, wires=3) r = 2.324 @@ -576,7 +644,7 @@ def quantum_control_circuit(r): def test_cond_qfunc_with_else(self, device): """Test that a qfunc can also used with qml.cond even when an else qfunc is provided.""" - dev = qml.device(device, wires=2) + dev = qml.device(device, wires=3) x = 0.3 y = 3.123 @@ -610,7 +678,25 @@ def cond_qnode(x, y): return qml.probs(wires=[0]) assert np.allclose(normal_circuit(x, y), cond_qnode(x, y)) - assert np.allclose(qml.matrix(normal_circuit)(x, y), qml.matrix(cond_qnode)(x, y)) + + def test_cond_on_measured_wire(self): + """Test that applying a conditional operation on the same wire + that is measured works as expected.""" + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + @qml.defer_measurements + def qnode(): + qml.Hadamard(0) + m = qml.measure(0) + qml.cond(m, qml.PauliX)(0) + return qml.density_matrix(0) + + # Above circuit will cause wire 0 to go back to the |0> computational + # basis state. We can inspect the reduced density matrix to confirm this + # without inspecting the extra wires + expected_dmat = np.array([[1, 0], [0, 0]]) + assert np.allclose(qnode(), expected_dmat) class TestExpressionConditionals: @@ -621,7 +707,7 @@ class TestExpressionConditionals: def test_conditional_rotations(self, r, op): """Test that the quantum conditional operations match the output of controlled rotations. And additionally that summing measurements works as expected.""" - dev = qml.device("default.qubit", wires=3) + dev = qml.device("default.qubit", wires=5) @qml.qnode(dev) def normal_circuit(rads): @@ -648,7 +734,7 @@ def quantum_control_circuit(rads): @pytest.mark.parametrize("r", np.linspace(0.1, 2 * np.pi - 0.1, 4)) def test_triple_measurement_condition_expression(self, r): """Test that combining the results of three mid-circuit measurements works as expected.""" - dev = qml.device("default.qubit", wires=4) + dev = qml.device("default.qubit", wires=7) @qml.qnode(dev) @qml.defer_measurements @@ -690,7 +776,7 @@ def test_multiple_conditions(self): """Test that when multiple "branches" of the mid-circuit measurements all satisfy the criteria then this translates to multiple control gates. """ - dev = qml.device("default.qubit", wires=4) + dev = qml.device("default.qubit", wires=7) @qml.qnode(dev) @qml.defer_measurements @@ -733,7 +819,7 @@ def quantum_control_circuit(rads): def test_composed_conditions(self): """Test that a complex nested expression gets resolved correctly to the corresponding correct control gates.""" - dev = qml.device("default.qubit", wires=4) + dev = qml.device("default.qubit", wires=7) @qml.qnode(dev) @qml.defer_measurements @@ -788,7 +874,7 @@ def test_basis_state_prep(self): basis_state = [0, 1, 1, 0] - dev = qml.device("default.qubit", wires=5) + dev = qml.device("default.qubit", wires=6) @qml.qnode(dev) def qnode1(): @@ -804,11 +890,9 @@ def qnode2(): qml.cond(m_0, template)(basis_state, wires=range(1, 5)) return qml.expval(qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3) @ qml.PauliZ(4)) - dev = qml.device("default.qubit", wires=2) - assert np.allclose(qnode1(), qnode2()) - assert len(qnode1.qtape.operations) == len(qnode2.qtape.operations) + assert len(qnode2.qtape.operations) == len(qnode1.qtape.operations) assert len(qnode1.qtape.measurements) == len(qnode2.qtape.measurements) # Check the operations @@ -827,7 +911,7 @@ def test_angle_embedding(self): template = qml.AngleEmbedding feature_vector = [1, 2, 3] - dev = qml.device("default.qubit", wires=5) + dev = qml.device("default.qubit", wires=6) @qml.qnode(dev) def qnode1(): @@ -843,13 +927,12 @@ def qnode2(): qml.cond(m_0, template)(features=feature_vector, wires=range(1, 5), rotation="Z") return qml.expval(qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3) @ qml.PauliZ(4)) - dev = qml.device("default.qubit", wires=2) res1 = qnode1() res2 = qnode2() assert np.allclose(res1, res2) - assert len(qnode1.qtape.operations) == len(qnode2.qtape.operations) + assert len(qnode2.qtape.operations) == len(qnode1.qtape.operations) assert len(qnode1.qtape.measurements) == len(qnode2.qtape.measurements) # Check the operations @@ -865,7 +948,7 @@ def qnode2(): @pytest.mark.parametrize("template", [qml.StronglyEntanglingLayers, qml.BasicEntanglerLayers]) def test_layers(self, template): """Test layers conditioned on mid-circuit measurement outcomes.""" - dev = qml.device("default.qubit", wires=3) + dev = qml.device("default.qubit", wires=4) num_wires = 2 @@ -888,7 +971,7 @@ def qnode2(parameters): assert np.allclose(qnode1(weights), qnode2(weights)) - assert len(qnode1.qtape.operations) == len(qnode2.qtape.operations) + assert len(qnode2.qtape.operations) == len(qnode1.qtape.operations) assert len(qnode1.qtape.measurements) == len(qnode2.qtape.measurements) # Check the operations @@ -902,13 +985,120 @@ def qnode2(parameters): assert np.allclose(op1.data, op2.data) +class TestQubitReset: + """Tests for the qubit reset functionality of `qml.measure`.""" + + def test_correct_cnot_for_reset(self): + """Test that a CNOT is applied from the wire that stores the measurement + to the measured wire after the measurement.""" + dev = qml.device("default.qubit", wires=3) + + @qml.qnode(dev) + def qnode1(x): + qml.Hadamard(0) + qml.CRX(x, [0, 1]) + return qml.expval(qml.PauliZ(1)) + + @qml.qnode(dev) + @qml.defer_measurements + def qnode2(x): + qml.Hadamard(0) + m0 = qml.measure(0, reset=True) + qml.cond(m0, qml.RX)(x, 1) + return qml.expval(qml.PauliZ(1)) + + assert np.allclose(qnode1(0.123), qnode2(0.123)) + + expected_circuit = [ + qml.Hadamard(0), + qml.CNOT([0, 2]), + qml.CNOT([2, 0]), + qml.ops.Controlled(qml.RX(0.123, 1), 2), + qml.expval(qml.PauliZ(1)), + ] + + assert len(qnode2.qtape.circuit) == len(expected_circuit) + assert all( + qml.equal(actual, expected) + for actual, expected in zip(qnode2.qtape.circuit, expected_circuit) + ) + + def test_wire_is_reset(self): + """Test that a wire is reset to the |0> state without any local phases + after measurement if reset is requested.""" + dev = qml.device("default.qubit", wires=3) + + @qml.qnode(dev) + @qml.defer_measurements + def qnode(x): + qml.Hadamard(0) + qml.PhaseShift(np.pi / 4, 0) + m = qml.measure(0, reset=True) + qml.cond(m, qml.RX)(x, 1) + return qml.density_matrix(wires=[0]) + + # Expected reduced density matrix on wire 0 + expected_mat = np.array([[1, 0], [0, 0]]) + assert np.allclose(qnode(0.123), expected_mat) + + def test_multiple_measurements_mixed_reset(self): + """Test that a QNode with multiple mid-circuit measurements with + different resets is transformed correctly.""" + dev = qml.device("default.qubit", wires=6) + + @qml.qnode(dev) + def qnode(p, x, y): + qml.Hadamard(0) + qml.PhaseShift(p, 0) + # Set measurement_ids so that the order of wires in combined + # measurement values is consistent + + mp0 = qml.measurements.MidMeasureMP(0, reset=True, id=0) + m0 = qml.measurements.MeasurementValue([mp0], lambda v: v) + qml.cond(~m0, qml.RX)(x, 1) + mp1 = qml.measurements.MidMeasureMP(1, reset=True, id=1) + m1 = qml.measurements.MeasurementValue([mp1], lambda v: v) + qml.cond(m0 & m1, qml.Hadamard)(0) + mp2 = qml.measurements.MidMeasureMP(0, id=2) + m2 = qml.measurements.MeasurementValue([mp2], lambda v: v) + qml.cond(m1 | m2, qml.RY)(y, 2) + return qml.expval(qml.PauliZ(2)) + + _ = qnode(0.123, 0.456, 0.789) + + expected_circuit = [ + qml.Hadamard(0), + qml.PhaseShift(0.123, 0), + qml.CNOT([0, 3]), + qml.CNOT([3, 0]), + qml.ops.Controlled(qml.RX(0.456, 1), 3, [False]), + qml.CNOT([1, 4]), + qml.CNOT([4, 1]), + qml.ops.Controlled(qml.Hadamard(0), [3, 4]), + qml.CNOT([0, 5]), + qml.ops.Controlled(qml.RY(0.789, 2), [4, 5], [False, True]), + qml.ops.Controlled(qml.RY(0.789, 2), [4, 5], [True, False]), + qml.ops.Controlled(qml.RY(0.789, 2), [4, 5], [True, True]), + qml.expval(qml.PauliZ(2)), + ] + + assert len(qnode.qtape.circuit) == len(expected_circuit) + assert all( + qml.equal(actual, expected) + for actual, expected in zip(qnode.qtape.circuit, expected_circuit) + ) + + class TestDrawing: """Tests drawing circuits with mid-circuit measurements and conditional operations that have been transformed""" - def test_drawing(self): + def test_drawing_no_reuse(self): """Test that drawing a func with mid-circuit measurements works and - that controlled operations are drawn for conditional operations.""" + that controlled operations are drawn for conditional operations when + the measured wires are not reused.""" + + # TODO: Update after drawing for mid-circuit measurements is updated. def qfunc(): m_0 = qml.measure(0) @@ -929,3 +1119,31 @@ def qfunc(): "2: ───────────╰●────────┤ " ) assert qml.draw(transformed_qnode)() == expected + + def test_drawing_with_reuse(self): + """Test that drawing a func with mid-circuit measurements works and + that controlled operations are drawn for conditional operations when + the measured wires are reused.""" + + # TODO: Update after drawing for mid-circuit measurements is updated. + + def qfunc(): + m_0 = qml.measure(0, reset=True) + qml.cond(m_0, qml.RY)(0.312, wires=1) + + m_2 = qml.measure(2) + qml.cond(m_2, qml.RY)(0.312, wires=1) + return qml.expval(qml.PauliZ(1)) + + dev = qml.device("default.qubit", wires=4) + + transformed_qfunc = qml.transforms.defer_measurements(qfunc) + transformed_qnode = qml.QNode(transformed_qfunc, dev) + + expected = ( + "0: ─╭●─╭X─────────────────────┤ \n" + "1: ─│──│──╭RY(0.31)─╭RY(0.31)─┤ \n" + "2: ─│──│──│─────────╰●────────┤ \n" + "3: ─╰X─╰●─╰●──────────────────┤ " + ) + assert qml.draw(transformed_qnode)() == expected