diff --git a/CHANGELOG.md b/CHANGELOG.md index 8323f4251..2f200eafe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,27 @@ The 4.0 release of pyQuil migrates its core functionality into Rigetti's latest - Python 3.7 is no longer supported. - The environment variable overrides for `quilc` and `QVM` URLs have been renamed to `QCS_APPLICATIONS_QUILC_URL` and `QCS_APPLICATIONS_QVM_URL`, respectively. - The `QuantumComputer`'s `run` method now takes an optional `memory_map` parameter. This mapping takes memory region names to a list of values to use for a run. This replaces the ability to use `write_memory` on `Program`s. -- `Pragma("DELAY", ...)` will now raise a parser error because it generates invalid Quil. Use the `Delay` instruction instead. +- `Program` and instructions have been re-written using the `quil` package. Much of the API remains the same, with the following exceptions: + - `SwapPhase` has been renamed to `SwapPhases` + - `TemplateWaveform` and its subclasses are no longer `@dataclass`es. + - `DefFrame` and `Frame` are no longer `@dataclass`es. + - The `pop` method has been removed from `Program`. + - A `Program` that uses `QubitPlaceholder`s or `LabelPlaceholder`s can no longer be pickled + - `DefMeasureCalibration` now requires a `MemoryReference`. + - `fill_placeholders` has been removed since it is no longer needed to expand calibrations. + - The `get_qubits` method on `Gate` now returns a `list` so that ordering is guaranteed. + - Setting the `offsets` property on `Declare` will raise a `ValueError` if no `shared_region` is set. + - When converting to Quil, a `Program` automatically places `DECLARE`s at the top of the program. +- The `parser` module has been removed. Parsing now happens by initializing a `Program` with the program string you want to be parsed. +- `PRAGMA` instructions can no longer have a directive that conflicts with a Quil keyword. If you were using directives like `DELAY` or `FENCE`, consider using the respective Quil-T instructions instead. +- `QubitPlaceholders` can no longer be used in `PRAGMA` instructions. +- `DefGate` and the other gate definition instructions will no longer accept names that conflict with Quil keywords. +- `Program#get_qubits()` will raise a `TypeError` if any of the qubits in the program are not a fixed index. +- A `Program`s `LabelPlaceholder`s are no longer resolved automatically when getting its instructions. Use the `resolve_label_placeholders` method to do it explicitly. Note that the `if_then` and `while_do` methods will add `LabelPlaceholder`s to your program. +- There may be some minor differences in how instructions are converted to a Quil string. These differences should only be cosmetic and should not affect the behavior of a program. However, they may break unit tests or other code that rely on specific formatting of programs. +- The `pyquil.quil.get_default_qubit_mapping` function for getting a mapping of `QubitPlaceholders` to resolved indices has been removed. Generating a default mapping is handled automatically by the placeholder resolving methods. +- The `JumpConditional` base class has been removed, use `JumpWhen` and/or `JumpUnless` directly instead. +- The `Program` class automatically sorts `DECLARE` instructions to the top of the Program when converting to Quil. ### Features @@ -24,11 +44,24 @@ The 4.0 release of pyQuil migrates its core functionality into Rigetti's latest - The new `QPUCompilerAPIOptions` class provides can now be used to customize how a program is compiled against a QPU. - The `diagnostics` module has been introduced with a `get_report` function that will gather information on the currently running pyQuil installation, perform diagnostics checks, and return a summary. +- `Program` has new methods for resolving Qubit and Label Placeholders in a program. +- `QubitPlaceholders` can now be used in programs that also use fixed or variable qubits. - `QAMExecutionResult` now has a `raw_readout_data` property that can be used to get the raw form of readout data returned from the executor. +- `WaveformInvocation` has been added as a simpler, more flexible class for invoking waveforms. +- Added two new instruction classes: + - The `Include` class for `INCLUDE` instructions. + - The `DefCircuit` class `DEFCIRCUIT` instructions. +- The `Program.copy` method now performs a deep copy. ### Deprecations - The `QAMExecutionResult` `readout_data` property has been deprecated to avoid confusion with the new `raw_readout_data` property. Use the `register_map` property instead. +- The `indices` flag on the `get_qubits` method on `Program`s and instruction classes continues to work, but will be removed in future versions. A separate `get_qubit_indices` method has been added to get indices. In future versions, `get_qubits` will only return a list of `QubitDesignator`s. +- The `is_protoquil`, `is_supported_on_qpu` methods on `Program` and the `validate_supported_quil` function will always return `True`. These methods were never reliable as they were implemented as client-side checks that don't necessarily reflect the latest available features on Rigetti compilers or QPUs. It's safe to stop using these functions and rely on the API to tell you if a program isn't supported. +- `percolate_declares` is a no-op and will be removed in future versions. `Program` now “percolates” declares automatically. +- `merge_programs` continues to work, but will be removed in future versions, use `Program` addition instead. +- The `format_parameter` function continues to work, but will be removed in future versions. +- The `WaveformReference` and `TemplateWaveform` classes continue to work, but will be removed in future versions. The new `WaveformInvocation` should be used instead. ## 3.5.4 diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index 207e9acdf..a8b01b930 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -146,15 +146,13 @@ where using ``QubitPlaceholder``\ s comes in. .. testoutput:: placeholders :hide: - H {q...} - CNOT {q...} {q...} + H Placeholder(QubitPlaceholder(0x...)) + CNOT Placeholder(QubitPlaceholder(0x...)) Placeholder(QubitPlaceholder(0x...)) .. parsed-literal:: - H {q4402789176} - CNOT {q4402789176} {q4402789120} - -If you try to use this program directly, it will not work + H Placeholder(QubitPlaceholder(0x600002DEB5B0)) + CNOT Placeholder(QubitPlaceholder(0x600002DEB5B0)) Placeholder(QubitPlaceholder(0x600002DEABB0)) .. Could not make this a doctest because it would keep failing. ``doctest`` is supposed to match the @@ -272,20 +270,21 @@ loop by following these steps: # Run inner_loop in a loop until flag_register is 0 outer_loop.while_do(flag_register, inner_loop) + outer_loop.resolve_label_placeholders() print(outer_loop) .. testoutput:: control-flow DECLARE flag_register BIT[1] - MOVE flag_register 1 - LABEL @START1 - JUMP-UNLESS @END2 flag_register + MOVE flag_register[0] 1 + LABEL @START_0 + JUMP-UNLESS @END_0 flag_register[0] X 0 H 0 - MEASURE 0 flag_register - JUMP @START1 - LABEL @END2 + MEASURE 0 flag_register[0] + JUMP @START_0 + LABEL @END_0 Notice that the ``outer_loop`` program applied a Quil instruction directly to a classical register. There are several classical commands that can be used in this fashion: @@ -323,21 +322,22 @@ method. # Measure qubit 0 into our readout register branching_prog += MEASURE(0, ro) + branching_prog.resolve_label_placeholders() print(branching_prog) .. testoutput:: control-flow - DECLARE test_register BIT[1] DECLARE ro BIT[1] + DECLARE test_register BIT[1] H 1 - MEASURE 1 test_register - JUMP-WHEN @THEN1 test_register - JUMP @END2 - LABEL @THEN1 + MEASURE 1 test_register[0] + JUMP-WHEN @THEN_0 test_register[0] + JUMP @END_0 + LABEL @THEN_0 X 0 - LABEL @END2 - MEASURE 0 ro + LABEL @END_0 + MEASURE 0 ro[0] We can run this program a few times to see what we get in the readout register ``ro``. @@ -437,7 +437,7 @@ The following shows an instructive example of all three. Quil to compute exp[iX] on qubit 0: H 0 - RZ(-2.0) 0 + RZ(-2) 0 H 0 ``exponential_map`` returns a function allowing you to fill in a multiplicative @@ -461,12 +461,12 @@ value for :math:`\alpha`. 1: H 0 - RZ(-2.0) 0 + RZ(-2) 0 H 0 2: H 0 - RZ(-4.0) 0 + RZ(-4) 0 H 0 To take it one step further, you can use :ref:`parametric_compilation` with ``exponential_map``. For instance: diff --git a/docs/source/conf.py b/docs/source/conf.py index 551e7230c..18459d3d5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -105,6 +105,12 @@ } } +suppress_warnings = [ + # TODO: Re-enable these warnings once Sphinx resolves this open issue: + # https://github.com/sphinx-doc/sphinx/issues/4961 + "ref.python", +] + # fun little hack to always build the rst changelog from the markdown dirname = os.path.dirname(__file__) diff --git a/docs/source/exercises.rst b/docs/source/exercises.rst index 76a2b70cd..394516682 100644 --- a/docs/source/exercises.rst +++ b/docs/source/exercises.rst @@ -179,10 +179,10 @@ We can see what this program looks like in Quil notation with ``print(qft(0, 1, SWAP 0 2 H 0 - CPHASE(-pi/2) 0 1 + CPHASE(-1.5707963267948966) 0 1 H 1 - CPHASE(-pi/4) 0 2 - CPHASE(-pi/2) 1 2 + CPHASE(-0.7853981633974483) 0 2 + CPHASE(-1.5707963267948966) 1 2 H 2 Part c: Execute the QFT diff --git a/docs/source/noise.rst b/docs/source/noise.rst index 4cb56838f..43c958aaf 100644 --- a/docs/source/noise.rst +++ b/docs/source/noise.rst @@ -617,12 +617,12 @@ The Idiomatic pyQuil Program .. testoutput:: decoherence H 0 - RX(pi/2) 1 + RX(1.5707963267948966) 1 CNOT 0 1 - RZ(2*pi/3) 1 + RZ(2.0943951023931953) 1 CNOT 0 1 H 0 - RX(-pi/2) 1 + RX(-1.5707963267948966) 1 The Compiled Program @@ -1165,10 +1165,10 @@ Pauli-Z moments that indicate the qubit correlations are corrupted (and correcte .. testoutput:: readout-noise + DECLARE ro BIT[3] PRAGMA READOUT-POVM 0 "(0.85 0.050000000000000044 0.15000000000000002 0.95)" PRAGMA READOUT-POVM 1 "(0.8 0.09999999999999998 0.19999999999999996 0.9)" PRAGMA READOUT-POVM 2 "(0.9 0.15000000000000002 0.09999999999999998 0.85)" - DECLARE ro BIT[3] H 0 CNOT 0 1 CNOT 1 2 diff --git a/docs/source/programs_and_gates.rst b/docs/source/programs_and_gates.rst index bc784dab2..a11526a4c 100644 --- a/docs/source/programs_and_gates.rst +++ b/docs/source/programs_and_gates.rst @@ -434,7 +434,7 @@ matrix representation of the gate. For example, below we define a .. testoutput:: define-gates - DEFGATE SQRT-X: + DEFGATE SQRT-X AS MATRIX: 0.5+0.5i, 0.5-0.5i 0.5-0.5i, 0.5+0.5i @@ -535,7 +535,7 @@ Some gates can be compactly represented as a permutation. For example, ``CCNOT`` [0, 0, 0, 0, 0, 0, 1, 0] ]) - ccnot_gate = DefGate("CCNOT", ccnot_matrix) + ccnot_gate = DefGate("MATRIX_CCNOT", ccnot_matrix) # etc @@ -546,7 +546,7 @@ It can equivalently be defined by the permutation import numpy as np from pyquil.quilbase import DefPermutationGate - ccnot_gate = DefPermutationGate("CCNOT", [0, 1, 2, 3, 4, 5, 7, 6]) + ccnot_gate = DefPermutationGate("PERMUTATION_CCNOT", [0, 1, 2, 3, 4, 5, 7, 6]) # etc diff --git a/docs/source/troubleshooting.rst b/docs/source/troubleshooting.rst index 95e1206da..ae2ce64e2 100644 --- a/docs/source/troubleshooting.rst +++ b/docs/source/troubleshooting.rst @@ -77,13 +77,21 @@ Collect debug information 3. Run your script with debug logging enabled by adding the following to the top of your script: - .. testcode:: python + .. testcode:: debug import logging logging.basicConfig(level=logging.DEBUG) .. note:: For information on how to filter the logs, see the `qcs-sdk-python logging documentation `_ + .. testcode:: debug + :hide: + + import logging + # Disable debug logging, otherwise doctests will run with + # debug logging enabled. + logging.basicConfig(level=logging.INFO) + If the problem still isn't clear, then we can help! Please file an issue on the `GitHub repo `_ if it's an issue with pyQuil itself, or contact us at our `support page `_ for problems with QCS. If applicable, diff --git a/mypy.ini b/mypy.ini index 6d3416db4..53e9e033e 100644 --- a/mypy.ini +++ b/mypy.ini @@ -26,10 +26,6 @@ no_implicit_reexport = False plugins = numpy.typing.mypy_plugin -# Ignore errors in all parser-related files -[mypy-pyquil._parser.*] -ignore_errors = True - # Ignore errors in vendored third-party libraries [mypy-pyquil.external.*] ignore_errors = True diff --git a/poetry.lock b/poetry.lock index b370cce82..090782fba 100644 --- a/poetry.lock +++ b/poetry.lock @@ -46,23 +46,12 @@ files = [ {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, ] -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] - [[package]] name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" category = "main" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, @@ -378,6 +367,17 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "colored" +version = "1.4.4" +description = "Simple library for color and formatting to terminal" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "colored-1.4.4.tar.gz", hash = "sha256:04ff4d4dd514274fe3b99a21bb52fb96f2688c01e93fba7bef37221e7cb56ce0"}, +] + [[package]] name = "commonmark" version = "0.9.1" @@ -522,6 +522,9 @@ files = [ {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, ] +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + [package.extras] toml = ["tomli"] @@ -581,14 +584,14 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] [[package]] name = "docutils" -version = "0.19" +version = "0.18.1" description = "Docutils -- Python Documentation Utilities" category = "main" optional = true -python-versions = ">=3.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, - {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, + {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, + {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, ] [[package]] @@ -711,21 +714,6 @@ ufo = ["fs (>=2.2.0,<3)"] unicode = ["unicodedata2 (>=15.0.0)"] woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] -[[package]] -name = "freezegun" -version = "1.2.2" -description = "Let your Python tests travel through time" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"}, - {file = "freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"}, -] - -[package.dependencies] -python-dateutil = ">=2.7" - [[package]] name = "h11" version = "0.14.0" @@ -1362,38 +1350,39 @@ files = [ [[package]] name = "mypy" -version = "1.4.1" +version = "1.5.1" description = "Optional static typing for Python" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, - {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, - {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, - {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, - {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, - {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, - {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, - {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, - {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, - {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, - {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, - {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, - {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, - {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, - {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, - {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, - {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, - {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, - {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, - {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, + {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, + {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, + {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, + {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, + {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, + {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, + {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, + {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, + {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, + {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, + {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, + {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, + {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, + {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, + {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, + {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, + {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, ] [package.dependencies] @@ -1404,7 +1393,6 @@ typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] [[package]] @@ -1928,18 +1916,6 @@ files = [ {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] - [[package]] name = "pycodestyle" version = "2.7.0" @@ -2061,81 +2037,46 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "6.2.5" +version = "7.4.0" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, ] [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" -version = "2.12.1" +version = "4.1.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" files = [ - {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, - {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, ] [package.dependencies] -coverage = ">=5.2.1" +coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" -toml = "*" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] -[[package]] -name = "pytest-forked" -version = "1.6.0" -description = "run tests in isolated forked subprocesses" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-forked-1.6.0.tar.gz", hash = "sha256:4dafd46a9a600f65d822b8f605133ecf5b3e1941ebb3588e943b4e3eb71a5a3f"}, - {file = "pytest_forked-1.6.0-py3-none-any.whl", hash = "sha256:810958f66a91afb1a1e2ae83089d8dc1cd2437ac96b12963042fbb9fb4d16af0"}, -] - -[package.dependencies] -py = "*" -pytest = ">=3.10" - -[[package]] -name = "pytest-freezegun" -version = "0.4.2" -description = "Wrap tests with fixtures in freeze_time" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "pytest-freezegun-0.4.2.zip", hash = "sha256:19c82d5633751bf3ec92caa481fb5cffaac1787bd485f0df6436fd6242176949"}, - {file = "pytest_freezegun-0.4.2-py2.py3-none-any.whl", hash = "sha256:5318a6bfb8ba4b709c8471c94d0033113877b3ee02da5bfcd917c1889cde99a7"}, -] - -[package.dependencies] -freezegun = ">0.3" -pytest = ">=3.0.0" - [[package]] name = "pytest-mock" version = "3.11.1" @@ -2156,19 +2097,19 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pytest-rerunfailures" -version = "9.1.1" +version = "12.0" description = "pytest plugin to re-run tests to eliminate flaky failures" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "pytest-rerunfailures-9.1.1.tar.gz", hash = "sha256:1cb11a17fc121b3918414eb5eaf314ee325f2e693ac7cb3f6abf7560790827f2"}, - {file = "pytest_rerunfailures-9.1.1-py3-none-any.whl", hash = "sha256:2eb7d0ad651761fbe80e064b0fd415cf6730cdbc53c16a145fd84b66143e609f"}, + {file = "pytest-rerunfailures-12.0.tar.gz", hash = "sha256:784f462fa87fe9bdf781d0027d856b47a4bfe6c12af108f6bd887057a917b48e"}, + {file = "pytest_rerunfailures-12.0-py3-none-any.whl", hash = "sha256:9a1afd04e21b8177faf08a9bbbf44de7a0fe3fc29f8ddbe83b9684bd5f8f92a9"}, ] [package.dependencies] -pytest = ">=5.0" -setuptools = ">=40.0" +packaging = ">=17.1" +pytest = ">=6.2" [[package]] name = "pytest-timeout" @@ -2187,20 +2128,19 @@ pytest = ">=3.6.0" [[package]] name = "pytest-xdist" -version = "2.5.0" -description = "pytest xdist plugin for distributed testing and loop-on-failing modes" +version = "3.3.1" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, - {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, + {file = "pytest-xdist-3.3.1.tar.gz", hash = "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93"}, + {file = "pytest_xdist-3.3.1-py3-none-any.whl", hash = "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2"}, ] [package.dependencies] execnet = ">=1.1" pytest = ">=6.2.0" -pytest-forked = "*" [package.extras] psutil = ["psutil (>=3.0)"] @@ -2212,7 +2152,7 @@ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" category = "main" -optional = false +optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, @@ -2787,7 +2727,7 @@ name = "setuptools" version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, @@ -2804,7 +2744,7 @@ name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -2849,21 +2789,21 @@ files = [ [[package]] name = "sphinx" -version = "6.2.1" +version = "7.1.2" description = "Python documentation generator" category = "main" optional = true python-versions = ">=3.8" files = [ - {file = "Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b"}, - {file = "sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912"}, + {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, + {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.20" +docutils = ">=0.18.1,<0.21" imagesize = ">=1.3" importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" @@ -2885,21 +2825,23 @@ test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] [[package]] name = "sphinx-rtd-theme" -version = "0.5.1" +version = "1.3.0" description = "Read the Docs theme for Sphinx" category = "main" optional = true -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, - {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, + {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, + {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, ] [package.dependencies] -sphinx = "*" +docutils = "<0.19" +sphinx = ">=1.6,<8" +sphinxcontrib-jquery = ">=4,<5" [package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinxcontrib-applehelp" @@ -2949,6 +2891,21 @@ files = [ lint = ["docutils-stubs", "flake8", "mypy"] test = ["html5lib", "pytest"] +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +category = "main" +optional = true +python-versions = ">=2.7" +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" + [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" @@ -2996,6 +2953,22 @@ files = [ lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] +[[package]] +name = "syrupy" +version = "3.0.6" +description = "Pytest Snapshot Test Utility" +category = "dev" +optional = false +python-versions = ">=3.7,<4" +files = [ + {file = "syrupy-3.0.6-py3-none-any.whl", hash = "sha256:9c18e22264026b34239bcc87ab7cc8d893eb17236ea7dae634217ea4f22a848d"}, + {file = "syrupy-3.0.6.tar.gz", hash = "sha256:583aa5ca691305c27902c3e29a1ce9da50ff9ab5f184c54b1dc124a16e4a6cf4"}, +] + +[package.dependencies] +colored = ">=1.3.92,<2.0.0" +pytest = ">=5.1.0,<8.0.0" + [[package]] name = "tenacity" version = "8.2.2" @@ -3301,4 +3274,4 @@ latex = ["ipython"] [metadata] lock-version = "2.0" python-versions = "^3.8,<4.0" -content-hash = "4af217787a8f9cfa5e5cab3e8a35c1734fd92abadec41e758c746820bf6a54c5" +content-hash = "5c82bce166003efdc56a15ae614e518c9f9d0a2ca2eea9a567585fc4eea7a0fa" diff --git a/pyproject.toml b/pyproject.toml index a1bdc6ec5..c8c96f39c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,8 +37,8 @@ packaging = "^23.1" ipython = { version = "^7.21.0", optional = true } # docs extra -Sphinx = { version = "^6.2.1", optional = true } -sphinx-rtd-theme = { version = "<=2.0.0", optional = true } +Sphinx = { version = "^7.1.2", optional = true } +sphinx-rtd-theme = { version = "^1.3.0,<=2.0.0", optional = true } nbsphinx = { version = "^0.9.1", optional = true } recommonmark = { version = "^0.7.1", optional = true } pandoc = {version = "2.4b0", optional = true} @@ -50,25 +50,29 @@ types-deprecated = "^1.2.9.2" [tool.poetry.dev-dependencies] black = "^22.8.0" flake8 = "^3.8.1" -pytest = "^6.2.2" -pytest-cov = "^2.11.1" -mypy = "^1.2.0" -pytest-xdist = "^2.2.1" -pytest-rerunfailures = "^9.1.1" +pytest = "^7.4.0" +pytest-cov = "^4.1.0" +mypy = "^1.5.0" +toml = "^0.10.2" +pytest-xdist = "^3.3.1" +pytest-rerunfailures = "^12.0.0" pytest-timeout = "^1.4.2" -pytest-mock = "^3.6.1" -pytest-freezegun = "^0.4.2" +pytest-mock = "^3.11.1" respx = "^0.20" nest-asyncio = "^1.5.6" mock = { version = "^4.0", python = "<3.8" } +syrupy = "^3.0.6" [tool.poetry.extras] latex = ["ipython"] -docs = ["Sphinx", "sphinx-rtd-theme", "nbsphinx", "recommonmark", "pandoc", "matplotlib", "seaborn"] +docs = ["Sphinx", "sphinx-rtd-theme", "nbsphinx", "recommonmark", "pandoc", "matplotlib", "seaborn", "toml"] + +[tool.ruff] +line-length = 120 [tool.black] line-length = 120 -target-version = ['py37'] +target-version = ['py38'] include = '\.pyi?$' exclude = ''' @@ -91,6 +95,9 @@ exclude = ''' ) ''' +[tool.pytest.ini_options] +filterwarnings = ["ignore::DeprecationWarning:pyquil.*:", "ignore::DeprecationWarning:test.unit.*:"] + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/pyquil/_parser/README.md b/pyquil/_parser/README.md deleted file mode 100644 index 26e6189a4..000000000 --- a/pyquil/_parser/README.md +++ /dev/null @@ -1,17 +0,0 @@ -The Quil Parser -=============== - -Introduction ------------- - -This package contains a number of items useful for parsing Quil programs, both with or -without pyQuil. It uses [Lark](https://github.com/lark-parser/lark) for quick and -efficient parsing of Quil. - -- `grammar.lark` defines the Quil grammar in the Lark grammar format. -- `parser.py` translates the Lark parse tree into pyQuil instruction objects. - -References ----------- - -- Lark documentation: https://lark-parser.readthedocs.io/ diff --git a/pyquil/_parser/__init__.py b/pyquil/_parser/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/pyquil/_parser/grammar.lark b/pyquil/_parser/grammar.lark deleted file mode 100644 index 3b7235299..000000000 --- a/pyquil/_parser/grammar.lark +++ /dev/null @@ -1,330 +0,0 @@ -quil : (all_instr _NEWLINE?)* - -?all_instr : COMMENT - | def_gate - | def_circuit - | def_frame - | def_waveform - | def_calibration - | def_measure_calibration - | instr - -?instr : fence - | pulse - | delay - | set_frequency - | shift_frequency - | shift_phase - | set_phase - | swap_phases - | set_scale - | declare - | capture - | raw_capture - | pragma - | measure - | halt - | gate - | nop - | def_label - | jump - | jump_when - | jump_unless - | reset - | wait - | store - | load - | convert - | exchange - | move - | classical_unary - | classical_binary - | classical_comparison - | include - | gate_no_qubits - -COMMENT : "#" /[^\n]*/ NEWLINE - -def_gate : "DEFGATE" name [ variables ] ":" matrix -> def_gate_matrix - | "DEFGATE" name [ variables ] "AS" "MATRIX" ":" matrix -> def_gate_matrix - | "DEFGATE" name "AS" "PERMUTATION" ":" _NEWLINE_TAB matrix_row -> def_gate_as_permutation - | "DEFGATE" name [ variables ] qubit_variables "AS" "PAULI-SUM" ":" pauli_terms -> def_pauli_gate - -!gate_type : "MATRIX" | "PERMUTATION" -pauli_terms : ( _NEWLINE_TAB pauli_term )* -pauli_term : name "(" expression ")" qubit_variables - -def_circuit : "DEFCIRCUIT" name [ variables ] [ qubit_designators ] ":" indented_instrs - -// def_circuit : "DEFCIRCUIT" name [ variables ] ":" indented_instrs -> def_circuit_bare -// | "DEFCIRCUIT" name [ variables ] qubit_designators ":" indented_instrs -> def_circuit_qubits - -def_frame : "DEFFRAME" frame ( ":" frame_spec+ )? -frame_spec : _NEWLINE_TAB frame_attr ":" ( expression | string ) -!frame_attr : "SAMPLE-RATE" - | "INITIAL-FREQUENCY" - | "DIRECTION" - | "HARDWARE-OBJECT" - | "CENTER-FREQUENCY" - | "ENABLE-RAW-CAPTURE" - | "CHANNEL-DELAY" - -def_waveform : "DEFWAVEFORM" waveform_name params ":" matrix - -def_calibration : "DEFCAL" name params qubit_designators ":" indented_instrs - -def_measure_calibration : "DEFCAL" "MEASURE" qubit_designator [ name ] ":" indented_instrs - -gate : [ modifiers ] name [ params ] qubit_designators -// The .2 tells lark to give this higher priority, which is required to resolve -// a collision with the above rule. If modifiers is empty, both rules are -// equivalent up to the name and thus Lark needs a way to choose which to follow -// first. -gate_no_qubits.2 : name - - -modifiers : modifier+ -!modifier : "CONTROLLED" | "DAGGER" | "FORKED" - -indented_instrs : (_NEWLINE_TAB instr)+ - -params : ( "(" param ("," param)* ")" )? -?param : expression - -matrix : ( _NEWLINE_TAB matrix_row )* -matrix_row : expression ( "," expression )* - -?expression : product - | expression "+" product -> add - | expression "-" product -> sub -?product : power - | product "*" power -> mul - | product "/" power -> div -?power : atom - | atom "^" power -> pow -?atom : number - | "-" atom -> neg - | "+" atom -> pos - | function "(" expression ")" -> apply_fun - | "(" expression ")" - | variable - | addr - -variables: ( "(" variable ( "," variable )* ")" )? -variable : "%" IDENTIFIER - -// Instructions - -fence : "FENCE" -> fence_all - | "FENCE" qubit_designators -> fence_some - -pulse : [ NONBLOCKING ] "PULSE" frame waveform - -?delay : delay_qubits | delay_frames -// TODO(notmgsk): this is a tremendously ugly hack. -// See the comment for QuilTransformer#delay_qubits. -delay_qubits : "DELAY" qubit_designators expression - | "DELAY" qubit_designators -delay_frames : "DELAY" qubit_designator ( "\"" name "\"" )+ expression - -set_frequency : "SET-FREQUENCY" frame expression -shift_frequency : "SHIFT-FREQUENCY" frame expression -shift_phase : "SHIFT-PHASE" frame expression -set_phase : "SET-PHASE" frame expression -swap_phases : "SWAP-PHASE" frame frame - | "SWAP-PHASES" frame frame -set_scale : "SET-SCALE" frame expression - -declare : "DECLARE" IDENTIFIER IDENTIFIER [ "[" INT "]" ] [ "SHARING" IDENTIFIER ( offset_descriptor )* ] -?offset_descriptor : "OFFSET" INT IDENTIFIER - -capture : [ NONBLOCKING ] "CAPTURE" frame waveform addr -raw_capture : [ NONBLOCKING ] "RAW-CAPTURE" frame expression addr -addr : IDENTIFIER -> addr | ( [ IDENTIFIER ] "[" INT "]" ) -> addr_subscript - -pragma : "PRAGMA" ( IDENTIFIER | keyword ) pragma_name* - | "PRAGMA" ( IDENTIFIER | keyword ) pragma_name* string -> pragma_freeform_string - -?pragma_name : IDENTIFIER | keyword | INT - -measure : "MEASURE" qubit_designator [ addr ] - -halt : "HALT" - -nop : "NOP" - -include : "INCLUDE" string - -def_label : "LABEL" label -jump : "JUMP" label -jump_when : "JUMP-WHEN" label addr -jump_unless : "JUMP-UNLESS" label addr -label : "@" name - -reset : "RESET" [ qubit ] -wait : "WAIT" -store : "STORE" name addr ( addr | number ) -load : "LOAD" addr name addr -convert : "CONVERT" addr addr -exchange : "EXCHANGE" addr addr -move : "MOVE" addr ( addr | signed_number ) -!classical_unary : ( "NEG" | "NOT" | "TRUE" | "FALSE" ) addr -?classical_binary : logical_binary_op | arithmetic_binary_op -!logical_binary_op : ( "AND" | "OR" | "IOR" | "XOR" ) addr (addr | int_n ) -!arithmetic_binary_op : ( "ADD" | "SUB" | "MUL" | "DIV" ) addr ( addr | signed_number ) -!classical_comparison : ( "EQ" | "GT" | "GE" | "LT" | "LE" ) addr addr ( addr | signed_number ) - -// Qubits, frames, waveforms -qubit_designators : ( qubit_designator+ ) -?qubit_designator : qubit | qubit_variable -qubits : qubit+ -qubit : int_n -qubit_variables : ( qubit_variable+ ) -qubit_variable : IDENTIFIER -named_param : IDENTIFIER ":" expression -waveform : waveform_name ( "(" named_param ( "," named_param )* ")" )? -waveform_name : name ( "/" name )? -frame : qubit_designators "\"" name "\"" - -!function : "SIN"i | "COS"i | "SQRT"i | "EXP"i | "CIS"i -// Numbers -?number : (int_n|float_n) "i" -> imag - | int_n - | float_n - | "i" -> i - | "pi" -> pi - -int_n : INT -float_n : FLOAT - -!signed_number : [ "+" | "-" ] number - -// Lexer -keyword : DEFGATE | DEFCIRCUIT | MEASURE | LABEL | HALT | JUMP | JUMPWHEN | JUMPUNLESS - | RESET | WAIT | NOP | INCLUDE | PRAGMA | DECLARE | SHARING | OFFSET | AS | MATRIX - | PERMUTATION | NEG | NOT | TRUE | FALSE | AND | IOR | XOR | OR | ADD | SUB | MUL - | DIV | MOVE | EXCHANGE | CONVERT | EQ | GT | GE | LT | LE | LOAD | STORE | PI | I - | SIN | COS | SQRT | EXP | CIS | CAPTURE | DEFCAL | DEFFRAME | DEFWAVEFORM - | DELAY | FENCE | INITIALFREQUENCY | CENTERFREQUENCY | NONBLOCKING | PULSE | SAMPLERATE - | SETFREQUENCY | SETPHASE | SETSCALE | SHIFTPHASE | SWAPPHASE | RAWCAPTURE | FILTERNODE - | CONTROLLED | DAGGER | FORKED - -// Keywords - -DEFGATE : "DEFGATE" -DEFCIRCUIT : "DEFCIRCUIT" -MEASURE : "MEASURE" - -LABEL : "LABEL" -HALT : "HALT" -JUMP : "JUMP" -JUMPWHEN : "JUMP-WHEN" -JUMPUNLESS : "JUMP-UNLESS" - -RESET : "RESET" -WAIT : "WAIT" -NOP : "NOP" -INCLUDE : "INCLUDE" -PRAGMA : "PRAGMA" - -DECLARE : "DECLARE" -SHARING : "SHARING" -OFFSET : "OFFSET" - -AS : "AS" -MATRIX : "MATRIX" -PERMUTATION : "PERMUTATION" -PAULISUM : "PAULI-SUM" - -NEG : "NEG" -NOT : "NOT" -TRUE : "TRUE" // Deprecated -FALSE : "FALSE" // Deprecated - -AND : "AND" -IOR : "IOR" -XOR : "XOR" -OR : "OR" // Deprecated - -ADD : "ADD" -SUB : "SUB" -MUL : "MUL" -DIV : "DIV" - -MOVE : "MOVE" -EXCHANGE : "EXCHANGE" -CONVERT : "CONVERT" - -EQ : "EQ" -GT : "GT" -GE : "GE" -LT : "LT" -LE : "LE" - -LOAD : "LOAD" -STORE : "STORE" - -PI : "pi" -I : "i" - -SIN : "SIN" -COS : "COS" -SQRT : "SQRT" -EXP : "EXP" -CIS : "CIS" - -// Operators - -PLUS : "+" -MINUS : "-" -TIMES : "*" -DIVIDE : "/" -POWER : "^" - -// analog keywords - -CAPTURE : "CAPTURE" -DEFCAL : "DEFCAL" -DEFFRAME : "DEFFRAME" -DEFWAVEFORM : "DEFWAVEFORM" -DELAY : "DELAY" -FENCE : "FENCE" -HARDWAREOBJECT : "HARDWARE-OBJECT" -INITIALFREQUENCY : "INITIAL-FREQUENCY" -CENTERFREQUENCY : "CENTER-FREQUENCY" -NONBLOCKING : "NONBLOCKING" -PULSE : "PULSE" -SAMPLERATE : "SAMPLE-RATE" -SETFREQUENCY : "SET-FREQUENCY" -SHIFTFREQUENCY : "SHIFT-FREQUENCY" -SETPHASE : "SET-PHASE" -SETSCALE : "SET-SCALE" -SHIFTPHASE : "SHIFT-PHASE" -SWAPPHASE : "SWAP-PHASE" -RAWCAPTURE : "RAW-CAPTURE" -FILTERNODE : "FILTER-NODE" - -// Modifiers - -CONTROLLED : "CONTROLLED" -DAGGER : "DAGGER" -FORKED : "FORKED" - -// Common -name : IDENTIFIER -IDENTIFIER : ("_"|LETTER) [ ("_"|"-"|LETTER|DIGIT)* ("_"|LETTER|DIGIT) ] -string : ESCAPED_STRING -_NEWLINE_TAB : NEWLINE " " | NEWLINE "\t" -_NEWLINE : NEWLINE -%import common.DIGIT -%import common.ESCAPED_STRING -%import common._STRING_INNER -%import common.FLOAT -%import common.INT -%import common.LETTER -%import common.NEWLINE -%import common.WS -%import common.WS_INLINE -%ignore WS_INLINE -%ignore /^\n/ -%ignore /#[^\n]*/ diff --git a/pyquil/_parser/parser.py b/pyquil/_parser/parser.py deleted file mode 100644 index 5f8af829f..000000000 --- a/pyquil/_parser/parser.py +++ /dev/null @@ -1,568 +0,0 @@ -import json -import pkgutil -import operator -from typing import List - -from deprecated import deprecated -from deprecated.sphinx import versionadded -from lark import Lark, Transformer, v_args - -import numpy as np - -from pyquil.quilbase import ( - AbstractInstruction, - DefGate, - DefPermutationGate, - DefGateByPaulis, - DefWaveform, - Qubit, - FormalArgument, - Frame, - Pulse, - Fence, - FenceAll, - DefCalibration, - DefMeasureCalibration, - DefFrame, - Parameter, - Declare, - Capture, - RawCapture, - MemoryReference, - Pragma, - RawInstr, - JumpTarget, - Jump, - JumpWhen, - JumpUnless, - Reset, - ResetQubit, - Wait, - ClassicalStore, - ClassicalLoad, - ClassicalConvert, - ClassicalExchange, - ClassicalMove, - ClassicalNeg, - ClassicalNot, - ClassicalAnd, - ClassicalInclusiveOr, - ClassicalExclusiveOr, - ClassicalAdd, - ClassicalSub, - ClassicalMul, - ClassicalDiv, - ClassicalEqual, - ClassicalGreaterEqual, - ClassicalGreaterThan, - ClassicalLessThan, - ClassicalLessEqual, -) -from pyquil.quiltwaveforms import _wf_from_dict -from pyquil.quilatom import ( - WaveformReference, - Expression, - quil_sqrt, - quil_sin, - quil_cos, - quil_cis, - quil_exp, - Label, - _contained_mrefs, -) -from pyquil.gates import ( - DELAY, - SHIFT_PHASE, - SET_PHASE, - SWAP_PHASES, - SET_SCALE, - SET_FREQUENCY, - SHIFT_FREQUENCY, - QUANTUM_GATES, - MEASURE, - HALT, - NOP, - Gate, -) - - -class QuilTransformer(Transformer): # type: ignore - def quil(self, instructions): - return instructions - - indented_instrs = list - - @v_args(inline=True) - def def_gate_matrix(self, name, variables, matrix): - return DefGate(name, matrix=matrix, parameters=variables) - - @v_args(inline=True) - def def_gate_as_permutation(self, name, matrix): - return DefPermutationGate(name, permutation=matrix) - - @v_args(inline=True) - def def_pauli_gate(self, name, variables, qubits, terms): - pg = DefGateByPaulis(name, parameters=variables, arguments=qubits, body=terms) - return pg - - pauli_terms = list - - @v_args(inline=True) - def pauli_term(self, name, expression, qubits): - from pyquil.paulis import PauliTerm - - return PauliTerm.from_list(list(zip(name, qubits)), expression) - - @v_args(inline=True) - def def_circuit(self, name, variables, qubits, instrs): - qubits = qubits if qubits else [] - space = " " if qubits else "" - if variables: - raw_defcircuit = "DEFCIRCUIT {}({}){}{}:".format( - name, ", ".join(map(str, variables)), space, " ".join(map(str, qubits)) - ) - else: - raw_defcircuit = "DEFCIRCUIT {}{}{}:".format(name, space, " ".join(map(str, qubits))) - - raw_defcircuit += "\n ".join([""] + [str(instr) for instr in instrs]) - return RawInstr(raw_defcircuit) - - @v_args(inline=True) - def def_circuit_without_qubits(self, name, variables, instrs): - return self.def_circuit_qubits(name, variables, [], instrs) - - @v_args(inline=True) - def def_frame(self, frame, *specs): - names = { - "DIRECTION": "direction", - "HARDWARE-OBJECT": "hardware_object", - "INITIAL-FREQUENCY": "initial_frequency", - "SAMPLE-RATE": "sample_rate", - "CENTER-FREQUENCY": "center_frequency", - "ENABLE-RAW-CAPTURE": "enable_raw_capture", - "CHANNEL-DELAY": "channel_delay", - } - options = {} - - for spec_name, spec_value in specs: - name = names.get(spec_name, None) - if name: - options[name] = json.loads(str(spec_value)) - else: - raise ValueError( - f"Unexpectected attribute {spec_name} in definition of frame {frame}. " f"{frame}, {specs}" - ) - - f = DefFrame(frame, **options) - return f - - frame_spec = list - frame_attr = v_args(inline=True)(str) - - @v_args(inline=True) - def def_waveform(self, name, params, matrix): - return DefWaveform(name, params, matrix[0]) - - @v_args(inline=True) - def def_calibration(self, name, params, qubits, instructions): - for p in params: - mrefs = _contained_mrefs(p) - if mrefs: - raise ValueError(f"Unexpected memory references {mrefs} in DEFCAL {name}. Did you forget a '%'?") - dc = DefCalibration(name, params, qubits, instructions) - return dc - - @v_args(inline=True) - def def_measure_calibration(self, qubit, name, instructions): - mref = FormalArgument(name) if name else None - dmc = DefMeasureCalibration(qubit, mref, instructions) - return dmc - - @v_args(inline=True) - def gate(self, modifiers, name, params, qubits): - # TODO Don't like this. - modifiers = modifiers or [] - params = params or [] - - # Some gate modifiers increase the arity of the base gate. The - # new qubit arguments prefix the old ones. - modifier_qubits = [] - for m in modifiers: - if m in ["CONTROLLED", "FORKED"]: - modifier_qubits.append(qubits[len(modifier_qubits)]) - - base_qubits = qubits[len(modifier_qubits) :] - forked_offset = len(params) >> modifiers.count("FORKED") - base_params = params[:forked_offset] - - if name in QUANTUM_GATES: - if base_params: - gate = QUANTUM_GATES[name](*base_params, *base_qubits) - else: - gate = QUANTUM_GATES[name](*base_qubits) - else: - gate = Gate(name, base_params, base_qubits) - - for modifier in modifiers[::-1]: - if modifier == "CONTROLLED": - gate.controlled(modifier_qubits.pop()) - elif modifier == "DAGGER": - gate.dagger() - elif modifier == "FORKED": - gate.forked(modifier_qubits.pop(), params[forked_offset : (2 * forked_offset)]) - forked_offset *= 2 - else: - raise ValueError(f"Unsupported gate modifier {modifier}.") - - return gate - - @v_args(inline=True) - def gate_no_qubits(self, name): - return RawInstr(name) - - modifiers = list - modifier = v_args(inline=True)(str) - - @v_args(inline=True) - def frame(self, qubits, name): - f = Frame(qubits, name) - return f - - @v_args(inline=True) - def pulse(self, nonblocking, frame, waveform): - p = Pulse(frame, waveform, nonblocking=bool(nonblocking)) - return p - - @v_args(inline=True) - def fence_some(self, qubits): - f = Fence(list(qubits)) - return f - - fence_all = v_args(inline=True)(FenceAll) - - @v_args(inline=True) - def declare(self, name, memory_type, memory_size, *sharing): - shared, *offsets = sharing - d = Declare( - str(name), - memory_type=str(memory_type), - memory_size=int(memory_size) if memory_size else 1, - shared_region=str(shared) if shared else None, - offsets=offsets if shared else None, - ) - return d - - @v_args(inline=True) - def capture(self, nonblocking, frame, waveform, addr): - c = Capture(frame, waveform, addr, nonblocking=nonblocking) - return c - - @v_args(inline=True) - def raw_capture(self, nonblocking, frame, expression, addr): - c = RawCapture(frame, expression, addr, nonblocking=nonblocking) - return c - - @v_args(inline=True) - def addr(self, name): - return MemoryReference(str(name)) - - @v_args(inline=True) - def addr_subscript(self, name, subscript): - return MemoryReference(str(name), int(subscript)) - - @v_args(inline=True) - def offset_descriptor(self, offset, name): - return (int(offset), str(name)) - - @v_args(inline=True) - def delay_qubits(self, qubits, delay_amount=None): - # TODO(notmgsk): This is a very nasty hack. I can't quite get - # the Lark grammar to recognize the last token (i.e. 1) in - # `DELAY 0 1` as the delay amount. I think it's because it - # matches 1 as a qubit rather than an expression (in the - # grammar). Then again I would expect look-ahead to see that - # it matches expression too, so it should give that - # preference. How do we encode that priority? - if delay_amount is None: - delay_amount = int(qubits[-1].index) - qubits = qubits[:-1] - d = DELAY(*[*qubits, delay_amount]) - return d - - @v_args(inline=True) - def delay_frames(self, qubit, *frames_and_delay_amount): - *frame_names, delay_amount = frames_and_delay_amount - frames = [Frame([qubit], name) for name in frame_names] - d = DELAY(*[*frames, delay_amount]) - return d - - @v_args(inline=True) - def shift_phase(self, frame, expression): - return SHIFT_PHASE(frame, expression) - - @v_args(inline=True) - def set_phase(self, frame, expression): - return SET_PHASE(frame, expression) - - @v_args(inline=True) - def set_scale(self, frame, expression): - return SET_SCALE(frame, expression) - - @v_args(inline=True) - def set_frequency(self, frame, expression): - return SET_FREQUENCY(frame, expression) - - @v_args(inline=True) - def shift_frequency(self, frame, expression): - return SHIFT_FREQUENCY(frame, expression) - - @deprecated(version="3.5.1", reason="The correct instruction is SWAP-PHASES, not SWAP-PHASE") - @v_args(inline=True) - def swap_phase(self, framea, frameb): - return SWAP_PHASES(framea, frameb) - - @versionadded(version="3.5.1", reason="The correct instruction is SWAP-PHASES, not SWAP-PHASE") - @v_args(inline=True) - def swap_phases(self, framea, frameb): - return SWAP_PHASES(framea, frameb) - - @v_args(inline=True) - def pragma(self, name, *pragma_names_and_string): - args = list(map(str, pragma_names_and_string)) - p = Pragma(str(name), args=args) - return p - - @v_args(inline=True) - def pragma_freeform_string(self, name, *pragma_names_and_string): - if len(pragma_names_and_string) == 1: - freeform_string = pragma_names_and_string[0] - args = () - else: - *args_identifiers, freeform_string = pragma_names_and_string - args = list(map(str, args_identifiers)) - # Strip the quotes from start/end of string which are matched - # by the Lark grammar - freeform_string = freeform_string[1:-1] - p = Pragma(str(name), args=args, freeform_string=freeform_string) - return p - - @v_args(inline=True) - def measure(self, qubit, address): - return MEASURE(qubit, address) - - @v_args(inline=True) - def halt(self): - return HALT - - @v_args(inline=True) - def nop(self): - return NOP - - @v_args(inline=True) - def include(self, string): - return RawInstr(f"INCLUDE {string}") - - @v_args(inline=True) - def def_label(self, label): - return JumpTarget(label) - - @v_args(inline=True) - def jump(self, label): - return Jump(label) - - @v_args(inline=True) - def jump_when(self, label, address): - return JumpWhen(label, address) - - @v_args(inline=True) - def jump_unless(self, label, address): - return JumpUnless(label, address) - - label = v_args(inline=True)(Label) - - @v_args(inline=True) - def reset(self, qubit): - if qubit: - return ResetQubit(qubit) - else: - return Reset() - - @v_args(inline=True) - def wait(self): - return Wait() - - @v_args(inline=True) - def store(self, left, subscript, right): - return ClassicalStore(left, subscript, right) - - @v_args(inline=True) - def load(self, left, right, subscript): - return ClassicalLoad(left, right, subscript) - - @v_args(inline=True) - def convert(self, left, right): - return ClassicalConvert(left, right) - - @v_args(inline=True) - def exchange(self, left, right): - return ClassicalExchange(left, right) - - @v_args(inline=True) - def move(self, left, right): - return ClassicalMove(left, right) - - @v_args(inline=True) - def classical_unary(self, op, target): - if op == "TRUE": - return ClassicalMove(target, 1) - elif op == "FALSE": - return ClassicalMove(target, 0) - elif op == "NEG": - return ClassicalNeg(target) - elif op == "NOT": - return ClassicalNot(target) - - @v_args(inline=True) - def logical_binary_op(self, op, left, right): - if op == "AND": - return ClassicalAnd(left, right) - elif op == "OR": - return ClassicalInclusiveOr(left, right) - elif op == "IOR": - return ClassicalInclusiveOr(left, right) - elif op == "XOR": - return ClassicalExclusiveOr(left, right) - - @v_args(inline=True) - def arithmetic_binary_op(self, op, left, right): - if op == "ADD": - return ClassicalAdd(left, right) - elif op == "SUB": - return ClassicalSub(left, right) - elif op == "MUL": - return ClassicalMul(left, right) - elif op == "DIV": - return ClassicalDiv(left, right) - - @v_args(inline=True) - def classical_comparison(self, op, target, left, right): - if op == "EQ": - return ClassicalEqual(target, left, right) - elif op == "GT": - return ClassicalGreaterThan(target, left, right) - elif op == "GE": - return ClassicalGreaterEqual(target, left, right) - elif op == "LT": - return ClassicalLessThan(target, left, right) - elif op == "LE": - return ClassicalLessEqual(target, left, right) - - @v_args(inline=True) - def waveform(self, name, *params): - param_dict = {k: v for (k, v) in params} - if param_dict: - return _wf_from_dict(name, param_dict) - else: - return WaveformReference(name) - - @v_args(inline=True) - def waveform_name(self, prefix, suffix=None): - return f"{prefix}/{suffix}" if suffix else prefix - - def matrix(self, rows): - return list(rows) - - def matrix_row(self, expressions): - return list(expressions) - - def params(self, params): - return list(params) - - @v_args(inline=True) - def named_param(self, name, val): - return (str(name), val) - - def qubit_designators(self, qubits): - return list(qubits) - - qubit = v_args(inline=True)(Qubit) - qubits = list - qubit_variable = v_args(inline=True)(FormalArgument) - qubit_variables = list - - @v_args(inline=True) - def variable(self, var): - variable = Parameter(str(var)) - return variable - - def variables(self, variables): - return list(variables) - - @v_args(inline=True) - def i(self): - return 1j - - @v_args(inline=True) - def imag(self, number): - return number * 1j - - @v_args(inline=True) - def pi(self): - return np.pi - - int_n = v_args(inline=True)(int) - float_n = v_args(inline=True)(float) - - name = v_args(inline=True)(str) - string = v_args(inline=True)(str) - - @v_args(inline=True) - def signed_number(self, sign, number): - if sign and sign == "-": - return -number - else: - return number - - @v_args(inline=True) - def apply_fun(self, fun, arg): - if fun.upper() == "SIN": - return quil_sin(arg) if isinstance(arg, Expression) else np.sin(arg) - if fun.upper() == "COS": - return quil_cos(arg) if isinstance(arg, Expression) else np.cos(arg) - if fun.upper() == "SQRT": - return quil_sqrt(arg) if isinstance(arg, Expression) else np.sqrt(arg) - if fun.upper() == "EXP": - return quil_exp(arg) if isinstance(arg, Expression) else np.exp(arg) - if fun.upper() == "CIS": - return quil_cis(arg) if isinstance(arg, Expression) else np.cos(arg) + 1j * np.sin(arg) - - add = v_args(inline=True)(operator.add) - sub = v_args(inline=True)(operator.sub) - mul = v_args(inline=True)(operator.mul) - div = v_args(inline=True)(operator.truediv) - pow = v_args(inline=True)(operator.pow) - neg = v_args(inline=True)(operator.neg) - pos = v_args(inline=True)(operator.pos) - function = v_args(inline=True)(str) - keyword = v_args(inline=True)(str) - - -grammar = pkgutil.get_data("pyquil._parser", "grammar.lark").decode() -parser = Lark( - grammar, - start="quil", - parser="lalr", - transformer=QuilTransformer(), - maybe_placeholders=True, -) - - -def run_parser(program: str) -> List[AbstractInstruction]: - """ - Parse a raw Quil program and return a corresponding list of PyQuil objects. - - :param str quil: a single or multiline Quil program - :return: list of instructions - """ - p = parser.parse(program) - return p diff --git a/pyquil/api/_abstract_compiler.py b/pyquil/api/_abstract_compiler.py index 715069743..cda2b0d7a 100644 --- a/pyquil/api/_abstract_compiler.py +++ b/pyquil/api/_abstract_compiler.py @@ -20,7 +20,7 @@ import json from qcs_sdk import QCSClient -from qcs_sdk.compiler.quilc import compile_program, TargetDevice, CompilerOpts +from qcs_sdk.compiler.quilc import compile_program, CompilerOpts, TargetDevice from pyquil._version import pyquil_version from pyquil.api._compiler_client import CompilerClient @@ -115,11 +115,9 @@ def quil_to_native_quil(self, program: Program, *, protoquil: Optional[bool] = N options=CompilerOpts(protoquil=protoquil, timeout=self._compiler_client.timeout), ) - native_program = Program(result.program) - native_program.num_shots = program.num_shots - native_program._calibrations = program._calibrations - native_program._waveforms = program._waveforms + native_program = program.copy_everything_except_instructions() native_program.native_quil_metadata = result.native_quil_metadata + native_program.inst(result.program) return native_program diff --git a/pyquil/api/_benchmark.py b/pyquil/api/_benchmark.py index b51bb7bc9..19e7dc85b 100644 --- a/pyquil/api/_benchmark.py +++ b/pyquil/api/_benchmark.py @@ -149,7 +149,7 @@ def generate_rb_sequence( for clifford in response.sequence: clifford_program = Program() if interleaver: - clifford_program._calibrations = interleaver.calibrations + clifford_program += interleaver.calibrations # Like below, we reversed the order because the API currently hands back the Clifford # decomposition right-to-left. for index in reversed(clifford): diff --git a/pyquil/api/_compiler.py b/pyquil/api/_compiler.py index 884235d7f..62d1fb4a1 100644 --- a/pyquil/api/_compiler.py +++ b/pyquil/api/_compiler.py @@ -25,7 +25,6 @@ from rpcq.messages import ParameterSpec from pyquil.api._abstract_compiler import AbstractCompiler, EncryptedProgram, QuantumExecutable -from pyquil.parser import parse_program from pyquil.quantum_processor import AbstractQuantumProcessor from pyquil.quil import Program from pyquil.quilatom import MemoryReference @@ -127,7 +126,7 @@ def _fetch_calibration_program(self) -> Program: response = get_quilt_calibrations( quantum_processor_id=self.quantum_processor_id, ) - return parse_program(response.quilt) + return Program(response.quilt) def get_calibration_program(self, force_refresh: bool = False) -> Program: """ diff --git a/pyquil/api/_rewrite_arithmetic.py b/pyquil/api/_rewrite_arithmetic.py index fc674dc1f..327d36c82 100644 --- a/pyquil/api/_rewrite_arithmetic.py +++ b/pyquil/api/_rewrite_arithmetic.py @@ -130,7 +130,7 @@ def expr_mref(expr: object) -> MemoryReference: expr = str(Div(inst.phase, 2 * np.pi)) updated.inst(inst.__class__(inst.frame, expr_mref(expr))) elif isinstance(inst, SetScale): - if isinstance(inst.scale, Real): + if isinstance(inst.scale, complex) and inst.scale.imag == 0.0: updated.inst(inst) else: # scale is in [-4,4) @@ -142,7 +142,7 @@ def expr_mref(expr: object) -> MemoryReference: updated.inst(inst) if mref_idx > 0: - updated._instructions.insert(0, Declare(mref_name, "REAL", mref_idx)) + updated = Program(Declare(mref_name, "REAL", mref_idx)) + updated return RewriteArithmeticResponse( quil=updated.out(), diff --git a/pyquil/gates.py b/pyquil/gates.py index 70a9d02a3..068caa19d 100644 --- a/pyquil/gates.py +++ b/pyquil/gates.py @@ -13,10 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. ############################################################################## -from deprecated import deprecated +from deprecated.sphinx import deprecated from deprecated.sphinx import versionadded from numbers import Real -from typing import Callable, Mapping, Optional, Tuple, Union, Iterable, no_type_check +from typing import Callable, Mapping, Optional, Tuple, Union, Sequence, no_type_check import numpy as np @@ -689,7 +689,7 @@ def DECLARE( memory_type: str = "BIT", memory_size: int = 1, shared_region: Optional[str] = None, - offsets: Optional[Iterable[Tuple[int, str]]] = None, + offsets: Optional[Sequence[Tuple[int, str]]] = None, ) -> Declare: return Declare( name=name, diff --git a/pyquil/latex/_diagram.py b/pyquil/latex/_diagram.py index fdcbd46a8..f587d363f 100644 --- a/pyquil/latex/_diagram.py +++ b/pyquil/latex/_diagram.py @@ -25,7 +25,6 @@ AbstractInstruction, Wait, ResetQubit, - JumpConditional, JumpWhen, JumpUnless, Jump, @@ -97,7 +96,6 @@ class DiagramSettings: UNSUPPORTED_INSTRUCTION_CLASSES = ( Wait, - JumpConditional, JumpWhen, JumpUnless, Jump, @@ -150,7 +148,7 @@ def TIKZ_MEASURE() -> str: def _format_parameter(param: ParameterDesignator, settings: Optional[DiagramSettings] = None) -> str: - formatted = format_parameter(param) + formatted: str = format_parameter(param) if settings and settings.texify_numerical_constants: formatted = formatted.replace("pi", r"\pi") return formatted @@ -303,7 +301,7 @@ def split_on_terminal_measures( else: remaining.insert(0, instr) if isinstance(instr, (Gate, ResetQubit)): - seen_qubits |= instr.get_qubits() + seen_qubits |= set(instr.get_qubit_indices() or {}) elif isinstance(instr, Pragma): if instr.command == PRAGMA_END_GROUP: warn("Alignment of terminal MEASURE operations may" "conflict with gate group declaration.") @@ -437,7 +435,7 @@ def _build_measure(self) -> None: instr = self.working_instructions[self.index] assert isinstance(instr, Measurement) assert self.diagram is not None - self.diagram.append(instr.qubit.index, TIKZ_MEASURE()) + self.diagram.append(instr.get_qubit_indices().pop(), TIKZ_MEASURE()) self.index += 1 def _build_custom_source_target_op(self) -> None: @@ -495,7 +493,6 @@ def _build_generic_unitary(self) -> None: assert self.diagram is not None self.diagram.extend_lines_to_common_edge(qubits) - control_qubits = qubits[:controls] # sort the target qubit list because the first qubit indicates wire placement on the diagram target_qubits = sorted(qubits[controls:]) @@ -521,9 +518,7 @@ def qubit_indices(instr: AbstractInstruction) -> List[int]: """ Get a list of indices associated with the given instruction. """ - if isinstance(instr, Measurement): - return [instr.qubit.index] - elif isinstance(instr, Gate): - return [qubit.index for qubit in instr.qubits] + if isinstance(instr, (Measurement, Gate)): + return list(instr.get_qubit_indices()) else: return [] diff --git a/pyquil/noise.py b/pyquil/noise.py index 230118d71..5651dae89 100644 --- a/pyquil/noise.py +++ b/pyquil/noise.py @@ -443,7 +443,7 @@ def _decoherence_noise_model( :math:`F = (p(0|0) + p(1|1))/2` either globally or in a dictionary indexed by qubit id. :return: A NoiseModel with the appropriate Kraus operators defined. """ - all_qubits = set(sum(([t.index for t in g.qubits] for g in gates), [])) + all_qubits = set(sum((g.get_qubit_indices() for g in gates), [])) if isinstance(T1, dict): all_qubits.update(T1.keys()) if isinstance(T2, dict): @@ -468,7 +468,7 @@ def _decoherence_noise_model( } kraus_maps = [] for g in gates: - targets = tuple(t.index for t in g.qubits) + targets = tuple(g.get_qubit_indices()) if g.name in NO_NOISE: continue matrix, _ = get_noisy_gate(g.name, g.params) diff --git a/pyquil/parser.py b/pyquil/parser.py deleted file mode 100644 index 68e9828cd..000000000 --- a/pyquil/parser.py +++ /dev/null @@ -1,44 +0,0 @@ -############################################################################## -# Copyright 2016-2018 Rigetti Computing -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -############################################################################## -""" -Module for parsing Quil programs from text into PyQuil objects -""" -from typing import List - -from pyquil._parser.parser import run_parser -from pyquil.quil import Program -from pyquil.quilbase import AbstractInstruction - - -def parse_program(quil: str) -> Program: - """ - Parse a raw Quil program and return a PyQuil program. - - :param str quil: a single or multiline Quil program - :return: PyQuil Program object - """ - return Program(parse(quil)) - - -def parse(quil: str) -> List[AbstractInstruction]: - """ - Parse a raw Quil program and return a corresponding list of PyQuil objects. - - :param str quil: a single or multiline Quil program - :return: list of instructions - """ - p = run_parser(quil) - return p diff --git a/pyquil/paulis.py b/pyquil/paulis.py index e63acf0bd..681dee59f 100644 --- a/pyquil/paulis.py +++ b/pyquil/paulis.py @@ -32,26 +32,33 @@ List, Optional, Sequence, + Set, Tuple, Union, cast, ) from pyquil.quilatom import ( + Qubit, QubitPlaceholder, FormalArgument, Expression, ExpressionDesignator, MemoryReference, + _convert_to_py_expression, + _convert_to_rs_expression, + _convert_to_py_qubits, ) +import quil.instructions as quil_rs + from .quil import Program from .gates import H, RZ, RX, CNOT, X, PHASE, QUANTUM_GATES from numbers import Number, Complex from collections import OrderedDict import warnings -PauliTargetDesignator = Union[int, FormalArgument, QubitPlaceholder] +PauliTargetDesignator = Union[int, FormalArgument, Qubit, QubitPlaceholder] PauliDesignator = Union["PauliTerm", "PauliSum"] PAULI_OPS = ["X", "Y", "Z", "I"] @@ -103,10 +110,6 @@ class UnequalLengthWarning(Warning): def __init__(self, *args: object, **kwargs: object): - # TODO: remove this "type: ignore" comment once mypy is upgraded to a version with a more - # recent typeshed that contains the following fix: - # https://github.com/python/typeshed/pull/1704 - # https://github.com/python/mypy/pull/8139 super().__init__(*args, **kwargs) @@ -159,6 +162,17 @@ def __init__( else: self.coefficient = coefficient + @classmethod + def _from_rs_pauli_term(cls, term: quil_rs.PauliTerm) -> "PauliTerm": + term_list = [(str(gate), FormalArgument(arg)) for (gate, arg) in term.arguments] + coefficient = _convert_to_py_expression(term.expression.into_simplified()) + + return cls.from_list(term_list, coefficient) + + def _to_rs_pauli_term(self) -> quil_rs.PauliTerm: + arguments = [(quil_rs.PauliGate.parse(gate), str(arg)) for arg, gate in self._ops.items()] + return quil_rs.PauliTerm(arguments, _convert_to_rs_expression(self.coefficient)) + def id(self, sort_ops: bool = True) -> str: """ Returns an identifier string for the PauliTerm (ignoring the coefficient). @@ -383,7 +397,9 @@ def compact_str(self) -> str: return f"{self.coefficient}*{self.id(sort_ops=False)}" @classmethod - def from_list(cls, terms_list: List[Tuple[str, int]], coefficient: float = 1.0) -> "PauliTerm": + def from_list( + cls, terms_list: Sequence[Tuple[str, PauliTargetDesignator]], coefficient: ExpressionDesignator = 1.0 + ) -> "PauliTerm": """ Allocates a Pauli Term from a list of operators and indices. This is more efficient than multiplying together individual terms. @@ -571,6 +587,22 @@ def __init__(self, terms: Sequence[PauliTerm]): else: self.terms = terms + @classmethod + def _from_rs_pauli_sum(cls, pauli_sum: quil_rs.PauliSum) -> "PauliSum": + return cls([PauliTerm._from_rs_pauli_term(term) for term in pauli_sum.terms]) + + def _to_rs_pauli_sum(self, arguments: Optional[List[PauliTargetDesignator]] = None) -> quil_rs.PauliSum: + rs_arguments: List[str] + if arguments is None: + argument_set: Dict[str, None] = {} + for term_arguments in [term.get_qubits() for term in self.terms]: + argument_set.update({str(arg): None for arg in term_arguments}) + rs_arguments = list(argument_set.keys()) + else: + rs_arguments = [str(arg) for arg in arguments] + terms = [term._to_rs_pauli_term() for term in self.terms] + return quil_rs.PauliSum(rs_arguments, terms) + def __eq__(self, other: object) -> bool: """Equality testing to see if two PauliSum's are equivalent. @@ -725,10 +757,10 @@ def get_qubits(self) -> List[PauliTargetDesignator]: :returns: A list of all the qubits in the sum of terms. """ - all_qubits = [] + all_qubits: Set[PauliTargetDesignator] = set() for term in self.terms: - all_qubits.extend(term.get_qubits()) - return list(set(all_qubits)) + all_qubits.update(term.get_qubits()) + return _convert_to_py_qubits(set(all_qubits)) def simplify(self) -> "PauliSum": """ diff --git a/pyquil/pyqvm.py b/pyquil/pyqvm.py index c4332e603..5f7cba21e 100644 --- a/pyquil/pyqvm.py +++ b/pyquil/pyqvm.py @@ -32,7 +32,6 @@ ResetQubit, DefGate, JumpTarget, - JumpConditional, JumpWhen, JumpUnless, Halt, @@ -310,23 +309,22 @@ def transition(self) -> bool: instruction = self.program[self.program_counter] if isinstance(instruction, Gate): + qubits = instruction.get_qubit_indices() if instruction.name in self.defined_gates: self.wf_simulator.do_gate_matrix( matrix=self.defined_gates[instruction.name], - qubits=[q.index for q in instruction.qubits], + qubits=qubits, ) else: self.wf_simulator.do_gate(gate=instruction) for noise_type, noise_prob in self.post_gate_noise_probabilities.items(): - self.wf_simulator.do_post_gate_noise( - noise_type, noise_prob, qubits=[q.index for q in instruction.qubits] - ) + self.wf_simulator.do_post_gate_noise(noise_type, noise_prob, qubits=qubits) self.program_counter += 1 elif isinstance(instruction, Measurement): - measured_val = self.wf_simulator.do_measurement(qubit=instruction.qubit.index) + measured_val = self.wf_simulator.do_measurement(qubit=instruction.get_qubit_indices().pop()) meas_reg: Optional[MemoryReference] = instruction.classical_reg assert meas_reg is not None self.ram[meas_reg.name][meas_reg.offset] = measured_val @@ -353,26 +351,26 @@ def transition(self) -> bool: # Label; pass straight over self.program_counter += 1 - elif isinstance(instruction, JumpConditional): - # JumpConditional; check classical reg + elif isinstance(instruction, (JumpWhen, JumpUnless)): + # JumpWhen/Unless; check classical reg jump_reg: Optional[MemoryReference] = instruction.condition assert jump_reg is not None cond = self.ram[jump_reg.name][jump_reg.offset] if not isinstance(cond, (bool, np.bool_, np.int8, int)): - raise ValueError("{} requires a data type of BIT; not {}".format(instruction.op, type(cond))) + raise ValueError("{} requires a data type of BIT; not {}".format(type(instruction), type(cond))) dest_index = self.find_label(instruction.target) if isinstance(instruction, JumpWhen): jump_if_cond = True elif isinstance(instruction, JumpUnless): jump_if_cond = False else: - raise TypeError("Invalid JumpConditional") + raise TypeError(f"Invalid {type(instruction)}") if not (cond ^ jump_if_cond): # jumping: set prog counter to JumpTarget self.program_counter = dest_index else: - # not jumping: hop over this JumpConditional + # not jumping: hop over this instruction self.program_counter += 1 elif isinstance(instruction, UnaryClassicalInstruction): diff --git a/pyquil/quil.py b/pyquil/quil.py index 77b6ebe85..301a75f69 100644 --- a/pyquil/quil.py +++ b/pyquil/quil.py @@ -16,12 +16,12 @@ """ Module for creating and defining Quil programs. """ -import itertools import types -import warnings from collections import defaultdict +from copy import deepcopy from typing import ( Any, + Callable, Dict, Generator, List, @@ -33,16 +33,14 @@ Tuple, Union, cast, - no_type_check, ) +import warnings -from copy import deepcopy - +from deprecated.sphinx import deprecated import numpy as np from qcs_sdk.compiler.quilc import NativeQuilMetadata -from pyquil._parser.parser import run_parser from pyquil.gates import MEASURE, RESET from pyquil.noise import _check_kraus_ops, _create_kraus_pragmas, pauli_kraus_map from pyquil.quilatom import ( @@ -66,47 +64,36 @@ Gate, Measurement, Pragma, - Halt, AbstractInstruction, Jump, - JumpConditional, JumpTarget, JumpUnless, JumpWhen, Declare, - Reset, - ResetQubit, - DelayFrames, - DelayQubits, - Fence, - FenceAll, - Pulse, - Capture, - RawCapture, - SetFrequency, - ShiftFrequency, - SetPhase, - ShiftPhase, - SwapPhases, - SetScale, - DefPermutationGate, DefCalibration, DefFrame, DefMeasureCalibration, DefWaveform, + _convert_to_rs_instruction, + _convert_to_rs_instructions, + _convert_to_py_instruction, + _convert_to_py_instructions, + _convert_to_py_qubits, ) from pyquil.quiltcalibrations import ( - CalibrationError, CalibrationMatch, - expand_calibration, - match_calibration, + _convert_to_calibration_match, ) +from quil.program import CalibrationSet, Program as RSProgram +import quil.instructions as quil_rs + InstructionDesignator = Union[ AbstractInstruction, - DefGate, + quil_rs.Instruction, "Program", - List[Any], + RSProgram, + Sequence[Any], Tuple[Any, ...], str, # required to be a pyquil program Generator[Any, Any, Any], @@ -125,55 +112,47 @@ class Program: """ def __init__(self, *instructions: InstructionDesignator): - self._defined_gates: List[DefGate] = [] - - self._calibrations: List[Union[DefCalibration, DefMeasureCalibration]] = [] - self._waveforms: Dict[str, DefWaveform] = {} - self._frames: Dict[Frame, DefFrame] = {} - - # Implementation note: the key difference between the private _instructions and - # the public instructions property below is that the private _instructions list - # may contain placeholder labels. - self._instructions: List[AbstractInstruction] = [] - - # Performance optimization: as stated above _instructions may contain placeholder - # labels so the program must first be have its labels instantiated. - # _synthesized_instructions is simply a cache on the result of the _synthesize() - # method. It is marked as None whenever new instructions are added. - self._synthesized_instructions: Optional[List[AbstractInstruction]] = None - - self._declarations: Dict[str, Declare] = {} - + self._program: RSProgram = RSProgram() self.inst(*instructions) - # Filled in with quil_to_native_quil - self.native_quil_metadata: Optional[NativeQuilMetadata] = None - # default number of shots to loop through self.num_shots = 1 - # Note to developers: Have you changed this method? Have you changed the fields which - # live on `Program`? Please update `Program.copy()`! + self.native_quil_metadata: Optional[NativeQuilMetadata] = None @property - def calibrations(self) -> List[Union[DefCalibration, DefMeasureCalibration]]: + def calibrations(self) -> List[DefCalibration]: """A list of Quil-T calibration definitions.""" - return self._calibrations + return [DefCalibration._from_rs_calibration(cal) for cal in self._program.calibrations.calibrations] + + @property + def measure_calibrations(self) -> List[DefMeasureCalibration]: + """A list of measure calibrations""" + return [ + DefMeasureCalibration._from_rs_measure_calibration_definition(cal) + for cal in self._program.calibrations.measure_calibrations + ] @property def waveforms(self) -> Dict[str, DefWaveform]: """A mapping from waveform names to their corresponding definitions.""" - return self._waveforms + return { + name: DefWaveform._from_rs_waveform_definition(quil_rs.WaveformDefinition(name, waveform)) + for name, waveform in self._program.waveforms.items() + } @property def frames(self) -> Dict[Frame, DefFrame]: """A mapping from Quil-T frames to their definitions.""" - return self._frames + return { + Frame._from_rs_frame_identifier(frame): DefFrame._from_rs_attribute_values(frame, attributes) + for frame, attributes in self._program.frames.get_all_frames().items() + } @property def declarations(self) -> Dict[str, Declare]: """A mapping from declared region names to their declarations.""" - return self._declarations + return {name: Declare._from_rs_declaration(inst) for name, inst in self._program.declarations.items()} def copy_everything_except_instructions(self) -> "Program": """ @@ -181,11 +160,12 @@ def copy_everything_except_instructions(self) -> "Program": :return: a new Program """ - new_prog = Program() - new_prog._calibrations = self.calibrations.copy() - new_prog._waveforms = self.waveforms.copy() - new_prog._defined_gates = self._defined_gates.copy() - new_prog._frames = self.frames.copy() + new_prog = Program( + list(self.frames.values()), + list(self.waveforms.values()), + self.calibrations, + self.measure_calibrations, + ) if self.native_quil_metadata is not None: new_prog.native_quil_metadata = deepcopy(self.native_quil_metadata) new_prog.num_shots = self.num_shots @@ -193,36 +173,36 @@ def copy_everything_except_instructions(self) -> "Program": def copy(self) -> "Program": """ - Perform a shallow copy of this program. - - QuilAtom and AbstractInstruction objects should be treated as immutable to avoid - strange behavior when performing a copy. + Perform a deep copy of this program. :return: a new Program """ - new_prog = self.copy_everything_except_instructions() # and declarations, which is a view - new_prog._instructions = self._instructions.copy() - new_prog._declarations = self._declarations.copy() - return new_prog + new_program = Program(self._program.copy()) + new_program.native_quil_metadata = self.native_quil_metadata + new_program.num_shots = self.num_shots + return new_program @property def defined_gates(self) -> List[DefGate]: """ A list of defined gates on the program. """ - return self._defined_gates + return [DefGate._from_rs_gate_definition(gate) for gate in self._program.defined_gates] @property def instructions(self) -> List[AbstractInstruction]: """ Fill in any placeholders and return a list of quil AbstractInstructions. """ - if self._synthesized_instructions is None: - self._synthesize() - assert self._synthesized_instructions is not None - return self._synthesized_instructions + return list(self.declarations.values()) + _convert_to_py_instructions(self._program.body_instructions) + + @instructions.setter + def instructions(self, instructions: List[AbstractInstruction]) -> None: + new_program = self.copy_everything_except_instructions() + new_program.inst(instructions) + self._program = new_program._program - def inst(self, *instructions: InstructionDesignator) -> "Program": + def inst(self, *instructions: Union[InstructionDesignator, RSProgram]) -> "Program": """ Mutates the Program object by appending new instructions. @@ -232,25 +212,25 @@ def inst(self, *instructions: InstructionDesignator) -> "Program": >>> from pyquil.gates import H >>> p = Program() >>> p.inst(H(0)) # A single instruction - + Program { ... } >>> p.inst(H(0), H(1)) # Multiple instructions - + Program { ... } >>> p.inst([H(0), H(1)]) # A list of instructions - + Program { ... } >>> p.inst(H(i) for i in range(4)) # A generator of instructions - + Program { ... } >>> p.inst(("H", 1)) # A tuple representing an instruction - + Program { ... } >>> p.inst("H 0") # A string representing an instruction - + Program { ... } >>> q = Program() >>> p.inst(q) # Another program - + Program { ... } It can also be chained: >>> p = Program() >>> p.inst(H(0)).inst(H(1)) - + Program { ... } :param instructions: A list of Instruction objects, e.g. Gates :return: self for method chaining @@ -263,119 +243,187 @@ def inst(self, *instructions: InstructionDesignator) -> "Program": elif isinstance(instruction, tuple): if len(instruction) == 0: raise ValueError("tuple should have at least one element") - elif len(instruction) == 1: - self.inst(instruction[0]) else: - op = instruction[0] - if op == "MEASURE": - if len(instruction) == 2: - self.measure(instruction[1], None) - else: - self.measure(instruction[1], instruction[2]) - else: - params: List[ParameterDesignator] = [] - possible_params = instruction[1] - rest: Sequence[Any] = instruction[2:] - if isinstance(possible_params, list): - params = possible_params - else: - rest = [possible_params] + list(rest) - self.gate(op, params, rest) + self.inst(" ".join(map(str, instruction))) elif isinstance(instruction, str): - self.inst(run_parser(instruction.strip())) + self.inst(RSProgram.parse(instruction.strip())) elif isinstance(instruction, Program): - if id(self) == id(instruction): - raise ValueError("Nesting a program inside itself is not supported") - - for defgate in instruction._defined_gates: - self.inst(defgate) - for instr in instruction._instructions: - self.inst(instr) - - # Implementation note: these two base cases are the only ones which modify the program - elif isinstance(instruction, DefGate): - # If the gate definition differs from the current one, print a warning and replace it. - r_idx, existing_defgate = next( - ( - (i, gate) - for i, gate in enumerate(reversed(self._defined_gates)) - if gate.name == instruction.name - ), - (0, None), - ) - if existing_defgate is None: - self._defined_gates.append(instruction) + self.inst(instruction._program) + elif isinstance(instruction, quil_rs.Instruction): + self._add_instruction(instruction) + elif isinstance(instruction, AbstractInstruction): + self._add_instruction(_convert_to_rs_instruction(instruction)) + elif isinstance(instruction, RSProgram): + self._program += instruction + else: + try: + instruction = quil_rs.Instruction(instruction) # type: ignore + self.inst(instruction) + except ValueError: + raise ValueError("Invalid instruction: {}, type: {}".format(instruction, type(instruction))) - # numerical unitary - elif (instruction.matrix.dtype == np.complex_) or (instruction.matrix.dtype == np.float_): - if not np.allclose(existing_defgate.matrix, instruction.matrix): - warnings.warn("Redefining gate {}".format(instruction.name)) - self._defined_gates[-r_idx] = instruction + return self - # parametric unitary - else: - if not np.all(existing_defgate.matrix == instruction.matrix): - warnings.warn("Redefining gate {}".format(instruction.name)) - self._defined_gates[-r_idx] = instruction - - elif isinstance(instruction, DefCalibration): - # If the instruction calibration differs from the current one, print a warning and replace it. - r_idx, existing_calibration = next( - ( - (i, gate) - for i, gate in enumerate(reversed(self.calibrations)) - if isinstance(gate, DefCalibration) - and (gate.name == instruction.name) - and (gate.parameters == instruction.parameters) - and (gate.qubits == instruction.qubits) - ), - (0, None), - ) + def resolve_placeholders(self) -> None: + """ + Resolve all label and qubit placeholders in the program using a default resolver that will generate + a unique value for each placeholder within the scope of the program. + """ + self._program.resolve_placeholders() - if existing_calibration is None: - self._calibrations.append(instruction) - else: - if existing_calibration.out() != instruction.out(): - warnings.warn("Redefining calibration {}".format(instruction.name)) - self._calibrations[-r_idx] = instruction - - elif isinstance(instruction, DefMeasureCalibration): - r_idx, existing_measure_calibration = next( - ( - (i, meas) - for i, meas in enumerate(reversed(self.calibrations)) - if isinstance(meas, DefMeasureCalibration) - and (meas.name == instruction.name) - and (meas.qubit == instruction.qubit) - ), - (0, None), + def resolve_placeholders_with_custom_resolvers( + self, + *, + label_resolver: Optional[Callable[[LabelPlaceholder], Optional[str]]] = None, + qubit_resolver: Optional[Callable[[QubitPlaceholder], Optional[int]]] = None, + ) -> None: + """ + Resolve ``LabelPlaceholder``\\s and ``QubitPlaceholder``\\s within the program using a function + to provide resolved values for the placeholdres. + + If you provide ``label_resolver`` and/or ``qubit_resolver``, they will be used to resolve those values + respectively. If your resolver returns `None` for a particular placeholder, it will not be replaced but + will be left as a placeholder. + + If you do not provide a resolver for a placeholder, a default resolver will be used which will generate a unique + value for that placeholder within the scope of the program using an auto-incrementing value (for qubit) or + suffix (for target) while ensuring that unique value is not already in use within the program. + """ + rs_qubit_resolver = None + if qubit_resolver is not None: + + def rs_qubit_resolver(placeholder: quil_rs.QubitPlaceholder) -> Optional[int]: + return qubit_resolver(QubitPlaceholder(placeholder=placeholder)) + + rs_label_resolver = None + if label_resolver is not None: + + def rs_label_resolver(placeholder: quil_rs.TargetPlaceholder) -> Optional[str]: + return label_resolver(LabelPlaceholder(placeholder=placeholder)) + + self._program.resolve_placeholders_with_custom_resolvers( + target_resolver=rs_label_resolver, qubit_resolver=rs_qubit_resolver + ) + + def resolve_qubit_placeholders(self) -> None: + """ + Resolve all qubit placeholders in the program. + """ + self._program.resolve_placeholders_with_custom_resolvers(target_resolver=lambda _: None) + + def resolve_qubit_placeholders_with_mapping(self, qubit_mapping: Dict[QubitPlaceholder, int]) -> None: + """ + Resolve all qubit placeholders in the program using a mapping of ``QubitPlaceholder``\\s to + the index they should resolve to. + """ + + def qubit_resolver(placeholder: quil_rs.QubitPlaceholder) -> Optional[int]: + return qubit_mapping.get(QubitPlaceholder(placeholder), None) + + def label_resolver(_: quil_rs.TargetPlaceholder) -> None: + return None + + self._program.resolve_placeholders_with_custom_resolvers( + qubit_resolver=qubit_resolver, target_resolver=label_resolver + ) + + def resolve_label_placeholders(self) -> None: + """ + Resolve all label placeholders in the program. + """ + self._program.resolve_placeholders_with_custom_resolvers(qubit_resolver=lambda _: None) + + def _add_instruction(self, instruction: quil_rs.Instruction) -> None: + """ + A helper method that adds an instruction to the Program after normalizing to a `quil_rs.Instruction`. + For backwards compatibility, it also prevents duplicate calibration, measurement, and gate definitions from + being added. Users of ``Program`` should use ``inst`` or ``Program`` addition instead. + """ + if instruction.is_gate_definition(): + defgate = instruction.to_gate_definition() + # If the gate definition differs from the current one, print a warning and replace it. + idx, existing_defgate = next( + ( + (i, gate) + for i, gate in enumerate( + map(lambda inst: inst.as_gate_definition(), self._program.body_instructions) + ) + if gate and gate.name == defgate.name + ), + (0, None), + ) + + if existing_defgate is None: + self._program.add_instruction(instruction) + elif ( + existing_defgate.specification != defgate.specification + or existing_defgate.specification.inner() != existing_defgate.specification.inner() + ): + warnings.warn("Redefining gate {}".format(defgate.name)) + new_instructions = ( + self._program.body_instructions[:idx] + [instruction] + self._program.body_instructions[idx + 1 :] ) - if existing_measure_calibration is None: - self._calibrations.append(instruction) - else: - warnings.warn("Redefining DefMeasureCalibration {}".format(instruction.name)) - self._calibrations[-r_idx] = instruction + self._program = self._program.clone_without_body_instructions() + self._program.add_instructions(new_instructions) + elif instruction.is_calibration_definition(): + defcal = instruction.to_calibration_definition() + idx, existing_calibration = next( + ( + (i, existing_calibration) + for i, existing_calibration in enumerate(self._program.calibrations.calibrations) + if defcal.name == existing_calibration.name + and defcal.parameters == existing_calibration.parameters + and defcal.qubits == existing_calibration.qubits + ), + (0, None), + ) + if existing_calibration is None: + self._program.add_instruction(instruction) - elif isinstance(instruction, DefWaveform): - self.waveforms[instruction.name] = instruction - elif isinstance(instruction, DefFrame): - self.frames[instruction.frame] = instruction - elif isinstance(instruction, AbstractInstruction): - self._instructions.append(instruction) - self._synthesized_instructions = None + elif ( + existing_calibration.instructions != defcal.instructions + or existing_calibration.modifiers != defcal.modifiers + ): + warnings.warn("Redefining calibration {}".format(defcal.name)) + current_calibrations = self._program.calibrations + new_calibrations = CalibrationSet( + current_calibrations.calibrations[:idx] + [defcal] + current_calibrations.calibrations[idx + 1 :], + current_calibrations.measure_calibrations, + ) + self._program.calibrations = new_calibrations + elif instruction.is_measure_calibration_definition(): + defmeasure = instruction.to_measure_calibration_definition() + idx, existing_measure_calibration = next( + ( + (i, existing_measure_calibration) + for i, existing_measure_calibration in enumerate(self._program.calibrations.measure_calibrations) + if existing_measure_calibration.parameter == defmeasure.parameter + and existing_measure_calibration.qubit == defmeasure.qubit + ), + (0, None), + ) + if existing_measure_calibration is None: + self._program.add_instruction(instruction) - if isinstance(instruction, Declare): - self._declarations[instruction.name] = instruction else: - raise TypeError("Invalid instruction: {}".format(instruction)) + warnings.warn("Redefining DefMeasureCalibration {}".format(instruction)) + current_calibrations = self._program.calibrations + new_calibrations = CalibrationSet( + current_calibrations.calibrations, + current_calibrations.measure_calibrations[:idx] + + [defmeasure] + + current_calibrations.measure_calibrations[idx + 1 :], + ) - return self + self._program.calibrations = new_calibrations + else: + self._program.add_instruction(instruction) def gate( self, name: str, - params: Iterable[ParameterDesignator], - qubits: Iterable[Union[Qubit, QubitPlaceholder]], + params: Sequence[ParameterDesignator], + qubits: Sequence[Union[Qubit, QubitPlaceholder]], ) -> "Program": """ Add a gate to the program. @@ -439,7 +487,7 @@ def define_noisy_gate(self, name: str, qubit_indices: Sequence[int], kraus_ops: _check_kraus_ops(len(qubit_indices), kraus_ops) return self.inst(_create_kraus_pragmas(name, tuple(qubit_indices), kraus_ops)) - def define_noisy_readout(self, qubit: Union[int, QubitPlaceholder], p00: float, p11: float) -> "Program": + def define_noisy_readout(self, qubit: Union[int], p00: float, p11: float) -> "Program": """ For this program define a classical bit flip readout error channel parametrized by ``p00`` and ``p11``. This models the effect of thermal noise that corrupts the readout @@ -540,9 +588,8 @@ def prepend_instructions(self, instructions: Iterable[AbstractInstruction]) -> " """ Prepend instructions to the beginning of the program. """ - self._instructions = [*instructions, *self._instructions] - self._synthesized_instructions = None - return self + new_prog = Program(*instructions) + return new_prog + self def while_do(self, classical_reg: MemoryReferenceDesignator, q_program: "Program") -> "Program": """ @@ -610,7 +657,8 @@ def if_then( label_then = LabelPlaceholder("THEN") label_end = LabelPlaceholder("END") - self.inst(JumpWhen(target=label_then, condition=unpack_classical_reg(classical_reg))) + jump_when = JumpWhen(target=label_then, condition=unpack_classical_reg(classical_reg)) + self.inst(jump_when) self.inst(else_program) self.inst(Jump(label_end)) self.inst(JumpTarget(label_then)) @@ -624,7 +672,7 @@ def declare( memory_type: str = "BIT", memory_size: int = 1, shared_region: Optional[str] = None, - offsets: Optional[Iterable[Tuple[int, str]]] = None, + offsets: Optional[Sequence[Tuple[int, str]]] = None, ) -> MemoryReference: """DECLARE a quil variable @@ -679,19 +727,16 @@ def out(self, *, calibrations: Optional[bool] = True) -> str: """ Serializes the Quil program to a string suitable for submitting to the QVM or QPU. """ + if calibrations: + return self._program.to_quil() + else: + return self._program.into_simplified().to_quil() - return "\n".join( - itertools.chain( - (dg.out() for dg in self._defined_gates), - (wf.out() for wf in self.waveforms.values()), - (fdef.out() for fdef in self.frames.values()), - (cal.out() for cal in self.calibrations) if calibrations else list(), - (instr.out() for instr in self.instructions), - [""], - ) - ) - - def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]: + @deprecated( + version="4.0", + reason="The indices flag will be removed. Use get_qubit_indices() instead.", + ) + def get_qubits(self, indices: bool = True) -> Union[Set[QubitDesignator], Set[int]]: """ Returns all of the qubit indices used in this program, including gate applications and allocated qubits. e.g. @@ -699,40 +744,29 @@ def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]: >>> from pyquil.gates import H >>> p = Program() >>> p.inst(("H", 1)) - + Program { ... } >>> p.get_qubits() {1} >>> q = QubitPlaceholder() >>> p.inst(H(q)) - - >>> len(p.get_qubits()) + Program { ... } + >>> len(p.get_qubits(indices=False)) 2 :param indices: Return qubit indices as integers intead of the wrapping :py:class:`Qubit` object :return: A set of all the qubit indices used in this program """ - qubits: Set[QubitDesignator] = set() - for instr in self.instructions: - if isinstance( - instr, - ( - Gate, - Measurement, - ResetQubit, - Pulse, - Capture, - RawCapture, - ShiftFrequency, - SetFrequency, - SetPhase, - ShiftPhase, - SwapPhases, - SetScale, - ), - ): - qubits |= instr.get_qubits(indices=indices) - return qubits + if indices: + return self.get_qubit_indices() + return set(_convert_to_py_qubits(self._program.get_used_qubits())) + + def get_qubit_indices(self) -> Set[int]: + """ + Returns the index of each qubit used in the program. Will raise an exception if any of the + qubits are placeholders. + """ + return {q.to_fixed() for q in self._program.get_used_qubits()} def match_calibrations(self, instr: AbstractInstruction) -> Optional[CalibrationMatch]: """ @@ -741,7 +775,7 @@ def match_calibrations(self, instr: AbstractInstruction) -> Optional[Calibration Note: preference is given to later calibrations, i.e. in a program with DEFCAL X 0: - + DEFCAL X 0: @@ -751,11 +785,19 @@ def match_calibrations(self, instr: AbstractInstruction) -> Optional[Calibration :param instr: An instruction. :returns: a CalibrationMatch object, if one can be found. """ - if isinstance(instr, (Gate, Measurement)): - for cal in reversed(self.calibrations): - match = match_calibration(instr, cal) - if match is not None: - return match + if not isinstance(instr, (Gate, Measurement)): + return None + + instruction = _convert_to_rs_instruction(instr) + if instruction.is_gate(): + gate = instruction.to_gate() + gate_match = self._program.calibrations.get_match_for_gate(gate) + return _convert_to_calibration_match(gate, gate_match) + + if instruction.is_measurement(): + measurement = instruction.to_measurement() + measure_match = self._program.calibrations.get_match_for_measurement(measurement) + return _convert_to_calibration_match(measurement, measure_match) return None @@ -796,113 +838,42 @@ def calibrate( """ if previously_calibrated_instructions is None: previously_calibrated_instructions = set() - elif instruction in previously_calibrated_instructions: - raise CalibrationError( - f"The instruction {instruction} appears in the set of " - f"previously calibrated instructions {previously_calibrated_instructions}" - " and would therefore result in a cyclic non-terminating expansion." - ) - else: - previously_calibrated_instructions = previously_calibrated_instructions.union({instruction}) - match = self.match_calibrations(instruction) - if match is not None: - return sum( - [ - self.calibrate(expansion, previously_calibrated_instructions) - for expansion in expand_calibration(match) - ], - [], - ) - else: - return [instruction] - - def is_protoquil(self, quilt: bool = False) -> bool: - """ - Protoquil programs may only contain gates, Pragmas, and RESET. It may not contain - classical instructions or jumps. - :return: True if the Program is Protoquil, False otherwise - """ - try: - if quilt: - validate_protoquil(self, quilt=quilt) - else: - validate_protoquil(self) - return True - except ValueError: - return False - - def is_supported_on_qpu(self) -> bool: - """ - Whether the program can be compiled to the hardware to execute on a QPU. These Quil - programs are more restricted than Protoquil: for instance, RESET must be before any - gates or MEASUREs, and MEASURE on a qubit must be after any gates on that qubit. + calibrated_instructions = self._program.calibrations.expand( + _convert_to_rs_instruction(instruction), + _convert_to_rs_instructions(previously_calibrated_instructions), + ) - :return: True if the Program is supported Quil, False otherwise - """ - try: - validate_supported_quil(self) - return True - except ValueError: - return False + return _convert_to_py_instructions(calibrated_instructions) - def _sort_declares_to_program_start(self) -> None: + @deprecated( + version="4.0", + reason="This function always returns True and will be removed.", + ) + def is_protoquil(self, quilt: bool = False) -> bool: """ - Re-order DECLARE instructions within this program to the beginning, followed by - all other instructions. Reordering is stable among DECLARE and non-DECLARE instructions. + This function has been deprecated and will always return True. """ - self._instructions = sorted(self._instructions, key=lambda instruction: not isinstance(instruction, Declare)) + return True - def pop(self) -> AbstractInstruction: + @deprecated( + version="4.0", + reason="This function always returns True and will be removed.", + ) + def is_supported_on_qpu(self) -> bool: """ - Pops off the last instruction. - - :return: The instruction that was popped. + This function has been deprecated and will always return True. """ - res = self._instructions.pop() - self._synthesized_instructions = None - return res + return True - def dagger(self, inv_dict: Optional[Any] = None, suffix: str = "-INV") -> "Program": + def dagger(self) -> "Program": """ Creates the conjugate transpose of the Quil program. The program must contain only gate applications. - Note: the keyword arguments inv_dict and suffix are kept only - for backwards compatibility and have no effect. - :return: The Quil program's inverse """ - if any(not isinstance(instr, Gate) for instr in self._instructions): - raise ValueError("Program to be daggered must contain only gate applications") - - # This is a bit hacky. Gate.dagger() mutates the gate object, rather than returning a fresh - # (and daggered) copy. Also, mypy doesn't understand that we already asserted that every - # instr in _instructions is a Gate, above, so help mypy out with a cast. - surely_gate_instructions = cast(List[Gate], Program(self.out())._instructions) - return Program([instr.dagger() for instr in reversed(surely_gate_instructions)]) - - def _synthesize(self) -> "Program": - """ - Assigns all placeholder labels to actual values. - - Changed in 1.9: Either all qubits must be defined or all undefined. If qubits are - undefined, this method will not help you. You must explicitly call `address_qubits` - which will return a new Program. - - Changed in 1.9: This function now returns ``self`` and updates - ``self._synthesized_instructions``. - - Changed in 2.0: This function will add an instruction to the top of the program - to declare a register of bits called ``ro`` if and only if there are no other - declarations in the program. - - Changed in 3.0: Removed above change regarding implicit ``ro`` declaration. - - :return: This object with the ``_synthesized_instructions`` member set. - """ - self._synthesized_instructions = instantiate_labels(self._instructions) - return self + return Program(self._program.dagger()) def __add__(self, other: InstructionDesignator) -> "Program": """ @@ -914,29 +885,8 @@ def __add__(self, other: InstructionDesignator) -> "Program": p = Program() p.inst(self) p.inst(other) - p._calibrations = self.calibrations.copy() - p._waveforms = self.waveforms.copy() - p._frames = self.frames.copy() - if isinstance(other, Program): - p.calibrations.extend(other.calibrations) - p.waveforms.update(other.waveforms) - p.frames.update(other.frames) return p - def __iadd__(self, other: InstructionDesignator) -> "Program": - """ - Concatenate two programs together using +=, returning a new one. - - :param other: Another program or instruction to concatenate to this one. - :return: A newly concatenated program. - """ - self.inst(other) - if isinstance(other, Program): - self.calibrations.extend(other.calibrations) - self.waveforms.update(other.waveforms) - self.frames.update(other.frames) - return self - def __getitem__(self, index: Union[slice, int]) -> Union[AbstractInstruction, "Program"]: """ Allows indexing into the program to get an action. @@ -944,7 +894,11 @@ def __getitem__(self, index: Union[slice, int]) -> Union[AbstractInstruction, "P :param index: The action at the specified index. :return: """ - return Program(self.instructions[index]) if isinstance(index, slice) else self.instructions[index] + return ( + Program(self._program.to_instructions()[index]) + if isinstance(index, slice) + else _convert_to_py_instruction(self._program.to_instructions()[index]) + ) def __iter__(self) -> Iterator[AbstractInstruction]: """ @@ -955,10 +909,9 @@ def __iter__(self) -> Iterator[AbstractInstruction]: return self.instructions.__iter__() def __eq__(self, other: object) -> bool: - return isinstance(other, self.__class__) and self.out() == other.out() - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) + if isinstance(other, Program): + return self._program.to_instructions() == other._program.to_instructions() + return False def __len__(self) -> int: return len(self.instructions) @@ -966,6 +919,9 @@ def __len__(self) -> int: def __hash__(self) -> int: return hash(self.out()) + def __repr__(self) -> str: + return repr(self._program) + def __str__(self) -> str: """ A string representation of the Quil program for inspection. @@ -973,163 +929,82 @@ def __str__(self) -> str: This may not be suitable for submission to a QPU or QVM for example if your program contains unaddressed QubitPlaceholders """ - return "\n".join( - itertools.chain( - (str(dg) for dg in self._defined_gates), - (str(wf) for wf in self.waveforms.values()), - (str(fdef) for fdef in self.frames.values()), - (str(cal) for cal in self.calibrations), - (str(instr) for instr in self.instructions), - [""], - ) - ) + return self._program.to_quil_or_debug() -def _what_type_of_qubit_does_it_use( - program: Program, -) -> Tuple[bool, bool, List[Union[Qubit, QubitPlaceholder]]]: - """Helper function to peruse through a program's qubits. +def merge_with_pauli_noise( + prog_list: Iterable[Program], probabilities: Sequence[float], qubits: Sequence[int] +) -> Program: + """ + Insert pauli noise channels between each item in the list of programs. + This noise channel is implemented as a single noisy identity gate acting on the provided qubits. + This method does not rely on merge_programs and so avoids the inclusion of redundant Kraus + Pragmas that would occur if merge_programs was called directly on programs with distinct noisy + gate definitions. - This function will also enforce the condition that a Program uses either all placeholders - or all instantiated qubits to avoid accidentally mixing the two. This function will warn - if your program doesn't use any qubits. + :param prog_list: an iterable such as a program or a list of programs. + If a program is provided, a single noise gate will be applied after each gate in the + program. If a list of programs is provided, the noise gate will be applied after each + program. + :param probabilities: The 4^num_qubits list of probabilities specifying the desired pauli + channel. There should be either 4 or 16 probabilities specified in the order + I, X, Y, Z or II, IX, IY, IZ, XI, XX, XY, etc respectively. + :param qubits: a list of the qubits that the noisy gate should act on. + :return: A single program with noisy gates inserted between each element of the program list. + """ + p = Program() + p.defgate("pauli_noise", np.eye(2 ** len(qubits))) + p.define_noisy_gate("pauli_noise", qubits, pauli_kraus_map(probabilities)) + for elem in prog_list: + p.inst(Program(elem)) + if isinstance(elem, Measurement): + continue # do not apply noise after measurement + p.inst(("pauli_noise", *qubits)) + return p - :return: tuple of (whether the program uses placeholder qubits, whether the program uses - real qubits, a list of qubits ordered by their first appearance in the program) + +def get_classical_addresses_from_program(program: Program) -> Dict[str, List[int]]: """ - has_placeholders = False - has_real_qubits = False + Returns a sorted list of classical addresses found in the MEASURE instructions in the program. - # We probably want to index qubits in the order they are encountered in the program - # so an ordered set would be nice. Python doesn't *have* an ordered set. Use the keys - # of an ordered dictionary instead - qubits = {} + :param program: The program from which to get the classical addresses. + :return: A mapping from memory region names to lists of offsets appearing in the program. + """ + addresses: Dict[str, List[int]] = defaultdict(list) + flattened_addresses = {} + # Required to use the `classical_reg.address` int attribute. + # See https://github.com/rigetti/pyquil/issues/388. for instr in program: - if isinstance(instr, Gate): - for q in instr.qubits: - qubits[q] = 1 - if isinstance(q, QubitPlaceholder): - has_placeholders = True - elif isinstance(q, Qubit): - has_real_qubits = True - else: - raise ValueError("Unknown qubit type {}".format(q)) - elif isinstance(instr, Measurement): - qubits[instr.qubit] = 1 - if isinstance(instr.qubit, QubitPlaceholder): - has_placeholders = True - elif isinstance(instr.qubit, Qubit): - has_real_qubits = True - else: - raise ValueError("Unknown qubit type {}".format(instr.qubit)) - elif isinstance(instr, Pragma): - for arg in instr.args: - if isinstance(arg, QubitPlaceholder): - qubits[arg] = 1 - has_placeholders = True - elif isinstance(arg, Qubit): - qubits[arg] = 1 - has_real_qubits = True - if not (has_placeholders or has_real_qubits): - warnings.warn("Your program doesn't use any qubits") - - if has_placeholders and has_real_qubits: - raise ValueError("Your program mixes instantiated qubits with placeholders") - - # The isinstance checks above make sure that if any qubit is a - # FormalArgument (which is permitted by Gate.qubits), then an - # error should be raised. Unfortunately this doesn't help mypy - # narrow down the return type, so gotta cast. - return ( - has_placeholders, - has_real_qubits, - cast(List[Union[Qubit, QubitPlaceholder]], list(qubits.keys())), - ) + if isinstance(instr, Measurement) and instr.classical_reg: + addresses[instr.classical_reg.name].append(instr.classical_reg.offset) + # flatten duplicates + for k, v in addresses.items(): + reduced_list = list(set(v)) + reduced_list.sort() + flattened_addresses[k] = reduced_list -def get_default_qubit_mapping(program: Program) -> Dict[Union[Qubit, QubitPlaceholder], Qubit]: - """ - Takes a program which contains qubit placeholders and provides a mapping to the integers - 0 through N-1. + return flattened_addresses - The output of this function is suitable for input to :py:func:`address_qubits`. - :param program: A program containing qubit placeholders - :return: A dictionary mapping qubit placeholder to an addressed qubit from 0 through N-1. - """ - fake_qubits, real_qubits, qubits = _what_type_of_qubit_does_it_use(program) - if real_qubits: - warnings.warn("This program contains integer qubits, so getting a mapping doesn't make sense.") - # _what_type_of_qubit_does_it_use ensures that if real_qubits is True, then qubits contains - # only real Qubits, not QubitPlaceholders. Help mypy figure this out with cast. - return {q: cast(Qubit, q) for q in qubits} - return {qp: Qubit(i) for i, qp in enumerate(qubits)} - - -@no_type_check -def address_qubits( - program: Program, qubit_mapping: Optional[Dict[QubitPlaceholder, Union[Qubit, int]]] = None -) -> Program: +def address_qubits(program: Program, qubit_mapping: Optional[Dict[QubitPlaceholder, int]] = None) -> Program: """ Takes a program which contains placeholders and assigns them all defined values. - Either all qubits must be defined or all undefined. If qubits are undefined, you may provide a qubit mapping to specify how placeholders get mapped to actual qubits. If a mapping is not provided, integers 0 through N are used. - This function will also instantiate any label placeholders. - :param program: The program. :param qubit_mapping: A dictionary-like object that maps from :py:class:`QubitPlaceholder` - to :py:class:`Qubit` or ``int`` (but not both). + to :py:class:`Qubit` or ``int`` (but not both). :return: A new Program with all qubit and label placeholders assigned to real qubits and labels. """ - fake_qubits, real_qubits, qubits = _what_type_of_qubit_does_it_use(program) - if real_qubits: - if qubit_mapping is not None: - warnings.warn("A qubit mapping was provided but the program does not " "contain any placeholders to map!") - return program - - if qubit_mapping is None: - qubit_mapping = {qp: Qubit(i) for i, qp in enumerate(qubits)} - else: - if all(isinstance(v, Qubit) for v in qubit_mapping.values()): - pass # we good - elif all(isinstance(v, int) for v in qubit_mapping.values()): - qubit_mapping = {k: Qubit(v) for k, v in qubit_mapping.items()} - else: - raise ValueError("Qubit mapping must map to type Qubit or int (but not both)") - - result: List[AbstractInstruction] = [] - for instr in program: - # Remap qubits on Gate, Measurement, and ResetQubit instructions - if isinstance(instr, Gate): - remapped_qubits = [qubit_mapping[q] for q in instr.qubits] - gate = Gate(instr.name, instr.params, remapped_qubits) - gate.modifiers = instr.modifiers - result.append(gate) - elif isinstance(instr, Measurement): - result.append(Measurement(qubit_mapping[instr.qubit], instr.classical_reg)) - elif isinstance(instr, ResetQubit): - result.append(ResetQubit(qubit_mapping[instr.qubit])) - elif isinstance(instr, Pragma): - new_args: List[Union[Qubit, int, str]] = [] - for arg in instr.args: - # Pragmas can have arguments that represent things besides qubits, so here we - # make sure to just look up the QubitPlaceholders. - if isinstance(arg, QubitPlaceholder): - new_args.append(qubit_mapping[arg]) - else: - new_args.append(arg) - result.append(Pragma(instr.command, new_args, instr.freeform_string)) - # Otherwise simply add it to the result - else: - result.append(instr) - new_program = program.copy() - new_program._instructions = result - + if qubit_mapping: + new_program.resolve_qubit_placeholders_with_mapping(qubit_mapping) + else: + new_program.resolve_qubit_placeholders() return new_program @@ -1140,7 +1015,6 @@ def _get_label( ) -> Tuple[Label, Dict[LabelPlaceholder, Label], int]: """Helper function to either get the appropriate label for a given placeholder or generate a new label and update the mapping. - See :py:func:`instantiate_labels` for usage. """ if placeholder in label_mapping: @@ -1156,7 +1030,6 @@ def instantiate_labels(instructions: Iterable[AbstractInstruction]) -> List[Abst """ Takes an iterable of instructions which may contain label placeholders and assigns them all defined values. - :return: list of instructions with all label placeholders assigned to real labels. """ label_i = 1 @@ -1166,7 +1039,7 @@ def instantiate_labels(instructions: Iterable[AbstractInstruction]) -> List[Abst if isinstance(instr, Jump) and isinstance(instr.target, LabelPlaceholder): new_target, label_mapping, label_i = _get_label(instr.target, label_mapping, label_i) result.append(Jump(new_target)) - elif isinstance(instr, JumpConditional) and isinstance(instr.target, LabelPlaceholder): + elif isinstance(instr, (JumpWhen, JumpUnless)) and isinstance(instr.target, LabelPlaceholder): new_target, label_mapping, label_i = _get_label(instr.target, label_mapping, label_i) cls = instr.__class__ # Make the correct subclass result.append(cls(new_target, instr.condition)) @@ -1179,207 +1052,51 @@ def instantiate_labels(instructions: Iterable[AbstractInstruction]) -> List[Abst return result -def merge_with_pauli_noise( - prog_list: Iterable[Program], probabilities: Sequence[float], qubits: Sequence[int] -) -> Program: +@deprecated( + version="4.0", + reason="The Program class now sorts instructions automatically. This function will be removed.", +) +def percolate_declares(program: Program) -> Program: """ - Insert pauli noise channels between each item in the list of programs. - This noise channel is implemented as a single noisy identity gate acting on the provided qubits. - This method does not rely on merge_programs and so avoids the inclusion of redundant Kraus - Pragmas that would occur if merge_programs was called directly on programs with distinct noisy - gate definitions. + As of pyQuil v4.0, the Program class does this automatically. This function is deprecated + and just immediately returns the passed in program. - :param prog_list: an iterable such as a program or a list of programs. - If a program is provided, a single noise gate will be applied after each gate in the - program. If a list of programs is provided, the noise gate will be applied after each - program. - :param probabilities: The 4^num_qubits list of probabilities specifying the desired pauli - channel. There should be either 4 or 16 probabilities specified in the order - I, X, Y, Z or II, IX, IY, IZ, XI, XX, XY, etc respectively. - :param qubits: a list of the qubits that the noisy gate should act on. - :return: A single program with noisy gates inserted between each element of the program list. + :param program: A program. """ - p = Program() - p.defgate("pauli_noise", np.eye(2 ** len(qubits))) - p.define_noisy_gate("pauli_noise", qubits, pauli_kraus_map(probabilities)) - for elem in prog_list: - p.inst(Program(elem)) - if isinstance(elem, Measurement): - continue # do not apply noise after measurement - p.inst(("pauli_noise", *qubits)) - return p + return program -# TODO: does this need modification? +@deprecated( + version="4.0", + reason="This function will be removed. Use Program addition instead.", +) def merge_programs(prog_list: Sequence[Program]) -> Program: """ Merges a list of pyQuil programs into a single one by appending them in sequence. - If multiple programs in the list contain the same gate and/or noisy gate definition - with identical name, this definition will only be applied once. If different definitions - with the same name appear multiple times in the program list, each will be applied once - in the order of last occurrence. - - :param prog_list: A list of pyquil programs - :return: a single pyQuil program - """ - definitions = [gate for prog in prog_list for gate in Program(prog).defined_gates] - seen: Dict[str, List[DefGate]] = {} - # Collect definitions in reverse order and reapply definitions in reverse - # collected order to ensure that the last occurrence of a definition is applied last. - for definition in reversed(definitions): - name = definition.name - if name in seen.keys(): - # Do not add truly identical definitions with the same name - # If two different definitions share a name, we include each definition so as to provide - # a waring to the user when the contradictory defgate is called. - if definition not in seen[name]: - seen[name].append(definition) - else: - seen[name] = [definition] - new_definitions = [gate for key in seen.keys() for gate in reversed(seen[key])] - - # Combine programs without gate definitions; avoid call to _synthesize by using _instructions - p = Program(*[prog._instructions for prog in prog_list]) - - for definition in new_definitions: - if isinstance(definition, DefPermutationGate): - p.inst(DefPermutationGate(definition.name, list(definition.permutation))) - else: - p.defgate(definition.name, definition.matrix, definition.parameters) - - return p - - -def get_classical_addresses_from_program(program: Program) -> Dict[str, List[int]]: """ - Returns a sorted list of classical addresses found in the MEASURE instructions in the program. - - :param program: The program from which to get the classical addresses. - :return: A mapping from memory region names to lists of offsets appearing in the program. - """ - addresses: Dict[str, List[int]] = defaultdict(list) - flattened_addresses = {} - - # Required to use the `classical_reg.address` int attribute. - # See https://github.com/rigetti/pyquil/issues/388. - for instr in program: - if isinstance(instr, Measurement) and instr.classical_reg: - addresses[instr.classical_reg.name].append(instr.classical_reg.offset) - - # flatten duplicates - for k, v in addresses.items(): - reduced_list = list(set(v)) - reduced_list.sort() - flattened_addresses[k] = reduced_list - - return flattened_addresses - - -def percolate_declares(program: Program) -> Program: - """ - Move all the DECLARE statements to the top of the program. Return a fresh object. - - :param program: Perhaps jumbled program. - :return: Program with DECLAREs all at the top and otherwise the same sorted contents. - """ - declare_program = Program() - instrs_program = Program() - - for instr in program: - if isinstance(instr, Declare): - declare_program += instr - else: - instrs_program += instr - - p = declare_program + instrs_program - p._defined_gates = program._defined_gates - - return p + merged_program = Program() + for prog in prog_list: + merged_program += prog + return merged_program +@deprecated( + version="4.0", + reason="This is now a no-op and will be removed in future versions of pyQuil.", +) def validate_protoquil(program: Program, quilt: bool = False) -> None: """ - Ensure that a program is valid ProtoQuil or Quil-T, otherwise raise a ValueError. - Protoquil is a subset of Quil which excludes control flow and classical instructions. - - :param quilt: Validate the program as Quil-T. - :param program: The Quil program to validate. - """ + This function has been deprecated. It is now a no-op. """ - Ensure that a program is valid ProtoQuil, otherwise raise a ValueError. - Protoquil is a subset of Quil which excludes control flow and classical instructions. - - :param program: The Quil program to validate. - """ - if quilt: - valid_instruction_types = tuple( - [ - Pragma, - Declare, - Halt, - Gate, - Measurement, - Reset, - ResetQubit, - DelayQubits, - DelayFrames, - Fence, - FenceAll, - ShiftFrequency, - SetFrequency, - SetScale, - ShiftPhase, - SetPhase, - SwapPhases, - Pulse, - Capture, - RawCapture, - DefCalibration, - DefFrame, - DefMeasureCalibration, - DefWaveform, - ] - ) - else: - valid_instruction_types = tuple([Pragma, Declare, Gate, Reset, ResetQubit, Measurement]) - if program.calibrations: - raise ValueError("ProtoQuil validation failed: Quil-T calibrations are not allowed.") - if program.waveforms: - raise ValueError("ProtoQuil validation failed: Quil-T waveform definitions are not allowed.") - if program.frames: - raise ValueError("ProtoQuil validation failed: Quil-T frame definitions are not allowed.") - - for instr in program.instructions: - if not isinstance(instr, valid_instruction_types): - # Instructions like MOVE, NOT, JUMP, JUMP-UNLESS will fail here - raise ValueError(f"ProtoQuil validation failed: {instr} is not allowed.") + pass +@deprecated( + version="4.0", + reason="This is now a no-op and will be removed in future versions of pyQuil.", +) def validate_supported_quil(program: Program) -> None: """ - Ensure that a program is supported Quil which can run on any QPU, otherwise raise a ValueError. - We support a global RESET before any gates, and MEASUREs on each qubit after any gates - on that qubit. PRAGMAs and DECLAREs are always allowed. - - :param program: The Quil program to validate. + This function has been deprecated. It is now a no-op. """ - gates_seen = False - measured_qubits: Set[int] = set() - for instr in program.instructions: - if isinstance(instr, Pragma) or isinstance(instr, Declare): - continue - elif isinstance(instr, Gate): - gates_seen = True - if any(q.index in measured_qubits for q in instr.qubits): - raise ValueError("Cannot apply gates to qubits that were already measured.") - elif isinstance(instr, Reset): - if gates_seen: - raise ValueError("RESET can only be applied before any gate applications.") - elif isinstance(instr, ResetQubit): - raise ValueError("Only global RESETs are currently supported.") - elif isinstance(instr, Measurement): - if instr.qubit.index in measured_qubits: - raise ValueError("Multiple measurements per qubit is not supported.") - measured_qubits.add(instr.qubit.index) - else: - raise ValueError(f"Unhandled instruction type in supported Quil validation: {instr}") + pass diff --git a/pyquil/quilatom.py b/pyquil/quilatom.py index af17cf8a8..2815185f8 100644 --- a/pyquil/quilatom.py +++ b/pyquil/quilatom.py @@ -14,26 +14,33 @@ # limitations under the License. ############################################################################## -from dataclasses import dataclass from fractions import Fraction -from numbers import Complex +from numbers import Number from typing import ( Any, Callable, ClassVar, + Dict, List, + Iterable, Mapping, NoReturn, Optional, Set, Sequence, Tuple, + Type, Union, cast, ) +from typing_extensions import Self +from deprecated.sphinx import deprecated import numpy as np +import quil.instructions as quil_rs +import quil.expression as quil_rs_expr + class QuilAtom(object): """ @@ -116,27 +123,14 @@ def __eq__(self, other: object) -> bool: class QubitPlaceholder(QuilAtom): - def out(self) -> str: - raise RuntimeError("Qubit {} has not been assigned an index".format(self)) - - @property - def index(self) -> NoReturn: - raise RuntimeError("Qubit {} has not been assigned an index".format(self)) - - def __str__(self) -> str: - return "q{}".format(id(self)) - - def __repr__(self) -> str: - return "".format(id(self)) - - def __hash__(self) -> int: - return hash(id(self)) - - def __eq__(self, other: object) -> bool: - return isinstance(other, QubitPlaceholder) and id(other) == id(self) + def __init__(self, placeholder: Optional[quil_rs.QubitPlaceholder] = None): + if placeholder is not None: + self._placeholder = placeholder + else: + self._placeholder = quil_rs.QubitPlaceholder() - @classmethod - def register(cls, n: int) -> List["QubitPlaceholder"]: + @staticmethod + def register(n: int) -> List["QubitPlaceholder"]: """Return a 'register' of ``n`` QubitPlaceholders. >>> from pyquil import Program @@ -154,12 +148,77 @@ def register(cls, n: int) -> List["QubitPlaceholder"]: :param n: The number of qubits in the register """ - return [cls() for _ in range(n)] + return [QubitPlaceholder() for _ in range(n)] + + def out(self) -> str: + raise RuntimeError("Qubit {} has not been assigned an index".format(self)) + + @property + def index(self) -> NoReturn: + raise RuntimeError("Qubit {} has not been assigned an index".format(self)) + + def __str__(self) -> str: + return f"q{id(self)}" + + def __repr__(self) -> str: + return f"q{id(self)}" + + def __hash__(self) -> int: + return hash(self._placeholder) + + def __eq__(self, other: object) -> bool: + if isinstance(other, quil_rs.QubitPlaceholder): + return self._placeholder == other + if isinstance(other, QubitPlaceholder): + return self._placeholder == other._placeholder + return False + + def __lt__(self, other: object) -> bool: + if isinstance(other, quil_rs.QubitPlaceholder): + return self._placeholder < other + if isinstance(other, QubitPlaceholder): + return self._placeholder < other._placeholder + raise TypeError(f"Comparison between LabelPlaceholder and {type(other)} is not supported.") QubitDesignator = Union[Qubit, QubitPlaceholder, FormalArgument, int] +def _convert_to_rs_qubit(qubit: Union[QubitDesignator, quil_rs.Qubit, QubitPlaceholder]) -> quil_rs.Qubit: + if isinstance(qubit, quil_rs.Qubit): + return qubit + if isinstance(qubit, Qubit): + return quil_rs.Qubit.from_fixed(qubit.index) + if isinstance(qubit, QubitPlaceholder): + return quil_rs.Qubit.from_placeholder(qubit._placeholder) + if isinstance(qubit, FormalArgument): + return quil_rs.Qubit.from_variable(qubit.name) + if isinstance(qubit, int): + return quil_rs.Qubit.from_fixed(qubit) + raise ValueError(f"{type(qubit)} is not a valid QubitDesignator") + + +def _convert_to_rs_qubits(qubits: Iterable[QubitDesignator]) -> List[quil_rs.Qubit]: + return [_convert_to_rs_qubit(qubit) for qubit in qubits] + + +def _convert_to_py_qubit(qubit: Union[QubitDesignator, quil_rs.Qubit, quil_rs.QubitPlaceholder]) -> QubitDesignator: + if isinstance(qubit, quil_rs.Qubit): + if qubit.is_fixed(): + return Qubit(qubit.to_fixed()) + if qubit.is_variable(): + return FormalArgument(qubit.to_variable()) + if qubit.is_placeholder(): + return QubitPlaceholder(placeholder=qubit.to_placeholder()) + if isinstance(qubit, (Qubit, QubitPlaceholder, FormalArgument, Parameter, int)): + return qubit + raise ValueError(f"{type(qubit)} is not a valid QubitDesignator") + + +def _convert_to_py_qubits(qubits: Iterable[Union[QubitDesignator, quil_rs.Qubit]]) -> List[QubitDesignator]: + return [_convert_to_py_qubit(qubit) for qubit in qubits] + + def unpack_qubit(qubit: Union[QubitDesignator, FormalArgument]) -> Union[Qubit, QubitPlaceholder, FormalArgument]: """ Get a qubit from an object. @@ -198,7 +257,7 @@ def qubit_index(qubit: QubitDesignator) -> int: # int. However, specifying Union[str, int] as the generic type argument to List doesn't sufficiently # constrain the types, and mypy gets confused in unpack_classical_reg, below. Hence, just specify # List[Any] here. -MemoryReferenceDesignator = Union["MemoryReference", Tuple[str, int], List[Any], str] +MemoryReferenceDesignator = Union["MemoryReference", quil_rs.MemoryReference, Tuple[str, int], List[Any], str] def unpack_classical_reg(c: MemoryReferenceDesignator) -> "MemoryReference": @@ -235,47 +294,93 @@ class Label(QuilAtom): """ def __init__(self, label_name: str): - self.name = label_name + self.target = quil_rs.Target.from_fixed(label_name) + + @staticmethod + def _from_rs_target(target: quil_rs.Target) -> "Label": + return Label(target.to_fixed()) def out(self) -> str: - return "@{name}".format(name=self.name) + return self.target.to_quil() + + @property + def name(self) -> str: + return self.target.to_fixed() + + @name.setter + def name(self, label_name: str) -> None: + self.target = quil_rs.Target.from_fixed(label_name) def __str__(self) -> str: - return "@{name}".format(name=self.name) + return self.target.to_quil_or_debug() def __repr__(self) -> str: - return "