diff --git a/.github/test_events/README.md b/.github/test_events/README.md new file mode 100644 index 00000000..d6b47603 --- /dev/null +++ b/.github/test_events/README.md @@ -0,0 +1,11 @@ +# GitHub Test Events + +This folder contains mock GitHub webhook events. They can be used as input to [nektos/act](https://github.com/nektos/act) to simulate what will happen for that event. This is useful for testing CI changes locally. + +For example, to simulate what will happen in GitHub actions run when a Python pre-release is published, you can run: + +```sh +act release -n -e .github/test_events/prerelease_python.json +``` + +These payloads are adapted from GitHub's examples in [Events that trigger workflows](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows). diff --git a/.github/test_events/python_prerelease.json b/.github/test_events/python_prerelease.json new file mode 100644 index 00000000..54cf8e81 --- /dev/null +++ b/.github/test_events/python_prerelease.json @@ -0,0 +1,78 @@ +{ + "action": "published", + "release": { + "url": "https://api.github.com/repos/octocat/Hello-World/releases/1", + "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.0", + "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/1/assets", + "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}", + "tarball_url": "https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0", + "zipball_url": "https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0", + "discussion_url": "https://github.com/octocat/Hello-World/discussions/90", + "id": 1, + "node_id": "MDc6UmVsZWFzZTE=", + "tag_name": "quil-py/v1.0.0", + "target_commitish": "master", + "name": "v1.0.0", + "body": "Description of the release", + "draft": false, + "prerelease": true, + "created_at": "2013-02-27T19:35:32Z", + "published_at": "2013-02-27T19:35:32Z", + "author": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "assets": [ + { + "url": "https://api.github.com/repos/octocat/Hello-World/releases/assets/1", + "browser_download_url": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/example.zip", + "id": 1, + "node_id": "MDEyOlJlbGVhc2VBc3NldDE=", + "name": "example.zip", + "label": "short description", + "state": "uploaded", + "content_type": "application/zip", + "size": 1024, + "download_count": 42, + "created_at": "2013-02-27T19:35:32Z", + "updated_at": "2013-02-27T19:35:32Z", + "uploader": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + } + } + ] + } +} diff --git a/.github/test_events/python_release.json b/.github/test_events/python_release.json new file mode 100644 index 00000000..365abd3c --- /dev/null +++ b/.github/test_events/python_release.json @@ -0,0 +1,78 @@ +{ + "action": "published", + "release": { + "url": "https://api.github.com/repos/octocat/Hello-World/releases/1", + "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.0", + "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/1/assets", + "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}", + "tarball_url": "https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0", + "zipball_url": "https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0", + "discussion_url": "https://github.com/octocat/Hello-World/discussions/90", + "id": 1, + "node_id": "MDc6UmVsZWFzZTE=", + "tag_name": "quil-py/v1.0.0", + "target_commitish": "master", + "name": "v1.0.0", + "body": "Description of the release", + "draft": false, + "prerelease": false, + "created_at": "2013-02-27T19:35:32Z", + "published_at": "2013-02-27T19:35:32Z", + "author": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "assets": [ + { + "url": "https://api.github.com/repos/octocat/Hello-World/releases/assets/1", + "browser_download_url": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/example.zip", + "id": 1, + "node_id": "MDEyOlJlbGVhc2VBc3NldDE=", + "name": "example.zip", + "label": "short description", + "state": "uploaded", + "content_type": "application/zip", + "size": 1024, + "download_count": 42, + "created_at": "2013-02-27T19:35:32Z", + "updated_at": "2013-02-27T19:35:32Z", + "uploader": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + } + } + ] + } +} diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 9bde6d6b..f3b76737 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -16,6 +16,13 @@ jobs: - stable steps: + # Some of the snapshots have long file paths, so we need to enable long file paths on Windows where the limit is 260 by default + - name: Allow Long File Paths + if: matrix.os == 'windows-latest' + run: | + reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1 /f + # https://github.com/actions/checkout/issues/1285#issuecomment-2042579471 + git config --system core.longpaths true - name: Checkout sources uses: actions/checkout@v2 with: @@ -41,7 +48,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: bench - args: --bench parser -- --warm-up-time=2 --measurement-time=3 --sample-size=500 --color=always + args: --bench parser --bench simplification -- --warm-up-time=2 --measurement-time=3 --sample-size=500 --color=always - name: Archive benchmark results if: ${{ github.ref == 'refs/heads/main'}} @@ -49,4 +56,4 @@ jobs: with: name: benchmarks-${{ matrix.os }} path: target/criterion - \ No newline at end of file + diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml index 9f667259..effc84d9 100644 --- a/.github/workflows/msrv.yml +++ b/.github/workflows/msrv.yml @@ -53,6 +53,23 @@ jobs: command: test args: --all-features + check-py: + name: Check quil-py + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + steps: + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - uses: davidB/rust-cargo-make@v1 + - uses: actions/checkout@v1 + - uses: snok/install-poetry@v1 + - name: Run quil-py tests, lints, and formatting checks. + run: cargo make --cwd quil-py + fmt: name: Rustfmt runs-on: ubuntu-latest @@ -103,7 +120,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: -- -D warnings + args: --all-targets --all-features -- -D warnings deny: name: Deny diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml deleted file mode 100644 index 0a1a0121..00000000 --- a/.github/workflows/prepare-release.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Prepare Release - -on: - push: - branches: - - main - workflow_dispatch: - inputs: - type: - description: Bump versions and trigger a new release. - required: true - default: release - options: - - release - - prerelease - -jobs: - prepare-release: - runs-on: ubuntu-latest - env: - GITHUB_TOKEN: ${{ secrets.PAT }} - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - token: ${{ secrets.PAT }} - - name: Install Knope - uses: knope-dev/action@v1 - with: - version: 0.7.1 # Test before updating, breaking changes likely: https://github.com/knope-dev/action#install-latest-version - - run: | - git config --global user.name "${{ github.triggering_actor }}" - git config --global user.email "${{ github.triggering_actor}}@users.noreply.github.com" - - name: Prepare Prerelease - run: knope prerelease - if: github.event_name == 'push' - - name: Prepare Release - run: knope ${{ inputs.type }} - if: github.event_name == 'workflow_dispatch' diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml new file mode 100644 index 00000000..989f9418 --- /dev/null +++ b/.github/workflows/publish-docs.yml @@ -0,0 +1,47 @@ +name: Publish quil-py documentation + +on: + push: + branches: + - main + pull_request: + types: + - opened + - reopened + - synchronize + - closed + +jobs: + publish-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install poetry + uses: snok/install-poetry@v1 + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-make + uses: actions-rs/cargo@v1 + with: + command: install + args: --debug cargo-make + - name: Build quil-py documentation + uses: actions-rs/cargo@v1 + with: + command: make + args: --cwd quil-py --makefile Makefile.toml docs + - name: Deploy preview + if: ${{ github.event_name == 'pull_request' }} + uses: rossjrw/pr-preview-action@v1 + with: + source-dir: quil-py/build/docs + preview-branch: quil-py-docs + - name: Deploy docs + if: ${{ github.event_name == 'push' }} + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: quil-py/build/docs + publish_branch: quil-py-docs diff --git a/.github/workflows/publish-quil-py.yml b/.github/workflows/publish-quil-py.yml new file mode 100644 index 00000000..809a7a25 --- /dev/null +++ b/.github/workflows/publish-quil-py.yml @@ -0,0 +1,194 @@ +name: Publish quil-py + +on: + release: + types: [published] + workflow_dispatch: + description: "Manually publish release" + inputs: + publishWheels: + description: "Build and publish wheels to PyPI" + type: boolean + default: false + +jobs: + is-python-release: + if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'quil-py/v') }} + runs-on: ubuntu-latest + steps: + - run: echo "Release tag starts with quil-py/v, proceeding with release" + should-publish-wheels: + if: (github.event_name == 'workflow_dispatch' && inputs.publishWheels) || (github.event_name == 'release' && !github.event.release.prerelease) + runs-on: ubuntu-latest + steps: + - run: echo "Publishing wheels" + + macos: + runs-on: macos-12 + needs: [ is-python-release, should-publish-wheels ] + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: aarch64-apple-darwin + profile: minimal + default: true + - name: Build wheels - universal2 + # universal2 supports both x86_64 and aarch64 + uses: messense/maturin-action@v1 + with: + args: -i python --release --target universal2-apple-darwin --manifest-path quil-py/Cargo.toml --out dist + - name: Install wheel + run: | + pip install quil --no-index --find-links dist --force-reinstall + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + linux: + runs-on: ubuntu-22.04 + needs: [ is-python-release, should-publish-wheels ] + env: + CXXFLAGS: "-std=c++11" + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + target: [x86_64, aarch64, ppc64le] + steps: + - uses: actions/checkout@v2 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + default: true + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Build wheels + uses: messense/maturin-action@v1 + with: + target: ${{ matrix.target }} + manylinux: 2014 + args: -i ${{ matrix.python-version }} --release --manifest-path quil-py/Cargo.toml --out dist + - name: Install wheel + if: ${{ matrix.target == 'x86_64' }} # pip can only install wheels for it's own architecture + run: | + pip install quil --no-index --find-links dist --force-reinstall + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + windows: + runs-on: windows-latest + needs: [ is-python-release, should-publish-wheels ] + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + target: [x64] + steps: + # Some of the snapshots have long file paths, so we need to enable long file paths on Windows where the limit is 260 by default + - name: Allow Long File Paths + run: | + reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1 /f + # https://github.com/actions/checkout/issues/1285#issuecomment-2042579471 + git config --system core.longpaths true + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + default: true + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + architecture: ${{ matrix.target }} + - name: Build wheels + uses: messense/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: -i python --release --manifest-path quil-py/Cargo.toml --out dist + - name: Install built wheel + run: | + pip install quil --find-links dist --force-reinstall --no-deps --no-index + - name: Upload wheels + uses: actions/upload-artifact@v2 + with: + name: wheels + path: dist + + sdist: + runs-on: ubuntu-latest + needs: is-python-release + env: + CXXFLAGS: "-std=c++11" + steps: + - uses: actions/checkout@v2 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + default: true + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Build sdist + uses: messense/maturin-action@v1 + with: + command: sdist + args: --manifest-path quil-py/Cargo.toml --out dist + - name: Upload sdist + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + publish-python-package: + name: Release + runs-on: ubuntu-latest + # `needs` forces this job to wait until all specified jobs + # are finished to run. Typically, those jobs would all have + # to be successful, but when combined with `if: always()`, + # this job is allowed to run after all the needed jobs + # finish, regardless of their outcome. In this case, we + # still make sure that at least a source distribution + # can be published. + needs: [ macos, linux, windows, sdist ] + if: always() && needs.sdist.result == 'success' + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v3 + - name: Publish to PyPi + uses: messense/maturin-action@v1 + with: + command: upload + args: --skip-existing wheels/* + + publish-rust-crate: + name: Release + runs-on: ubuntu-latest + needs: publish-python-package + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + token: ${{ secrets.PAT }} + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - run: cargo publish --manifest-path=quil-py/Cargo.toml --token ${{ secrets.CRATES_IO_TOKEN }} diff --git a/.github/workflows/publish-quil-rs.yml b/.github/workflows/publish-quil-rs.yml new file mode 100644 index 00000000..95a6db25 --- /dev/null +++ b/.github/workflows/publish-quil-rs.yml @@ -0,0 +1,28 @@ +name: Publish quil-rs + +on: + release: + types: [published] + workflow_dispatch: + description: "Manually publish release" + +jobs: + is-quil-rs-release: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || startswith(github.event.release.tag_name, 'quil-rs/v') }} + steps: + - run: echo "release tag starts with quil-rs/v, proceeding with release" + + release: + runs-on: ubuntu-latest + needs: is-quil-rs-release + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + token: ${{ secrets.PAT }} + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - run: cargo publish --manifest-path=quil-rs/Cargo.toml --token ${{ secrets.CRATES_IO_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2b9ce2e7..14cefc78 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,21 +1,39 @@ name: Release on: - release: - types: [published] - + push: + branches: + - main + workflow_dispatch: + inputs: + type: + description: Bump versions and trigger a new release. + required: true + default: release + options: + - release + - prerelease + jobs: release: runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.PAT }} steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - token: ${{ secrets.PAT }} - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - uses: katyo/publish-crates@v1 - with: - registry-token: ${{ secrets.CRATES_IO_TOKEN }} + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + token: ${{ secrets.PAT }} + - name: Install Knope + uses: knope-dev/action@v1 + with: + version: 0.7.1 # Test before updating, breaking changes likely: https://github.com/knope-dev/action#install-latest-version + - run: | + git config --global user.name "${{ github.triggering_actor }}" + git config --global user.email "${{ github.triggering_actor}}@users.noreply.github.com" + - name: Prepare Prerelease + run: knope release --prerelease-label=rc + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.type == 'prerelease') + - name: Prepare Release + run: knope release + if: github.event_name == 'workflow_dispatch' && inputs.type == 'release' diff --git a/.gitignore b/.gitignore index 224fdc61..d91fab29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,22 @@ /target node_modules -Cargo.lock +.venv +.DS_Store -# JetBrains Editors -.idea/ \ No newline at end of file +# unreviewed insta snapshots +*.snap.new + +# IDEs and editors +.idea/ +.vscode/ + +# Python artifacts +*.so +__pycache__ +**./.null-ls-cache* + +# quil-py documentation +quil-py/build + +# unversioned developer notes +scratch/ diff --git a/.gitmodules b/.gitmodules index 4769fdae..c9d91faf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "benches/quilc"] - path = benches/quilc +[submodule "quil-rs/benches/quilc"] + path = quil-rs/benches/quilc url = https://github.com/quil-lang/quilc.git diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 31b91c92..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,621 +0,0 @@ -## 0.16.0-rc.1 - -### Breaking Changes - -- empty commit so knope calculates current version - -### Fixes - -- allow for variable qubit in DEFCAL MEASURE -- correctly expand delays (#142) - -## 0.10.0-rc.2 - -### Breaking Changes - -- empty commit to force version bump -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- RESET frame computation -- Program.into_simplified -- Support Expression arithmetic operations (#126) -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- allow for variable qubit in DEFCAL MEASURE -- correctly expand delays (#142) -- update snapshots -- Waveforms w/o params need no parens -- support escaped double quotes and backslashes in strings (#120) -- make dynamic error Sync as well (#131) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.10.0-rc.1 - -### Breaking Changes - -- empty commit to force version bump -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- RESET frame computation -- Program.into_simplified -- Support Expression arithmetic operations (#126) -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- allow for variable qubit in DEFCAL MEASURE -- correctly expand delays (#142) -- update snapshots -- Waveforms w/o params need no parens -- support escaped double quotes and backslashes in strings (#120) -- make dynamic error Sync as well (#131) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.10.0-rc.0 - -### Breaking Changes - -- empty commit to force version bump -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- RESET frame computation -- Program.into_simplified -- Support Expression arithmetic operations (#126) -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- allow for variable qubit in DEFCAL MEASURE -- correctly expand delays (#142) -- update snapshots -- Waveforms w/o params need no parens -- support escaped double quotes and backslashes in strings (#120) -- make dynamic error Sync as well (#131) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.16.0-rc.0 - -### Breaking Changes - -- empty commit to force version bump -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- RESET frame computation -- Program.into_simplified -- Support Expression arithmetic operations (#126) -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- correctly expand delays (#142) -- update snapshots -- Waveforms w/o params need no parens -- support escaped double quotes and backslashes in strings (#120) -- make dynamic error Sync as well (#131) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.15.0 - -### Breaking Changes - -- empty commit to force version bump - -## 0.10.0 - -### Breaking Changes - -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- RESET frame computation -- Program.into_simplified -- Support Expression arithmetic operations (#126) -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- update snapshots -- Waveforms w/o params need no parens -- support escaped double quotes and backslashes in strings (#120) -- make dynamic error Sync as well (#131) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.10.0-rc.2 - -### Breaking Changes - -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- RESET frame computation -- Program.into_simplified -- Support Expression arithmetic operations (#126) -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- update snapshots -- Waveforms w/o params need no parens -- support escaped double quotes and backslashes in strings (#120) -- make dynamic error Sync as well (#131) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.10.0-rc.1 - -### Breaking Changes - -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- RESET frame computation -- Program.into_simplified -- Support Expression arithmetic operations (#126) -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- support escaped double quotes and backslashes in strings (#120) -- make dynamic error Sync as well (#131) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.10.0-rc.0 - -### Breaking Changes - -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- Support Expression arithmetic operations (#126) -- Program.into_simplified -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- make dynamic error Sync as well (#131) -- support escaped double quotes and backslashes in strings (#120) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.15.0-rc.0 - -### Breaking Changes - -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- Support Expression arithmetic operations (#126) -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- make dynamic error Sync as well (#131) -- support escaped double quotes and backslashes in strings (#120) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.14.2 - -### Features - -- Support Expression arithmetic operations (#126) - -### Fixes - -- make dynamic error Sync as well (#131) -- support escaped double quotes and backslashes in strings (#120) - -## 0.14.2-rc.0 - -### Features - -- Support Expression arithmetic operations (#126) - -### Fixes - -- make dynamic error Sync as well (#131) -- support escaped double quotes and backslashes in strings (#120) - -## 0.10.0-rc.1 - -### Breaking Changes - -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- Support Expression arithmetic operations (#126) -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- make dynamic error Sync as well (#131) -- support escaped double quotes and backslashes in strings (#120) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.10.0-rc.0 - -### Breaking Changes - -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- make dynamic error Sync as well (#131) -- support escaped double quotes and backslashes in strings (#120) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.10.0-rc.1 - -### Breaking Changes - -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- make dynamic error Sync as well (#131) -- support escaped double quotes and backslashes in strings (#120) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.10.0-rc.0 - -### Breaking Changes - -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- make dynamic error Sync as well (#131) -- support escaped double quotes and backslashes in strings (#120) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.11.0-rc.0 - -### Breaking Changes - -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- support INCLUDE -- support CONVERT -- support NOP -- impl FromStr for MemoryReference - -### Fixes - -- support escaped double quotes and backslashes in strings (#120) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.10.0 - -### Breaking Changes - -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- impl FromStr for MemoryReference - -### Fixes - -- support escaped double quotes and backslashes in strings (#120) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) - -## 0.15.0-rc.0 - -### Breaking Changes - -- genericize parsing errors and remove error Strings -- fix all compilation errors from error refactor - -### Features - -- impl FromStr for MemoryReference - -### Fixes - -- support escaped double quotes and backslashes in strings (#120) -- fix performance regression (#113) -- do not get line/column info for tokens except on error -- require dynamic error to by Send (#108) -- bump thiserror version and update import name (#103) -- identifier parser (#100) -- test cases with rstest -- test cases should not violate the spec -- remove a `dbg!` statement left over from #88 -- use structured error -- update node version and dependencies for semantic-release (#84) -- update semantic-release version as per dependabot suggestion (#83) -- DEFCAL MEASURE serialization -- test roundtrip of program->string->program -- linting -- Instruction used/blocked frames calculation (#74) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..0c3d5da7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,67 @@ +# Contributing to quil-rs and quil-py + +Welcome to the `quil-rs` project, and thanks for contributing! + +This guide is to help walk you through contributing in different ways to `quil-rs` and `quil-py`, +as well as share some general how-tos for development, testing, and maintenance. + +## Table of Contents + +[Developer Guidelines](#developer-guidelines) + +- [Instruction Module](#instruction-module) + +[Tips for Maintainers](#tips-for-maintainers) + +- [Makefile Tasks](#makefiles-tasks) + +## Developer Guidelines + +### Instruction Module + +The `instruction` module is large, so we follow a few guidelines when adding or changing instructions to help keep +them organized and consistent in both the `quil-rs` and `quil-py` crates. + +All instruction definitions live inside of the `instruction` module. For ease of development, +instruction types are grouped into submodules so that similar instructions can be modified and viewed together +rather than having all instructions in one large file. However, to avoid imposing potentially arbritrary +instruction categories onto the user, the `instruction` module publically re-exports all of its types from +its root. + +This module structure should remain consistent between both `quil-rs` and `quil-py`. For example, the file +`src/instruction/gate.rs` exists in both `quil-rs` and `quil-py` and contains the same set of instruction +definitions (for their respective target language, of course). + +In short, an instruction should: + 1. Be organized in the equivalent `instruction` submodule for both `quil-rs` and `quil-py` crates + 2. Only be re-exported publicly from the root of the `Instruction` module + +## Tips for Maintainers + +### Makefile Tasks + +This repo uses [cargo-make](https://github.com/sagiegurari/cargo-make) to define task flows that perform +common operations. We've extended the default flow with tasks specific to the `quil-rs` and `quil-py` +crates. You can run the default task with: + +```sh +cargo make +# or +makers +``` + +It's worth periodically running this command as you develop to make sure there are no unexpected failures. + +Check the [`cargo-make documentation`](https://github.com/sagiegurari/cargo-make#predefined-makefiles) for +more information on the predefined tasks available. The custom flows we've added are described below: + +#### `stubtest-flow` + +The `quil-py` crate defines `stubtest-flow`. It builds and installs the Python package, then runs +[stubtest](https://mypy.readthedocs.io/en/stable/stubtest.html) to check that the manually written type hints +are consistent with what the package exports. If any errors are reported, they should be fixed so users +of the Python package are given accurate type hints by their tooling. + +### `pytest-flow` + +The `quil-py` crate also defines `pytest-flow`. It builds and installs the Python package, then runs a test suite against the package using [pytest](https://docs.pytest.org/en/7.2.x/). These tests are used to validate functionality of the `quil` package at the Python level and should always pass. diff --git a/Cargo.lock b/Cargo.lock index da024494..ec21256a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -18,21 +18,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] -name = "atty" -version = "0.2.14" +name = "anstream" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ - "hermit-abi", - "libc", - "winapi", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] [[package]] -name = "autocfg" +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-complex", + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bit-set" @@ -51,27 +105,27 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytecount" -version = "0.6.3" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] -name = "byteorder" -version = "1.4.3" +name = "bytemuck" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "cast" @@ -87,9 +141,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ciborium" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -98,15 +152,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -114,53 +168,77 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ - "bitflags", + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +dependencies = [ + "anstream", + "anstyle", "clap_lex", - "indexmap", - "textwrap", + "strsim", ] [[package]] -name = "clap_lex" -version = "0.2.4" +name = "clap_derive" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ - "os_str_bytes", + "heck 0.5.0", + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.68", ] +[[package]] +name = "clap_lex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "console" -version = "0.15.2" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", - "terminal_size", - "winapi", + "windows-sys", ] [[package]] name = "criterion" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", - "atty", "cast", "ciborium", "clap", "criterion-plot", - "itertools", - "lazy_static", + "is-terminal", + "itertools 0.10.5", "num-traits", + "once_cell", "oorandom", "plotters", "rayon", @@ -179,63 +257,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if", - "crossbeam-utils", + "itertools 0.10.5", ] [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.11" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "deranged" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ - "cfg-if", + "powerfmt", ] [[package]] name = "dot-writer" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1defb949b123415131ecae40ab5dca70e9f39eedda3de2cebb03a8a98fdf5894" +checksum = "3d1b11bd5e7e98406c6ff39fbc94d6e910a489b978ce7f17c19fce91a1195b7a" [[package]] name = "either" -version = "1.8.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "encode_unicode" @@ -244,14 +319,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] -name = "fastrand" -version = "1.8.0" +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "instant", + "libc", + "windows-sys", ] +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + [[package]] name = "fixedbitset" version = "0.4.2" @@ -266,9 +354,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -281,9 +369,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -291,15 +379,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -308,44 +396,44 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2 1.0.47", - "quote 1.0.21", - "syn 1.0.103", + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.68", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -361,74 +449,106 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "half" -version = "1.8.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "indexmap" -version = "1.9.1" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ - "autocfg", + "equivalent", "hashbrown", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "insta" -version = "1.21.0" +version = "1.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581d4e3314cae4536e5d22ffd23189d4a374696c5ef733eadafae0ed273fd303" +checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" dependencies = [ "console", "lazy_static", "linked-hash-map", "similar", - "yaml-rust", ] [[package]] -name = "instant" -version = "0.1.12" +name = "inventory" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" + +[[package]] +name = "is-terminal" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "cfg-if", + "hermit-abi", + "libc", + "windows-sys", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.10.5" @@ -438,26 +558,35 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lexical" @@ -534,9 +663,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.137" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "linked-hash-map" @@ -544,26 +679,49 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" -version = "0.4.17" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "matrixmultiply" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" dependencies = [ - "cfg-if", + "autocfg", + "rawpointer", ] [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" -version = "0.6.5" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -574,11 +732,54 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "nalgebra" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d506eb7e08d6329505faa8a3a00a5dcc6de9f76e0c77e4b75763ae3c770831ff" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "rand", + "rand_distr", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 1.0.109", +] + +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -586,9 +787,9 @@ dependencies = [ [[package]] name = "nom_locate" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37794436ca3029a3089e0b95d42da1f0b565ad271e4d3bb4bad0c7bb70b10605" +checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3" dependencies = [ "bytecount", "memchr", @@ -597,37 +798,68 @@ dependencies = [ [[package]] name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ + "num-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] -name = "num_cpus" -version = "1.13.1" +name = "numpy" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "bef41cbb417ea83b30525259e30ccef6af39b31c240bda578889494c5392d331" dependencies = [ - "hermit-abi", "libc", + "ndarray", + "num-complex", + "num-integer", + "num-traits", + "pyo3", + "rustc-hash", ] [[package]] name = "once_cell" -version = "1.17.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" @@ -636,16 +868,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] -name = "os_str_bytes" -version = "6.3.0" +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap", @@ -653,9 +908,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -665,9 +920,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "plotters" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" dependencies = [ "num-traits", "plotters-backend", @@ -678,24 +933,36 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" [[package]] name = "plotters-svg" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" dependencies = [ "plotters-backend", ] +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" @@ -708,31 +975,31 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.0.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", + "bit-vec", "bitflags", - "byteorder", "lazy_static", "num-traits", - "quick-error 2.0.1", "rand", "rand_chacha", "rand_xorshift", "regex-syntax", "rusty-fork", "tempfile", + "unarray", ] [[package]] @@ -746,6 +1013,72 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "pyo3" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" +dependencies = [ + "cfg-if", + "indexmap", + "indoc", + "inventory", + "libc", + "memoffset", + "num-complex", + "parking_lot", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" +dependencies = [ + "proc-macro2 1.0.86", + "pyo3-macros-backend", + "quote 1.0.36", + "syn 2.0.68", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" +dependencies = [ + "heck 0.4.1", + "proc-macro2 1.0.86", + "pyo3-build-config", + "quote 1.0.36", + "syn 2.0.68", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -753,20 +1086,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +name = "quil-cli" +version = "0.3.1" +dependencies = [ + "anyhow", + "clap", + "quil-rs", +] + +[[package]] +name = "quil-py" +version = "0.10.1" +dependencies = [ + "indexmap", + "ndarray", + "numpy", + "pyo3", + "pyo3-build-config", + "quil-rs", + "rigetti-pyo3", + "strum", +] [[package]] name = "quil-rs" -version = "0.16.0-rc.1" +version = "0.26.1" dependencies = [ + "approx", + "clap", "criterion", "dot-writer", "indexmap", "insta", + "itertools 0.12.1", "lexical", + "ndarray", "nom", "nom_locate", "num-complex", @@ -774,9 +1128,12 @@ dependencies = [ "petgraph", "proptest", "proptest-derive", + "rand", + "rasciigraph", "regex", "rstest", "serde", + "statrs", "strum", "thiserror", ] @@ -792,11 +1149,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.21" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "proc-macro2 1.0.47", + "proc-macro2 1.0.86", ] [[package]] @@ -829,6 +1186,16 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "rand_xorshift" version = "0.3.0" @@ -838,44 +1205,64 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rasciigraph" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a3273d76a0cdbb09ecead2f6007d6d169211f3c9c6dd30cd8a2e8175adcb74" + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" -version = "1.5.3" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.7.2" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -884,24 +1271,35 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rigetti-pyo3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "c59068763670399bb3e895a17cc631d9214c3172b64fc74d3ab63dac7371c340" dependencies = [ - "winapi", + "indexmap", + "num-complex", + "num-traits", + "paste", + "pyo3", + "time", ] [[package]] name = "rstest" -version = "0.15.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c9dc66cc29792b663ffb5269be669f1613664e69ad56441fdb895c2347b930" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" dependencies = [ "futures", "futures-timer", @@ -911,17 +1309,27 @@ dependencies = [ [[package]] name = "rstest_macros" -version = "0.14.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5015e68a0685a95ade3eee617ff7101ab6a3fc689203101ca16ebc16f2b89c66" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" dependencies = [ "cfg-if", - "proc-macro2 1.0.47", - "quote 1.0.21", + "glob", + "proc-macro2 1.0.86", + "quote 1.0.36", + "regex", + "relative-path", "rustc_version", - "syn 1.0.103", + "syn 2.0.68", + "unicode-ident", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -931,11 +1339,24 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rusty-fork" @@ -944,16 +1365,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", - "quick-error 1.2.3", + "quick-error", "tempfile", "wait-timeout", ] [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "safe_arch" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +dependencies = [ + "bytemuck", +] [[package]] name = "same-file" @@ -966,88 +1396,126 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.14" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.147" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ - "proc-macro2 1.0.47", - "quote 1.0.21", - "syn 1.0.103", + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.68", ] [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "simba" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b7840f121a46d63066ee7a99fc81dcabbc6105e437cae43528cea199b5a05f" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "similar" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "statrs" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35a062dbadac17a42e0fc64c27f419b25d6fae98572eb43c8814c9e873d7721" +dependencies = [ + "approx", + "lazy_static", + "nalgebra", + "num-traits", + "rand", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" -version = "0.24.1" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck", - "proc-macro2 1.0.47", - "quote 1.0.21", + "heck 0.5.0", + "proc-macro2 1.0.86", + "quote 1.0.36", "rustversion", - "syn 1.0.103", + "syn 2.0.68", ] [[package]] @@ -1063,65 +1531,83 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.103" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.47", - "quote 1.0.21", + "proc-macro2 1.0.86", + "quote 1.0.36", "unicode-ident", ] [[package]] -name = "tempfile" -version = "3.3.0" +name = "syn" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "proc-macro2 1.0.86", + "quote 1.0.36", + "unicode-ident", ] [[package]] -name = "terminal_size" -version = "0.1.17" +name = "target-lexicon" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] -name = "textwrap" -version = "0.16.0" +name = "tempfile" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys", +] [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.68", +] + +[[package]] +name = "time" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ - "proc-macro2 1.0.47", - "quote 1.0.21", - "syn 1.0.103", + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", ] +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + [[package]] name = "tinytemplate" version = "1.2.1" @@ -1132,11 +1618,23 @@ dependencies = [ "serde_json", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-xid" @@ -1144,6 +1642,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -1155,12 +1665,11 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -1172,9 +1681,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1182,94 +1691,146 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.47", - "quote 1.0.21", - "syn 1.0.103", + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.68", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ - "quote 1.0.21", + "quote 1.0.36", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ - "proc-macro2 1.0.47", - "quote 1.0.21", - "syn 1.0.103", + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.68", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "winapi" -version = "0.3.9" +name = "wide" +version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "8a040b111774ab63a19ef46bbc149398ab372b4ccdcfd719e9814dbd7dfd76c8" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "bytemuck", + "safe_arch", ] -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] [[package]] -name = "yaml-rust" -version = "0.4.5" +name = "windows-targets" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "linked-hash-map", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml index 4cd3cedf..85969111 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,42 +1,12 @@ -[package] -name = "quil-rs" -description = "Rust tooling for Quil (Quantum Instruction Language)" -version ="0.16.0-rc.1" -edition = "2021" -license = "Apache-2.0" -repository = "https://github.com/rigetti/quil-rust" -keywords = ["Quil", "Quantum", "Rigetti"] -categories = ["parser-implementations", "science", "compilers", "emulators"] - -[dependencies] -dot-writer = { version = "0.1.2", optional = true } -indexmap = "1.6.1" -lexical = "6.1.1" -nom = "7.1.1" -nom_locate = "4.0.0" -num-complex = "0.4.0" -once_cell = "1.17.1" -petgraph = "0.6.2" -regex = "1.7.2" -serde = { version = "1.0.125", features = ["derive"] } -strum = { version = "0.24.1", features = ["derive"] } -thiserror = "1.0.37" - -[dev-dependencies] -criterion = { version = "0.4.0", features = ["html_reports"] } -insta = "1.7.1" -proptest = "1.0.0" -proptest-derive = "0.3.0" -rstest = "0.15.0" - -[features] -graphviz-dot = ["dot-writer"] -latex = [] +[workspace] +members = ["quil-rs", "quil-py", "quil-cli"] +resolver = "2" [profile.release] lto = true codegen-units = 1 -[[bench]] -name = "parser" -harness = false +[workspace.dependencies] +ndarray = { version = "0.15.6", features = ["approx-0_5"] } +strum = { version = "0.26.0", features = ["derive"] } +indexmap = "2.2.6" diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 00000000..4ae4d011 --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,3 @@ +[tasks.sync-versions] +workspace = false +script = "sh ./scripts/sync_versions.sh" diff --git a/README.md b/README.md index f503b71b..b26ace68 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,10 @@ It serves three purposes: It should be considered unstable until the release of v1.0. - ## Testing When testing this crate, you should run with the `--all-features` flag to ensure all tests are executed. ```sh cargo test --all-features -``` \ No newline at end of file +``` diff --git a/benches/parser.rs b/benches/parser.rs deleted file mode 100644 index c966b1ca..00000000 --- a/benches/parser.rs +++ /dev/null @@ -1,84 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion}; -use std::{fs, path::Path, process::Command, str::FromStr}; - -fn benchmark_sample_calibration(c: &mut Criterion) { - let input = fs::read_to_string(concat!( - env!("CARGO_MANIFEST_DIR"), - "/benches/sample-calibrations.quil" - )) - .expect("benches/sample-calibrations.quil should exist"); - - let mut group = c.benchmark_group("calibration file"); - group.sample_size(100); - group.bench_function("Sample calibration file", |b| { - b.iter(|| { - let _ = quil_rs::Program::from_str(&input); - }) - }); - group.finish(); -} - -fn benchmark_quil_corpus(c: &mut Criterion) { - from_corpus().iter().for_each(|cfg| { - c.bench_function(&cfg.name, |b| { - b.iter(|| { - let _ = quil_rs::Program::from_str(&cfg.program); - }) - }); - }) -} - -struct QuilBenchConfig { - name: String, - program: String, -} - -fn from_corpus() -> Vec { - const PATH_SRC: &str = "benches/quilc/tests/good-test-files"; - - // collect valid quil programs - let mut programs = vec![]; - let corpus_dir = Path::new(PATH_SRC); - if !corpus_dir.exists() { - init_submodules() - } - - let dir = fs::read_dir(corpus_dir).expect("failed to locate quil corpus directory"); - - dir.filter_map(Result::ok) - .filter(|entry| { - entry - .metadata() - .expect("failed to read file metadata") - .is_file() - }) - .for_each(|entry| { - let program = - fs::read_to_string(entry.path()).expect("failed to read quil program file"); - let name = entry - .file_name() - .to_str() - .expect("bad filename") - .to_string(); - - // attempt to parse the quil once, ignoring unparsable input (only benchmark parsable code) - if quil_rs::Program::from_str(&program).is_ok() { - programs.push(QuilBenchConfig { name, program }); - } - }); - - programs -} - -// in the event someone wants to run the benchmarks locally, this will download the corpus of quil used -fn init_submodules() { - Command::new("git") - .args(["submodule", "update", "--init", "--recursive"]) - .spawn() - .expect("failed to spawn git process") - .wait_with_output() - .expect("failed to init submodules, verify `git` is installed"); -} - -criterion_group!(benches, benchmark_sample_calibration, benchmark_quil_corpus); -criterion_main!(benches); diff --git a/benches/quilc b/benches/quilc deleted file mode 160000 index a2925c5d..00000000 --- a/benches/quilc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a2925c5dd1764534ef8ea4e66b051cc43cecea30 diff --git a/deny.toml b/deny.toml index e89660a5..ec107f0b 100644 --- a/deny.toml +++ b/deny.toml @@ -19,13 +19,13 @@ vulnerability = "deny" # The lint level for unmaintained crates unmaintained = "deny" # The lint level for crates that have been yanked from their source registry -yanked = "deny" +yanked = "warn" # The lint level for crates with security notices. notice = "deny" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ - "RUSTSEC-2023-0018" # remove_dir_all is used by proptest, a dev dependency + "RUSTSEC-2024-0320" # yaml-rust is unmaintained, dependency of insta ] # This section is considered when running `cargo deny check licenses` @@ -33,7 +33,14 @@ ignore = [ # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] unlicensed = "deny" -allow = ["Apache-2.0", "Apache-2.0 WITH LLVM-exception", "BSD-2-Clause", "BSD-3-Clause", "MIT", "Unicode-DFS-2016"] +allow = [ + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "MIT", + "Unicode-DFS-2016", +] # List of explictly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. @@ -71,12 +78,16 @@ multiple-versions = "deny" wildcards = "deny" highlight = "all" skip-tree = [ - { name = "syn", version = "*", depth = 5 }, # Used in both serde_derive and proptest-derive - { name = "quick-error", version = "*" }, # proptest relies on two versions of this - { name = "itoa", version = "*" }, # various dependencies rely on two versions of this - { name = "hermit-abi", version = "*" }, # various dependencies rely on two versions of this - { name = "memoffset", version = "*" }, # various dependencies rely on two versions of this - { name = "windows-sys", version = "*" }, # various dependencies rely on two versions of this + { name = "syn", version = "*", depth = 5 }, # Used in both serde_derive and proptest-derive + { name = "quick-error", version = "*" }, # proptest relies on two versions of this + { name = "itoa", version = "*" }, # various dependencies rely on two versions of this + { name = "hermit-abi", version = "*" }, # various dependencies rely on two versions of this + { name = "memoffset", version = "*" }, # various dependencies rely on two versions of this + { name = "windows-sys", version = "*" }, # various dependencies rely on two versions of this + { name = "regex-syntax", version = "*" }, # proptest and criterion rely on two versions of this + { name = "redox_syscall", version = "*" }, # proptest and pyo3 rely on two versions of this + { name = "itertools", version = "*" }, # proptest relies on an older version of itertools than we use + { name = "heck", version = "*" }, # conflicting dependency with pyo3 and clap ] # This section is considered when running `cargo deny check sources`. @@ -92,4 +103,3 @@ unknown-git = "deny" # List of URLs for allowed crate registries. Defaults to the crates.io index # if not specified. If it is specified but empty, no registries are allowed. allow-registry = ["https://github.com/rust-lang/crates.io-index"] - diff --git a/knope.toml b/knope.toml index 31ec98ee..b36c3693 100644 --- a/knope.toml +++ b/knope.toml @@ -1,41 +1,47 @@ -[package] -versioned_files = ["Cargo.toml"] -changelog = "CHANGELOG.md" +[packages.quil-rs] +versioned_files = ["quil-rs/Cargo.toml"] +changelog = "quil-rs/CHANGELOG.md" +scopes = ["rs", "rust", "quil-rs"] + +[packages.quil-py] +versioned_files = ["quil-py/Cargo.toml", "quil-py/pyproject.toml"] +changelog = "quil-py/CHANGELOG.md" +scopes = ["py", "python", "quil-py"] + +[packages.quil-cli] +versioned_files = ["quil-cli/Cargo.toml"] +changelog = "quil-cli/CHANGELOG.md" +scopes = ["cli", "quil-cli"] [[workflows]] -name = "prerelease" +name = "release" [[workflows.steps]] type = "PrepareRelease" -prerelease_label = "rc" [[workflows.steps]] type = "Command" -command = "cargo update -w && git add Cargo.lock && git commit -m \"chore: prepare pre-release $version [skip ci]\"" -variables = { "$version" = "Version" } +command = "sh ./scripts/sync_versions.sh" [[workflows.steps]] type = "Command" -command = "git push" +command = "git add quil-py/Cargo.toml" [[workflows.steps]] -type = "Release" - -[[workflows]] -name = "release" - -[[workflows.steps]] -type = "PrepareRelease" +type = "Command" +command = "git add quil-cli/Cargo.toml" [[workflows.steps]] type = "Command" -command = "git add Cargo.lock && git commit -m \"chore: prepare release $version [skip ci]\"" -variables = { "$version" = "Version" } +command = "git commit -m \"chore: prepare release [skip ci]\"" [[workflows.steps]] type = "Command" command = "git push" +[[workflows.steps]] +type = "Release" + [github] owner = "rigetti" repo = "quil-rs" diff --git a/quil-cli/CHANGELOG.md b/quil-cli/CHANGELOG.md new file mode 100644 index 00000000..0517a714 --- /dev/null +++ b/quil-cli/CHANGELOG.md @@ -0,0 +1,383 @@ +## 0.3.1 + +### Features + +- add waveform templates (#369) + +### Fixes + +- Parsing programs with integers that overflow a u64 will no longer panic; instead, they will raise an error. (#372) + +## 0.3.1-rc.1 + +### Features + +- add waveform templates (#369) + +### Fixes + +- Parsing programs with integers that overflow a u64 will no longer panic; instead, they will raise an error. (#372) + +## 0.3.1-rc.0 + +### Features + +- add waveform templates (#369) + +## 0.3.0 + +### Breaking Changes + +- reduce number of classical instruction edges in InstructionBlock::graph + +## 0.3.0-rc.0 + +### Breaking Changes + +- reduce number of classical instruction edges in InstructionBlock::graph + +## 0.2.1 + +### Features + +- Support constructing ControlFlowGraph and BasicBlocks. (#359) + +## 0.2.1-rc.0 + +### Features + +- Support constructing ControlFlowGraph and BasicBlocks. (#359) + +## 0.2.0 + +### Breaking Changes + +- Program instruction iteration and serialization is deterministic. (#355) + +### Fixes + +- Program equality is sensitive to the order of calibration instructions. (#357) + +## 0.2.0-rc.1 + +### Breaking Changes + +- Program instruction iteration and serialization is deterministic. (#355) + +### Fixes + +- Program equality is sensitive to the order of calibration instructions. (#357) + +## 0.1.1-rc.0 + +### Fixes + +- Program equality is sensitive to the order of calibration instructions. (#357) + +## 0.1.0 + +### Breaking Changes + +- CalibrationSet's and Program's will be considered equal if they contain the same set of calibrations, regardless of order. (#352) +- #334: program scheduling and analysis utilities (#336) +- Program now has a gate_definitions property that stores all DEFGATEs in a program. These instructions will no longer appear in body_instructions. (#306) +- Support for Qubit and Target Placeholdres have been added. Converting programs and instructions to a string has been removed and replaced with a fallible to_quil() method. The `Label` struct has been repurposed to support `Label` instructions specifically. The `Target` enum has been added to express `@targets` as part of an instruction. (#266) +- Decouple expression hashing and equality (#277) +- allow overriding Instruction getters (#260) +- This release is identical to 0.18.0. An error in our CI caused a continuity error with our published releases. (#254) +- When adding two Programs, the resulting Program will have a correct used qubit cache. (#249) +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) +- Release quil-py +- introduce ExecutionDependency::Scheduled (#186) +- empty commit so knope calculates current version +- empty commit to force version bump +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- Add CLI for interacting with quil-rs (#348) +- Add methods for identifying Quil-T instructions and filtering instructions from `Program`s (#323) +- Add `Program.wrap_in_loop()` method (#321) +- Add methods for identifying Quil-T instructions and filtering instructions from Programs +- Make in-place addition of Program more efficient (#290) +- Add get_qubits method to Instruction +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. +- RESET frame computation +- Program.into_simplified +- Support Expression arithmetic operations (#126) +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference +- update program to use btreemap for deterministic ordering + +### Fixes + +- Revert "match exactly one qubit for DELAYs without frame specifier" (#342) +- include separators between DEFCIRCUIT parameters (#338) +- `is_quil_t()` now correctly returns false for WAIT instructions (#331) +- The `wrap_in_loop` method now applies the end target to the program (#329) +- use internal QuotedString wrapper to quote Quil strings correctly (#317) +- misc instruction memory accesses (#304) +- match exactly one qubit for DELAYs without frame specifier (#300) +- trigger release +- calibration definitions don't contribute to Instrution::get_qubits +- implement PartialOrd correctly for types implementing Ord (#295) +- no percent symbol in variable qubit outputs +- Implement not equal comparisons (#289) +- Allow whitespace to delimit matrix specifications, better support parsing (#286) +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) +- The parser now follows the correct precedence rules for ungrouped infix expressions (#207) +- The imaginary part of a complex number will now always be formatted as a floating point number. (#205) +- mark additional instructions as scheduled (#203) +- parse SWAP-PHASES (#200) +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) +- update snapshots +- Waveforms w/o params need no parens +- support escaped double quotes and backslashes in strings (#120) +- make dynamic error Sync as well (#131) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) +- cargo fmt & passing tests +- PRAGMA instruction `Display` impl (#62) +- change quilc submodule to use HTTPS +- move cargo update inline with semantic-release prepare cmd +- lex keywords from identifiers (#47) +- DEFWAVEFORM and waveform name parsing (#40) +- remove unused imports +- `Program::from_str` and `Expression::from_str` will no longer panic on bad input. (#37) +- Allow indented comments when parsing. (#24) +- Unbox calibrations and waveform invocations +- parse and print real values as double precision +- support Windows line separators \r\n +- make program::graph public (#4) + +## 0.1.0-rc.0 + +### Breaking Changes + +- CalibrationSet's and Program's will be considered equal if they contain the same set of calibrations, regardless of order. (#352) +- #334: program scheduling and analysis utilities (#336) +- Program now has a gate_definitions property that stores all DEFGATEs in a program. These instructions will no longer appear in body_instructions. (#306) +- Support for Qubit and Target Placeholdres have been added. Converting programs and instructions to a string has been removed and replaced with a fallible to_quil() method. The `Label` struct has been repurposed to support `Label` instructions specifically. The `Target` enum has been added to express `@targets` as part of an instruction. (#266) +- Decouple expression hashing and equality (#277) +- allow overriding Instruction getters (#260) +- This release is identical to 0.18.0. An error in our CI caused a continuity error with our published releases. (#254) +- When adding two Programs, the resulting Program will have a correct used qubit cache. (#249) +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) +- Release quil-py +- introduce ExecutionDependency::Scheduled (#186) +- empty commit so knope calculates current version +- empty commit to force version bump +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- Add CLI for interacting with quil-rs (#348) +- Add methods for identifying Quil-T instructions and filtering instructions from `Program`s (#323) +- Add `Program.wrap_in_loop()` method (#321) +- Add methods for identifying Quil-T instructions and filtering instructions from Programs +- Make in-place addition of Program more efficient (#290) +- Add get_qubits method to Instruction +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. +- RESET frame computation +- Program.into_simplified +- Support Expression arithmetic operations (#126) +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference +- update program to use btreemap for deterministic ordering + +### Fixes + +- Revert "match exactly one qubit for DELAYs without frame specifier" (#342) +- include separators between DEFCIRCUIT parameters (#338) +- `is_quil_t()` now correctly returns false for WAIT instructions (#331) +- The `wrap_in_loop` method now applies the end target to the program (#329) +- use internal QuotedString wrapper to quote Quil strings correctly (#317) +- misc instruction memory accesses (#304) +- match exactly one qubit for DELAYs without frame specifier (#300) +- trigger release +- calibration definitions don't contribute to Instrution::get_qubits +- implement PartialOrd correctly for types implementing Ord (#295) +- no percent symbol in variable qubit outputs +- Implement not equal comparisons (#289) +- Allow whitespace to delimit matrix specifications, better support parsing (#286) +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) +- The parser now follows the correct precedence rules for ungrouped infix expressions (#207) +- The imaginary part of a complex number will now always be formatted as a floating point number. (#205) +- mark additional instructions as scheduled (#203) +- parse SWAP-PHASES (#200) +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) +- update snapshots +- Waveforms w/o params need no parens +- support escaped double quotes and backslashes in strings (#120) +- make dynamic error Sync as well (#131) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) +- cargo fmt & passing tests +- PRAGMA instruction `Display` impl (#62) +- change quilc submodule to use HTTPS +- move cargo update inline with semantic-release prepare cmd +- lex keywords from identifiers (#47) +- DEFWAVEFORM and waveform name parsing (#40) +- remove unused imports +- `Program::from_str` and `Expression::from_str` will no longer panic on bad input. (#37) +- Allow indented comments when parsing. (#24) +- Unbox calibrations and waveform invocations +- parse and print real values as double precision +- support Windows line separators \r\n +- make program::graph public (#4) + +## 0.2.0-rc.0 + +### Breaking Changes + +- #334: program scheduling and analysis utilities (#336) +- Program now has a gate_definitions property that stores all DEFGATEs in a program. These instructions will no longer appear in body_instructions. (#306) +- Support for Qubit and Target Placeholdres have been added. Converting programs and instructions to a string has been removed and replaced with a fallible to_quil() method. The `Label` struct has been repurposed to support `Label` instructions specifically. The `Target` enum has been added to express `@targets` as part of an instruction. (#266) +- Decouple expression hashing and equality (#277) +- allow overriding Instruction getters (#260) +- This release is identical to 0.18.0. An error in our CI caused a continuity error with our published releases. (#254) +- When adding two Programs, the resulting Program will have a correct used qubit cache. (#249) +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) +- Release quil-py +- introduce ExecutionDependency::Scheduled (#186) +- empty commit so knope calculates current version +- empty commit to force version bump +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- Add CLI for interacting with quil-rs (#348) +- Add methods for identifying Quil-T instructions and filtering instructions from `Program`s (#323) +- Add `Program.wrap_in_loop()` method (#321) +- Add methods for identifying Quil-T instructions and filtering instructions from Programs +- Make in-place addition of Program more efficient (#290) +- Add get_qubits method to Instruction +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. +- RESET frame computation +- Program.into_simplified +- Support Expression arithmetic operations (#126) +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference +- update program to use btreemap for deterministic ordering + +### Fixes + +- Revert "match exactly one qubit for DELAYs without frame specifier" (#342) +- include separators between DEFCIRCUIT parameters (#338) +- `is_quil_t()` now correctly returns false for WAIT instructions (#331) +- The `wrap_in_loop` method now applies the end target to the program (#329) +- use internal QuotedString wrapper to quote Quil strings correctly (#317) +- misc instruction memory accesses (#304) +- match exactly one qubit for DELAYs without frame specifier (#300) +- trigger release +- calibration definitions don't contribute to Instrution::get_qubits +- implement PartialOrd correctly for types implementing Ord (#295) +- no percent symbol in variable qubit outputs +- Implement not equal comparisons (#289) +- Allow whitespace to delimit matrix specifications, better support parsing (#286) +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) +- The parser now follows the correct precedence rules for ungrouped infix expressions (#207) +- The imaginary part of a complex number will now always be formatted as a floating point number. (#205) +- mark additional instructions as scheduled (#203) +- parse SWAP-PHASES (#200) +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) +- update snapshots +- Waveforms w/o params need no parens +- support escaped double quotes and backslashes in strings (#120) +- make dynamic error Sync as well (#131) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) +- cargo fmt & passing tests +- PRAGMA instruction `Display` impl (#62) +- change quilc submodule to use HTTPS +- move cargo update inline with semantic-release prepare cmd +- lex keywords from identifiers (#47) +- DEFWAVEFORM and waveform name parsing (#40) +- remove unused imports +- `Program::from_str` and `Expression::from_str` will no longer panic on bad input. (#37) +- Allow indented comments when parsing. (#24) +- Unbox calibrations and waveform invocations +- parse and print real values as double precision +- support Windows line separators \r\n +- make program::graph public (#4) diff --git a/quil-cli/Cargo.toml b/quil-cli/Cargo.toml new file mode 100644 index 00000000..a40dcbd0 --- /dev/null +++ b/quil-cli/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "quil-cli" +version = "0.3.1" +edition = "2021" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +quil-rs = { path = "../quil-rs", version = "0.26.1" } +clap = {version = "4.5.4", features = ["derive"]} +anyhow = "1.0.81" diff --git a/quil-cli/src/main.rs b/quil-cli/src/main.rs new file mode 100644 index 00000000..99273a19 --- /dev/null +++ b/quil-cli/src/main.rs @@ -0,0 +1,60 @@ +use anyhow::Context; +use clap::{Parser, Subcommand, ValueEnum}; +use quil_rs::{expression::Expression, quil::Quil, Program}; +use std::str::FromStr; + +#[derive(Parser, Debug)] +struct Cli { + #[clap(subcommand)] + command: Command, +} + +#[derive(Subcommand, Clone, Debug)] +pub enum Command { + /// Parse a Quil program or expression + Parse { + #[arg(short = 't', long = "type", value_enum, default_value_t)] + input_type: InputType, + input: String, + }, +} + +#[derive(ValueEnum, Clone, Debug, Default)] +pub enum InputType { + /// Parse a Quil program (default) + #[default] + Program, + /// Parse a Quil expression + Expression, +} + +fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + + match cli.command { + Command::Parse { input_type, input } => handle_parse(input_type, input)?, + }; + + Ok(()) +} + +fn handle_parse(input_type: InputType, input: String) -> anyhow::Result<()> { + let parsed = match input_type { + InputType::Program => { + Program::from_str(&input) + .context("Failed to parse program from input string.")? + .to_quil() + .context("Parsed Program from valid Quil string, but was unable to convert it back to valid Quil. This is probably a bug in the quil-rs parser.")? + } + InputType::Expression => { + Expression::from_str(&input) + .context("Failed to parse expression from input string.")? + .to_quil() + .context("Parsed Expression from valid Quil expression, but was unable to convert it back to valid Expression. This is probably a bug in the quil-rs parser.")? + } + }; + + println!("{parsed}"); + + Ok(()) +} diff --git a/quil-py/.flake8 b/quil-py/.flake8 new file mode 100644 index 00000000..3f7422a3 --- /dev/null +++ b/quil-py/.flake8 @@ -0,0 +1,7 @@ +[flake8] +max-line-length = 120 +# E203, E302 ignored for black compatibility +extend-ignore = E203, E302 + +per-file-ignores = + **/__init__.pyi:F401,F403 # Disable "unused" warning for top-level exports diff --git a/quil-py/.stubtest-allowlist b/quil-py/.stubtest-allowlist new file mode 100644 index 00000000..5c8526d9 --- /dev/null +++ b/quil-py/.stubtest-allowlist @@ -0,0 +1 @@ +quil.quil diff --git a/quil-py/CHANGELOG.md b/quil-py/CHANGELOG.md new file mode 100644 index 00000000..b660a009 --- /dev/null +++ b/quil-py/CHANGELOG.md @@ -0,0 +1,804 @@ +## 0.10.1 + +### Features + +- add waveform templates (#369) + +### Fixes + +- Parsing programs with integers that overflow a u64 will no longer panic; instead, they will raise an error. (#372) + +## 0.10.1-rc.1 + +### Features + +- add waveform templates (#369) + +### Fixes + +- Parsing programs with integers that overflow a u64 will no longer panic; instead, they will raise an error. (#372) + +## 0.10.1-rc.0 + +### Features + +- add waveform templates (#369) + +## 0.10.0 + +### Breaking Changes + +- reduce number of classical instruction edges in InstructionBlock::graph + +## 0.10.0-rc.0 + +### Breaking Changes + +- reduce number of classical instruction edges in InstructionBlock::graph + +## 0.9.1 + +### Features + +- Support constructing ControlFlowGraph and BasicBlocks. (#359) + +## 0.9.1-rc.0 + +### Features + +- Support constructing ControlFlowGraph and BasicBlocks. (#359) + +## 0.9.0 + +### Breaking Changes + +- Program instruction iteration and serialization is deterministic. (#355) + +### Fixes + +- Program equality is sensitive to the order of calibration instructions. (#357) + +## 0.9.0-rc.0 + +### Breaking Changes + +- Program instruction iteration and serialization is deterministic. (#355) + +### Fixes + +- Program equality is sensitive to the order of calibration instructions. (#357) + +## 0.8.1-rc.0 + +### Fixes + +- Program equality is sensitive to the order of calibration instructions. (#357) + +## 0.8.0 + +### Breaking Changes + +- CalibrationSet's and Program's will be considered equal if they contain the same set of calibrations, regardless of order. (#352) + +## 0.8.0-rc.0 + +### Breaking Changes + +- CalibrationSet's and Program's will be considered equal if they contain the same set of calibrations, regardless of order. (#352) + +## 0.7.1 + +### Features + +- linux ppc64le wheel + +## 0.7.1-rc.0 + +### Features + +- linux ppc64le wheel + +## 0.7.0 + +### Breaking Changes + +- #334: program scheduling and analysis utilities (#336) + +### Fixes + +- Revert "match exactly one qubit for DELAYs without frame specifier" (#342) + +## 0.7.0-rc.1 + +### Breaking Changes + +- #334: program scheduling and analysis utilities (#336) + +### Fixes + +- Revert "match exactly one qubit for DELAYs without frame specifier" (#342) + +## 0.7.0-rc.0 + +### Breaking Changes + +- #334: program scheduling and analysis utilities (#336) + +## 0.6.6 + +### Fixes + +- include separators between DEFCIRCUIT parameters (#338) + +## 0.6.6-rc.0 + +### Fixes + +- include separators between DEFCIRCUIT parameters (#338) + +## 0.6.5 + +### Fixes + +- `is_quil_t()` now correctly returns false for WAIT instructions (#331) + +## 0.6.5-rc.0 + +### Fixes + +- `is_quil_t()` now correctly returns false for WAIT instructions (#331) + +## 0.6.4 + +### Fixes + +- The `wrap_in_loop` method now applies the end target to the program (#329) + +## 0.6.4-rc.0 + +### Fixes + +- The `wrap_in_loop` method now applies the end target to the program (#329) + +## 0.6.3 + +### Features + +- Add methods for identifying Quil-T instructions and filtering instructions from `Program`s (#323) +- Add `Program.wrap_in_loop()` method (#321) +- Add methods for identifying Quil-T instructions and filtering instructions from Programs + +## 0.6.3-rc.1 + +### Features + +- Add methods for identifying Quil-T instructions and filtering instructions from `Program`s (#323) +- Add `Program.wrap_in_loop()` method (#321) +- Add methods for identifying Quil-T instructions and filtering instructions from Programs + +## 0.6.3-rc.0 + +### Features + +- Add `Program.wrap_in_loop()` method (#321) +- Add methods for identifying Quil-T instructions and filtering instructions from Programs + +## 0.6.2 + +### Fixes + +- use internal QuotedString wrapper to quote Quil strings correctly (#317) + +## 0.6.2-rc.0 + +### Fixes + +- use internal QuotedString wrapper to quote Quil strings correctly (#317) + +## 0.6.1 + +### Features + +- Support Python 3.12 (#310) + +### Fixes + +- misc instruction memory accesses (#304) + +## 0.6.1-rc.1 + +### Features + +- Support Python 3.12 (#310) + +### Fixes + +- misc instruction memory accesses (#304) + +## 0.6.1-rc.0 + +### Fixes + +- misc instruction memory accesses (#304) + +## 0.6.0 + +### Breaking Changes + +- Program now has a gate_definitions property that stores all DEFGATEs in a program. These instructions will no longer appear in body_instructions. (#306) + +## 0.6.0-rc.0 + +### Breaking Changes + +- Program now has a gate_definitions property that stores all DEFGATEs in a program. These instructions will no longer appear in body_instructions. (#306) + +## 0.5.8 + +### Fixes + +- match exactly one qubit for DELAYs without frame specifier (#300) + +## 0.5.8-rc.0 + +### Fixes + +- match exactly one qubit for DELAYs without frame specifier (#300) + +## 0.5.7 + +### Fixes + +- trigger release + +## 0.5.7-rc.0 + +### Fixes + +- trigger release + +## 0.5.6 + +### Fixes + +- implement PartialOrd correctly for types implementing Ord (#295) +- no percent symbol in variable qubit outputs + +## 0.5.6-rc.1 + +### Fixes + +- implement PartialOrd correctly for types implementing Ord (#295) +- no percent symbol in variable qubit outputs + +## 0.5.6-rc.0 + +### Fixes + +- no percent symbol in variable qubit outputs + +## 0.5.5 + +### Features + +- Make in-place addition of Program more efficient (#290) + +## 0.5.5-rc.0 + +### Features + +- Make in-place addition of Program more efficient (#290) + +## 0.5.4 + +### Fixes + +- Implement not equal comparisons (#289) + +## 0.5.4-rc.0 + +### Fixes + +- Implement not equal comparisons (#289) + +## 0.5.3 + +### Fixes + +- Allow whitespace to delimit matrix specifications, better support parsing (#286) + +## 0.5.3-rc.0 + +### Fixes + +- Allow whitespace to delimit matrix specifications, better support parsing (#286) + +## 0.5.2 + +### Features + +- Add get_qubits method to Instruction +- Instruction classes now implement `__copy__`, and `__deepcopy__`, making them compatible with Python's `copy` module. (#283) + +## 0.5.2-rc.1 + +### Features + +- Add get_qubits method to Instruction +- Instruction classes now implement `__copy__`, and `__deepcopy__`, making them compatible with Python's `copy` module. (#283) + +## 0.5.2-rc.0 + +### Features + +- Instruction classes now implement `__copy__`, and `__deepcopy__`, making them compatible with Python's `copy` module. (#283) + +## 0.5.1 + +### Features + +- Build & publish wheels for Windows (#280) + +## 0.5.1-rc.1 + +### Features + +- Build & publish wheels for Windows (#280) + +## 0.5.1-rc.0 + +### Features + +- Build & publish wheels for Windows (#280) + +## 0.5.0 + +### Breaking Changes + +- Support for Qubit and Target Placeholdres have been added. Converting programs and instructions to a string has been removed and replaced with a fallible to_quil() method. The `Label` struct has been repurposed to support `Label` instructions specifically. The `Target` enum has been added to express `@targets` as part of an instruction. (#266) +- Decouple expression hashing and equality (#277) + +## 0.5.0-rc.1 + +### Breaking Changes + +- Support for Qubit and Target Placeholdres have been added. Converting programs and instructions to a string has been removed and replaced with a fallible to_quil() method. The `Label` struct has been repurposed to support `Label` instructions specifically. The `Target` enum has been added to express `@targets` as part of an instruction. (#266) +- Decouple expression hashing and equality (#277) + +## 0.5.0-rc.0 + +### Breaking Changes + +- Decouple expression hashing and equality (#277) + +## 0.4.0 + +### Breaking Changes + +- allow overriding Instruction getters (#260) + +### Features + +- Add setters for `Program` calibrations, waveforms, frames, and memory_regions (#264) + +## 0.4.0-rc.2 + +### Breaking Changes + +- allow overriding Instruction getters (#260) + +### Features + +- Add setters for `Program` calibrations, waveforms, frames, and memory_regions (#264) + +## 0.4.0-rc.1 + +### Breaking Changes + +- allow overriding Instruction getters (#260) + +### Features + +- Add setters for `Program` calibrations, waveforms, frames, and memory_regions (#264) + +## 0.4.0-rc.0 + +### Breaking Changes + +- allow overriding Instruction getters (#260) + +## 0.3.0 + +### Breaking Changes + +- This release is identical to 0.18.0. An error in our CI caused a continuity error with our published releases. (#254) + +## 0.3.0-rc.0 + +### Breaking Changes + +- This release is identical to 0.18.0. An error in our CI caused a continuity error with our published releases. (#254) + +## 0.2.0 + +### Breaking Changes + +- When adding two Programs, the resulting Program will have a correct used qubit cache. (#249) +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.3.0-rc.1 + +### Breaking Changes + +- When adding two Programs, the resulting Program will have a correct used qubit cache. (#249) +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0 + +### Breaking Changes + +- When adding two Programs, the resulting Program will have a correct used qubit cache. (#249) +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.3.0-rc.0 + +### Breaking Changes + +- When adding two Programs, the resulting Program will have a correct used qubit cache. (#249) +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0-rc.14 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0-rc.13 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0-rc.12 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0-rc.11 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- Expand all analog control instructions (#238) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0-rc.10 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0-rc.9 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0-rc.8 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0-rc.7 + +### Breaking Changes + +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0-rc.6 + +### Breaking Changes + +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0-rc.5 + +### Breaking Changes + +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0-rc.4 + +### Breaking Changes + +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.2.0-rc.3 + +### Breaking Changes + +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.1.0 + +This first release of the `quil` package exposes a Python API for most of the `quil-rs` package. This includes a parser, as well as types for instructions, programs, and expressions. diff --git a/quil-py/Cargo.toml b/quil-py/Cargo.toml new file mode 100644 index 00000000..c080034e --- /dev/null +++ b/quil-py/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "quil-py" +description = "Python bindings for quil-rs" +version = "0.10.1" +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/rigetti/quil-rs" +keywords = ["pyquil", "SDK", "Rigetti", "Quil", "Quantum"] +categories = ["api-bindings", "parsers", "science", "emulators"] +readme = "./README.md" + +[lib] +# The name of the native library. This is the name which will be used in Python to import the +# library (i.e. `import quil`). If you change this, you must also change the name of the +# `#[pymodule]` in `src/lib.rs`. +name = "quil" +# "cdylib" is necessary to produce a shared library for Python to import from. +# +# Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able +# to `use quil;` unless the "lib" and "rlib" crate type is also included: +crate-type = ["cdylib", "rlib"] + +[dependencies] +ndarray.workspace = true +quil-rs = { path = "../quil-rs", version = "0.26.1" } +strum.workspace = true +# pyo3 dependencies should be updated together +numpy = "0.20.0" +pyo3 = { version = "0.20.3", features = ["indexmap"] } +rigetti-pyo3 = {version = "0.3.4", features = ["indexmap"]} +indexmap.workspace = true + +[build-dependencies] +pyo3-build-config = "0.20.0" diff --git a/quil-py/Makefile.toml b/quil-py/Makefile.toml new file mode 100644 index 00000000..a3727ac5 --- /dev/null +++ b/quil-py/Makefile.toml @@ -0,0 +1,65 @@ +[env] +RUST_BACKTRACE = 0 + +[tasks.poetry-install] +command = "poetry" +args = ["install"] + +[tasks.install-quil] +command = "poetry" +args = ["run", "maturin", "develop"] + +[tasks.stubtest] +dependencies = ["poetry-install"] +command = "poetry" +args = ["run", "stubtest", "--allowlist", ".stubtest-allowlist", "quil"] + +[tasks.stubtest-flow] +dependencies = [ + "poetry-install", + "install-quil", + "stubtest", +] + +[tasks.format] +dependencies = ["poetry-install"] +command = "poetry" +args = ["run", "ruff", "format"] + +[tasks.check-format] +dependencies = ["poetry-install"] +command = "poetry" +args = ["run", "ruff", "format", "--check"] + +[tasks.lint] +dependencies = ["poetry-install"] +command = "poetry" +args = ["run", "ruff", "check"] + +[tasks.pytest] +command = "poetry" +args = ["run", "pytest"] + +[tasks.pytest-flow] +dependencies = [ + "poetry-install", + "install-quil", + "pytest", +] + +[tasks.docs] +dependencies = ["poetry-install", "install-quil"] +command = "poetry" +args = ["run", "pdoc", "-o", "build/docs", "quil", "!quil.quil", "--logo", "https://qcs.rigetti.com/static/img/rigetti-logo.svg"] + +[tasks.dev-flow] +dependencies = [ + "dev-test-flow", + "pytest-flow", + "stubtest", + "lint", + "check-format" +] + +[tasks.default] +alias = "dev-flow" diff --git a/quil-py/README-py.md b/quil-py/README-py.md new file mode 100644 index 00000000..b51f462a --- /dev/null +++ b/quil-py/README-py.md @@ -0,0 +1,11 @@ +# Quil + +⚠️ In Development + +The `quil` package provides tools for constructing, manipulating, parsing, and printing [Quil](https://github.com/quil-lang/quil) programs. Internally, it is powered by [quil-rs](https://github.com/rigetti/quil-rs). + +This package is still in early development and breaking changes should be expected between minor versions. + +# Documentation + +Documentation for the current release of `quil` is published [here](https://rigetti.github.io/quil-rs/quil.html). Every version of `quil` ships [with type stubs](https://github.com/rigetti/quil-rs/tree/main/quil-py/quil) that provide type hints and documentation to Python tooling and editors that support the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) or similar. diff --git a/quil-py/README.md b/quil-py/README.md new file mode 100644 index 00000000..2f1c6630 --- /dev/null +++ b/quil-py/README.md @@ -0,0 +1,7 @@ +# quil-py + +⚠️ In Development + +This crate exposes [`pyo3`](https://github.com/PyO3/pyo3) bindings for [`quil-rs`](https://github.com/rigetti/quil-rs) allowing them to be extended or used in other `pyo3` based Python packages. + +This package is still in early development and breaking changes should be expected between minor versions. diff --git a/quil-py/build.rs b/quil-py/build.rs new file mode 100644 index 00000000..dace4a9b --- /dev/null +++ b/quil-py/build.rs @@ -0,0 +1,3 @@ +fn main() { + pyo3_build_config::add_extension_module_link_args(); +} diff --git a/quil-py/make_docs.py b/quil-py/make_docs.py new file mode 100644 index 00000000..4c157d1a --- /dev/null +++ b/quil-py/make_docs.py @@ -0,0 +1,14 @@ +import sys +from pathlib import Path + +import pdoc + +import quil # noqa - we need to import quil for it to appear in sys.modules + +if __name__ == "__main__": + print(dir(sys.modules["quil"])) + print(dir(sys.modules["quil.validation"])) + print([k for k in sys.modules.keys() if "quil" in k]) + del sys.modules["quil.validation"] + + pdoc.pdoc("quil", "!quil.quil", output_directory=Path("docs")) diff --git a/quil-py/out/index.html b/quil-py/out/index.html new file mode 100644 index 00000000..967079b4 --- /dev/null +++ b/quil-py/out/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/quil-py/out/quil.html b/quil-py/out/quil.html new file mode 100644 index 00000000..7ba583d2 --- /dev/null +++ b/quil-py/out/quil.html @@ -0,0 +1,237 @@ + + + + + + + quil API documentation + + + + + + + + + +
+
+

+quil

+ + + + + + +
1from .quil import *
+
+ + +
+
+ + \ No newline at end of file diff --git a/quil-py/out/quil/quil.html b/quil-py/out/quil/quil.html new file mode 100644 index 00000000..93ef2bec --- /dev/null +++ b/quil-py/out/quil/quil.html @@ -0,0 +1,233 @@ + + + + + + + quil.quil API documentation + + + + + + + + + +
+
+

+quil.quil

+ + + + + +
+
+ + \ No newline at end of file diff --git a/quil-py/out/search.js b/quil-py/out/search.js new file mode 100644 index 00000000..9575c96b --- /dev/null +++ b/quil-py/out/search.js @@ -0,0 +1,46 @@ +window.pdocSearch = (function(){ +/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o

\n"}, "quil.quil": {"fullname": "quil.quil", "modulename": "quil.quil", "kind": "module", "doc": "

\n"}}, "docInfo": {"quil": {"qualname": 0, "fullname": 1, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "quil.quil": {"qualname": 0, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}}, "length": 2, "save": true}, "index": {"qualname": {"root": {"docs": {}, "df": 0}}, "fullname": {"root": {"docs": {}, "df": 0, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {"quil": {"tf": 1}, "quil.quil": {"tf": 1.4142135623730951}}, "df": 2}}}}}}, "annotation": {"root": {"docs": {}, "df": 0}}, "default_value": {"root": {"docs": {}, "df": 0}}, "signature": {"root": {"docs": {}, "df": 0}}, "bases": {"root": {"docs": {}, "df": 0}}, "doc": {"root": {"docs": {"quil": {"tf": 1.7320508075688772}, "quil.quil": {"tf": 1.7320508075688772}}, "df": 2}}}, "pipeline": ["trimmer"], "_isPrebuiltIndex": true}; + + // mirrored in build-search-index.js (part 1) + // Also split on html tags. this is a cheap heuristic, but good enough. + elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/); + + let searchIndex; + if (docs._isPrebuiltIndex) { + console.info("using precompiled search index"); + searchIndex = elasticlunr.Index.load(docs); + } else { + console.time("building search index"); + // mirrored in build-search-index.js (part 2) + searchIndex = elasticlunr(function () { + this.pipeline.remove(elasticlunr.stemmer); + this.pipeline.remove(elasticlunr.stopWordFilter); + this.addField("qualname"); + this.addField("fullname"); + this.addField("annotation"); + this.addField("default_value"); + this.addField("signature"); + this.addField("bases"); + this.addField("doc"); + this.setRef("fullname"); + }); + for (let doc of docs) { + searchIndex.addDoc(doc); + } + console.timeEnd("building search index"); + } + + return (term) => searchIndex.search(term, { + fields: { + qualname: {boost: 4}, + fullname: {boost: 2}, + annotation: {boost: 2}, + default_value: {boost: 2}, + signature: {boost: 2}, + bases: {boost: 2}, + doc: {boost: 1}, + }, + expand: true + }); +})(); \ No newline at end of file diff --git a/quil-py/poetry.lock b/quil-py/poetry.lock new file mode 100644 index 00000000..dd8c2607 --- /dev/null +++ b/quil-py/poetry.lock @@ -0,0 +1,465 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "astunparse" +version = "1.6.3" +description = "An AST unparser for Python" +optional = false +python-versions = "*" +files = [ + {file = "astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"}, + {file = "astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872"}, +] + +[package.dependencies] +six = ">=1.6.1,<2.0" +wheel = ">=0.23.0,<1.0" + +[[package]] +name = "attrs" +version = "22.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.6" +files = [ + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {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" +optional = false +python-versions = "*" +files = [ + {file = "colored-1.4.4.tar.gz", hash = "sha256:04ff4d4dd514274fe3b99a21bb52fb96f2688c01e93fba7bef37221e7cb56ce0"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "maturin" +version = "1.2.3" +description = "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "maturin-1.2.3-py3-none-linux_armv6l.whl", hash = "sha256:7b6484d7c94d6d6188ccf4ed8a6167cb8f1e98f13c653bfa715c9ee9eac4be0c"}, + {file = "maturin-1.2.3-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b44fb4d1d116d69ce7c713c22b322debd5fc222db09eb1cdfa0e1c1b7f3e2e9c"}, + {file = "maturin-1.2.3-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:735375559c8c75bdc910c377f6dcc9197637ee2a312a60e361ef0e08fb31fcb5"}, + {file = "maturin-1.2.3-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:619f4f7b7e3a842a4f6cbae1d138a71d67aeba460f6217b38f2150ad53bb4dc1"}, + {file = "maturin-1.2.3-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:4e1035c102f87aa3e6733d28c2248b7303afa11f93a21f2ac88636e0430b0258"}, + {file = "maturin-1.2.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:aef8ddb9e775dd3781e6f56e10cc3d26f648735723ab5c47ce938542b9b5bbb6"}, + {file = "maturin-1.2.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:23c6fdc5750b96fd10d28c125dd795e9b75cd5cd768c8a403dc91dfde641243a"}, + {file = "maturin-1.2.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:40b4d69f9e5be5eacedd80ae496fae67cfd71d386b5604f7ce2e9ac9d34d0460"}, + {file = "maturin-1.2.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:826e90533789ff6dd3f3f0541fbe46c3549ec985d19edceff7913f9bdf9c3131"}, + {file = "maturin-1.2.3-py3-none-win32.whl", hash = "sha256:e414e56896d904c255e80190ac81fa8299b1d7df52f7e2e3f10df33f92784fd8"}, + {file = "maturin-1.2.3-py3-none-win_amd64.whl", hash = "sha256:1f5516dbe68491bf4bf7e047caf139596a3cd9d4a5ec8bb43034980e3710e550"}, + {file = "maturin-1.2.3-py3-none-win_arm64.whl", hash = "sha256:7d47e9a0fe56d25de98a2bed7d1c75975516e3a25fa5b552b2ee61fb1add41c0"}, + {file = "maturin-1.2.3.tar.gz", hash = "sha256:ef3f42af453d64f233b99543c3001bee645019a9c2022c7972210a9cacb5301f"}, +] + +[package.dependencies] +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +patchelf = ["patchelf"] +zig = ["ziglang (>=0.10.0,<0.11.0)"] + +[[package]] +name = "mypy" +version = "1.1.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39c7119335be05630611ee798cc982623b9e8f0cff04a0b48dfc26100e0b97af"}, + {file = "mypy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61bf08362e93b6b12fad3eab68c4ea903a077b87c90ac06c11e3d7a09b56b9c1"}, + {file = "mypy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbb19c9f662e41e474e0cff502b7064a7edc6764f5262b6cd91d698163196799"}, + {file = "mypy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:315ac73cc1cce4771c27d426b7ea558fb4e2836f89cb0296cbe056894e3a1f78"}, + {file = "mypy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5cb14ff9919b7df3538590fc4d4c49a0f84392237cbf5f7a816b4161c061829e"}, + {file = "mypy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:26cdd6a22b9b40b2fd71881a8a4f34b4d7914c679f154f43385ca878a8297389"}, + {file = "mypy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b5f81b40d94c785f288948c16e1f2da37203c6006546c5d947aab6f90aefef2"}, + {file = "mypy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b437be1c02712a605591e1ed1d858aba681757a1e55fe678a15c2244cd68a5"}, + {file = "mypy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d809f88734f44a0d44959d795b1e6f64b2bbe0ea4d9cc4776aa588bb4229fc1c"}, + {file = "mypy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:a380c041db500e1410bb5b16b3c1c35e61e773a5c3517926b81dfdab7582be54"}, + {file = "mypy-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b7c7b708fe9a871a96626d61912e3f4ddd365bf7f39128362bc50cbd74a634d5"}, + {file = "mypy-1.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1c10fa12df1232c936830839e2e935d090fc9ee315744ac33b8a32216b93707"}, + {file = "mypy-1.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0a28a76785bf57655a8ea5eb0540a15b0e781c807b5aa798bd463779988fa1d5"}, + {file = "mypy-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ef6a01e563ec6a4940784c574d33f6ac1943864634517984471642908b30b6f7"}, + {file = "mypy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d64c28e03ce40d5303450f547e07418c64c241669ab20610f273c9e6290b4b0b"}, + {file = "mypy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64cc3afb3e9e71a79d06e3ed24bb508a6d66f782aff7e56f628bf35ba2e0ba51"}, + {file = "mypy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce61663faf7a8e5ec6f456857bfbcec2901fbdb3ad958b778403f63b9e606a1b"}, + {file = "mypy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2b0c373d071593deefbcdd87ec8db91ea13bd8f1328d44947e88beae21e8d5e9"}, + {file = "mypy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:2888ce4fe5aae5a673386fa232473014056967f3904f5abfcf6367b5af1f612a"}, + {file = "mypy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:19ba15f9627a5723e522d007fe708007bae52b93faab00f95d72f03e1afa9598"}, + {file = "mypy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:59bbd71e5c58eed2e992ce6523180e03c221dcd92b52f0e792f291d67b15a71c"}, + {file = "mypy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9401e33814cec6aec8c03a9548e9385e0e228fc1b8b0a37b9ea21038e64cdd8a"}, + {file = "mypy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b398d8b1f4fba0e3c6463e02f8ad3346f71956b92287af22c9b12c3ec965a9f"}, + {file = "mypy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:69b35d1dcb5707382810765ed34da9db47e7f95b3528334a3c999b0c90fe523f"}, + {file = "mypy-1.1.1-py3-none-any.whl", hash = "sha256:4e4e8b362cdf99ba00c2b218036002bdcdf1e0de085cdb296a49df03fb31dfc4"}, + {file = "mypy-1.1.1.tar.gz", hash = "sha256:ae9ceae0f5b9059f33dbc62dea087e942c0ccab4b7a003719cb70f9b8abfa32f"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "numpy" +version = "1.24.3" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"}, + {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"}, + {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"}, + {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"}, + {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"}, + {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"}, + {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"}, + {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"}, + {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"}, + {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"}, + {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"}, +] + +[[package]] +name = "packaging" +version = "23.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, +] + +[[package]] +name = "pdoc" +version = "14.1.0" +description = "API Documentation for Python Projects" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pdoc-14.1.0-py3-none-any.whl", hash = "sha256:e8869dffe21296b3bd5545b28e7f07cae0656082aca43f8915323187e541b126"}, + {file = "pdoc-14.1.0.tar.gz", hash = "sha256:3a0bd921a05c39a82b1505089eb6dc99d857b71b856aa60d1aca4d9086d0e18c"}, +] + +[package.dependencies] +astunparse = {version = "*", markers = "python_version < \"3.9\""} +Jinja2 = ">=2.11.0" +MarkupSafe = "*" +pygments = ">=2.12.0" + +[package.extras] +dev = ["black", "hypothesis", "mypy", "pygments (>=2.14.0)", "pytest", "pytest-cov", "pytest-timeout", "ruff", "tox", "types-pygments"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pygments" +version = "2.16.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pytest" +version = "7.2.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"}, + {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"}, +] + +[package.dependencies] +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" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "ruff" +version = "0.3.7" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"}, + {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"}, + {file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"}, + {file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"}, + {file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"}, + {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "syrupy" +version = "3.0.6" +description = "Pytest Snapshot Test Utility" +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 = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "typing-extensions" +version = "4.5.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, +] + +[[package]] +name = "wheel" +version = "0.41.3" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "wheel-0.41.3-py3-none-any.whl", hash = "sha256:488609bc63a29322326e05560731bf7bfea8e48ad646e1f5e40d366607de0942"}, + {file = "wheel-0.41.3.tar.gz", hash = "sha256:4d4987ce51a49370ea65c0bfd2234e8ce80a12780820d9dc462597a6e60d0841"}, +] + +[package.extras] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "c4cf08c102dd7ec09c483f00c9dbbabdca03917d1393809aafa65beab8253376" diff --git a/quil-py/pyproject.toml b/quil-py/pyproject.toml new file mode 100644 index 00000000..57e26fc4 --- /dev/null +++ b/quil-py/pyproject.toml @@ -0,0 +1,123 @@ +[project] +name = "quil" +requires-python = ">=3.8" +description = "A Python package for building and parsing Quil programs." +documentation = "https://rigetti.github.io/quil-rs/quil.html" +readme = "README-py.md" +license = { text = "Apache-2.0" } +authors = [{ name = "Rigetti Computing", email = "softapps@rigetti.com" }] +classifiers = [ + "Development Status :: 3 - Alpha", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Operating System :: OS Independent", +] + +# PEP 621 specifies the [project] table as the source for project metadata. However, Poetry only supports [tool.poetry] +# We can remove this table once this issue is resolved: https://github.com/python-poetry/poetry/issues/3332 +[tool.poetry] +name = "quil" +version = "0.10.1" +description = "A Python package for building and parsing Quil programs." +readme = "README-py.md" +authors = ["Rigetti Computing "] + +[tool.poetry.dependencies] +python = "^3.8" +numpy = "^1.21" + +[tool.poetry.group.dev.dependencies] +ruff = "^0.3.7" +maturin = "^1.2.3" +mypy = "^1.1.1" +pytest = "^7.2.2" +pdoc = "^14.1.0" +syrupy = "^3.0.6" + +[tool.maturin] +features = ["pyo3/extension-module"] +bindings = "pyo3" +compatibility = "linux" +sdist-include = ["README.md"] + +[build-system] +requires = ["maturin>=1.0.0,<2.0.0"] +build-backend = "maturin" + +[tool.ruff] +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] +line-length = 120 +indent-width = 4 +target-version = "py38" + +[tool.ruff.lint] +select = ["D", "E4", "E7", "E9", "F", "I", "B", "S", "W"] +ignore = [ + "E741" # "Ambiguous" variable names like "I" aren't ambiguous in this contex. +] +fixable = ["ALL"] +unfixable = [] +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.lint.per-file-ignores] +"quil/**/*.py" = [ + "F403", # * imports allowed in extension module glue. + "D100", # docstrings belong in type stubs + "D104", +] +"test/**/*.py" = [ + "D", # docstrings are not enforced in tests + "S101", # asserts are allowed in tests + "S301", # we need to test pickling +] +"make_docs.py" = [ "D" ] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.pyright] +# This diagnostic is raised when a type stub is found without a corresponding source file. This is +# necessarily the case for a pure Rust pyo3 module, so disabling it. +reportMissingModuleSource = false + +[[tool.mypy.overrides]] +module = [ + "quil.quil", +] +ignore_missing_imports = true diff --git a/quil-py/quil.html b/quil-py/quil.html new file mode 100644 index 00000000..aaa4aa3a --- /dev/null +++ b/quil-py/quil.html @@ -0,0 +1,237 @@ + + + + + + + quil API documentation + + + + + + + + + +
+
+

+quil

+ + + + + + +
1from .quil import *
+
+ + +
+
+ + \ No newline at end of file diff --git a/quil-py/quil/__init__.py b/quil-py/quil/__init__.py new file mode 100644 index 00000000..c04d99fc --- /dev/null +++ b/quil-py/quil/__init__.py @@ -0,0 +1,6 @@ +"""The `quil` package provides tools for constructing, manipulating, parsing, and printing [Quil](https://github.com/quil-lang/quil) programs. + +⚠️ This package is still in early development and breaking changes should be expected between minor versions. +""" + +from .quil import * diff --git a/quil-py/quil/__init__.pyi b/quil-py/quil/__init__.pyi new file mode 100644 index 00000000..e69de29b diff --git a/quil-py/quil/expression/__init__.py b/quil-py/quil/expression/__init__.py new file mode 100644 index 00000000..720522e6 --- /dev/null +++ b/quil-py/quil/expression/__init__.py @@ -0,0 +1 @@ +from quil.expression import * diff --git a/quil-py/quil/expression/__init__.pyi b/quil-py/quil/expression/__init__.pyi new file mode 100644 index 00000000..9d1da781 --- /dev/null +++ b/quil-py/quil/expression/__init__.pyi @@ -0,0 +1,197 @@ +"""The ``expression`` module contains classes for representing Quil expressions.""" + +from enum import Enum +from typing import Dict, Optional, Sequence, Union, final + +from quil.instructions import MemoryReference + +class EvaluationError(ValueError): + """Error that may occur while evaluation an ``Expression``.""" + +class ParseExpressionError(ValueError): + """Error that may occur while parsing an ``Expression``.""" + +@final +class Expression: + """A Quil expression. + + # Variants: + - ``address``: An address defined by a `quil.instructions.MemoryReference`. + - ``function_call``: A `FunctionCallExpression`. + - ``infix``: An `InfixExpression`. + - ``number``: A number defined as a `complex`. + - ``pi``: The constant ``pi``. No inner data. + - ``prefix``: A `PrefixExpression`. + - ``variable``: A variable defined as a `str`. + + As seen above, some variants contain inner data that fully specify the expression. + For example, the ``number`` variant contains an ``int``. This is in contrast to variants like + ``pi`` that have no inner data because they require none to fully specify the expression. + This difference is important for determining which methods are available for each variant. + + Methods (for every variant): + - ``is_*``: Returns ``True`` if the expression is that variant, ``False`` otherwise. + + If the variant has inner data: + - ``as_*``: returns the inner data if it is the given variant, ``None`` otherwise. + - ``to_*``: returns the inner data if it is the given variant, raises ``ValueError`` otherwise. + - ``from_*``: Creates a new ``Expression`` of the given variant from an instance of the inner type. + + If the variant doesn't have inner data (e.g. ``pi``) + - ``new_*``: Creates a new ``Expression`` for the variant + """ + + def inner( + self, + ) -> Union[ + MemoryReference, + FunctionCallExpression, + InfixExpression, + int, + PrefixExpression, + str, + ]: + """Returns the inner value of the variant. Raises a ``RuntimeError`` if inner data doesn't exist.""" + ... + @staticmethod + def parse(input: str) -> Expression: + """Parses an ``Expression`` from a string. + + Raises a ``ParseExpressionError`` if the string isn't a valid Quil expression. + """ + def is_address(self) -> bool: ... + def is_function_call(self) -> bool: ... + def is_infix(self) -> bool: ... + def is_number(self) -> bool: ... + def is_pi(self) -> bool: ... + def is_prefix(self) -> bool: ... + def is_variable(self) -> bool: ... + @staticmethod + def new_pi() -> "Expression": ... + @staticmethod + def from_address(inner: MemoryReference) -> "Expression": ... + @staticmethod + def from_function_call(inner: FunctionCallExpression) -> "Expression": ... + @staticmethod + def from_infix(inner: InfixExpression) -> "Expression": ... + @staticmethod + def from_number(inner: complex) -> "Expression": ... + @staticmethod + def from_prefix(inner: PrefixExpression) -> "Expression": ... + @staticmethod + def from_variable(inner: str) -> "Expression": ... + def as_address(self) -> Optional[MemoryReference]: ... + def to_address(self) -> MemoryReference: ... + def as_function_call(self) -> Optional[FunctionCallExpression]: ... + def to_function_call(self) -> FunctionCallExpression: ... + def as_infix(self) -> Optional[InfixExpression]: ... + def to_infix(self) -> InfixExpression: ... + def as_number(self) -> Optional[complex]: ... + def to_number(self) -> complex: ... + def as_prefix(self) -> Optional[PrefixExpression]: ... + def to_prefix(self) -> PrefixExpression: ... + def as_variable(self) -> Optional[str]: ... + def to_variable(self) -> str: ... + def simplify(self): + """Simplify the expression as much as possible, in-place.""" + ... + def into_simplified(self) -> "Expression": + """Return a simplified copy of the expression.""" + def evaluate( + self, + variables: Dict[str, complex], + memory_references: Dict[str, Sequence[float]], + ) -> complex: + """Evaluate an expression, expecting that it may be fully reduced to a single complex number. + + If it cannot be reduced to a complex number, raises an ``EvaluationError``. + """ + ... + def substitute_variables(self, variable_values: Dict[str, "Expression"]) -> "Expression": + """Returns a copy of the expression where every matching variable in `variable_values` is replaced by the corresponding expression.""" + ... + def to_real(self) -> float: + """If this is a number with imaginary part "equal to" zero (of small absolute value), return that number. Otherwise, raises an ``EvaluationError``.""" + def __add__(self, other: "Expression") -> "Expression": ... + def __sub__(self, other: "Expression") -> "Expression": ... + def __mul__(self, other: "Expression") -> "Expression": ... + def __truediv__(self, other: "Expression") -> "Expression": ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. Raises an exception if the instruction can't be converted to valid Quil.""" + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug + format that isn't valid Quil. + """ + +class FunctionCallExpression: + """A Quil function call.""" + @staticmethod + def __new__(cls, function: ExpressionFunction, expression: Expression) -> "FunctionCallExpression": ... + @property + def function(self) -> ExpressionFunction: ... + @function.setter + def function(self, function: ExpressionFunction): ... + @property + def expression(self) -> Expression: ... + @expression.setter + def expression(self, expression: Expression): ... + +class InfixExpression: + """A Quil infix expression.""" + @staticmethod + def __new__(cls, left: Expression, operator: InfixOperator, right: Expression): ... + @property + def left(self) -> Expression: ... + @left.setter + def left(self, expression: Expression): ... + @property + def operator(self) -> InfixOperator: ... + @operator.setter + def operator(self, operator: InfixOperator): ... + @property + def right(self) -> Expression: ... + @right.setter + def right(self, expression: Expression): ... + +class PrefixExpression: + """A Quil prefix expression.""" + @staticmethod + def __new__(cls, operator: PrefixOperator, expression: Expression): ... + @property + def operator(self) -> PrefixOperator: ... + @operator.setter + def operator(self, operator: PrefixOperator): ... + @property + def expression(self) -> Expression: ... + @expression.setter + def expression(self, expression: Expression): ... + +@final +class ExpressionFunction(Enum): + """An enum representing a Quil function that can be applied to an expression.""" + + Cis = "CIS" + Cosine = "COSINE" + Exponent = "EXPONENT" + Sine = "SINE" + SquareRoot = "SQUAREROOT" + +@final +class PrefixOperator(Enum): + """An enum that represents the operators supported on a prefix expression.""" + + Plus = "PLUS" + Minus = "MINUS" + +@final +class InfixOperator(Enum): + """An enum that represents the operators supported on an infix expression.""" + + Caret = "CARET" + Plus = "PLUS" + Minus = "MINUS" + Slash = "SLASH" + Star = "STAR" diff --git a/quil-py/quil/instructions/__init__.py b/quil-py/quil/instructions/__init__.py new file mode 100644 index 00000000..7062b812 --- /dev/null +++ b/quil-py/quil/instructions/__init__.py @@ -0,0 +1,3 @@ +"""The `quil.instructions` module contains classes for representing Quil instructions.""" + +from quil.instructions import * diff --git a/quil-py/quil/instructions/__init__.pyi b/quil-py/quil/instructions/__init__.pyi new file mode 100644 index 00000000..6997ca8b --- /dev/null +++ b/quil-py/quil/instructions/__init__.pyi @@ -0,0 +1,2304 @@ +from enum import Enum +from typing import Dict, List, Optional, Sequence, Tuple, Union, final + +import numpy as np +from numpy.typing import NDArray +from typing_extensions import Self + +from quil.expression import Expression + +@final +class Instruction: + """A Quil instruction. Each variant corresponds to a possible type of Quil instruction. + + # Variants: + - ``arithmetic``: An arithmetic expression defined by an ``Arithmetic``. + - ``binary_logic``: A binary expression defined by a ``BinaryLogic``. + - ``calibration_definition``: Corresponds to a `DEFCAL` instruction (not `DEFCAL MEASURE`) + - defined by a ``Calibration``. + - ``capture``: Corresponds to a `CAPTURE` instruction + - ``calibration``: Corresponds to a `DEFCAL` instruction. + - ``circuit_definition``: Corresponds to a `DEFCIRCUIT` instruction and its body, + - defined by a ``CircuitDefinition``. + - ``convert``: Corresponds to a `CONVERT` instruction. + - ``comparison``: Corresponds to a comparison of two `MemoryReference`s + - ``declaration``: Corresponds to a `DECLARE` statement defined by a ``Declaration``. + - ``delay``: Corresponds to a `DELAY` instruction. + - ``exchange``: Corresponds to an `EXCHANGE` instruction. + - ``fence``: Corresponds to a `FENCE` instruction. + - ``frame_definition``: Corresponds to a `DEFFRAME` statement, defined by a ``FrameDefinition``. + - ``gate``: A Quil quantum gate instruction defined by a ``Gate``. + - ``gate_definition``: A quantum gate definition defined by a ``GateDefinition``. + - ``halt``: Corresponds to the `HALT` instruction. No inner data. + - ``include``: Corresponds to an `INCLUDE` directive. + - ``jump``: Corresponds to a `JUMP` instruction + - ``jump_when``: Corresponds to a `JUMP-WHEN` instruction + - ``jump_unless``: Corresponds to a `JUMP-UNLESS` instruction + - ``label``: Corresponds to a `LABEL` + - ``load``: Corresponds to a `LOAD` instruction. + - ``measure_calibration_definition``: Corresponds to a `DEFCAL MEASURE` instruction. Defined by a ``MeasureCalibrationDefinition``. + - ``measurement``: Corresponds to a `MEASURE` instruction. + - ``move``: Corresponds to a `MOVE` instruction. + - ``nop``: Corresponds to the `NOP` instruction. No inner data. + - ``pragma``: Corresponds to a `PRAGMA` instruction. + - ``pulse``: Corresponds to a `PULSE` instruction. + - ``raw_capture``: Corresponds to a `RAW-CAPTURE` instruction. + - ``reset``: Corresponds to a `RESET` instruction. + - ``set_frequency``: Corresponds to a `SET-FREQUENCY` instruction. + - ``set_phase``: Corresponds to a `SET-PHASE` instruction. + - ``set_scale``: Corresponds to a `SET-SCALE` instruction. + - ``shift_frequency``: Corresponds to a `SHIFT-FREQUENCY` instruction. + - ``shift_phase``: Corresponds to a `SHIFT-PHASE` instruction. + - ``store``: Corresponds to a `STORE` instruction. + - ``swap_phases``: Corresponds to a `SWAP-PHASES` instruction. + - ``unary_logic``: Corresponds to a unary operation on a `MemoryReference`. + - ``waveform_definition``: A waveform defined by a ``WaveformDefinition``. + - ``wait``: Corresponds to a `WAIT` instruction. No inner data. + + As seen above, some variants contain inner data that fully specify the instruction. + For example, the ``gate`` variant contains a ``Gate``. This is in contrast to variants like + ``halt`` that have no inner data because they require none to fully specify an instruction. + This difference is important for determining which methods are available for each variant. + + Methods (for each variant): + - ``is_*``: Returns ``True`` if the instruction is that variant, ``False`` otherwise. + + If the variant has inner data (e.g. ``gate``): + - ``as_*``: Returns the inner data if it is the given variant, ``None`` otherwise. + - ``to_*``: Returns the inner data if it is the given variant, raises ``ValueError`` otherwise. + - ``from_*``: Creates a new ``Instruction`` of the given variant from an instance of the inner type. + + If the variant doesn't have inner data (e.g. ``halt``) + - ``new_*``: Creates a new ``Instruction`` for the variant. + """ + + def __new__( + cls, + instruction: Union[ + Arithmetic, + Calibration, + Capture, + BinaryLogic, + CircuitDefinition, + Convert, + Comparison, + Declaration, + Delay, + Exchange, + Fence, + FrameDefinition, + Gate, + GateDefinition, + Include, + Jump, + JumpWhen, + JumpUnless, + Label, + Load, + MeasureCalibrationDefinition, + Measurement, + Move, + Pragma, + Pulse, + RawCapture, + Reset, + SetFrequency, + SetPhase, + SetScale, + ShiftFrequency, + ShiftPhase, + Store, + SwapPhases, + UnaryLogic, + WaveformDefinition, + ], + ) -> Self: + """Returns a new ``Instruction`` from the given inner data.""" + def is_quil_t(self) -> bool: + """Returns ``True`` if the instruction is a Quil-T instruction, ``False`` otherwise.""" + ... + def inner( + self, + ) -> Union[ + Arithmetic, + Calibration, + Capture, + BinaryLogic, + CircuitDefinition, + Convert, + Comparison, + Declaration, + Delay, + Exchange, + Fence, + FrameDefinition, + Gate, + GateDefinition, + Include, + Jump, + JumpWhen, + JumpUnless, + Label, + Load, + MeasureCalibrationDefinition, + Measurement, + Move, + Pragma, + Pulse, + RawCapture, + Reset, + SetFrequency, + SetPhase, + SetScale, + ShiftFrequency, + ShiftPhase, + Store, + SwapPhases, + UnaryLogic, + WaveformDefinition, + ]: + """Returns the inner value of the variant. Raises a ``RuntimeError`` if inner data doesn't exist.""" + ... + def is_arithmetic(self) -> bool: ... + def is_binary_logic(self) -> bool: ... + def is_calibration_definition(self) -> bool: ... + def is_capture(self) -> bool: ... + def is_circuit_definition(self) -> bool: ... + def is_convert(self) -> bool: ... + def is_comparison(self) -> bool: ... + def is_declaration(self) -> bool: ... + def is_delay(self) -> bool: ... + def is_exchange(self) -> bool: ... + def is_fence(self) -> bool: ... + def is_frame_definition(self) -> bool: ... + def is_gate(self) -> bool: ... + def is_gate_definition(self) -> bool: ... + def is_halt(self) -> bool: ... + def is_include(self) -> bool: ... + def is_jump(self) -> bool: ... + def is_jump_when(self) -> bool: ... + def is_jump_unless(self) -> bool: ... + def is_label(self) -> bool: ... + def is_load(self) -> bool: ... + def is_measure_calibration_definition(self) -> bool: ... + def is_measurement(self) -> bool: ... + def is_move(self) -> bool: ... + def is_nop(self) -> bool: ... + def is_pragma(self) -> bool: ... + def is_pulse(self) -> bool: ... + def is_raw_capture(self) -> bool: ... + def is_reset(self) -> bool: ... + def is_set_frequency(self) -> bool: ... + def is_set_phase(self) -> bool: ... + def is_set_scale(self) -> bool: ... + def is_shift_frequency(self) -> bool: ... + def is_shift_phase(self) -> bool: ... + def is_unary_logic(self) -> bool: ... + def is_store(self) -> bool: ... + def is_swap_phases(self) -> bool: ... + def is_waveform_definition(self) -> bool: ... + def is_wait(self) -> bool: ... + @staticmethod + def new_halt() -> Instruction: ... + @staticmethod + def new_nop() -> Instruction: ... + @staticmethod + def new_wait() -> Instruction: ... + @staticmethod + def from_arithmetic(inner: Arithmetic) -> Instruction: ... + @staticmethod + def from_binary_logic(inner: BinaryLogic) -> Instruction: ... + @staticmethod + def from_calibration_definition(inner: Calibration) -> Instruction: ... + @staticmethod + def from_capture(inner: Capture) -> Instruction: ... + @staticmethod + def from_circuit_definition( + inner: CircuitDefinition, + ) -> Instruction: ... + @staticmethod + def from_convert(inner: Convert) -> Instruction: ... + @staticmethod + def from_comparison(inner: Comparison) -> Instruction: ... + @staticmethod + def from_declaration(inner: Declaration) -> Instruction: ... + @staticmethod + def from_delay(inner: Delay) -> Instruction: ... + @staticmethod + def from_exchange(inner: Exchange) -> Instruction: ... + @staticmethod + def from_fence(inner: Fence) -> Instruction: ... + @staticmethod + def from_frame_definition(inner: FrameDefinition) -> Instruction: ... + @staticmethod + def from_gate(inner: Gate) -> Instruction: ... + @staticmethod + def from_gate_definition(inner: GateDefinition) -> Instruction: ... + @staticmethod + def from_include(inner: Include) -> Instruction: ... + @staticmethod + def from_jump(inner: Jump) -> Instruction: ... + @staticmethod + def from_jump_when(inner: JumpWhen) -> Instruction: ... + @staticmethod + def from_jump_unless(inner: JumpUnless) -> Instruction: ... + @staticmethod + def from_label(inner: Label) -> Instruction: ... + @staticmethod + def from_load(inner: Load) -> Instruction: ... + @staticmethod + def from_measure_calibration_definition( + inner: MeasureCalibrationDefinition, + ) -> Instruction: ... + @staticmethod + def from_measurement( + inner: Measurement, + ) -> Instruction: ... + @staticmethod + def from_move(inner: Move) -> Instruction: ... + @staticmethod + def from_pragma(inner: Pragma) -> Instruction: ... + @staticmethod + def from_pulse(inner: Pulse) -> Instruction: ... + @staticmethod + def from_raw_capture(inner: RawCapture) -> Instruction: ... + @staticmethod + def from_set_frequency(inner: SetFrequency) -> Instruction: ... + @staticmethod + def from_set_phase(inner: SetPhase) -> Instruction: ... + @staticmethod + def from_set_scale(inner: SetScale) -> Instruction: ... + @staticmethod + def from_shift_frequency(inner: ShiftFrequency) -> Instruction: ... + @staticmethod + def from_shift_phase(inner: ShiftPhase) -> Instruction: ... + @staticmethod + def from_unary_logic(inner: UnaryLogic) -> Instruction: ... + @staticmethod + def from_store(inner: Store) -> Instruction: ... + @staticmethod + def from_swap_phases(inner: SwapPhases) -> Instruction: ... + @staticmethod + def from_reset(inner: Reset) -> Instruction: ... + @staticmethod + def from_waveform_definition( + inner: WaveformDefinition, + ) -> Instruction: ... + def as_arithmetic(self) -> Optional[Arithmetic]: ... + def to_arithmetic(self) -> Arithmetic: ... + def as_binary_logic(self) -> Optional[BinaryLogic]: ... + def to_binary_logic(self) -> BinaryLogic: ... + def as_convert(self) -> Optional[Convert]: ... + def to_convert(self) -> Convert: ... + def as_comparison(self) -> Optional[Comparison]: ... + def to_comparison(self) -> Comparison: ... + def as_circuit_definition(self) -> Optional[CircuitDefinition]: ... + def to_circuit_definition(self) -> CircuitDefinition: ... + def as_calibration_definition(self) -> Optional[Calibration]: ... + def to_calibration_definition(self) -> Calibration: ... + def as_capture(self) -> Optional[Capture]: ... + def to_capture(self) -> Capture: ... + def as_declaration(self) -> Optional[Declaration]: ... + def to_declaration(self) -> Declaration: ... + def as_delay(self) -> Optional[Delay]: ... + def to_delay(self) -> Delay: ... + def as_exchange(self) -> Optional[Exchange]: ... + def to_exchange(self) -> Exchange: ... + def as_fence(self) -> Optional[Fence]: ... + def to_fence(self) -> Fence: ... + def as_gate(self) -> Optional[Gate]: ... + def to_gate(self) -> Gate: ... + def as_gate_definition(self) -> Optional[GateDefinition]: ... + def to_gate_definition(self) -> GateDefinition: ... + def as_include(self) -> Optional[Include]: ... + def to_include(self) -> Include: ... + def as_jump(self) -> Optional[Jump]: ... + def to_jump(self) -> Jump: ... + def as_jump_when(self) -> Optional[JumpWhen]: ... + def to_jump_when(self) -> JumpWhen: ... + def as_jump_unless(self) -> Optional[JumpUnless]: ... + def to_jump_unless(self) -> JumpUnless: ... + def as_label(self) -> Optional[Label]: ... + def to_label(self) -> Label: ... + def as_load(self) -> Optional[Load]: ... + def to_load(self) -> Load: ... + def as_frame_definition(self) -> Optional[FrameDefinition]: ... + def to_frame_definition(self) -> FrameDefinition: ... + def as_measure_calibration_definition( + self, + ) -> Optional[MeasureCalibrationDefinition]: ... + def to_measure_calibration_definition(self) -> MeasureCalibrationDefinition: ... + def as_measurement( + self, + ) -> Optional[Measurement]: ... + def to_measurement(self) -> Measurement: ... + def as_move(self) -> Optional[Move]: ... + def to_move(self) -> Move: ... + def as_pragma(self) -> Optional[Pragma]: ... + def to_pragma(self) -> Pragma: ... + def as_pulse(self) -> Optional[Pulse]: ... + def to_pulse(self) -> Pulse: ... + def as_raw_capture(self) -> Optional[RawCapture]: ... + def to_raw_capture(self) -> RawCapture: ... + def as_reset(self) -> Optional[Reset]: ... + def to_reset(self) -> Reset: ... + def as_set_frequency(self) -> Optional[SetFrequency]: ... + def to_set_frequency(self) -> SetFrequency: ... + def as_set_phase(self) -> Optional[SetPhase]: ... + def to_set_phase(self) -> SetPhase: ... + def as_set_scale(self) -> Optional[SetScale]: ... + def to_set_scale(self) -> SetScale: ... + def as_shift_frequency(self) -> Optional[ShiftFrequency]: ... + def to_shift_frequency(self) -> ShiftFrequency: ... + def as_shift_phase(self) -> Optional[ShiftPhase]: ... + def to_shift_phase(self) -> ShiftPhase: ... + def as_unary_logic(self) -> Optional[UnaryLogic]: ... + def to_unary_logic(self) -> UnaryLogic: ... + def as_store(self) -> Optional[Store]: ... + def to_store(self) -> Store: ... + def as_swap_phases(self) -> Optional[SwapPhases]: ... + def to_swap_phases(self) -> SwapPhases: ... + def as_waveform_definition(self) -> Optional[WaveformDefinition]: ... + def to_waveform_definition(self) -> WaveformDefinition: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. Raises an exception if the instruction can't be converted to valid Quil.""" + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +@final +class ArithmeticOperand: + """A Quil arithmetic operand. + + # Variants: + - ``literal_integer``: An integer literal. + - ``literal_real``: A real numbered literal. + - ``memory_reference``: A Quil ``MemoryReference``. + + Methods (for each variant): + - ``is_*``: Returns ``True`` if the operand is that variant, ``False`` otherwise. + - ``as_*``: Returns the inner data if it is the given variant, ``None`` otherwise. + - ``to_*``: Returns the inner data if it is the given variant, raises ``ValueError`` otherwise. + - ``from_*``: Creates a new ``ArithmeticOperand`` of the given variant from an instance of the inner type. + """ + + def inner(self) -> Union[int, float, MemoryReference]: + """Returns the inner value of the variant. Raises a ``RuntimeError`` if inner data doesn't exist.""" + ... + def is_literal_integer(self) -> bool: ... + def is_literal_real(self) -> bool: ... + def is_memory_reference(self) -> bool: ... + def as_literal_integer(self) -> Optional[int]: ... + def as_literal_real(self) -> Optional[float]: ... + def as_memory_reference(self) -> Optional[MemoryReference]: ... + def to_literal_integer(self) -> int: ... + def to_literal_real(self) -> float: ... + def to_memory_reference(self) -> MemoryReference: ... + @staticmethod + def from_literal_integer(inner: int) -> ArithmeticOperand: ... + @staticmethod + def from_literal_real(inner: float) -> ArithmeticOperand: ... + @staticmethod + def from_memory_reference( + inner: MemoryReference, + ) -> ArithmeticOperand: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +@final +class ArithmeticOperator(Enum): + Add = "Add" + Subtract = "Subtract" + Divide = "Divide" + Multiply = "Multiply" + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class Arithmetic: + def __new__( + cls, + operator: ArithmeticOperator, + destination: ArithmeticOperand, + source: ArithmeticOperand, + ) -> Self: ... + @property + def operator(self) -> ArithmeticOperator: ... + @operator.setter + def operator(self, operator: ArithmeticOperator) -> None: ... + @property + def destination(self) -> ArithmeticOperand: ... + @destination.setter + def destination(self, operand: ArithmeticOperand) -> None: ... + @property + def source(self) -> ArithmeticOperand: ... + @source.setter + def source(self, operand: ArithmeticOperand) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. Should be used by passing + an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +@final +class BinaryOperand: + """A Quil binary operand. + + # Variants: + - ``literal_integer``: An integer literal. + - ``memory_reference``: A Quil ``MemoryReference``. + + Methods (for each variant): + - ``is_*``: Returns ``True`` if the operand is that variant, ``False`` otherwise. + - ``as_*``: Returns the inner data if it is the given variant, ``None`` otherwise. + - ``to_*``: Returns the inner data if it is the given variant, raises ``ValueError`` otherwise. + - ``from_*``: Creates a new ``BinaryOperand`` of the given variant from an instance of the inner type. + """ + + def inner(self) -> Union[int, MemoryReference]: + """Returns the inner value of the variant. Raises a ``RuntimeError`` if inner data doesn't exist.""" + ... + def is_literal_integer(self) -> bool: ... + def is_memory_reference(self) -> bool: ... + def as_literal_integer(self) -> Optional[int]: ... + def as_memory_reference(self) -> Optional[MemoryReference]: ... + def to_literal_integer(self) -> int: ... + def to_memory_reference(self) -> MemoryReference: ... + @staticmethod + def from_literal_integer(inner: int) -> BinaryOperand: ... + @staticmethod + def from_memory_reference( + inner: MemoryReference, + ) -> BinaryOperand: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +@final +class BinaryOperator(Enum): + And = "AND" + Ior = "IOR" + Xor = "XOR" + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class BinaryOperands: + def __new__( + cls, + memory_reference: MemoryReference, + operand: BinaryOperand, + ) -> Self: ... + @property + def memory_reference(self) -> MemoryReference: ... + @memory_reference.setter + def memory_reference(self, memory_reference: MemoryReference) -> None: ... + @property + def operand(self) -> BinaryOperand: ... + @operand.setter + def operand(self, operand: BinaryOperand) -> None: ... + +class BinaryLogic: + def __new__( + cls, + operator: BinaryOperator, + operands: BinaryOperands, + ) -> Self: ... + @property + def operator(self) -> BinaryOperator: ... + @operator.setter + def operator(self, operator: BinaryOperator) -> None: ... + @property + def operands(self) -> BinaryOperands: ... + @operands.setter + def operands(self, operands: BinaryOperands) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Convert: + def __new__(cls, destination: MemoryReference, source: MemoryReference) -> Self: ... + @property + def destination(self) -> MemoryReference: ... + @destination.setter + def destination(self, destination: MemoryReference) -> None: ... + @property + def source(self) -> MemoryReference: ... + @source.setter + def source(self, source: MemoryReference) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Move: + def __new__(cls, destination: MemoryReference, source: ArithmeticOperand) -> Self: ... + @property + def destination(self) -> MemoryReference: ... + @destination.setter + def destination(self, destination: MemoryReference) -> None: ... + @property + def source(self) -> ArithmeticOperand: ... + @source.setter + def source(self, source: ArithmeticOperand) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Exchange: + def __new__(cls, left: MemoryReference, right: MemoryReference) -> Self: ... + @property + def left(self) -> MemoryReference: ... + @left.setter + def left(self, left: MemoryReference) -> None: ... + @property + def right(self) -> MemoryReference: ... + @right.setter + def right(self, right: MemoryReference) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +@final +class ComparisonOperand: + """A Quil binary operand. + + # Variants: + - ``literal_integer``: An integer literal. + - ``literal_real``: A floating point literal. + - ``memory_reference``: A Quil ``MemoryReference``. + + Methods (for each variant): + - ``is_*``: Returns ``True`` if the operand is that variant, ``False`` otherwise. + - ``as_*``: Returns the inner data if it is the given variant, ``None`` otherwise. + - ``to_*``: Returns the inner data if it is the given variant, raises ``ValueError`` otherwise. + - ``from_*``: Creates a new ``BinaryOperand`` of the given variant from an instance of the inner type. + """ + + def inner(self) -> Union[int, float, MemoryReference]: + """Returns the inner value of the variant.""" + ... + def is_literal_integer(self) -> bool: ... + def is_literal_real(self) -> bool: ... + def is_memory_reference(self) -> bool: ... + def as_literal_integer(self) -> Optional[int]: ... + def as_literal_real(self) -> Optional[float]: ... + def as_memory_reference(self) -> Optional[MemoryReference]: ... + def to_literal_integer(self) -> int: ... + def to_literal_real(self) -> float: ... + def to_memory_reference(self) -> MemoryReference: ... + @staticmethod + def from_literal_integer(inner: int) -> ComparisonOperand: ... + @staticmethod + def from_literal_real(inner: float) -> ComparisonOperand: ... + @staticmethod + def from_memory_reference( + inner: MemoryReference, + ) -> ComparisonOperand: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +@final +class ComparisonOperator(Enum): + Equal = "EQUAL" + GreaterThanOrEqual = "GREATERTHANOREQUAL" + GreaterThan = "GREATERTHAN" + LessThanOrEqual = "GREATERTHANOREQUAL" + LessThan = "LESSTHAN" + +class Comparison: + def __new__( + cls, + operator: ComparisonOperator, + operands: Tuple[MemoryReference, MemoryReference, ComparisonOperand], + ) -> Self: ... + @property + def operator(self) -> ComparisonOperator: ... + @operator.setter + def operator(self, operator: ComparisonOperator) -> None: ... + @property + def operands( + self, + ) -> Tuple[MemoryReference, MemoryReference, ComparisonOperand]: ... + @operands.setter + def operands(self, operands: Tuple[MemoryReference, MemoryReference, ComparisonOperand]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +@final +class UnaryOperator(Enum): + Neg = "NEG" + Not = "NOT" + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class UnaryLogic: + def __new__(cls, operator: UnaryOperator, operand: MemoryReference) -> Self: ... + @property + def operator(self) -> UnaryOperator: ... + @operator.setter + def operator(self, operator: UnaryOperator) -> None: ... + @property + def operand(self) -> MemoryReference: ... + @operand.setter + def operand(self, operand: MemoryReference) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Calibration: + def __new__( + cls, + name: str, + parameters: Sequence[Expression], + qubits: Sequence[Qubit], + instructions: Sequence[Instruction], + modifiers: Sequence[GateModifier], + ) -> Self: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def parameters(self) -> List[Expression]: ... + @parameters.setter + def parameters(self, parameters: Sequence[Expression]) -> None: ... + @property + def qubits(self) -> List[Qubit]: ... + @qubits.setter + def qubits(self, qubits: Sequence[Qubit]) -> None: ... + @property + def instructions(self) -> List[Instruction]: ... + @instructions.setter + def instructions(self, instructions: Sequence[Instruction]) -> None: ... + @property + def modifiers(self) -> List[GateModifier]: ... + @modifiers.setter + def modifiers(self, modifiers: Sequence[GateModifier]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class MeasureCalibrationDefinition: + def __new__( + cls, + qubit: Optional[Qubit], + parameter: str, + instructions: Sequence[Instruction], + ) -> Self: ... + @property + def qubit(self) -> Optional[Qubit]: ... + @qubit.setter + def qubit(self, qubit: Optional[Qubit]) -> None: ... + @property + def parameter(self) -> str: ... + @parameter.setter + def parameter(self, parameter: str) -> None: ... + @property + def instructions(self) -> List[Instruction]: ... + @instructions.setter + def instructions(self, instructions: Sequence[Instruction]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class CircuitDefinition: + def __new__( + cls, + name: str, + parameters: Sequence[str], + qubit_variables: Sequence[str], + instructions: Sequence[Instruction], + ) -> Self: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def parameters(self) -> List[str]: ... + @parameters.setter + def parameters(self, parameters: Sequence[str]) -> None: ... + @property + def qubit_variables(self) -> List[str]: ... + @qubit_variables.setter + def qubit_variables(self, qubit_variables: Sequence[str]) -> None: ... + @property + def instructions(self) -> List[Instruction]: ... + @instructions.setter + def instructions(self, instructions: Sequence[Instruction]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Offset: + def __new__( + cls, + offset: int, + data_type: ScalarType, + ) -> Self: ... + @property + def offset(self) -> int: ... + @offset.setter + def offset(self, offset: int) -> None: ... + @property + def data_type(self) -> ScalarType: ... + @data_type.setter + def data_type(self, data_type: ScalarType) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class Sharing: + def __new__( + cls, + name: str, + offsets: Sequence[Offset], + ) -> Self: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def offsets(self) -> List[Offset]: ... + @offsets.setter + def offsets(self, offsets: Sequence[Offset]) -> None: ... + +class Declaration: + def __new__(cls, name: str, size: Vector, sharing: Optional[Sharing]) -> Self: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def size(self) -> Vector: ... + @size.setter + def size(self, vector: Vector) -> None: ... + @property + def sharing(self) -> Optional[Sharing]: ... + @sharing.setter + def sharing(self, sharing: Optional[Sharing]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Vector: + def __new__(cls, data_type: ScalarType, length: int) -> Self: ... + @property + def data_type(self) -> ScalarType: ... + @data_type.setter + def data_type(self, data_type: ScalarType) -> None: ... + @property + def length(self) -> int: ... + @length.setter + def length(self, data_type: int) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +@final +class ScalarType(Enum): + Bit = "BIT" + Integer = "INTEGER" + Octet = "OCTET" + Real = "REAL" + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +@final +class AttributeValue: + """A frame attribute value. + + # Variants: + - ``string``: A string attribute containing a ``str``. + - ``expression``: An expression attribute containing an ``Expression``. + + Methods (for each variant): + - ``is_*``: Returns ``True`` if the ``AttributeValue`` is that variant, ``False`` otherwise. + - ``as_*``: Returns the inner data if it is the given variant, ``None`` otherwise. + - ``to_*``: Returns the inner data if it is the given variant, raises ``ValueError`` otherwise. + - ``from_*``: Creates a new ``AttributeValue`` of the given variant from an instance of the inner type. + """ + + def inner(self) -> Union[str, Expression]: + """Returns the inner value of the variant. Raises a ``RuntimeError`` if inner data doesn't exist.""" + ... + def is_string(self) -> bool: ... + def is_expression(self) -> bool: ... + @staticmethod + def from_string(inner: str) -> AttributeValue: ... + @staticmethod + def from_expression(inner: Expression) -> AttributeValue: ... + def as_string(self) -> Optional[str]: ... + def to_string(self) -> str: ... + def as_expression(self) -> Optional[Expression]: ... + def to_expression(self) -> Expression: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class FrameDefinition: + def __new__( + cls, + identifier: FrameIdentifier, + attributes: Dict[str, AttributeValue], + ) -> Self: ... + @property + def identifier(self) -> FrameIdentifier: ... + @identifier.setter + def identifier(self, identifier: FrameIdentifier) -> None: ... + @property + def attributes(self) -> Dict[str, AttributeValue]: ... + @attributes.setter + def attributes(self, identifier: Dict[str, AttributeValue]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class FrameIdentifier: + def __new__(cls, name: str, qubits: Sequence[Qubit]) -> Self: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def qubits(self) -> List[Qubit]: ... + @qubits.setter + def qubits(self, qubits: Sequence[Qubit]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class Capture: + def __new__( + cls, + blocking: bool, + frame: FrameIdentifier, + memory_reference: MemoryReference, + waveform: WaveformInvocation, + ) -> Self: ... + @property + def blocking(self) -> bool: ... + @blocking.setter + def blocking(self, blocking: bool) -> None: ... + @property + def frame(self) -> FrameIdentifier: ... + @frame.setter + def frame(self, frame: FrameIdentifier) -> None: ... + @property + def memory_reference(self) -> MemoryReference: ... + @memory_reference.setter + def memory_reference(self, memory_reference: MemoryReference) -> None: ... + @property + def waveform(self) -> WaveformInvocation: ... + @waveform.setter + def waveform(self, waveform: WaveformInvocation) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Pulse: + def __new__( + cls, + blocking: bool, + frame: FrameIdentifier, + waveform: WaveformInvocation, + ) -> Self: ... + @property + def blocking(self) -> bool: ... + @blocking.setter + def blocking(self, blocking: bool) -> None: ... + @property + def frame(self) -> FrameIdentifier: ... + @frame.setter + def frame(self, frame: FrameIdentifier) -> None: ... + @property + def waveform(self) -> WaveformInvocation: ... + @waveform.setter + def waveform(self, waveform: WaveformInvocation) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class RawCapture: + def __new__( + cls, + blocking: bool, + frame: FrameIdentifier, + duration: Expression, + memory_reference: MemoryReference, + ) -> Self: ... + @property + def blocking(self) -> bool: ... + @blocking.setter + def blocking(self, blocking: bool) -> None: ... + @property + def frame(self) -> FrameIdentifier: ... + @frame.setter + def frame(self, frame: FrameIdentifier) -> None: ... + @property + def duration(self) -> Expression: ... + @duration.setter + def duration(self, duration: Expression) -> None: ... + @property + def memory_reference(self) -> MemoryReference: ... + @memory_reference.setter + def memory_reference(self, memory_reference: MemoryReference) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class SetFrequency: + def __new__(cls, frame: FrameIdentifier, frequency: Expression) -> Self: ... + @property + def frame(self) -> FrameIdentifier: ... + @frame.setter + def frame(self, frame: FrameIdentifier) -> None: ... + @property + def frequency(self) -> Expression: ... + @frequency.setter + def frequency(self, frequency: Expression) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class SetPhase: + def __new__(cls, frame: FrameIdentifier, phase: Expression) -> Self: ... + @property + def frame(self) -> FrameIdentifier: ... + @frame.setter + def frame(self, frame: FrameIdentifier) -> None: ... + @property + def phase(self) -> Expression: ... + @phase.setter + def phase(self, phase: Expression) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class SetScale: + def __new__(cls, frame: FrameIdentifier, phase: Expression) -> Self: ... + @property + def frame(self) -> FrameIdentifier: ... + @frame.setter + def frame(self, frame: FrameIdentifier) -> None: ... + @property + def scale(self) -> Expression: ... + @scale.setter + def scale(self, scale: Expression) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class ShiftFrequency: + def __new__(cls, frame: FrameIdentifier, frequency: Expression) -> Self: ... + @property + def frame(self) -> FrameIdentifier: ... + @frame.setter + def frame(self, frame: FrameIdentifier) -> None: ... + @property + def frequency(self) -> Expression: ... + @frequency.setter + def frequency(self, frequency: Expression) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class ShiftPhase: + def __new__(cls, frame: FrameIdentifier, phase: Expression) -> Self: ... + @property + def frame(self) -> FrameIdentifier: ... + @frame.setter + def frame(self, frame: FrameIdentifier) -> None: ... + @property + def phase(self) -> Expression: ... + @phase.setter + def phase(self, phase: Expression) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class SwapPhases: + def __new__(cls, frame_1: FrameIdentifier, frame_2: FrameIdentifier) -> Self: ... + @property + def frame_1(self) -> FrameIdentifier: ... + @frame_1.setter + def frame_1(self, frame_1: FrameIdentifier) -> None: ... + @property + def frame_2(self) -> FrameIdentifier: ... + @frame_2.setter + def frame_2(self, frame_2: FrameIdentifier) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class GateError(ValueError): + """An error that may occur when performing operations on a ``Gate``.""" + + ... + +@final +class GateModifier(Enum): + Controlled = "CONTROLLED" + Dagger = "DAGGER" + Forked = "FORKED" + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class Gate: + def __new__( + cls, + name: str, + parameters: Sequence[Expression], + qubits: Sequence[Qubit], + modifiers: Sequence[GateModifier], + ) -> Self: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def parameters(self) -> List[Expression]: ... + @parameters.setter + def parameters(self, parameters: Sequence[Expression]) -> None: ... + @property + def qubits(self) -> List[Qubit]: ... + @qubits.setter + def qubits(self, qubits: Sequence[Qubit]) -> None: ... + @property + def modifiers(self) -> List[GateModifier]: ... + @modifiers.setter + def modifiers(self, modifiers: Sequence[GateModifier]) -> None: ... + def dagger(self) -> Self: + """Returns a copy of the gate with the ``DAGGER`` modifier added to it.""" + ... + def controlled(self, control_qubit: Qubit) -> Self: + """Returns a copy of the gate with the ``CONTROLLED`` modifier added to it.""" + def forked(self, fork_qubit: Qubit, alt_params: Sequence[Expression]) -> Self: + """Returns a copy of the gate with the ``FORKED`` modifier added to it. + + Raises a ``GateError`` if the number of provided alternate parameters don't + equal the number of existing parameters. + """ + ... + def to_unitary_mut(self, n_qubits: int) -> NDArray[np.complex_]: + """Lift a Gate to the full `n_qubits`-qubit Hilbert space. + + Returns a ``GateError` if any of the parameters of this gate are + non-constant, if any of the qubits are variable, if the name of this + gate is unknown, or if there are an unexpected number of parameters. + """ + ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +@final +class PauliGate(Enum): + I = "I" + X = "X" + Y = "Y" + Z = "Z" + @staticmethod + def parse(input: str) -> PauliGate: + """Parses a ``PauliGate`` from a string. Raises a ``ParseEnumError`` if the string isn't a valid Pauli word.""" + ... + +class PauliTerm: + def __new__( + cls, + arguments: Sequence[Tuple[PauliGate, str]], + expression: Expression, + ) -> Self: ... + @property + def arguments(self) -> List[Tuple[PauliGate, str]]: ... + @arguments.setter + def arguments(self, word: Sequence[Tuple[PauliGate, str]]) -> None: ... + @property + def expression(self) -> Expression: ... + @expression.setter + def expression(self, expression: Expression) -> None: ... + +class PauliSum: + def __new__(cls, arguments: Sequence[str], terms: Sequence[PauliTerm]) -> Self: ... + @property + def arguments(self) -> List[str]: ... + @arguments.setter + def arguments(self, arguments: Sequence[str]) -> None: ... + @property + def terms(self) -> List[PauliTerm]: ... + @terms.setter + def terms(self, terms: Sequence[PauliTerm]) -> None: ... + +@final +class GateSpecification: + """A specification for a gate definition. + + # Variants: + - ``matrix``: A gate specified by a matrix of ``Expression``s representing a unitary operation. + - ``permutation``: A gate specified by a vector of integers that defines a permutation. + + Methods (for each variant): + - is_*: Returns ``True`` if the inner type is of that variant. + - as_*: Returns the inner data if it is the given variant, ``None`` otherwise. + - to_*: Returns the inner data if it is the given variant, raises ``ValueError`` otherwise. + - from_*: Creates a new ``GateSpecification`` using an instance of the inner type for the variant. + """ + + def inner(self) -> Union[List[List[Expression]], List[int], PauliSum]: + """Returns the inner value of the variant. Raises a ``RuntimeError`` if inner data doesn't exist.""" + ... + def is_matrix(self) -> bool: ... + def is_permutation(self) -> bool: ... + def is_pauli_sum(self) -> bool: ... + def as_matrix(self) -> Optional[List[List[Expression]]]: ... + def to_matrix(self) -> List[List[Expression]]: ... + def as_permutation(self) -> Optional[List[int]]: ... + def to_permutation(self) -> List[int]: ... + def as_pauli_sum(self) -> Optional[PauliSum]: ... + def to_pauli_sum(self) -> PauliSum: ... + @staticmethod + def from_matrix(inner: Sequence[Sequence[Expression]]) -> GateSpecification: ... + @staticmethod + def from_permutation(inner: Sequence[int]) -> GateSpecification: ... + @staticmethod + def from_pauli_sum(inner: PauliSum) -> GateSpecification: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class GateDefinition: + def __new__( + cls, + name: str, + parameters: Sequence[str], + specification: GateSpecification, + ) -> Self: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def parameters(self) -> List[str]: ... + @parameters.setter + def parameters(self, parameters: Sequence[str]) -> None: ... + @property + def specification(self) -> GateSpecification: ... + @specification.setter + def specification(self, specification: GateSpecification) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +@final +class Qubit: + """A Qubit. + + # Variants: + - ``fixed``: A qubit represented as a fixed integer index. + - ``variable``: A qubit represented by a name. + + Methods (for each variant): + - ``is_*``: Returns ``True`` if the inner type is of that variant. + - ``as_*``: Returns the inner data if it is the given variant, ``None`` otherwise. + - ``to_*``: Returns the inner data if it is the given variant, raises ``ValueError`` otherwise. + - ``from_*``: Creates a new ``Qubit`` using an instance of the inner type for the variant. + """ + + def inner(self) -> Union[int, str]: + """Returns the inner value of the variant. Raises a ``RuntimeError`` if inner data doesn't exist.""" + ... + def is_fixed(self) -> bool: ... + def is_variable(self) -> bool: ... + def is_placeholder(self) -> bool: ... + def as_fixed(self) -> Optional[int]: ... + def to_fixed(self) -> int: ... + def as_variable(self) -> Optional[str]: ... + def to_variable(self) -> str: ... + def as_placeholder(self) -> Optional[QubitPlaceholder]: ... + def to_placeholder(self) -> QubitPlaceholder: ... + @staticmethod + def from_fixed(inner: int) -> Qubit: ... + @staticmethod + def from_variable(inner: str) -> Qubit: ... + @staticmethod + def from_placeholder(inner: QubitPlaceholder) -> Qubit: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class QubitPlaceholder: + """A qubit that can be used as a placeholder. + + Placeholders must be resolved before converting a program to valid Quil. See ``quil.program.Program#resolve_placeholders``. + """ + + def __new__(cls) -> Self: ... + def __lt__(self, other: QubitPlaceholder) -> bool: ... + +class Reset: + def __new__(cls, qubit: Optional[Qubit]) -> Self: ... + @property + def qubit(self) -> Optional[Qubit]: ... + @qubit.setter + def qubit(self, qubit: Optional[Qubit]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Delay: + def __new__(cls, duration: Expression, frame_names: Sequence[str], qubits: Sequence[Qubit]) -> Self: ... + @property + def duration(self) -> Expression: ... + @duration.setter + def duration(self, duration: Expression) -> None: ... + @property + def frame_names(self) -> List[str]: ... + @frame_names.setter + def frame_names(self, frame_names: Sequence[str]) -> None: ... + @property + def qubits(self) -> List[Qubit]: ... + @qubits.setter + def qubits(self, qubits: Sequence[Qubit]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Fence: + def __new__(cls, qubits: Sequence[Qubit]) -> Self: ... + @property + def qubits(self) -> List[Qubit]: ... + @qubits.setter + def qubits(self, qubits: Sequence[Qubit]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +@final +class PragmaArgument: + """A PRAGMA argument. + + Variants: + - ``identifier``: A Pragma argument defined by a Quil identifier + - ``integer``: A Pragma argument defined by an integer + + Methods (for each variant): + - ``is_*``: Returns ``True`` if the inner type is of that variant. + - ``as_*``: Returns the inner data if it is the given variant, ``None`` otherwise. + - ``to_*``: Returns the inner data if it is the given variant, raises ``ValueError`` otherwise. + - ``from_*``: Creates a new ``PragmaArgument`` using an instance of the inner type for the variant. + """ + + def inner(self) -> Union[str, int]: + """Returns the inner value of the variant. Raises a ``RuntimeError`` if inner data doesn't exist.""" + ... + def is_identifier(self) -> bool: ... + def is_integer(self) -> bool: ... + def as_identifier(self) -> Optional[str]: ... + def as_integer(self) -> Optional[int]: ... + def to_identifier(self) -> str: ... + def to_integer(self) -> int: ... + @staticmethod + def from_identifier(inner: str) -> PragmaArgument: ... + @staticmethod + def from_integer(inner: int) -> PragmaArgument: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class Include: + def __new__(cls, filename: str) -> Self: ... + @property + def filename(self) -> str: ... + @filename.setter + def filename(self, filename: str) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Pragma: + def __new__(cls, name: str, arguments: Sequence[PragmaArgument], data: Optional[str]) -> Self: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def arguments(self) -> List[PragmaArgument]: ... + @arguments.setter + def arguments(self, arguments: Sequence[PragmaArgument]) -> None: ... + @property + def data(self) -> Optional[str]: ... + @data.setter + def data(self, data: Optional[str]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Measurement: + def __new__(cls, qubit: Qubit, target: Optional[MemoryReference]) -> Self: ... + @property + def qubit(self) -> Qubit: ... + @qubit.setter + def qubit(self, qubit: Qubit) -> None: ... + @property + def target(self) -> Optional[MemoryReference]: ... + @target.setter + def target(self, target: Optional[MemoryReference]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class ParseMemoryReferenceError(ValueError): + """Errors that may occur while parsing a ``MemoryReference``.""" + +class MemoryReference: + def __new__(cls, name: str, index: int) -> Self: ... + @staticmethod + def parse(input: str) -> MemoryReference: + """Parses a ``MemoryReference`` from a string. + + Raises a ``ParseMemoryReference`` error if the string isn't a valid Quil memory reference. + """ + ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def index(self) -> int: ... + @index.setter + def index(self, index: int) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class Load: + def __new__(cls, destination: MemoryReference, source: str, offset: MemoryReference) -> Self: ... + @property + def destination(self) -> MemoryReference: ... + @destination.setter + def destination(self, destination: MemoryReference) -> None: ... + @property + def source(self) -> str: ... + @source.setter + def source(self, source: str) -> None: ... + @property + def offset(self) -> MemoryReference: ... + @offset.setter + def offset(self, offset: MemoryReference) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Store: + def __new__(cls, destination: str, offset: MemoryReference, source: ArithmeticOperand) -> Self: ... + @property + def destination(self) -> str: ... + @destination.setter + def destination(self, destination: str) -> None: ... + @property + def offset(self) -> MemoryReference: ... + @offset.setter + def offset(self, offset: MemoryReference) -> None: ... + @property + def source(self) -> ArithmeticOperand: ... + @source.setter + def source(self, source: ArithmeticOperand) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class Waveform: + def __new__(cls, matrix: Sequence[Expression], parameters: Sequence[str]) -> Self: ... + @property + def matrix(self) -> List[Expression]: ... + @matrix.setter + def matrix(self, matrix: Sequence[Expression]) -> None: ... + @property + def parameters(self) -> List[str]: ... + @parameters.setter + def parameters(self, parameters: Sequence[str]) -> None: ... + +class WaveformDefinition: + def __new__(cls, name: str, definition: Waveform) -> Self: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def definition(self) -> Waveform: ... + @definition.setter + def definition(self, definition: Waveform) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class WaveformInvocation: + def __new__(cls, name: str, parameters: Dict[str, Expression]) -> Self: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def parameters(self) -> Dict[str, Expression]: ... + @parameters.setter + def parameters(self, parameters: Dict[str, Expression]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class Label: + def __new__(cls, target: Target) -> Self: ... + @property + def target(self) -> Target: ... + @target.setter + def target(self, target: Target) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +@final +class Target: + """Represents a Quil target. + + # Variants: + - ``fixed``: A fixed target defined by a Quil identifier + - ``placeholder``: A placeholder target that can be assigned a new name at a later time. + + Methods (for each variant): + - ``is_*``: Returns ``True`` if the inner type is of that variant. + - ``as_*``: Returns the inner data if it is the given variant, ``None`` otherwise. + - ``to_*``: Returns the inner data if it is the given variant, raises ``ValueError`` otherwise. + - ``from_*``: Creates a new ``PragmaArgument`` using an instance of the inner type for the variant. + """ + + def __new__(cls, inner: Union[str, TargetPlaceholder]) -> Target: ... + @staticmethod + def from_fixed(inner: str) -> Target: ... + @staticmethod + def from_placeholder(inner: TargetPlaceholder) -> Target: ... + def is_fixed(self) -> bool: ... + def is_placeholder(self) -> bool: ... + def as_fixed(self) -> Optional[str]: ... + def as_placeholder(self) -> Optional[TargetPlaceholder]: ... + def to_fixed(self) -> str: ... + def to_placeholder(self) -> TargetPlaceholder: ... + def inner(self) -> Union[str, TargetPlaceholder]: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + +class TargetPlaceholder: + """A placeholder target that must be assigned a fixed name before creating a program with valid quil. + + See ``quil.program.Program#resolve_placeholders`` for more information. + """ + + def __new__(cls, base_target: str) -> Self: ... + @property + def base_label(self) -> str: ... + +class Jump: + def __new__(cls, target: Target) -> Self: ... + @property + def target(self) -> Target: ... + @target.setter + def target(self, target: Target) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class JumpWhen: + def __new__(cls, target: Target, condition: MemoryReference) -> Self: ... + @property + def target(self) -> Target: ... + @target.setter + def target(self, target: Target) -> None: ... + @property + def condition(self) -> MemoryReference: ... + @condition.setter + def condition(self, condition: MemoryReference) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + +class JumpUnless: + def __new__(cls, target: Target, condition: MemoryReference) -> Self: ... + @property + def target(self) -> Target: ... + @target.setter + def target(self, target: Target) -> None: ... + @property + def condition(self) -> MemoryReference: ... + @condition.setter + def condition(self, condition: MemoryReference) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" diff --git a/quil-py/quil/program/__init__.py b/quil-py/quil/program/__init__.py new file mode 100644 index 00000000..bbf0cda4 --- /dev/null +++ b/quil-py/quil/program/__init__.py @@ -0,0 +1,3 @@ +"""The `quil.program` module contains classes for constructing and representing a Quil program.""" + +from quil.program import * diff --git a/quil-py/quil/program/__init__.pyi b/quil-py/quil/program/__init__.pyi new file mode 100644 index 00000000..5478a841 --- /dev/null +++ b/quil-py/quil/program/__init__.pyi @@ -0,0 +1,414 @@ +from typing import Callable, Dict, FrozenSet, List, Optional, Sequence, Set, final + +import numpy as np +from numpy.typing import NDArray +from typing_extensions import Self + +from quil.instructions import ( + AttributeValue, + Calibration, + Declaration, + FrameIdentifier, + Gate, + GateDefinition, + Instruction, + MeasureCalibrationDefinition, + Measurement, + MemoryReference, + Qubit, + QubitPlaceholder, + Sharing, + Target, + TargetPlaceholder, + Vector, + Waveform, +) + +@final +class Program: + @staticmethod + def __new__(cls) -> "Program": ... + @property + def body_instructions(self) -> List[Instruction]: ... + @property + def calibrations(self) -> CalibrationSet: ... + @calibrations.setter + def calibrations(self, calibration_set: CalibrationSet): ... + @property + def waveforms(self) -> Dict[str, Waveform]: ... + @waveforms.setter + def waveforms(self, waveforms: Dict[str, Waveform]): ... + @property + def gate_definitions(self) -> Dict[str, GateDefinition]: ... + @gate_definitions.setter + def gate_definitions(self, gate_definitions: Dict[str, GateDefinition]): ... + @property + def frames(self) -> FrameSet: ... + @frames.setter + def frames(self, frames: FrameSet): ... + @property + def memory_regions(self) -> Dict[str, MemoryRegion]: ... + @memory_regions.setter + def memory_regions(self, memory_regions: Dict[str, MemoryRegion]): ... + @property + def declarations(self) -> Dict[str, Declaration]: ... + def dagger(self) -> "Program": + """Creates a new conjugate transpose of the ``Program`` by reversing the order of gate instructions and applying the DAGGER modifier to each. + + Raises a ``GateError`` if any of the instructions in the program are not a ``Gate` + """ + ... + def expand_calibrations(self) -> "Program": + """Expand any instructions in the program which have a matching calibration, leaving the others unchanged. + + Recurses though each instruction while ensuring there is no cycle in the expansion graph (i.e. no calibration + expands directly or indirectly into itself) + """ + ... + def into_simplified(self) -> "Program": + """Simplify this program into a new `Program` which contains only instructions and definitions which are executed; effectively, perform dead code removal. + + Removes: + - All calibrations, following calibration expansion + - Frame definitions which are not used by any instruction such as `PULSE` or `CAPTURE` + - Waveform definitions which are not used by any instruction + + When a valid program is simplified, it remains valid. + """ + ... + def get_used_qubits(self) -> Set[Qubit]: + """Returns a set consisting of every Qubit that is used in the program.""" + ... + def add_instruction(self, instruction: Instruction): + """Add an instruction to the end of the program.""" + ... + def add_instructions(self, instructions: Sequence[Instruction]): + """Add a list of instructions to the end of the program.""" + ... + @staticmethod + def parse(input: str) -> "Program": + """Parses the given Quil string and returns a new ``Program``. + + Raises a ``ProgramError`` if the given string isn't valid Quil. + """ + def to_instructions(self) -> Sequence[Instruction]: ... + def to_unitary(self, n_qubits: int) -> NDArray[np.complex_]: ... + def copy(self) -> "Program": + """Creates a clone of this ``Program``.""" + ... + def clone_without_body_instructions(self) -> "Program": + """Creates a clone of this ``Program`` with an empty body instructions list.""" + def __add__(self, rhs: Program) -> Program: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def filter_instructions(self, predicate: Callable[[Instruction], bool]) -> "Program": + """Return a new ``Program`` containing only the instructions for which ``predicate`` returns ``True``.""" + ... + def wrap_in_loop( + self, loop_count_reference: MemoryReference, start_target: Target, end_target: Target, iterations: int + ) -> "Program": + """Return a copy of the `Program` wrapped in a loop that repeats ``iterations`` times. + + The loop is constructed by wrapping the body of the program in classical Quil instructions. + The given ``loop_count_reference`` must refer to an INTEGER memory region. The value at the + reference given will be set to ``iterations`` and decremented in the loop. The loop will + terminate when the reference reaches 0. For this reason your program should not itself + modify the value at the reference unless you intend to modify the remaining number of + iterations (i.e. to break the loop). + + The given ``start_target`` and ``end_target`` will be used as the entry and exit points for + the loop, respectively. You should provide unique `quil.instructions.Target`s that won't be + used elsewhere in the program. + + If `iterations` is 0, then a copy of the program is returned without any changes. Raises a + `TypeError` if `iterations` is negative. + """ + ... + def resolve_placeholders(self) -> None: + """Resolve ``TargetPlaceholder``s and ``QubitPlaceholder``s within the program using default resolvers. + + The default resolver will be used to 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. + """ + ... + def resolve_placeholders_with_custom_resolvers( + self, + *, + target_resolver: Optional[Callable[[TargetPlaceholder], Optional[str]]] = None, + qubit_resolver: Optional[Callable[[QubitPlaceholder], Optional[int]]] = None, + ): + """Resolve ``TargetPlaceholder``s and ``QubitPlaceholder``s within the program. + + The resolved values will remain unique to that placeholder within the scope of the program. + + If you provide ``target_resolver`` and/or ``qubit_resolver``, those 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. + """ + ... + def control_flow_graph(self) -> "ControlFlowGraph": + """Return the `control flow graph`_ of the program. + + .. _control flow graph: https://en.wikipedia.org/wiki/Control-flow_graph + """ + +class BasicBlock: + def __new__(cls, instance: "BasicBlock") -> Self: + """Create a new instance of a `BasicBlock` (or a subclass) using an existing instance.""" + + def as_schedule_seconds(self, program: Program) -> ScheduleSeconds: + """Return the ``ScheduleSeconds`` representing the timing of the instructions within the block. + + * Expanding each instruction within the block using the program's calibration definitions + * Resolving the `ScheduleSeconds` of the expanded instructions + * Mapping calibrated instructions back to the original instructions within this block, such that the + block's instruction is represented as a timespan encompassing all of its expanded instructions + + :param program: The program containing the calibrations to be used to schedule this block. Generally, + this should be the program from which the block was extracted. + + Important note: If the basic block contains gates, the program must contain corresponding `DEFCAL`s for those gates. + Gates do not inherently have durations, but rather inherit them from the `PULSE`, `CAPTURE`, `DELAY`, + and other instructions within their calibrations. Without a calibration, a gate's duration cannot be computed. + + The following example demonstrates construction of such a schedule for a simple program without explicit control + flow (and thus with only one basic block): + + .. example-code:: + + .. code-block:: python + + from quil.program import Program + + program = Program.parse("CZ 0 1; CZ 0 2") + + print(program.to_quil()) + + control_flow_graph = program.control_flow_graph() + assert control_flow_graph.has_dynamic_control_flow() == False + + basic_blocks = control_flow_graph.basic_blocks() + assert len(basic_blocks) == 1 + + schedule = blocks[0].as_schedule_seconds(program) + print(f"Duration = {schedule.duration()}") + + print(schedule.items()) + + + Note: when an instruction is expanded, the "time" of that original instruction includes + the times of all of the resulting instructions. This may cause gate times to be + longer than a user might expect. + + To understand why, consider a program like this: + + .. example-code:: + + .. code-block:: text + + # One-qubit frame + DEFFRAME 0 "a": + ATTRIBUTE: 1 + + # Two-qubit frame + DEFFRAME 0 1 "b": + ATTRIBUTE: 1 + + DEFCAL A 0: + PULSE 0 "a" flat(duration: 1.0) + + DEFCAL B 0 1: + FENCE 1 + PULSE 0 1 "b" flat(duration: 1.0) + + A 0 + B 0 1 + + `B 0` will be scheduled from time 0 to time 2, because its inner `FENCE` is scheduled for time 0. + This may be unexpected if the user expects to see only the timing of the inner `PULSE`. + """ + def gate_depth(self, gate_minimum_qubit_count: int) -> int: + """Returns the length of the longest path from an initial instruction (one with no prerequisite instructions) to a final instruction (one with no dependent instructions), where the length of a path is the number of gate instructions in the path. + + :param gate_minimum_qubit_count: The minimum number of qubits in a gate for it to be counted in the depth. + """ + def label(self) -> Optional[Target]: + """Return the label of the block, if any. This is used to target this block in control flow.""" + def instructions(self) -> List[Instruction]: + """Return a list of the instructions in the block, in order of definition. + + This does not include the label or terminator instructions. + """ + def terminator(self) -> Optional[Instruction]: + """Return the control flow terminator instruction of the block, if any. + + If this is ``None``, the implicit behavior is to "continue" to the subsequent block. + """ + +@final +class CalibrationSet: + @staticmethod + def __new__( + cls, + calibrations: Sequence[Calibration], + measure_calibration_definitions: Sequence[MeasureCalibrationDefinition], + ) -> "CalibrationSet": ... + @property + def calibrations(self) -> List[Calibration]: ... + @property + def measure_calibrations(self) -> List[MeasureCalibrationDefinition]: ... + def expand(self, instruction: Instruction, previous_calibrations: Sequence[Instruction]) -> List[Instruction]: + """Given an instruction, return the instructions to which it is expanded if there is a match. + + Recursively calibrate instructions, returning an error if a calibration directly or indirectly + expands into itself. + """ + ... + + def get_match_for_measurement(self, measurement: Measurement) -> Optional[MeasureCalibrationDefinition]: + """Returns the last-specified ``MeasureCalibrationDefinition`` that matches the target qubit (if any), or otherwise the last-specified one that specified no qubit.""" + ... + def get_match_for_gate(self, gate: Gate) -> Optional[Calibration]: + """Return the final calibration which matches the gate per the QuilT specification. + + A calibration matches a gate if: + 1. It has the same name + 2. It has the same modifiers + 3. It has the same qubit count (any mix of fixed & variable) + 4. It has the same parameter count (both specified and unspecified) + 5. All fixed qubits in the calibration definition match those in the gate + 6. All specified parameters in the calibration definition match those in the gate + """ + def __len__(self) -> int: ... + def is_empty(self) -> bool: + """Returns ``True`` if the ``CalibrationSet`` contains no data.""" + ... + def insert_calibration(self, calibration: Calibration) -> Optional[Calibration]: + """Insert another ``Calibration`` (`DEFCAL`) to the set. + + If a calibration with the same name already exists, it is overwritten and this + function returns the previous calibration. Otherwise, None is returned. + """ + ... + def insert_measurement_calibration( + self, calibration: MeasureCalibrationDefinition + ) -> Optional[MeasureCalibrationDefinition]: + """Add another ``MeasureCalibrationDefinition`` (`DEFCAL MEASURE`) to the set. + + If a calibration with the same name already exists, it is overwritten and this + function returns the previous calibration. Otherwise, None is returned. + """ + def extend(self, other: CalibrationSet): + """Append another [`CalibrationSet`] onto this one.""" + ... + def to_instructions(self): + """Return the Quil instructions which describe the contained calibrations.""" + ... + +class ScheduleSecondsItem: + """A single item within a fixed schedule, representing a single instruction within a basic block.""" + + @property + def instruction_index(self) -> int: + """The index of the instruction within the basic block.""" + @property + def time_span(self) -> TimeSpanSeconds: + """The time span during which the instruction is scheduled.""" + +class ControlFlowGraph: + """Representation of a control flow graph (CFG) for a Quil program. + + The CFG is a directed graph where each node is a basic block and each edge is a control flow + transition between two basic blocks. + """ + def __new__(cls, instance: "ControlFlowGraph") -> Self: + """Create a new instance of a `ControlFlowGraph` (or a subclass) using an existing instance.""" + + def has_dynamic_control_flow(self) -> bool: + """Return ``True`` if the program has dynamic control flow, i.e. contains a conditional branch instruction. + + ``False`` does not imply that there is only one basic block in the program. Multiple basic blocks may have + non-conditional control flow among them, in which the execution order is deterministic and does not depend + on program state. This may be a sequence of basic blocks with fixed `JUMP`s or without explicit terminators. + """ + def basic_blocks(self) -> List["BasicBlock"]: + """Return a list of all the basic blocks in the control flow graph, in order of definition.""" + +class ScheduleSeconds: + def items(self) -> List[ScheduleSecondsItem]: + """All the items in the schedule, in unspecified order.""" + def duration(self) -> float: + """The duration of the schedule, in seconds. + + This is the maximum of the end time of all the items. + """ + +class TimeSpanSeconds: + """Representation of a time span in seconds.""" + + @property + def start(self) -> float: + """The start time of the time span, in seconds. + + This is relative to the start of the scheduling context (such as the basic block). + """ + @property + def duration(self) -> float: + """The duration of the time span, in seconds.""" + @property + def end(self) -> float: + """The end time of the time span, in seconds. + + This is the sum of the start time and duration. + """ + +@final +class FrameSet: + @staticmethod + def __new__(cls) -> "FrameSet": ... + def get(self, identifier: FrameIdentifier) -> Optional[Dict[str, AttributeValue]]: + """Retrieve the attributes of a frame by its identifier.""" + ... + def get_keys(self) -> List[FrameIdentifier]: + """Return a list of all ``FrameIdentifier``s described by this ``FrameSet``.""" + ... + def insert(self, identifier: FrameIdentifier, attributes: Dict[str, AttributeValue]): + """Insert a new ``FrameIdentifier``, overwriting any existing one.""" + ... + def merge(self, other: FrameSet): + """Merge another ``FrameSet`` into this one, overwriting any existing keys.""" + def intersection(self, identifiers: FrozenSet[FrameIdentifier]) -> "FrameSet": + """Return a new ``FrameSet`` which describes only the given ``FrameIdentifier``s.""" + ... + def is_empty(self) -> bool: + """Returns ``True`` if this ``FrameSet`` defines no frames.""" + ... + def to_instructions(self) -> List[Instruction]: + """Return the Quil instructions which define the contained frames.""" + ... + def get_all_frames(self) -> Dict[FrameIdentifier, Dict[str, AttributeValue]]: ... + +class MemoryRegion: + @staticmethod + def __new__(cls, size: Vector, sharing: Optional[Sharing]) -> "MemoryRegion": ... + @property + def size(self) -> Vector: ... + @size.setter + def size(self, size: Vector): ... + @property + def sharing(self) -> Optional[Sharing]: ... + @sharing.setter + def sharing(self, sharing: Optional[Sharing]): ... diff --git a/quil-py/quil/py.typed b/quil-py/quil/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/quil-py/quil/validation/__init__.py b/quil-py/quil/validation/__init__.py new file mode 100644 index 00000000..9a7d29a9 --- /dev/null +++ b/quil-py/quil/validation/__init__.py @@ -0,0 +1,3 @@ +"""The `quil.validation` module contains classes and functions that can validate components of a Quil program or instruction.""" + +from quil.validation import * diff --git a/quil-py/quil/validation/__init__.pyi b/quil-py/quil/validation/__init__.pyi new file mode 100644 index 00000000..bd850da7 --- /dev/null +++ b/quil-py/quil/validation/__init__.pyi @@ -0,0 +1 @@ +from . import identifier as identifier diff --git a/quil-py/quil/validation/identifier.py b/quil-py/quil/validation/identifier.py new file mode 100644 index 00000000..e7271869 --- /dev/null +++ b/quil-py/quil/validation/identifier.py @@ -0,0 +1 @@ +from quil.validation.identifier import * diff --git a/quil-py/quil/validation/identifier.pyi b/quil-py/quil/validation/identifier.pyi new file mode 100644 index 00000000..16950278 --- /dev/null +++ b/quil-py/quil/validation/identifier.pyi @@ -0,0 +1,12 @@ +class IdentifierValidationError(ValueError): + """Errors that may occur when validating a Quil identifier.""" + + ... + +def validate_identifier(ident: str): + """Raises an ``IdentifierValidationError` if ident isn't a valid Quil identifier.""" + ... + +def validate_user_identifier(ident: str): + """Raises an ``IdentifierValidationError` if ident is reserved keyword or isn't a valid Quil identifier.""" + ... diff --git a/quil-py/src/expression.rs b/quil-py/src/expression.rs new file mode 100644 index 00000000..60a60f96 --- /dev/null +++ b/quil-py/src/expression.rs @@ -0,0 +1,230 @@ +use std::collections::HashMap; + +use quil_rs::expression::{ + Expression, ExpressionFunction, FunctionCallExpression, InfixExpression, InfixOperator, + PrefixExpression, PrefixOperator, +}; + +use rigetti_pyo3::{ + create_init_submodule, impl_from_str, impl_hash, impl_parse, impl_repr, impl_str, + num_complex::Complex64, + py_wrap_data_struct, py_wrap_error, py_wrap_simple_enum, py_wrap_union_enum, + pyo3::{ + exceptions::PyValueError, + pymethods, + types::{PyComplex, PyString}, + Py, PyResult, Python, + }, + wrap_error, PyTryFrom, PyWrapper, PyWrapperMut, ToPython, ToPythonError, +}; + +use crate::{impl_eq, impl_to_quil, instruction::PyMemoryReference}; + +wrap_error!(RustEvaluationError(quil_rs::expression::EvaluationError)); +py_wrap_error!(quil, RustEvaluationError, EvaluationError, PyValueError); +wrap_error!(RustParseExpressionError(quil_rs::program::ParseProgramError)); +py_wrap_error!( + quil, + RustParseExpressionError, + ParseExpressionError, + PyValueError +); + +py_wrap_union_enum! { + #[derive(Debug, Hash, PartialEq, Eq)] + #[pyo3(module="quil.expression")] + PyExpression(Expression) as "Expression" { + address: Address => PyMemoryReference, + function_call: FunctionCall => PyFunctionCallExpression, + infix: Infix => PyInfixExpression, + number: Number => Py, + pi: PiConstant, + prefix: Prefix => PyPrefixExpression, + variable: Variable => Py + } +} +impl_repr!(PyExpression); +impl_to_quil!(PyExpression); +impl_from_str!(PyExpression, RustParseExpressionError); +impl_hash!(PyExpression); +impl_parse!(PyExpression); +impl_eq!(PyExpression); + +#[pymethods] +impl PyExpression { + pub fn simplify(&mut self) { + self.as_inner_mut().simplify() + } + + pub fn into_simplified(&self, py: Python<'_>) -> PyResult { + self.as_inner().clone().into_simplified().to_python(py) + } + + pub fn evaluate( + &self, + variables: HashMap, + memory_references: HashMap<&str, Vec>, + ) -> PyResult { + self.as_inner() + .evaluate(&variables, &memory_references) + .map_err(RustEvaluationError::from) + .map_err(RustEvaluationError::to_py_err) + } + + pub fn substitute_variables( + &self, + py: Python<'_>, + variable_values: HashMap, + ) -> PyResult { + Ok(PyExpression(self.as_inner().clone().substitute_variables( + &HashMap::::py_try_from(py, &variable_values)?, + ))) + } + + pub fn to_real(&self) -> PyResult { + self.as_inner() + .to_real() + .map_err(RustEvaluationError::from) + .map_err(RustEvaluationError::to_py_err) + } + + pub fn __add__(&self, other: PyExpression) -> Self { + PyExpression(self.as_inner().clone() + other.as_inner().clone()) + } + + pub fn __sub__(&self, other: PyExpression) -> Self { + PyExpression(self.as_inner().clone() - other.as_inner().clone()) + } + + pub fn __mul__(&self, other: PyExpression) -> Self { + PyExpression(self.as_inner().clone() * other.as_inner().clone()) + } + + pub fn __truediv__(&self, other: PyExpression) -> Self { + PyExpression(self.as_inner().clone() / other.as_inner().clone()) + } +} + +py_wrap_data_struct! { + #[pyo3(subclass)] + #[derive(Debug)] + PyFunctionCallExpression(FunctionCallExpression) as "FunctionCallExpression" { + function: ExpressionFunction => PyExpressionFunction, + expression: Box => PyExpression + } +} +impl_repr!(PyFunctionCallExpression); + +#[pymethods] +impl PyFunctionCallExpression { + #[new] + pub fn new( + py: Python<'_>, + function: PyExpressionFunction, + expression: PyExpression, + ) -> PyResult { + Ok(PyFunctionCallExpression(FunctionCallExpression::new( + ExpressionFunction::py_try_from(py, &function)?, + Box::::py_try_from(py, &expression)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug)] + #[pyo3(subclass)] + PyInfixExpression(InfixExpression) as "InfixExpression" { + left: Box => PyExpression, + operator: InfixOperator => PyInfixOperator, + right: Box => PyExpression + } +} +impl_repr!(PyInfixExpression); + +#[pymethods] +impl PyInfixExpression { + #[new] + pub fn new( + py: Python<'_>, + left: PyExpression, + operator: PyInfixOperator, + right: PyExpression, + ) -> PyResult { + Ok(PyInfixExpression(InfixExpression::new( + Box::::py_try_from(py, &left)?, + InfixOperator::py_try_from(py, &operator)?, + Box::::py_try_from(py, &right)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug)] + #[pyo3(subclass)] + PyPrefixExpression(PrefixExpression) as "PrefixExpression" { + operator: PrefixOperator => PyPrefixOperator, + expression: Box => PyExpression + } +} + +#[pymethods] +impl PyPrefixExpression { + #[new] + pub fn new( + py: Python<'_>, + operator: PyPrefixOperator, + expression: PyExpression, + ) -> PyResult { + Ok(PyPrefixExpression(PrefixExpression::new( + PrefixOperator::py_try_from(py, &operator)?, + Box::::py_try_from(py, &expression)?, + ))) + } +} + +py_wrap_simple_enum! { + #[derive(Debug, PartialEq, Eq, Hash)] + PyExpressionFunction(ExpressionFunction) as "ExpressionFunction" { + Cis, + Cosine, + Exponent, + Sine, + SquareRoot + } +} +impl_repr!(PyExpressionFunction); +impl_str!(PyExpressionFunction); +impl_hash!(PyExpressionFunction); +impl_eq!(PyExpressionFunction); + +py_wrap_simple_enum! { + #[derive(Debug, PartialEq, Eq, Hash)] + PyPrefixOperator(PrefixOperator) as "PrefixOperator" { + Plus, + Minus + } +} +impl_repr!(PyPrefixOperator); +impl_str!(PyPrefixOperator); +impl_hash!(PyPrefixOperator); +impl_eq!(PyPrefixOperator); + +py_wrap_simple_enum! { + #[derive(Debug, PartialEq, Eq, Hash)] + PyInfixOperator(InfixOperator) as "InfixOperator" { + Caret, + Plus, + Minus, + Slash, + Star + } +} +impl_repr!(PyInfixOperator); +impl_str!(PyInfixOperator); +impl_hash!(PyInfixOperator); +impl_eq!(PyInfixOperator); + +create_init_submodule! { + classes: [PyExpression, PyFunctionCallExpression, PyInfixExpression, PyPrefixExpression, PyExpressionFunction, PyPrefixOperator, PyInfixOperator], + errors: [EvaluationError, ParseExpressionError], +} diff --git a/quil-py/src/instruction/calibration.rs b/quil-py/src/instruction/calibration.rs new file mode 100644 index 00000000..146211c0 --- /dev/null +++ b/quil-py/src/instruction/calibration.rs @@ -0,0 +1,90 @@ +use quil_rs::{ + expression::Expression, + instruction::{Calibration, GateModifier, Instruction, MeasureCalibrationDefinition, Qubit}, +}; + +use rigetti_pyo3::{ + impl_repr, py_wrap_data_struct, + pyo3::{pymethods, types::PyString, Py, PyResult, Python}, + PyTryFrom, ToPythonError, +}; + +use crate::{ + expression::PyExpression, + impl_copy_for_instruction, impl_eq, impl_to_quil, + instruction::{PyGateModifier, PyInstruction, PyQubit}, + validation::identifier::RustIdentifierValidationError, +}; + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyCalibration(Calibration) as "Calibration" { + instructions: Vec => Vec, + modifiers: Vec => Vec, + name: String => Py, + parameters: Vec => Vec, + qubits: Vec => Vec + } +} +impl_repr!(PyCalibration); +impl_to_quil!(PyCalibration); +impl_copy_for_instruction!(PyCalibration); +impl_eq!(PyCalibration); + +#[pymethods] +impl PyCalibration { + #[new] + pub fn new( + py: Python<'_>, + name: &str, + parameters: Vec, + qubits: Vec, + instructions: Vec, + modifiers: Vec, + ) -> PyResult { + Ok(Self( + Calibration::new( + name, + Vec::::py_try_from(py, ¶meters)?, + Vec::::py_try_from(py, &qubits)?, + Vec::::py_try_from(py, &instructions)?, + Vec::::py_try_from(py, &modifiers)?, + ) + .map_err(RustIdentifierValidationError::from) + .map_err(RustIdentifierValidationError::to_py_err)?, + )) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyMeasureCalibrationDefinition(MeasureCalibrationDefinition) as "MeasureCalibrationDefinition" { + qubit: Option => Option, + parameter: String => Py, + instructions: Vec => Vec + } +} +impl_repr!(PyMeasureCalibrationDefinition); +impl_to_quil!(PyMeasureCalibrationDefinition); +impl_copy_for_instruction!(PyMeasureCalibrationDefinition); +impl_eq!(PyMeasureCalibrationDefinition); + +#[pymethods] +impl PyMeasureCalibrationDefinition { + #[new] + #[pyo3(signature = (qubit, parameter, instructions))] + pub fn new( + py: Python<'_>, + qubit: Option, + parameter: String, + instructions: Vec, + ) -> PyResult { + Ok(Self(MeasureCalibrationDefinition::new( + Option::::py_try_from(py, &qubit)?, + parameter, + Vec::::py_try_from(py, &instructions)?, + ))) + } +} diff --git a/quil-py/src/instruction/circuit.rs b/quil-py/src/instruction/circuit.rs new file mode 100644 index 00000000..5e2599a2 --- /dev/null +++ b/quil-py/src/instruction/circuit.rs @@ -0,0 +1,44 @@ +use quil_rs::instruction::{CircuitDefinition, Instruction}; + +use rigetti_pyo3::{ + impl_repr, py_wrap_data_struct, + pyo3::{pymethods, types::PyString, Py, PyResult, Python}, + PyTryFrom, +}; + +use super::PyInstruction; +use crate::{impl_copy_for_instruction, impl_eq, impl_to_quil}; + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyCircuitDefinition(CircuitDefinition) as "CircuitDefinition" { + name: String => Py, + parameters: Vec => Vec>, + qubit_variables: Vec => Vec>, + instructions: Vec => Vec + } +} +impl_repr!(PyCircuitDefinition); +impl_to_quil!(PyCircuitDefinition); +impl_copy_for_instruction!(PyCircuitDefinition); +impl_eq!(PyCircuitDefinition); + +#[pymethods] +impl PyCircuitDefinition { + #[new] + pub fn new( + py: Python<'_>, + name: String, + parameters: Vec, + qubit_variables: Vec, + instructions: Vec, + ) -> PyResult { + Ok(Self(CircuitDefinition::new( + name, + parameters, + qubit_variables, + Vec::::py_try_from(py, &instructions)?, + ))) + } +} diff --git a/quil-py/src/instruction/classical.rs b/quil-py/src/instruction/classical.rs new file mode 100644 index 00000000..1ca692b4 --- /dev/null +++ b/quil-py/src/instruction/classical.rs @@ -0,0 +1,415 @@ +use quil_rs::instruction::{ + Arithmetic, ArithmeticOperand, ArithmeticOperator, BinaryLogic, BinaryOperand, BinaryOperands, + BinaryOperator, Comparison, ComparisonOperand, ComparisonOperator, Convert, Exchange, + MemoryReference, Move, UnaryLogic, UnaryOperator, +}; + +use rigetti_pyo3::{ + impl_as_mut_for_wrapper, impl_hash, impl_repr, py_wrap_data_struct, py_wrap_simple_enum, + py_wrap_type, py_wrap_union_enum, + pyo3::{ + pymethods, + types::{PyFloat, PyInt}, + Py, PyResult, Python, + }, + PyTryFrom, PyWrapper, PyWrapperMut, ToPython, +}; + +use super::PyMemoryReference; +use crate::{impl_copy_for_instruction, impl_eq, impl_to_quil}; + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyArithmetic(Arithmetic) as "Arithmetic" { + operator: ArithmeticOperator => PyArithmeticOperator, + destination: ArithmeticOperand => PyArithmeticOperand, + source: ArithmeticOperand => PyArithmeticOperand + } +} +impl_repr!(PyArithmetic); +impl_to_quil!(PyArithmetic); +impl_copy_for_instruction!(PyArithmetic); +impl_hash!(PyArithmetic); +impl_eq!(PyArithmetic); + +#[pymethods] +impl PyArithmetic { + #[new] + pub fn new( + py: Python<'_>, + operator: PyArithmeticOperator, + destination: PyArithmeticOperand, + source: PyArithmeticOperand, + ) -> PyResult { + Ok(PyArithmetic(Arithmetic::new( + ArithmeticOperator::py_try_from(py, &operator)?, + ArithmeticOperand::py_try_from(py, &destination)?, + ArithmeticOperand::py_try_from(py, &source)?, + ))) + } +} + +py_wrap_union_enum! { + #[derive(Debug, PartialEq)] + PyArithmeticOperand(ArithmeticOperand) as "ArithmeticOperand" { + literal_integer: LiteralInteger => Py, + literal_real: LiteralReal => Py, + memory_reference: MemoryReference => PyMemoryReference + } +} +impl_repr!(PyArithmeticOperand); +impl_to_quil!(PyArithmeticOperand); +impl_hash!(PyArithmeticOperand); +impl_eq!(PyArithmeticOperand); + +py_wrap_simple_enum! { + #[derive(Debug, PartialEq)] + PyArithmeticOperator(ArithmeticOperator) as "ArithmeticOperator" { + Add, + Subtract, + Divide, + Multiply + } +} +impl_repr!(PyArithmeticOperator); +impl_to_quil!(PyArithmeticOperator); +impl_hash!(PyArithmeticOperator); +impl_eq!(PyArithmeticOperator); + +py_wrap_union_enum! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyBinaryOperand(BinaryOperand) as "BinaryOperand" { + literal_integer: LiteralInteger => Py, + memory_reference: MemoryReference => PyMemoryReference + } +} +impl_repr!(PyBinaryOperand); +impl_to_quil!(PyBinaryOperand); +impl_hash!(PyBinaryOperand); +impl_eq!(PyBinaryOperand); + +py_wrap_type! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyBinaryOperands(BinaryOperands) as "BinaryOperands" +} +impl_repr!(PyBinaryOperands); +impl_hash!(PyBinaryOperands); +impl_as_mut_for_wrapper!(PyBinaryOperands); +impl_eq!(PyBinaryOperands); + +#[pymethods] +impl PyBinaryOperands { + #[new] + pub fn new( + py: Python<'_>, + memory_reference: PyMemoryReference, + operand: PyBinaryOperand, + ) -> PyResult { + Ok(Self(( + MemoryReference::py_try_from(py, &memory_reference)?, + BinaryOperand::py_try_from(py, &operand)?, + ))) + } + + #[getter] + pub fn get_memory_reference(&self, py: Python<'_>) -> PyResult { + self.as_inner().0.to_python(py) + } + + #[setter] + pub fn set_memory_reference( + &mut self, + py: Python<'_>, + memory_reference: PyMemoryReference, + ) -> PyResult<()> { + self.as_inner_mut().0 = MemoryReference::py_try_from(py, &memory_reference)?; + Ok(()) + } + + #[getter] + pub fn get_operand(&self, py: Python<'_>) -> PyResult { + self.as_inner().1.to_python(py) + } + + #[setter] + pub fn set_operand(&mut self, py: Python<'_>, binary_operand: PyBinaryOperand) -> PyResult<()> { + self.as_inner_mut().1 = BinaryOperand::py_try_from(py, &binary_operand)?; + Ok(()) + } +} + +py_wrap_simple_enum! { + #[derive(Debug, PartialEq, Eq)] + PyBinaryOperator(BinaryOperator) as "BinaryOperator" { + And, + Ior, + Xor + } +} +impl_repr!(PyBinaryOperator); +impl_to_quil!(PyBinaryOperator); +impl_hash!(PyBinaryOperator); +impl_eq!(PyBinaryOperator); + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyBinaryLogic(BinaryLogic) as "BinaryLogic" { + operator: BinaryOperator => PyBinaryOperator, + operands: BinaryOperands => PyBinaryOperands + } +} +impl_repr!(PyBinaryLogic); +impl_to_quil!(PyBinaryLogic); +impl_copy_for_instruction!(PyBinaryLogic); +impl_eq!(PyBinaryLogic); + +#[pymethods] +impl PyBinaryLogic { + #[new] + pub fn new( + py: Python<'_>, + operator: PyBinaryOperator, + operands: PyBinaryOperands, + ) -> PyResult { + Ok(PyBinaryLogic(BinaryLogic::new( + BinaryOperator::py_try_from(py, &operator)?, + BinaryOperands::py_try_from(py, &operands)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyConvert(Convert) as "Convert" { + destination: MemoryReference => PyMemoryReference, + source: MemoryReference => PyMemoryReference + } +} +impl_repr!(PyConvert); +impl_to_quil!(PyConvert); +impl_copy_for_instruction!(PyConvert); +impl_hash!(PyConvert); +impl_eq!(PyConvert); + +#[pymethods] +impl PyConvert { + #[new] + fn new( + py: Python<'_>, + destination: PyMemoryReference, + source: PyMemoryReference, + ) -> PyResult { + Ok(Self(Convert::new( + MemoryReference::py_try_from(py, &destination)?, + MemoryReference::py_try_from(py, &source)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyMove(Move) as "Move" { + destination: MemoryReference => PyMemoryReference, + source: ArithmeticOperand => PyArithmeticOperand + } +} +impl_repr!(PyMove); +impl_to_quil!(PyMove); +impl_copy_for_instruction!(PyMove); +impl_hash!(PyMove); +impl_eq!(PyMove); + +#[pymethods] +impl PyMove { + #[new] + fn new( + py: Python<'_>, + destination: PyMemoryReference, + source: PyArithmeticOperand, + ) -> PyResult { + Ok(Self(Move::new( + MemoryReference::py_try_from(py, &destination)?, + ArithmeticOperand::py_try_from(py, &source)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyExchange(Exchange) as "Exchange" { + left: MemoryReference => PyMemoryReference, + right: MemoryReference => PyMemoryReference + } +} +impl_repr!(PyExchange); +impl_to_quil!(PyExchange); +impl_copy_for_instruction!(PyExchange); +impl_hash!(PyExchange); +impl_eq!(PyExchange); + +#[pymethods] +impl PyExchange { + #[new] + pub fn new( + py: Python<'_>, + left: PyMemoryReference, + right: PyMemoryReference, + ) -> PyResult { + Ok(Self(Exchange::new( + MemoryReference::py_try_from(py, &left)?, + MemoryReference::py_try_from(py, &right)?, + ))) + } +} + +py_wrap_union_enum! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyComparisonOperand(ComparisonOperand) as "ComparisonOperand" { + literal_integer: LiteralInteger => Py, + literal_real: LiteralReal => Py, + memory_reference: MemoryReference => PyMemoryReference + } +} +impl_repr!(PyComparisonOperand); +impl_to_quil!(PyComparisonOperand); +impl_hash!(PyComparisonOperand); + +py_wrap_simple_enum! { + #[derive(Debug, PartialEq, Eq)] + PyComparisonOperator(ComparisonOperator) as "ComparisonOperator" { + Equal, + GreaterThanOrEqual, + GreaterThan, + LessThanOrEqual, + LessThan + } +} + +type RustComparisonOperands = (MemoryReference, MemoryReference, ComparisonOperand); + +// This is a helper type to manage easy conversion of the inner tuple +// with the macros. It should not be exposed directly. +py_wrap_type! { + PyComparisonOperands(RustComparisonOperands) +} + +impl PyComparisonOperands { + pub(crate) fn from_py_tuple( + py: Python<'_>, + tuple: (PyMemoryReference, PyMemoryReference, PyComparisonOperand), + ) -> PyResult { + Ok(Self(( + MemoryReference::py_try_from(py, &tuple.0)?, + MemoryReference::py_try_from(py, &tuple.1)?, + ComparisonOperand::py_try_from(py, &tuple.2)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyComparison(Comparison) as "Comparison" { + operator: ComparisonOperator => PyComparisonOperator, + operands: RustComparisonOperands => PyComparisonOperands + } +} +impl_repr!(PyComparison); +impl_to_quil!(PyComparison); +impl_copy_for_instruction!(PyComparison); +impl_hash!(PyComparison); +impl_eq!(PyComparison); + +#[pymethods] +impl PyComparison { + #[new] + pub fn new( + py: Python<'_>, + operator: PyComparisonOperator, + operands: (PyMemoryReference, PyMemoryReference, PyComparisonOperand), + ) -> PyResult { + Ok(Self(Comparison::new( + ComparisonOperator::py_try_from(py, &operator)?, + RustComparisonOperands::py_try_from( + py, + &PyComparisonOperands::from_py_tuple(py, operands)?, + )?, + ))) + } + + // Override the getters/setters generated by [`py_wrap_data_struct!`] so that they + // return/take tuples instead of the wrapping [`PyComparisonOperands`] type. + #[getter(operands)] + fn get_operands_as_tuple( + &self, + py: Python<'_>, + ) -> PyResult<(PyMemoryReference, PyMemoryReference, PyComparisonOperand)> { + let operands = &self.as_inner().operands; + Ok(( + operands.0.to_python(py)?, + operands.1.to_python(py)?, + operands.2.to_python(py)?, + )) + } + + #[setter(operands)] + fn set_operands_from_tuple( + &mut self, + py: Python<'_>, + operands: (PyMemoryReference, PyMemoryReference, PyComparisonOperand), + ) -> PyResult<()> { + self.as_inner_mut().operands = RustComparisonOperands::py_try_from( + py, + &PyComparisonOperands::from_py_tuple(py, operands)?, + )?; + Ok(()) + } +} + +py_wrap_simple_enum! { + #[derive(Debug, PartialEq, Eq)] + PyUnaryOperator(UnaryOperator) as "UnaryOperator" { + Neg, + Not + } +} +impl_repr!(PyUnaryOperator); +impl_to_quil!(PyUnaryOperator); +impl_hash!(PyUnaryOperator); + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyUnaryLogic(UnaryLogic) as "UnaryLogic" { + operator: UnaryOperator => PyUnaryOperator, + operand: MemoryReference => PyMemoryReference + } +} +impl_repr!(PyUnaryLogic); +impl_to_quil!(PyUnaryLogic); +impl_copy_for_instruction!(PyUnaryLogic); +impl_hash!(PyUnaryLogic); +impl_eq!(PyUnaryLogic); + +#[pymethods] +impl PyUnaryLogic { + #[new] + pub fn new( + py: Python<'_>, + operator: PyUnaryOperator, + operand: PyMemoryReference, + ) -> PyResult { + Ok(Self(UnaryLogic::new( + UnaryOperator::py_try_from(py, &operator)?, + MemoryReference::py_try_from(py, &operand)?, + ))) + } +} diff --git a/quil-py/src/instruction/control_flow.rs b/quil-py/src/instruction/control_flow.rs new file mode 100644 index 00000000..d694a504 --- /dev/null +++ b/quil-py/src/instruction/control_flow.rs @@ -0,0 +1,157 @@ +use quil_rs::instruction::{ + Jump, JumpUnless, JumpWhen, Label, MemoryReference, Target, TargetPlaceholder, +}; +use rigetti_pyo3::{ + impl_compare, impl_hash, impl_repr, py_wrap_data_struct, py_wrap_type, py_wrap_union_enum, + pyo3::{pymethods, types::PyString, Py}, + PyWrapper, +}; + +use crate::{impl_eq, impl_to_quil, instruction::PyMemoryReference}; + +/// Implements __copy__ and __deepcopy__ for instructions containing a [`Target`]. +/// +/// __copy__ implements a shallow copy by returning a reference to the object. +/// +/// __deepcopy__ performs a deep copy by cloning the Rust reference, and replacing +/// any [`TargetPlaceholder`]s from the original intruction with new instances so +/// that resolving placeholders on the copy does not affect the original. +macro_rules! impl_copy_for_target_containing_instructions { + ($name: ident) => { + #[pyo3::pymethods] + impl $name { + pub fn __copy__(&self) -> Self { + self.clone() + } + + pub fn __deepcopy__(&self, _memo: &pyo3::types::PyDict) -> Self { + use quil_rs::instruction::{Target, TargetPlaceholder}; + let mut copy = rigetti_pyo3::PyWrapper::into_inner(self.clone()); + if let Target::Placeholder(placeholder) = copy.target { + copy.target = Target::Placeholder(TargetPlaceholder::new( + placeholder.as_inner().to_string(), + )) + } + + Self(copy) + } + } + }; +} + +py_wrap_data_struct! { + #[pyo3(subclass)] + #[derive(Debug, Hash, PartialEq, Eq)] + PyLabel(Label) as "Label" { + target: Target => PyTarget + } +} +impl_repr!(PyLabel); +impl_hash!(PyLabel); +impl_to_quil!(PyLabel); +impl_copy_for_target_containing_instructions!(PyLabel); +impl_eq!(PyLabel); + +#[pymethods] +impl PyLabel { + #[new] + fn new(target: PyTarget) -> Self { + PyLabel(Label::new(target.into_inner())) + } +} + +py_wrap_union_enum! { + #[derive(Debug, Hash, PartialEq, Eq)] + PyTarget(Target) as "Target" { + fixed: Fixed => Py, + placeholder: Placeholder => PyTargetPlaceholder + } +} +impl_repr!(PyTarget); +impl_hash!(PyTarget); +impl_to_quil!(PyTarget); +impl_eq!(PyTarget); + +py_wrap_type! { + #[pyo3(subclass)] + #[derive(Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] + PyTargetPlaceholder(TargetPlaceholder) as "TargetPlaceholder" +} +impl_repr!(PyTargetPlaceholder); +impl_hash!(PyTargetPlaceholder); +impl_compare!(PyTargetPlaceholder); + +#[pymethods] +impl PyTargetPlaceholder { + #[new] + pub fn new(base_label: String) -> Self { + Self(TargetPlaceholder::new(base_label)) + } + + #[getter] + pub fn base_label(&self) -> &str { + PyWrapper::as_inner(self).as_inner() + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyJump(Jump) as "Jump" { + target: Target => PyTarget + } +} +impl_repr!(PyJump); +impl_to_quil!(PyJump); +impl_copy_for_target_containing_instructions!(PyJump); +impl_eq!(PyJump); + +#[pymethods] +impl PyJump { + #[new] + fn new(target: PyTarget) -> Self { + Self(Jump::new(target.into_inner())) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyJumpWhen(JumpWhen) as "JumpWhen" { + target: Target => PyTarget, + condition: MemoryReference => PyMemoryReference + } +} +impl_repr!(PyJumpWhen); +impl_to_quil!(PyJumpWhen); +impl_copy_for_target_containing_instructions!(PyJumpWhen); +impl_eq!(PyJumpWhen); + +#[pymethods] +impl PyJumpWhen { + #[new] + fn new(target: PyTarget, condition: PyMemoryReference) -> Self { + Self(JumpWhen::new(target.into_inner(), condition.into_inner())) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyJumpUnless(JumpUnless) as "JumpUnless" { + target: Target => PyTarget, + condition: MemoryReference => PyMemoryReference + } +} +impl_repr!(PyJumpUnless); +impl_to_quil!(PyJumpUnless); +impl_copy_for_target_containing_instructions!(PyJumpUnless); +impl_eq!(PyJumpUnless); + +#[pymethods] +impl PyJumpUnless { + #[new] + fn new(target: PyTarget, condition: PyMemoryReference) -> Self { + Self(JumpUnless::new(target.into_inner(), condition.into_inner())) + } +} diff --git a/quil-py/src/instruction/declaration.rs b/quil-py/src/instruction/declaration.rs new file mode 100644 index 00000000..2e88d722 --- /dev/null +++ b/quil-py/src/instruction/declaration.rs @@ -0,0 +1,229 @@ +use quil_rs::instruction::{ + ArithmeticOperand, Declaration, Load, MemoryReference, Offset, ScalarType, Sharing, Store, + Vector, +}; + +use super::PyArithmeticOperand; +use crate::{impl_copy_for_instruction, impl_eq, impl_to_quil}; + +use rigetti_pyo3::{ + impl_from_str, impl_hash, impl_parse, impl_repr, py_wrap_data_struct, py_wrap_error, + py_wrap_simple_enum, + pyo3::{ + exceptions::PyValueError, + pymethods, + types::{PyInt, PyString}, + Py, PyResult, Python, + }, + wrap_error, PyTryFrom, +}; + +wrap_error!(RustParseMemoryReferenceError(quil_rs::program::SyntaxError)); +py_wrap_error!( + quil, + RustParseMemoryReferenceError, + ParseMemoryReferenceError, + PyValueError +); + +py_wrap_simple_enum! { + PyScalarType(ScalarType) as "ScalarType" { + Bit, + Integer, + Octet, + Real + } +} +impl_repr!(PyScalarType); +impl_to_quil!(PyScalarType); +impl_hash!(PyScalarType); + +py_wrap_data_struct! { + #[derive(Debug, Hash, PartialEq, Eq)] + #[pyo3(subclass)] + PyVector(Vector) as "Vector" { + data_type: ScalarType => PyScalarType, + length: u64 => Py + } +} +impl_repr!(PyVector); +impl_to_quil!(PyVector); +impl_hash!(PyVector); +impl_eq!(PyVector); + +#[pymethods] +impl PyVector { + #[new] + pub fn new(py: Python<'_>, data_type: PyScalarType, length: u64) -> PyResult { + Ok(Self(Vector::new( + ScalarType::py_try_from(py, &data_type)?, + length, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq, Hash)] + #[pyo3(subclass)] + PyOffset(Offset) as "Offset" { + offset: u64 => Py, + data_type: ScalarType => PyScalarType + } +} +impl_repr!(PyOffset); +impl_to_quil!(PyOffset); +impl_hash!(PyOffset); +impl_eq!(PyOffset); + +#[pymethods] +impl PyOffset { + #[new] + pub fn new(py: Python<'_>, offset: u64, data_type: PyScalarType) -> PyResult { + Ok(Self(Offset::new( + offset, + ScalarType::py_try_from(py, &data_type)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq, Hash)] + #[pyo3(subclass)] + PySharing(Sharing) as "Sharing" { + name: String => Py, + offsets: Vec => Vec + } +} +impl_repr!(PySharing); +impl_hash!(PySharing); +impl_eq!(PySharing); + +#[pymethods] +impl PySharing { + #[new] + pub fn new(py: Python<'_>, name: String, offsets: Vec) -> PyResult { + Ok(Self(Sharing::new( + name, + Vec::::py_try_from(py, &offsets)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyDeclaration(Declaration) as "Declaration" { + name: String => Py, + size: Vector => PyVector, + sharing: Option => Option + } +} +impl_repr!(PyDeclaration); +impl_to_quil!(PyDeclaration); +impl_copy_for_instruction!(PyDeclaration); +impl_hash!(PyDeclaration); +impl_eq!(PyDeclaration); + +#[pymethods] +impl PyDeclaration { + #[new] + pub fn new( + py: Python<'_>, + name: String, + size: PyVector, + sharing: Option, + ) -> PyResult { + Ok(Self(Declaration::new( + name, + Vector::py_try_from(py, &size)?, + Option::::py_try_from(py, &sharing)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, Hash, PartialEq)] + #[pyo3(subclass)] + PyMemoryReference(MemoryReference) as "MemoryReference" { + name: String => Py, + index: u64 => Py + } +} +impl_hash!(PyMemoryReference); +impl_repr!(PyMemoryReference); +impl_to_quil!(PyMemoryReference); +impl_from_str!(PyMemoryReference, RustParseMemoryReferenceError); +impl_parse!(PyMemoryReference); +impl_eq!(PyMemoryReference); + +#[pymethods] +impl PyMemoryReference { + #[new] + pub fn new(name: String, index: u64) -> Self { + Self(MemoryReference::new(name, index)) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyLoad(Load) as "Load" { + destination: MemoryReference => PyMemoryReference, + source: String => Py, + offset: MemoryReference => PyMemoryReference + } +} +impl_repr!(PyLoad); +impl_to_quil!(PyLoad); +impl_copy_for_instruction!(PyLoad); +impl_hash!(PyLoad); +impl_eq!(PyLoad); + +#[pymethods] +impl PyLoad { + #[new] + pub fn new( + py: Python<'_>, + destination: PyMemoryReference, + source: String, + offset: PyMemoryReference, + ) -> PyResult { + Ok(Self(Load::new( + MemoryReference::py_try_from(py, &destination)?, + source, + MemoryReference::py_try_from(py, &offset)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyStore(Store) as "Store" { + destination: String => Py, + offset: MemoryReference => PyMemoryReference, + source: ArithmeticOperand => PyArithmeticOperand + } +} +impl_repr!(PyStore); +impl_to_quil!(PyStore); +impl_copy_for_instruction!(PyStore); +impl_hash!(PyStore); +impl_eq!(PyStore); + +#[pymethods] +impl PyStore { + #[new] + pub fn new( + py: Python<'_>, + destination: String, + offset: PyMemoryReference, + source: PyArithmeticOperand, + ) -> PyResult { + Ok(Self(Store::new( + destination, + MemoryReference::py_try_from(py, &offset)?, + ArithmeticOperand::py_try_from(py, &source)?, + ))) + } +} diff --git a/quil-py/src/instruction/frame.rs b/quil-py/src/instruction/frame.rs new file mode 100644 index 00000000..487aef66 --- /dev/null +++ b/quil-py/src/instruction/frame.rs @@ -0,0 +1,357 @@ +use std::hash::Hash; + +use indexmap::IndexMap; + +use quil_rs::{ + expression::Expression, + instruction::{ + AttributeValue, Capture, FrameAttributes, FrameDefinition, FrameIdentifier, + MemoryReference, Pulse, Qubit, RawCapture, SetFrequency, SetPhase, SetScale, + ShiftFrequency, ShiftPhase, SwapPhases, WaveformInvocation, + }, +}; +use rigetti_pyo3::{ + impl_hash, impl_repr, py_wrap_data_struct, py_wrap_union_enum, + pyo3::{ + pymethods, + types::{PyBool, PyString}, + Py, PyResult, Python, + }, + PyTryFrom, +}; + +use super::PyQubit; +use crate::{ + expression::PyExpression, + impl_copy_for_instruction, impl_eq, impl_to_quil, + instruction::{PyMemoryReference, PyWaveformInvocation}, +}; + +py_wrap_union_enum! { + #[derive(Debug, PartialEq, Eq)] + PyAttributeValue(AttributeValue) as "AttributeValue" { + string: String => Py, + expression: Expression => PyExpression + } +} +impl_repr!(PyAttributeValue); +impl_to_quil!(PyAttributeValue); +impl_hash!(PyAttributeValue); +impl_eq!(PyAttributeValue); + +pub type PyFrameAttributes = IndexMap; + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyFrameDefinition(FrameDefinition) as "FrameDefinition" { + identifier: FrameIdentifier => PyFrameIdentifier, + attributes: FrameAttributes => PyFrameAttributes + } +} +impl_repr!(PyFrameDefinition); +impl_to_quil!(PyFrameDefinition); +impl_copy_for_instruction!(PyFrameDefinition); +impl_eq!(PyFrameDefinition); + +#[pymethods] +impl PyFrameDefinition { + #[new] + pub fn new( + py: Python<'_>, + identifier: PyFrameIdentifier, + attributes: PyFrameAttributes, + ) -> PyResult { + Ok(Self(FrameDefinition::new( + FrameIdentifier::py_try_from(py, &identifier)?, + FrameAttributes::py_try_from(py, &attributes)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq, Hash)] + #[pyo3(subclass)] + PyFrameIdentifier(FrameIdentifier) as "FrameIdentifier" { + name: String => Py, + qubits: Vec => Vec + } +} +impl_repr!(PyFrameIdentifier); +impl_to_quil!(PyFrameIdentifier); +impl_hash!(PyFrameIdentifier); +impl_eq!(PyFrameIdentifier); + +#[pymethods] +impl PyFrameIdentifier { + #[new] + pub fn new(py: Python<'_>, name: String, qubits: Vec) -> PyResult { + Ok(Self(FrameIdentifier::new( + name, + Vec::::py_try_from(py, &qubits)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyCapture(Capture) as "Capture" { + blocking: bool => Py, + frame: FrameIdentifier => PyFrameIdentifier, + memory_reference: MemoryReference => PyMemoryReference, + waveform: WaveformInvocation => PyWaveformInvocation + } +} +impl_repr!(PyCapture); +impl_to_quil!(PyCapture); +impl_copy_for_instruction!(PyCapture); +impl_eq!(PyCapture); + +#[pymethods] +impl PyCapture { + #[new] + pub fn new( + py: Python<'_>, + blocking: bool, + frame: PyFrameIdentifier, + memory_reference: PyMemoryReference, + waveform: PyWaveformInvocation, + ) -> PyResult { + Ok(Self(Capture::new( + blocking, + FrameIdentifier::py_try_from(py, &frame)?, + MemoryReference::py_try_from(py, &memory_reference)?, + WaveformInvocation::py_try_from(py, &waveform)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyPulse(Pulse) as "Pulse" { + blocking: bool => Py, + frame: FrameIdentifier => PyFrameIdentifier, + waveform: WaveformInvocation => PyWaveformInvocation + } +} +impl_repr!(PyPulse); +impl_to_quil!(PyPulse); +impl_copy_for_instruction!(PyPulse); +impl_eq!(PyPulse); + +#[pymethods] +impl PyPulse { + #[new] + pub fn new( + py: Python<'_>, + blocking: bool, + frame: PyFrameIdentifier, + waveform: PyWaveformInvocation, + ) -> PyResult { + Ok(Self(Pulse::new( + blocking, + FrameIdentifier::py_try_from(py, &frame)?, + WaveformInvocation::py_try_from(py, &waveform)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyRawCapture(RawCapture) as "RawCapture" { + blocking: bool => Py, + frame: FrameIdentifier => PyFrameIdentifier, + duration: Expression => PyExpression, + memory_reference: MemoryReference => PyMemoryReference + } +} + +impl_repr!(PyRawCapture); +impl_to_quil!(PyRawCapture); +impl_copy_for_instruction!(PyRawCapture); +impl_hash!(PyRawCapture); +impl_eq!(PyRawCapture); + +#[pymethods] +impl PyRawCapture { + #[new] + pub fn new( + py: Python<'_>, + blocking: bool, + frame: PyFrameIdentifier, + duration: PyExpression, + memory_reference: PyMemoryReference, + ) -> PyResult { + Ok(Self(RawCapture::new( + blocking, + FrameIdentifier::py_try_from(py, &frame)?, + Expression::py_try_from(py, &duration)?, + MemoryReference::py_try_from(py, &memory_reference)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PySetFrequency(SetFrequency) as "SetFrequency" { + frame: FrameIdentifier => PyFrameIdentifier, + frequency: Expression => PyExpression + } +} +impl_repr!(PySetFrequency); +impl_to_quil!(PySetFrequency); +impl_copy_for_instruction!(PySetFrequency); +impl_hash!(PySetFrequency); +impl_eq!(PySetFrequency); + +#[pymethods] +impl PySetFrequency { + #[new] + pub fn new( + py: Python<'_>, + frame: PyFrameIdentifier, + frequency: PyExpression, + ) -> PyResult { + Ok(Self(SetFrequency::new( + FrameIdentifier::py_try_from(py, &frame)?, + Expression::py_try_from(py, &frequency)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PySetPhase(SetPhase) as "SetPhase" { + frame: FrameIdentifier => PyFrameIdentifier, + phase: Expression => PyExpression + } +} +impl_repr!(PySetPhase); +impl_to_quil!(PySetPhase); +impl_copy_for_instruction!(PySetPhase); +impl_hash!(PySetPhase); +impl_eq!(PySetPhase); + +#[pymethods] +impl PySetPhase { + #[new] + pub fn new(py: Python<'_>, frame: PyFrameIdentifier, phase: PyExpression) -> PyResult { + Ok(Self(SetPhase::new( + FrameIdentifier::py_try_from(py, &frame)?, + Expression::py_try_from(py, &phase)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PySetScale(SetScale) as "SetScale" { + frame: FrameIdentifier => PyFrameIdentifier, + scale: Expression => PyExpression + } +} +impl_repr!(PySetScale); +impl_to_quil!(PySetScale); +impl_copy_for_instruction!(PySetScale); +impl_hash!(PySetScale); +impl_eq!(PySetScale); + +#[pymethods] +impl PySetScale { + #[new] + pub fn new(py: Python<'_>, frame: PyFrameIdentifier, scale: PyExpression) -> PyResult { + Ok(Self(SetScale::new( + FrameIdentifier::py_try_from(py, &frame)?, + Expression::py_try_from(py, &scale)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyShiftFrequency(ShiftFrequency) as "ShiftFrequency" { + frame: FrameIdentifier => PyFrameIdentifier, + frequency: Expression => PyExpression + } +} +impl_repr!(PyShiftFrequency); +impl_to_quil!(PyShiftFrequency); +impl_copy_for_instruction!(PyShiftFrequency); +impl_hash!(PyShiftFrequency); +impl_eq!(PyShiftFrequency); + +#[pymethods] +impl PyShiftFrequency { + #[new] + pub fn new( + py: Python<'_>, + frame: PyFrameIdentifier, + frequency: PyExpression, + ) -> PyResult { + Ok(Self(ShiftFrequency::new( + FrameIdentifier::py_try_from(py, &frame)?, + Expression::py_try_from(py, &frequency)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyShiftPhase(ShiftPhase) as "ShiftPhase" { + frame: FrameIdentifier => PyFrameIdentifier, + phase: Expression => PyExpression + } +} +impl_repr!(PyShiftPhase); +impl_to_quil!(PyShiftPhase); +impl_copy_for_instruction!(PyShiftPhase); +impl_hash!(PyShiftPhase); +impl_eq!(PyShiftPhase); + +#[pymethods] +impl PyShiftPhase { + #[new] + pub fn new(py: Python<'_>, frame: PyFrameIdentifier, phase: PyExpression) -> PyResult { + Ok(Self(ShiftPhase::new( + FrameIdentifier::py_try_from(py, &frame)?, + Expression::py_try_from(py, &phase)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PySwapPhases(SwapPhases) as "SwapPhases" { + frame_1: FrameIdentifier => PyFrameIdentifier, + frame_2: FrameIdentifier => PyFrameIdentifier + } +} +impl_repr!(PySwapPhases); +impl_to_quil!(PySwapPhases); +impl_copy_for_instruction!(PySwapPhases); +impl_hash!(PySwapPhases); +impl_eq!(PySwapPhases); + +#[pymethods] +impl PySwapPhases { + #[new] + pub fn new( + py: Python<'_>, + frame_1: PyFrameIdentifier, + frame_2: PyFrameIdentifier, + ) -> PyResult { + Ok(Self(SwapPhases::new( + FrameIdentifier::py_try_from(py, &frame_1)?, + FrameIdentifier::py_try_from(py, &frame_2)?, + ))) + } +} diff --git a/quil-py/src/instruction/gate.rs b/quil-py/src/instruction/gate.rs new file mode 100644 index 00000000..3d35f280 --- /dev/null +++ b/quil-py/src/instruction/gate.rs @@ -0,0 +1,294 @@ +use numpy::{PyArray2, ToPyArray}; +use quil_rs::{ + expression::Expression, + instruction::{ + Gate, GateDefinition, GateModifier, GateSpecification, PauliGate, PauliSum, PauliTerm, + Qubit, + }, +}; +use rigetti_pyo3::{ + impl_from_str, impl_hash, impl_parse, impl_repr, impl_str, + num_complex::Complex64, + py_wrap_data_struct, py_wrap_error, py_wrap_simple_enum, py_wrap_type, py_wrap_union_enum, + pyo3::{ + exceptions::PyValueError, + pymethods, + types::{PyInt, PyString}, + Py, PyErr, PyResult, Python, + }, + wrap_error, PyTryFrom, PyWrapper, PyWrapperMut, ToPython, ToPythonError, +}; +use strum; + +use crate::{ + expression::PyExpression, impl_copy_for_instruction, impl_eq, impl_to_quil, + instruction::PyQubit, +}; + +wrap_error!(RustGateError(quil_rs::instruction::GateError)); +py_wrap_error!(quil, RustGateError, GateError, PyValueError); +wrap_error!(RustParseEnumError(strum::ParseError)); +py_wrap_error!(quil, RustParseEnumError, EnumParseError, PyValueError); + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyGate(Gate) as "Gate" { + name: String => Py, + parameters: Vec => Vec, + qubits: Vec => Vec, + modifiers: Vec => Vec + } +} +impl_repr!(PyGate); +impl_copy_for_instruction!(PyGate); +impl_to_quil!(PyGate); +impl_hash!(PyGate); +impl_eq!(PyGate); + +#[pymethods] +impl PyGate { + #[new] + fn new( + py: Python<'_>, + name: String, + parameters: Vec, + qubits: Vec, + modifiers: Vec, + ) -> PyResult { + Ok(Self( + Gate::new( + &name, + Vec::::py_try_from(py, ¶meters)?, + Vec::::py_try_from(py, &qubits)?, + Vec::::py_try_from(py, &modifiers)?, + ) + .map_err(RustGateError::from) + .map_err(RustGateError::to_py_err)?, + )) + } + + fn dagger(&self, py: Python<'_>) -> PyResult { + self.as_inner().clone().dagger().to_python(py) + } + + fn controlled(&self, py: Python<'_>, control_qubit: PyQubit) -> PyResult { + self.as_inner() + .clone() + .controlled(Qubit::py_try_from(py, &control_qubit)?) + .to_python(py) + } + + fn forked( + &self, + py: Python<'_>, + fork_qubit: PyQubit, + alt_params: Vec, + ) -> PyResult { + self.as_inner() + .clone() + .forked( + Qubit::py_try_from(py, &fork_qubit)?, + Vec::::py_try_from(py, &alt_params)?, + ) + .map_err(RustGateError::from) + .map_err(RustGateError::to_py_err)? + .to_python(py) + } + + fn to_unitary_mut( + &mut self, + py: Python<'_>, + n_qubits: u64, + ) -> PyResult>> { + Ok(self + .as_inner_mut() + .to_unitary(n_qubits) + .map_err(RustGateError::from) + .map_err(RustGateError::to_py_err)? + .to_pyarray(py) + .to_owned()) + } +} + +py_wrap_simple_enum! { + #[derive(Debug, PartialEq, Eq)] + PyGateModifier(GateModifier) as "GateModifier" { + Controlled, + Dagger, + Forked + } +} +impl_repr!(PyGateModifier); +impl_to_quil!(PyGateModifier); +impl_hash!(PyGateModifier); +impl_eq!(PyGateModifier); + +py_wrap_simple_enum! { + #[derive(Debug, PartialEq, Eq)] + PyPauliGate(PauliGate) as "PauliGate" { + I, + X, + Y, + Z + } +} +impl_repr!(PyPauliGate); +impl_str!(PyPauliGate); +impl_hash!(PyPauliGate); +impl_from_str!(PyPauliGate, RustParseEnumError); +impl_parse!(PyPauliGate); + +// This is a helper type to help manage easy conversion of the inner tuple +// with the macros. It should not be exposed directly. +py_wrap_type! { + PyPauliPair((PauliGate, String)) +} + +impl PyPauliPair { + pub(crate) fn from_py_tuple(py: Python<'_>, tuple: (PyPauliGate, String)) -> PyResult { + Ok(Self((PauliGate::py_try_from(py, &tuple.0)?, tuple.1))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyPauliTerm(PauliTerm) as "PauliTerm" { + arguments: Vec<(PauliGate, String)> => Vec, + expression: Expression => PyExpression + } +} + +#[pymethods] +impl PyPauliTerm { + #[new] + pub fn new( + py: Python<'_>, + arguments: Vec<(PyPauliGate, String)>, + expression: PyExpression, + ) -> PyResult { + Ok(Self(PauliTerm::new( + Vec::<(PauliGate, String)>::py_try_from( + py, + &PyPauliTerm::py_pairs_from_tuples(py, arguments)?, + )?, + Expression::py_try_from(py, &expression)?, + ))) + } + + // Override the getters/setters generated by [`py_wrap_data_struct!`] so that they + // return/take tuples instead of the wrapping [`PyPauliPair`] type. + #[getter(arguments)] + fn get_arguments_as_tuple(&self, py: Python<'_>) -> PyResult> { + let mut pairs: Vec<(PyPauliGate, String)> = + Vec::with_capacity(self.as_inner().arguments.len()); + self.as_inner() + .arguments + .iter() + .try_for_each(|(gate, arg)| { + pairs.push((gate.to_python(py)?, arg.clone())); + Ok::<(), PyErr>(()) + })?; + Ok(pairs) + } + + #[setter(arguments)] + fn set_arguments_from_tuple( + &mut self, + py: Python<'_>, + arguments: Vec<(PyPauliGate, String)>, + ) -> PyResult<()> { + self.as_inner_mut().arguments = Vec::<(PauliGate, String)>::py_try_from( + py, + &PyPauliTerm::py_pairs_from_tuples(py, arguments)?, + )?; + Ok(()) + } +} + +impl PyPauliTerm { + pub(crate) fn py_pairs_from_tuples( + py: Python<'_>, + tuples: Vec<(PyPauliGate, String)>, + ) -> PyResult> { + let mut pairs: Vec = Vec::with_capacity(tuples.len()); + tuples.into_iter().try_for_each(|tuple| { + pairs.push(PyPauliPair::from_py_tuple(py, tuple)?); + Ok::<(), PyErr>(()) + })?; + Ok(pairs) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyPauliSum(PauliSum) as "PauliSum" { + arguments: Vec => Vec>, + terms: Vec => Vec + } +} +impl_repr!(PyPauliSum); +impl_eq!(PyPauliSum); + +#[pymethods] +impl PyPauliSum { + #[new] + pub fn new(py: Python<'_>, arguments: Vec, terms: Vec) -> PyResult { + Ok(Self( + PauliSum::new(arguments, Vec::::py_try_from(py, &terms)?) + .map_err(RustGateError::from) + .map_err(RustGateError::to_py_err)?, + )) + } +} + +py_wrap_union_enum! { + #[derive(Debug, PartialEq, Eq)] + PyGateSpecification(GateSpecification) as "GateSpecification" { + matrix: Matrix => Vec>, + permutation: Permutation => Vec>, + pauli_sum: PauliSum => PyPauliSum + } +} +impl_repr!(PyGateSpecification); +impl_to_quil!(PyGateSpecification); +impl_hash!(PyGateSpecification); +impl_eq!(PyGateSpecification); + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyGateDefinition(GateDefinition) as "GateDefinition" { + name: String => Py, + parameters: Vec => Vec>, + specification: GateSpecification => PyGateSpecification + } +} +impl_repr!(PyGateDefinition); +impl_to_quil!(PyGateDefinition); +impl_copy_for_instruction!(PyGateDefinition); +impl_hash!(PyGateDefinition); +impl_eq!(PyGateDefinition); + +#[pymethods] +impl PyGateDefinition { + #[new] + pub fn new( + py: Python<'_>, + name: String, + parameters: Vec, + specification: PyGateSpecification, + ) -> PyResult { + Ok(Self( + GateDefinition::new( + name, + parameters, + GateSpecification::py_try_from(py, &specification)?, + ) + .map_err(RustGateError::from) + .map_err(RustGateError::to_py_err)?, + )) + } +} diff --git a/quil-py/src/instruction/measurement.rs b/quil-py/src/instruction/measurement.rs new file mode 100644 index 00000000..ea13ea8a --- /dev/null +++ b/quil-py/src/instruction/measurement.rs @@ -0,0 +1,40 @@ +use quil_rs::instruction::{Measurement, MemoryReference, Qubit}; +use rigetti_pyo3::{ + impl_hash, impl_repr, py_wrap_data_struct, + pyo3::{pymethods, PyResult, Python}, + PyTryFrom, +}; + +use crate::{ + impl_copy_for_instruction, impl_eq, impl_to_quil, + instruction::{PyMemoryReference, PyQubit}, +}; + +py_wrap_data_struct! { + #[pyo3(subclass)] + #[derive(Debug, PartialEq, Eq)] + PyMeasurement(Measurement) as "Measurement" { + qubit: Qubit => PyQubit, + target: Option => Option + } +} +impl_to_quil!(PyMeasurement); +impl_copy_for_instruction!(PyMeasurement); +impl_hash!(PyMeasurement); +impl_repr!(PyMeasurement); +impl_eq!(PyMeasurement); + +#[pymethods] +impl PyMeasurement { + #[new] + pub fn new( + py: Python<'_>, + qubit: PyQubit, + target: Option, + ) -> PyResult { + Ok(Self(Measurement::new( + Qubit::py_try_from(py, &qubit)?, + Option::::py_try_from(py, &target)?, + ))) + } +} diff --git a/quil-py/src/instruction/mod.rs b/quil-py/src/instruction/mod.rs new file mode 100644 index 00000000..bb2c4ba7 --- /dev/null +++ b/quil-py/src/instruction/mod.rs @@ -0,0 +1,257 @@ +use quil_rs::instruction::Instruction; +use rigetti_pyo3::{ + create_init_submodule, impl_repr, py_wrap_union_enum, + pyo3::{pymethods, types::PyDict, PyResult, Python}, + PyWrapper, +}; + +use crate::{impl_eq, impl_to_quil}; + +pub use self::{ + calibration::{PyCalibration, PyMeasureCalibrationDefinition}, + circuit::PyCircuitDefinition, + classical::{ + PyArithmetic, PyArithmeticOperand, PyArithmeticOperator, PyBinaryLogic, PyBinaryOperand, + PyBinaryOperands, PyBinaryOperator, PyComparison, PyComparisonOperand, + PyComparisonOperator, PyConvert, PyExchange, PyMove, PyUnaryLogic, PyUnaryOperator, + }, + control_flow::{PyJump, PyJumpUnless, PyJumpWhen, PyLabel, PyTarget, PyTargetPlaceholder}, + declaration::{ + ParseMemoryReferenceError, PyDeclaration, PyLoad, PyMemoryReference, PyOffset, + PyScalarType, PySharing, PyStore, PyVector, + }, + frame::{ + PyAttributeValue, PyCapture, PyFrameAttributes, PyFrameDefinition, PyFrameIdentifier, + PyPulse, PyRawCapture, PySetFrequency, PySetPhase, PySetScale, PyShiftFrequency, + PyShiftPhase, PySwapPhases, + }, + gate::{ + GateError, PyGate, PyGateDefinition, PyGateModifier, PyGateSpecification, PyPauliGate, + PyPauliSum, PyPauliTerm, + }, + measurement::PyMeasurement, + pragma::{PyInclude, PyPragma, PyPragmaArgument}, + qubit::{PyQubit, PyQubitPlaceholder}, + reset::PyReset, + timing::{PyDelay, PyFence}, + waveform::{PyWaveform, PyWaveformDefinition, PyWaveformInvocation}, +}; + +mod calibration; +mod circuit; +mod classical; +mod control_flow; +mod declaration; +mod frame; +mod gate; +mod measurement; +mod pragma; +mod qubit; +mod reset; +mod timing; +mod waveform; + +py_wrap_union_enum! { + #[derive(Debug, PartialEq)] + PyInstruction(Instruction) as "Instruction" { + arithmetic: Arithmetic => PyArithmetic, + binary_logic: BinaryLogic => PyBinaryLogic, + calibration_definition: CalibrationDefinition => PyCalibration, + capture: Capture => PyCapture, + circuit_definition: CircuitDefinition => PyCircuitDefinition, + convert: Convert => PyConvert, + comparison: Comparison => PyComparison, + declaration: Declaration => PyDeclaration, + delay: Delay => PyDelay, + exchange: Exchange => PyExchange, + fence: Fence => PyFence, + frame_definition: FrameDefinition => PyFrameDefinition, + gate: Gate => PyGate, + gate_definition: GateDefinition => PyGateDefinition, + halt: Halt, + include: Include => PyInclude, + jump: Jump => PyJump, + jump_when: JumpWhen => PyJumpWhen, + jump_unless: JumpUnless => PyJumpUnless, + label: Label => PyLabel, + load: Load => PyLoad, + measure_calibration_definition: MeasureCalibrationDefinition => PyMeasureCalibrationDefinition, + measurement: Measurement => PyMeasurement, + move: Move => PyMove, + nop: Nop, + pragma: Pragma => PyPragma, + pulse: Pulse => PyPulse, + raw_capture: RawCapture => PyRawCapture, + reset: Reset => PyReset, + set_frequency: SetFrequency => PySetFrequency, + set_phase: SetPhase => PySetPhase, + set_scale: SetScale => PySetScale, + shift_frequency: ShiftFrequency => PyShiftFrequency, + shift_phase: ShiftPhase => PyShiftPhase, + store: Store => PyStore, + swap_phases: SwapPhases => PySwapPhases, + unary_logic: UnaryLogic => PyUnaryLogic, + waveform_definition: WaveformDefinition => PyWaveformDefinition, + wait: Wait + } +} +impl_repr!(PyInstruction); +impl_to_quil!(PyInstruction); +impl_eq!(PyInstruction); + +#[pymethods] +impl PyInstruction { + pub fn is_quil_t(&self) -> bool { + self.as_inner().is_quil_t() + } + + // Implement the __copy__ and __deepcopy__ dunder methods, which are used by Python's + // `copy` module. + // + // If the instruction contains some inner data, then the implementation for __deepcopy__ + // is delegated to that inner type so that each type can define its own copy behavior. + // This comes with the caveat that this implementation will error if the inner type doesn't + // implement __deepcopy__ itself. See [`impl_copy_for_instruction!`] for an easy way to + // implement these methods on any variant of [`PyInstruction`]. + pub fn __copy__(&self) -> Self { + self.clone() + } + + pub fn __deepcopy__(&self, py: Python<'_>, memo: &PyDict) -> PyResult { + match self.inner(py) { + Ok(inner) => Ok(PyInstruction::new( + py, + inner.call_method1(py, "__deepcopy__", (memo,))?.as_ref(py), + )?), + Err(_) => Ok(self.clone()), // No inner data implies this is a simple instruction, safe to + // just clone. + } + } +} + +create_init_submodule! { + classes: [ + PyInstruction, + PyArithmetic, + PyArithmeticOperand, + PyArithmeticOperator, + PyBinaryLogic, + PyBinaryOperand, + PyBinaryOperands, + PyBinaryOperator, + PyComparison, + PyComparisonOperand, + PyComparisonOperator, + PyConvert, + PyExchange, + PyMove, + PyUnaryLogic, + PyUnaryOperator, + PyCalibration, + PyCircuitDefinition, + PyMeasureCalibrationDefinition, + PyDeclaration, + PyLoad, + PyOffset, + PySharing, + PyStore, + PyScalarType, + PyVector, + PyMeasurement, + PyInclude, + PyPragma, + PyPragmaArgument, + PyAttributeValue, + PyCapture, + PyFrameDefinition, + PyFrameIdentifier, + PyPulse, + PyRawCapture, + PySetFrequency, + PySetPhase, + PySetScale, + PyShiftFrequency, + PyShiftPhase, + PySwapPhases, + PyGate, + PyGateDefinition, + PyGateModifier, + PyGateSpecification, + PyPauliGate, + PyPauliTerm, + PyPauliSum, + PyJump, + PyJumpWhen, + PyJumpUnless, + PyLabel, + PyTarget, + PyTargetPlaceholder, + PyMeasurement, + PyMemoryReference, + PyQubit, + PyQubitPlaceholder, + PyReset, + PyDelay, + PyFence, + PyWaveform, + PyWaveformDefinition, + PyWaveformInvocation + ], + errors: [ GateError, ParseMemoryReferenceError ], +} + +/// Implements __copy__ and __deepcopy__ on any variant of the [`PyInstruction`] class, making +/// them compatible with Python's `copy` module. +/// +/// The `__copy__` method returns a reference to the instruction, making it shallow: any changes +/// to the copy will update the original. +/// +/// The `__deepcopy__` method creates a deep copy by cloning the inner instruction, querying its +/// qubits, and replacing any [`quil_rs::instruction::QubitPlaceholder`]s with new instances so +/// that resolving them in one copy doesn't affect the other. Duplicates of the same instruction in +/// the original instruction will be replaced with the same copy in the new instruction. +#[macro_export] +macro_rules! impl_copy_for_instruction { + ($py_name: ident) => { + #[pyo3::pymethods] + impl $py_name { + pub fn __deepcopy__( + &self, + py: Python<'_>, + _memo: &pyo3::types::PyDict, + ) -> pyo3::PyResult { + let mut instruction = $crate::instruction::PyInstruction::new( + py, + pyo3::ToPyObject::to_object(&self, py).as_ref(py), + )?; + + use quil_rs::instruction::{Qubit, QubitPlaceholder}; + use std::collections::HashMap; + let mut placeholders: HashMap = HashMap::new(); + + for qubit in + rigetti_pyo3::PyWrapperMut::as_inner_mut(&mut instruction).get_qubits_mut() + { + match qubit { + Qubit::Fixed(_) | Qubit::Variable(_) => *qubit = qubit.clone(), + Qubit::Placeholder(placeholder) => { + *qubit = Qubit::Placeholder( + placeholders.entry(placeholder.clone()).or_default().clone(), + ) + } + } + } + + Ok(instruction + .inner(py) + .unwrap() + .extract::<$py_name>(py) + .expect("a copy of a type should extract to the same type")) + } + + pub fn __copy__(&self) -> Self { + self.clone() + } + } + }; +} diff --git a/quil-py/src/instruction/pragma.rs b/quil-py/src/instruction/pragma.rs new file mode 100644 index 00000000..94dd318a --- /dev/null +++ b/quil-py/src/instruction/pragma.rs @@ -0,0 +1,78 @@ +use quil_rs::instruction::{Include, Pragma, PragmaArgument}; + +use rigetti_pyo3::{ + impl_hash, impl_repr, py_wrap_data_struct, py_wrap_union_enum, + pyo3::{ + pymethods, + types::{PyInt, PyString}, + Py, PyResult, Python, + }, + PyTryFrom, +}; + +use crate::{impl_copy_for_instruction, impl_eq, impl_to_quil}; + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyPragma(Pragma) as "Pragma" { + name: String => Py, + arguments: Vec => Vec, + data: Option => Option> + } +} +impl_repr!(PyPragma); +impl_to_quil!(PyPragma); +impl_copy_for_instruction!(PyPragma); +impl_hash!(PyPragma); +impl_eq!(PyPragma); + +#[pymethods] +impl PyPragma { + #[new] + fn new( + py: Python<'_>, + name: String, + arguments: Vec, + data: Option, + ) -> PyResult { + Ok(Self(Pragma::new( + name, + Vec::::py_try_from(py, &arguments)?, + data, + ))) + } +} + +py_wrap_union_enum! { + #[derive(Debug, PartialEq, Eq)] + PyPragmaArgument(PragmaArgument) as "PragmaArgument" { + identifier: Identifier => Py, + integer: Integer => Py + } +} +impl_repr!(PyPragmaArgument); +impl_to_quil!(PyPragmaArgument); +impl_hash!(PyPragmaArgument); +impl_eq!(PyPragmaArgument); + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyInclude(Include) as "Include" { + filename: String => Py + } +} +impl_repr!(PyInclude); +impl_to_quil!(PyInclude); +impl_copy_for_instruction!(PyInclude); +impl_hash!(PyInclude); +impl_eq!(PyInclude); + +#[pymethods] +impl PyInclude { + #[new] + pub fn new(filename: String) -> Self { + Self(Include::new(filename)) + } +} diff --git a/quil-py/src/instruction/qubit.rs b/quil-py/src/instruction/qubit.rs new file mode 100644 index 00000000..e99e3e3b --- /dev/null +++ b/quil-py/src/instruction/qubit.rs @@ -0,0 +1,42 @@ +use quil_rs::instruction::{Qubit, QubitPlaceholder}; + +use rigetti_pyo3::{ + impl_compare, impl_hash, impl_repr, py_wrap_type, py_wrap_union_enum, + pyo3::{ + pymethods, + types::{PyLong, PyString}, + Py, + }, +}; + +use crate::{impl_eq, impl_to_quil}; + +py_wrap_union_enum! { + #[derive(Debug, Eq, Hash, PartialEq)] + PyQubit(Qubit) as "Qubit" { + fixed: Fixed => Py, + variable: Variable => Py, + placeholder: Placeholder => PyQubitPlaceholder + } +} +impl_repr!(PyQubit); +impl_to_quil!(PyQubit); +impl_hash!(PyQubit); +impl_eq!(PyQubit); + +py_wrap_type! { + #[pyo3(subclass)] + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] + PyQubitPlaceholder(QubitPlaceholder) as "QubitPlaceholder" +} +impl_repr!(PyQubitPlaceholder); +impl_hash!(PyQubitPlaceholder); +impl_compare!(PyQubitPlaceholder); + +#[pymethods] +impl PyQubitPlaceholder { + #[new] + fn new() -> Self { + Self(QubitPlaceholder::default()) + } +} diff --git a/quil-py/src/instruction/reset.rs b/quil-py/src/instruction/reset.rs new file mode 100644 index 00000000..14d5ba1b --- /dev/null +++ b/quil-py/src/instruction/reset.rs @@ -0,0 +1,30 @@ +use quil_rs::instruction::{Qubit, Reset}; + +use rigetti_pyo3::{ + impl_hash, impl_repr, py_wrap_data_struct, + pyo3::{pymethods, PyResult, Python}, + PyTryFrom, +}; + +use crate::{impl_copy_for_instruction, impl_eq, impl_to_quil, instruction::PyQubit}; + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyReset(Reset) as "Reset" { + qubit: Option => Option + } +} +impl_repr!(PyReset); +impl_copy_for_instruction!(PyReset); +impl_to_quil!(PyReset); +impl_hash!(PyReset); +impl_eq!(PyReset); + +#[pymethods] +impl PyReset { + #[new] + fn new(py: Python<'_>, qubit: Option) -> PyResult { + Ok(Self(Reset::new(Option::::py_try_from(py, &qubit)?))) + } +} diff --git a/quil-py/src/instruction/timing.rs b/quil-py/src/instruction/timing.rs new file mode 100644 index 00000000..cfcf4eda --- /dev/null +++ b/quil-py/src/instruction/timing.rs @@ -0,0 +1,64 @@ +use quil_rs::expression::Expression; +use quil_rs::instruction::{Delay, Fence, Qubit}; + +use rigetti_pyo3::{ + impl_hash, impl_repr, py_wrap_data_struct, + pyo3::{pymethods, types::PyString, Py, PyResult, Python}, + PyTryFrom, +}; + +use super::PyQubit; +use crate::{expression::PyExpression, impl_copy_for_instruction, impl_eq, impl_to_quil}; + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyDelay(Delay) as "Delay" { + duration: Expression => PyExpression, + frame_names: Vec => Vec>, + qubits: Vec => Vec + } +} +impl_repr!(PyDelay); +impl_to_quil!(PyDelay); +impl_copy_for_instruction!(PyDelay); +impl_hash!(PyDelay); +impl_eq!(PyDelay); + +#[pymethods] +impl PyDelay { + #[new] + pub fn new( + py: Python<'_>, + duration: PyExpression, + frame_names: Vec, + qubits: Vec, + ) -> PyResult { + Ok(Self(Delay::new( + Expression::py_try_from(py, &duration)?, + frame_names, + Vec::::py_try_from(py, &qubits)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyFence(Fence) as "Fence" { + qubits: Vec => Vec + } +} +impl_repr!(PyFence); +impl_to_quil!(PyFence); +impl_copy_for_instruction!(PyFence); +impl_hash!(PyFence); +impl_eq!(PyFence); + +#[pymethods] +impl PyFence { + #[new] + pub fn new(py: Python<'_>, qubits: Vec) -> PyResult { + Ok(Self(Fence::new(Vec::::py_try_from(py, &qubits)?))) + } +} diff --git a/quil-py/src/instruction/waveform.rs b/quil-py/src/instruction/waveform.rs new file mode 100644 index 00000000..a670663b --- /dev/null +++ b/quil-py/src/instruction/waveform.rs @@ -0,0 +1,91 @@ +use indexmap::IndexMap; + +use quil_rs::{ + expression::Expression, + instruction::{Waveform, WaveformDefinition, WaveformInvocation, WaveformParameters}, +}; + +use rigetti_pyo3::{ + impl_hash, impl_repr, py_wrap_data_struct, + pyo3::{pymethods, types::PyString, Py, PyResult, Python}, + PyTryFrom, +}; + +use crate::{expression::PyExpression, impl_copy_for_instruction, impl_eq, impl_to_quil}; + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyWaveform(Waveform) as "Waveform" { + matrix: Vec => Vec, + parameters: Vec => Vec> + } +} +impl_repr!(PyWaveform); +impl_hash!(PyWaveform); +impl_eq!(PyWaveform); + +#[pymethods] +impl PyWaveform { + #[new] + pub fn new( + py: Python<'_>, + matrix: Vec, + parameters: Vec, + ) -> PyResult { + Ok(Self(Waveform::new( + Vec::::py_try_from(py, &matrix)?, + parameters, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyWaveformDefinition(WaveformDefinition) as "WaveformDefinition" { + name: String => Py, + definition: Waveform => PyWaveform + } +} +impl_repr!(PyWaveformDefinition); +impl_to_quil!(PyWaveformDefinition); +impl_copy_for_instruction!(PyWaveformDefinition); +impl_hash!(PyWaveformDefinition); +impl_eq!(PyWaveformDefinition); + +#[pymethods] +impl PyWaveformDefinition { + #[new] + pub fn new(py: Python<'_>, name: String, definition: PyWaveform) -> PyResult { + Ok(Self(WaveformDefinition::new( + name, + Waveform::py_try_from(py, &definition)?, + ))) + } +} + +pub type PyWaveformParameters = IndexMap; + +py_wrap_data_struct! { + #[derive(Debug, PartialEq, Eq)] + #[pyo3(subclass)] + PyWaveformInvocation(WaveformInvocation) as "WaveformInvocation" { + name: String => Py, + parameters: WaveformParameters => PyWaveformParameters + } +} +impl_repr!(PyWaveformInvocation); +impl_to_quil!(PyWaveformInvocation); +impl_eq!(PyWaveformInvocation); + +#[pymethods] +impl PyWaveformInvocation { + #[new] + pub fn new(py: Python<'_>, name: String, parameters: PyWaveformParameters) -> PyResult { + Ok(Self(WaveformInvocation::new( + name, + WaveformParameters::py_try_from(py, ¶meters)?, + ))) + } +} diff --git a/quil-py/src/lib.rs b/quil-py/src/lib.rs new file mode 100644 index 00000000..17ba774e --- /dev/null +++ b/quil-py/src/lib.rs @@ -0,0 +1,68 @@ +use pyo3::prelude::*; +use rigetti_pyo3::create_init_submodule; + +pub mod expression; +pub mod instruction; +pub mod program; +pub mod validation; + +create_init_submodule! { + submodules: [ + "expression": expression::init_submodule, + "instructions": instruction::init_submodule, + "program": program::init_submodule, + "validation": validation::init_submodule + ], +} + +#[pymodule] +fn quil(py: Python<'_>, m: &PyModule) -> PyResult<()> { + init_submodule("quil", py, m)?; + Ok(()) +} + +pub fn init_quil_submodule(name: &str, py: Python<'_>, m: &PyModule) -> PyResult<()> { + init_submodule(name, py, m)?; + Ok(()) +} + +/// Implement `to_quil` and `to_quil_or_debug` methods for wrapper types whose inner type +/// implements [`Quil`](quil_rs::quil::Quil). +#[macro_export] +macro_rules! impl_to_quil { + ($name: ident) => { + #[pyo3::pymethods] + impl $name { + pub fn to_quil(&self) -> pyo3::PyResult { + quil_rs::quil::Quil::to_quil(rigetti_pyo3::PyWrapper::as_inner(self)) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string())) + } + + pub fn to_quil_or_debug(&self) -> String { + quil_rs::quil::Quil::to_quil_or_debug(rigetti_pyo3::PyWrapper::as_inner(self)) + } + } + }; +} + +#[macro_export] +macro_rules! impl_eq { + ($name: ident) => { + #[pyo3::pymethods] + impl $name { + pub fn __richcmp__( + &self, + py: pyo3::Python<'_>, + other: &Self, + op: pyo3::pyclass::CompareOp, + ) -> pyo3::PyObject { + use pyo3::IntoPy; + match op { + pyo3::pyclass::CompareOp::Eq => (self == other).into_py(py), + pyo3::pyclass::CompareOp::Ne => (self != other).into_py(py), + _ => py.NotImplemented(), + } + } + } + }; +} diff --git a/quil-py/src/program/analysis.rs b/quil-py/src/program/analysis.rs new file mode 100644 index 00000000..edb655f0 --- /dev/null +++ b/quil-py/src/program/analysis.rs @@ -0,0 +1,104 @@ +use pyo3::{exceptions::PyValueError, types::PyType}; +use quil_rs::program::analysis::{ + BasicBlock, BasicBlockOwned, BasicBlockScheduleError, ControlFlowGraph, ControlFlowGraphOwned, + QubitGraph, QubitGraphError, +}; +use rigetti_pyo3::{ + impl_repr, py_wrap_error, py_wrap_type, pyo3::prelude::*, wrap_error, PyWrapper, ToPythonError, +}; + +use crate::instruction::{PyInstruction, PyTarget}; + +use super::{scheduling::PyScheduleSeconds, PyProgram}; + +py_wrap_type! { + #[pyo3(subclass)] + PyControlFlowGraph(ControlFlowGraphOwned) as "ControlFlowGraph" +} + +impl_repr!(PyControlFlowGraph); + +#[pymethods] +impl PyControlFlowGraph { + #[new] + #[classmethod] + pub fn new(_: Py, instance: Self) -> Self { + instance + } + + pub fn has_dynamic_control_flow(&self) -> bool { + ControlFlowGraph::from(self.as_inner()).has_dynamic_control_flow() + } + + pub fn basic_blocks(&self) -> Vec { + ControlFlowGraph::from(self.as_inner()) + .into_blocks() + .into_iter() + .map(BasicBlockOwned::from) + .map(PyBasicBlock::from) + .collect() + } +} + +py_wrap_type! { + #[pyo3(subclass)] + PyBasicBlock(BasicBlockOwned) as "BasicBlock" +} +impl_repr!(PyBasicBlock); + +wrap_error!(RustBasicBlockScheduleError(BasicBlockScheduleError)); +py_wrap_error!( + quil, + RustBasicBlockScheduleError, + PyBasicBlockScheduleError, + PyValueError +); + +wrap_error!(RustQubitGraphError(QubitGraphError)); +py_wrap_error!(quil, RustQubitGraphError, PyQubitGraphError, PyValueError); + +#[pymethods] +impl PyBasicBlock { + #[new] + #[classmethod] + pub fn new(_: Py, instance: Self) -> Self { + instance + } + + pub fn as_schedule_seconds(&self, program: &PyProgram) -> PyResult { + BasicBlock::from(self.as_inner()) + .as_schedule_seconds(program.as_inner()) + .map(|v| v.into()) + .map_err(RustBasicBlockScheduleError::from) + .map_err(RustBasicBlockScheduleError::to_py_err) + } + + pub fn gate_depth(&self, gate_minimum_qubit_count: usize) -> PyResult { + let block = BasicBlock::from(self.as_inner()); + QubitGraph::try_from(&block) + .map(|graph| graph.gate_depth(gate_minimum_qubit_count)) + .map_err(RustQubitGraphError::from) + .map_err(RustQubitGraphError::to_py_err) + } + + pub fn instructions(&self) -> Vec { + BasicBlock::from(self.as_inner()) + .instructions() + .iter() + .copied() + .map(PyInstruction::from) + .collect() + } + + pub fn label(&self) -> Option { + BasicBlock::from(self.as_inner()).label().map(|l| l.into()) + } + + pub fn terminator(&self) -> Option { + BasicBlock::from(self.as_inner()) + .terminator() + .clone() + .into_instruction() + .map(PyInstruction::from) + } +} diff --git a/quil-py/src/program/calibration.rs b/quil-py/src/program/calibration.rs new file mode 100644 index 00000000..4b54cb57 --- /dev/null +++ b/quil-py/src/program/calibration.rs @@ -0,0 +1,142 @@ +use quil_rs::{ + instruction::{Calibration, Gate, Instruction, MeasureCalibrationDefinition, Measurement}, + program::Calibrations, +}; +use rigetti_pyo3::{ + impl_as_mut_for_wrapper, impl_repr, py_wrap_type, + pyo3::{pymethods, PyResult, Python}, + PyTryFrom, PyWrapper, PyWrapperMut, ToPython, ToPythonError, +}; + +use crate::{ + impl_eq, + instruction::{ + PyCalibration, PyGate, PyInstruction, PyMeasureCalibrationDefinition, PyMeasurement, + }, +}; + +use super::ProgramError; + +py_wrap_type! { + #[derive(Debug, PartialEq)] + PyCalibrationSet(Calibrations) as "CalibrationSet" +} +impl_as_mut_for_wrapper!(PyCalibrationSet); +impl_repr!(PyCalibrationSet); +impl_eq!(PyCalibrationSet); + +#[pymethods] +impl PyCalibrationSet { + #[new] + pub fn new( + calibrations: Vec, + measure_calibrations: Vec, + ) -> PyResult { + Ok(Self(Calibrations { + calibrations: calibrations + .into_iter() + .map(|c| c.into_inner()) + .collect::>() + .into(), + measure_calibrations: measure_calibrations + .into_iter() + .map(|c| c.into_inner()) + .collect::>() + .into(), + })) + } + + #[getter] + pub fn calibrations(&self, py: Python<'_>) -> PyResult> { + self.as_inner().calibrations().to_python(py) + } + + #[getter] + pub fn measure_calibrations( + &self, + py: Python<'_>, + ) -> PyResult> { + self.as_inner().measure_calibrations().to_python(py) + } + + pub fn expand( + &self, + py: Python<'_>, + instruction: PyInstruction, + previous_calibrations: Vec, + ) -> PyResult>> { + self.as_inner() + .expand( + &Instruction::py_try_from(py, &instruction)?, + &Vec::::py_try_from(py, &previous_calibrations)?, + ) + .map_err(ProgramError::from) + .map_err(ProgramError::to_py_err)? + .to_python(py) + } + + pub fn get_match_for_measurement( + &self, + py: Python<'_>, + measurement: PyMeasurement, + ) -> PyResult> { + Ok(self + .as_inner() + .get_match_for_measurement(&Measurement::py_try_from(py, &measurement)?) + .map(PyMeasureCalibrationDefinition::from)) + } + + pub fn get_match_for_gate( + &self, + py: Python<'_>, + gate: PyGate, + ) -> PyResult> { + Ok(self + .as_inner() + .get_match_for_gate(&Gate::py_try_from(py, &gate)?) + .map(PyCalibration::from)) + } + + pub fn __len__(&self) -> usize { + self.as_inner().len() + } + + pub fn is_empty(&self) -> bool { + self.as_inner().is_empty() + } + + pub fn insert_calibration( + &mut self, + py: Python<'_>, + calibration: PyCalibration, + ) -> PyResult> { + Ok(self + .as_inner_mut() + .insert_calibration(Calibration::py_try_from(py, &calibration)?) + .map(PyCalibration::from)) + } + + pub fn insert_measurement_calibration( + &mut self, + py: Python<'_>, + calibration: PyMeasureCalibrationDefinition, + ) -> PyResult> { + Ok(self + .as_inner_mut() + .insert_measurement_calibration(MeasureCalibrationDefinition::py_try_from( + py, + &calibration, + )?) + .map(PyMeasureCalibrationDefinition::from)) + } + + pub fn extend(&mut self, py: Python<'_>, other: PyCalibrationSet) -> PyResult<()> { + self.as_inner_mut() + .extend(Calibrations::py_try_from(py, &other)?); + Ok(()) + } + + pub fn to_instructions(&self, py: Python<'_>) -> PyResult> { + self.as_inner().to_instructions().to_python(py) + } +} diff --git a/quil-py/src/program/frame.rs b/quil-py/src/program/frame.rs new file mode 100644 index 00000000..d40ec3ab --- /dev/null +++ b/quil-py/src/program/frame.rs @@ -0,0 +1,110 @@ +use std::borrow::Borrow; +use std::collections::{HashMap, HashSet}; +use std::convert::AsRef; + +use quil_rs::{ + instruction::{FrameAttributes, FrameIdentifier}, + program::FrameSet, +}; +use rigetti_pyo3::{ + impl_as_mut_for_wrapper, impl_repr, py_wrap_type, + pyo3::{pymethods, PyResult, Python}, + PyTryFrom, PyWrapper, PyWrapperMut, ToPython, +}; + +use crate::{ + impl_eq, + instruction::{PyFrameAttributes, PyFrameIdentifier, PyInstruction}, +}; + +py_wrap_type! { + #[derive(Debug, PartialEq, Eq)] + PyFrameSet(FrameSet) as "FrameSet" +} +impl_repr!(PyFrameSet); +impl_as_mut_for_wrapper!(PyFrameSet); +impl_eq!(PyFrameSet); + +#[pymethods] +impl PyFrameSet { + #[new] + pub fn new() -> Self { + Self(FrameSet::new()) + } + + pub fn get( + &self, + py: Python<'_>, + identifier: PyFrameIdentifier, + ) -> PyResult> { + self.as_inner() + .get(&FrameIdentifier::py_try_from(py, &identifier)?) + .to_python(py) + } + + pub fn get_keys(&self, py: Python<'_>) -> PyResult> { + self.as_inner().get_keys().to_python(py) + } + + pub fn get_all_frames( + &self, + py: Python<'_>, + ) -> PyResult> { + self.as_inner() + .iter() + .map(|(ident, attribs)| Ok((ident.to_python(py)?, attribs.to_python(py)?))) + .collect() + } + + pub fn insert( + &mut self, + py: Python<'_>, + identifier: PyFrameIdentifier, + attributes: PyFrameAttributes, + ) -> PyResult<()> { + self.as_inner_mut().insert( + FrameIdentifier::py_try_from(py, &identifier)?, + FrameAttributes::py_try_from(py, &attributes)?, + ); + Ok(()) + } + + pub fn merge(&mut self, py: Python<'_>, other: PyFrameSet) -> PyResult<()> { + self.as_inner_mut() + .merge(FrameSet::py_try_from(py, &other)?); + Ok(()) + } + + pub fn intersection( + &self, + py: Python<'_>, + identifiers: HashSet, + ) -> PyResult { + Ok(Self( + self.as_inner().intersection( + &HashSet::::py_try_from(py, &identifiers)? + .iter() + .map(Borrow::borrow) + .collect(), + ), + )) + } + + pub fn is_empty(&self) -> bool { + self.as_inner().is_empty() + } + + pub fn to_instructions(&self, py: Python<'_>) -> PyResult> { + self.as_inner().to_instructions().to_python(py) + } + + pub fn __len__(&self) -> usize { + self.as_inner().len() + } +} + +impl Default for PyFrameSet { + fn default() -> Self { + Self::new() + } +} diff --git a/quil-py/src/program/memory.rs b/quil-py/src/program/memory.rs new file mode 100644 index 00000000..654cc18a --- /dev/null +++ b/quil-py/src/program/memory.rs @@ -0,0 +1,37 @@ +use quil_rs::{ + instruction::{Sharing, Vector}, + program::MemoryRegion, +}; +use rigetti_pyo3::{ + impl_hash, impl_repr, py_wrap_data_struct, + pyo3::{pymethods, PyResult, Python}, + PyTryFrom, +}; + +use crate::{ + impl_eq, + instruction::{PySharing, PyVector}, +}; + +py_wrap_data_struct! { + #[derive(Debug, Eq, PartialEq, Hash)] + #[pyo3(subclass)] + PyMemoryRegion(MemoryRegion) as "MemoryRegion" { + size: Vector => PyVector, + sharing: Option => Option + } +} +impl_repr!(PyMemoryRegion); +impl_hash!(PyMemoryRegion); +impl_eq!(PyMemoryRegion); + +#[pymethods] +impl PyMemoryRegion { + #[new] + pub fn new(py: Python<'_>, size: PyVector, sharing: Option) -> PyResult { + Ok(Self(MemoryRegion::new( + Vector::py_try_from(py, &size)?, + Option::::py_try_from(py, &sharing)?, + ))) + } +} diff --git a/quil-py/src/program/mod.rs b/quil-py/src/program/mod.rs new file mode 100644 index 00000000..9df4ab7d --- /dev/null +++ b/quil-py/src/program/mod.rs @@ -0,0 +1,378 @@ +use std::{ + collections::{HashMap, HashSet}, + str::FromStr, +}; + +use indexmap::IndexMap; +use numpy::{PyArray2, ToPyArray}; +use quil_rs::{ + instruction::{Instruction, QubitPlaceholder, TargetPlaceholder, Waveform}, + program::{ + analysis::{ControlFlowGraph, ControlFlowGraphOwned}, + Calibrations, FrameSet, MemoryRegion, + }, + Program, +}; +use rigetti_pyo3::{ + create_init_submodule, impl_as_mut_for_wrapper, impl_from_str, impl_parse, impl_repr, + num_complex::Complex64, + py_wrap_error, py_wrap_type, + pyo3::{ + exceptions::PyValueError, + prelude::*, + types::{PyBytes, PyFunction, PyList}, + IntoPy, + }, + wrap_error, PyTryFrom, PyWrapper, PyWrapperMut, ToPython, ToPythonError, +}; + +use crate::{ + impl_eq, impl_to_quil, + instruction::{ + PyDeclaration, PyGateDefinition, PyInstruction, PyMemoryReference, PyQubit, PyTarget, + PyWaveform, + }, +}; + +use self::{ + analysis::{PyBasicBlock, PyControlFlowGraph}, + scheduling::{PyScheduleSeconds, PyScheduleSecondsItem, PyTimeSpanSeconds}, +}; +pub use self::{calibration::PyCalibrationSet, frame::PyFrameSet, memory::PyMemoryRegion}; + +mod analysis; +mod calibration; +mod frame; +mod memory; +mod scheduling; + +wrap_error!(ProgramError(quil_rs::program::ProgramError)); +py_wrap_error!(quil, ProgramError, PyProgramError, PyValueError); + +py_wrap_type! { + #[derive(Debug, PartialEq)] + // If unset, the module defaults to builtin, which can't be pickled + #[pyo3(module = "quil.program")] + PyProgram(Program) as "Program" +} +impl_as_mut_for_wrapper!(PyProgram); +impl_repr!(PyProgram); +impl_from_str!(PyProgram, ProgramError); +impl_parse!(PyProgram); +impl_to_quil!(PyProgram); +impl_eq!(PyProgram); + +impl Default for PyProgram { + fn default() -> Self { + Self::new() + } +} + +#[pymethods] +impl PyProgram { + #[new] + pub fn new() -> Self { + Self(Program::default()) + } + + pub fn clone_without_body_instructions(&self) -> Self { + Self(self.as_inner().clone_without_body_instructions()) + } + + pub fn copy(&self) -> Self { + Self(self.as_inner().clone()) + } + + pub fn control_flow_graph(&self) -> PyControlFlowGraph { + ControlFlowGraphOwned::from(ControlFlowGraph::from(self.as_inner())).into() + } + + #[getter] + pub fn body_instructions<'a>(&self, py: Python<'a>) -> PyResult<&'a PyList> { + Ok(PyList::new( + py, + self.as_inner() + .body_instructions() + .map(|i| i.to_python(py)) + .collect::>>()?, + )) + } + + #[getter] + pub fn calibrations(&self, py: Python<'_>) -> PyResult { + self.as_inner().calibrations.to_python(py) + } + + #[setter] + pub fn set_calibrations( + &mut self, + py: Python<'_>, + calibrations: PyCalibrationSet, + ) -> PyResult<()> { + let program = self.as_inner_mut(); + program.calibrations = Calibrations::py_try_from(py, &calibrations)?; + Ok(()) + } + + #[getter] + pub fn waveforms(&self, py: Python<'_>) -> PyResult> { + self.as_inner().waveforms.to_python(py) + } + + #[setter] + pub fn set_waveforms( + &mut self, + py: Python<'_>, + waveforms: IndexMap, + ) -> PyResult<()> { + self.as_inner_mut().waveforms = IndexMap::::py_try_from(py, &waveforms)?; + Ok(()) + } + + #[getter] + pub fn frames(&self, py: Python<'_>) -> PyResult { + self.as_inner().frames.to_python(py) + } + + #[setter] + pub fn set_frames(&mut self, py: Python<'_>, frames: PyFrameSet) -> PyResult<()> { + self.as_inner_mut().frames = FrameSet::py_try_from(py, &frames)?; + Ok(()) + } + + #[getter] + pub fn memory_regions(&self, py: Python<'_>) -> PyResult> { + self.as_inner() + .memory_regions + .iter() + .map(|(name, memory_region)| Ok((name.to_python(py)?, memory_region.to_python(py)?))) + .collect() + } + + #[setter] + pub fn set_memory_regions( + &mut self, + py: Python<'_>, + memory_regions: IndexMap, + ) -> PyResult<()> { + self.as_inner_mut().memory_regions = + IndexMap::::py_try_from(py, &memory_regions)?; + Ok(()) + } + + #[getter] + // TODO: Should this filtering move to Program? Should we assume memory_regions will always make up all + // declarations and simplify this? + pub fn declarations(&self, py: Python<'_>) -> PyResult> { + self.as_inner() + .to_instructions() + .iter() + .filter_map(|inst| match inst { + Instruction::Declaration(declaration) => Some(declaration), + _ => None, + }) + .map(|declaration| Ok((declaration.name.clone(), declaration.to_python(py)?))) + .collect() + } + + #[getter] + pub fn gate_definitions(&self, py: Python<'_>) -> PyResult> { + self.as_inner() + .gate_definitions + .iter() + .map(|(name, gate_def)| Ok((name.to_python(py)?, gate_def.to_python(py)?))) + .collect() + } + + #[setter] + pub fn set_gate_definitions(&mut self, definitions: IndexMap) { + self.as_inner_mut().gate_definitions = definitions + .into_iter() + .map(|(name, gate_def)| (name, gate_def.into_inner())) + .collect(); + } + + pub fn dagger(&self) -> PyResult { + self.as_inner() + .dagger() + .map(PyProgram::from) + .map_err(ProgramError::from) + .map_err(ProgramError::to_py_err) + } + + pub fn expand_calibrations(&self) -> PyResult { + self.as_inner() + .expand_calibrations() + .map(PyProgram::from) + .map_err(ProgramError::from) + .map_err(ProgramError::to_py_err) + } + + pub fn into_simplified(&self) -> PyResult { + self.as_inner() + .into_simplified() + .map(PyProgram::from) + .map_err(ProgramError::from) + .map_err(ProgramError::to_py_err) + } + + pub fn get_used_qubits(&self, py: Python<'_>) -> PyResult> { + self.as_inner() + .get_used_qubits() + .iter() + .map(|q| q.to_python(py)) + .collect() + } + + pub fn add_instruction(&mut self, instruction: PyInstruction) { + self.as_inner_mut().add_instruction(instruction.into()) + } + + pub fn add_instructions(&mut self, instructions: Vec) { + self.as_inner_mut() + .add_instructions(instructions.into_iter().map(Into::into)) + } + + pub fn to_instructions(&self, py: Python<'_>) -> PyResult> { + self.as_inner() + .to_instructions() + .iter() + .map(|i| i.to_python(py)) + .collect() + } + + pub fn to_unitary(&self, py: Python<'_>, n_qubits: u64) -> PyResult>> { + Ok(self + .as_inner() + .to_unitary(n_qubits) + .map_err(ProgramError::from) + .map_err(ProgramError::to_py_err)? + .to_pyarray(py) + .to_owned()) + } + + pub fn filter_instructions(&self, py: Python, predicate: Py) -> PyResult { + let filtered = self.as_inner().filter_instructions(|inst| { + Python::with_gil(|py| { + predicate + .call1(py, (inst.to_python(py).unwrap(),)) + .unwrap_or_else(|err| panic!("predicate function returned an error: {err}")) + .extract(py) + .unwrap_or_else(|err| panic!("predicate function must return a bool: {err}")) + }) + }); + filtered.to_python(py) + } + + pub fn resolve_placeholders(&mut self) { + self.as_inner_mut().resolve_placeholders(); + } + + pub fn wrap_in_loop( + &self, + loop_count_reference: PyMemoryReference, + start_target: PyTarget, + end_target: PyTarget, + iterations: u32, + ) -> Self { + PyProgram(self.as_inner().wrap_in_loop( + loop_count_reference.into_inner(), + start_target.into_inner(), + end_target.into_inner(), + iterations, + )) + } + + // Because we can't bubble up an error from inside the closures, they panic when the given + // Python functions return an error or an unexpected type. This is unusual, but in a Python + // program, this function will only raise because [`pyo3`] wraps Rust panics in a + // `PanicException`. + #[pyo3(signature = (*, target_resolver = None, qubit_resolver = None))] + pub fn resolve_placeholders_with_custom_resolvers( + &mut self, + target_resolver: Option>, + qubit_resolver: Option>, + ) { + #[allow(clippy::type_complexity)] + let rs_qubit_resolver: Box Option> = + if let Some(resolver) = qubit_resolver { + Box::new(move |placeholder: &QubitPlaceholder| -> Option { + Python::with_gil(|py| { + let resolved_qubit = resolver + .call1( + py, + (placeholder + .to_python(py) + .expect("QubitPlaceholder.to_python() should be infallible"),), + ) + .unwrap_or_else(|err| { + panic!("qubit_resolver returned an error: {err}") + }); + + resolved_qubit.extract(py).unwrap_or_else(|err| { + panic!("qubit_resolver must return None or int: {err}") + }) + }) + }) + } else { + self.as_inner().default_qubit_resolver() + }; + + #[allow(clippy::type_complexity)] + let rs_target_resolver: Box Option> = + if let Some(resolver) = target_resolver { + Box::new(move |placeholder: &TargetPlaceholder| -> Option { + Python::with_gil(|py| { + let resolved_label = resolver + .call1( + py, + (placeholder + .to_python(py) + .expect("TargetPlaceholder.to_python() should be infallibe"),), + ) + .unwrap_or_else(|err| { + panic!("label_resolver returned an error: {err}") + }); + + resolved_label.extract(py).unwrap_or_else(|err| { + panic!("label_resolver must return None or str: {err}") + }) + }) + }) + } else { + self.as_inner().default_target_resolver() + }; + + self.as_inner_mut() + .resolve_placeholders_with_custom_resolvers(rs_target_resolver, rs_qubit_resolver); + } + + pub fn __add__(&self, py: Python<'_>, rhs: Self) -> PyResult { + let new = self.as_inner().clone() + rhs.as_inner().clone(); + new.to_python(py) + } + + pub fn __iadd__(&mut self, rhs: Self) { + *self.as_inner_mut() += rhs.as_inner().clone() + } + + // This will raise an error if the program contains any unresolved + // placeholders. This is because they can't be converted to valid quil, + // nor can they be serialized and deserialized in a consistent + // way. + pub fn __getstate__(&self, py: Python<'_>) -> PyResult> { + Ok(PyBytes::new(py, self.to_quil()?.as_bytes()).into_py(py)) + } + + pub fn __setstate__(&mut self, py: Python<'_>, state: &PyBytes) -> PyResult<()> { + *self = Program::from_str(std::str::from_utf8(state.as_bytes())?) + .map_err(ProgramError::from) + .map_err(ProgramError::to_py_err)? + .to_python(py)?; + Ok(()) + } +} + +create_init_submodule! { + classes: [ PyFrameSet, PyProgram, PyCalibrationSet, PyMemoryRegion, PyBasicBlock, PyControlFlowGraph, PyScheduleSeconds, PyScheduleSecondsItem, PyTimeSpanSeconds ], +} diff --git a/quil-py/src/program/scheduling.rs b/quil-py/src/program/scheduling.rs new file mode 100644 index 00000000..7b7ffc5a --- /dev/null +++ b/quil-py/src/program/scheduling.rs @@ -0,0 +1,89 @@ +use pyo3::exceptions::PyValueError; +use quil_rs::program::scheduling::{ + ComputedScheduleError, ComputedScheduleItem, ScheduleSeconds, Seconds, TimeSpan, +}; +use rigetti_pyo3::{ + impl_repr, py_wrap_error, py_wrap_type, pyo3::prelude::*, wrap_error, PyWrapper, +}; + +use crate::impl_eq; + +wrap_error!(RustComputedScheduleError(ComputedScheduleError)); +py_wrap_error!( + quil, + RustComputedScheduleError, + PyComputedScheduleError, + PyValueError +); +py_wrap_type! { + #[pyo3(subclass)] + #[derive(Debug, PartialEq)] + PyScheduleSeconds(ScheduleSeconds) as "ScheduleSeconds" +} + +impl_repr!(PyScheduleSeconds); +impl_eq!(PyScheduleSeconds); + +#[pymethods] +impl PyScheduleSeconds { + pub fn items(&self) -> Vec { + self.as_inner() + .items() + .iter() + .map(PyScheduleSecondsItem::from) + .collect() + } + + pub fn duration(&self) -> f64 { + self.as_inner().duration().0 + } +} + +py_wrap_type! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyScheduleSecondsItem(ComputedScheduleItem) as "ScheduleSecondsItem" +} + +impl_repr!(PyScheduleSecondsItem); +impl_eq!(PyScheduleSecondsItem); + +#[pymethods] +impl PyScheduleSecondsItem { + #[getter] + pub fn time_span(&self) -> PyTimeSpanSeconds { + (&self.as_inner().time_span).into() + } + + #[getter] + pub fn instruction_index(&self) -> usize { + self.as_inner().instruction_index + } +} + +py_wrap_type! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyTimeSpanSeconds(TimeSpan) as "TimeSpanSeconds" +} + +impl_repr!(PyTimeSpanSeconds); +impl_eq!(PyTimeSpanSeconds); + +#[pymethods] +impl PyTimeSpanSeconds { + #[getter] + pub fn start(&self) -> f64 { + self.as_inner().start_time().0 + } + + #[getter] + pub fn duration(&self) -> f64 { + self.as_inner().duration().0 + } + + #[getter] + pub fn end(&self) -> f64 { + self.as_inner().end().0 + } +} diff --git a/quil-py/src/validation/identifier.rs b/quil-py/src/validation/identifier.rs new file mode 100644 index 00000000..88bba695 --- /dev/null +++ b/quil-py/src/validation/identifier.rs @@ -0,0 +1,38 @@ +use quil_rs::validation::identifier::{validate_identifier, validate_user_identifier}; +use rigetti_pyo3::{ + create_init_submodule, py_wrap_error, + pyo3::{exceptions::PyValueError, pyfunction, PyResult}, + wrap_error, ToPythonError, +}; + +wrap_error!(RustIdentifierValidationError( + quil_rs::validation::identifier::IdentifierValidationError +)); + +py_wrap_error!( + quil, + RustIdentifierValidationError, + IdentifierValidationError, + PyValueError +); + +#[pyfunction] +#[pyo3(name = "validate_identifier")] +pub fn py_validate_identifier(ident: &str) -> PyResult<()> { + validate_identifier(ident) + .map_err(RustIdentifierValidationError::from) + .map_err(RustIdentifierValidationError::to_py_err) +} + +#[pyfunction] +#[pyo3(name = "validate_user_identifier")] +pub fn py_validate_user_identifier(ident: &str) -> PyResult<()> { + validate_user_identifier(ident) + .map_err(RustIdentifierValidationError::from) + .map_err(RustIdentifierValidationError::to_py_err) +} + +create_init_submodule! { + errors: [IdentifierValidationError], + funcs: [py_validate_identifier, py_validate_user_identifier], +} diff --git a/quil-py/src/validation/mod.rs b/quil-py/src/validation/mod.rs new file mode 100644 index 00000000..3189849c --- /dev/null +++ b/quil-py/src/validation/mod.rs @@ -0,0 +1,7 @@ +use rigetti_pyo3::create_init_submodule; + +pub mod identifier; + +create_init_submodule! { + submodules : ["identifier": identifier::init_submodule], +} diff --git a/quil-py/test/instructions/test_copy.py b/quil-py/test/instructions/test_copy.py new file mode 100644 index 00000000..e43ed78b --- /dev/null +++ b/quil-py/test/instructions/test_copy.py @@ -0,0 +1,54 @@ +from copy import copy, deepcopy + +from quil.expression import Expression +from quil.instructions import ( + Calibration, + Delay, + FrameIdentifier, + Instruction, + Pulse, + Qubit, + QubitPlaceholder, + WaveformInvocation, +) + + +def test_instruction_with_qubit(): + frame_identifier = FrameIdentifier("frame", [Qubit.from_placeholder(QubitPlaceholder())]) + waveform_invocation = WaveformInvocation("wf", {}) + pulse = Pulse(False, frame_identifier, waveform_invocation) + + pulse_copy = copy(pulse) + assert pulse_copy == pulse + pulse_copy.frame.name = "renamed_frame" + assert pulse_copy == pulse + + pulse_deepcopy = deepcopy(pulse) + assert pulse_deepcopy.blocking == pulse.blocking + assert pulse_deepcopy.waveform == pulse.waveform + assert pulse_deepcopy.frame.name == pulse.frame.name + assert pulse_deepcopy.frame.qubits != pulse.frame.qubits + + instruction = Instruction(pulse) + instruction_deepcopy = deepcopy(instruction) + assert instruction_deepcopy != instruction + + +def test_instruction_with_duplicate_placeholders(): + placeholder = Qubit.from_placeholder(QubitPlaceholder()) + + calibration = Calibration( + "MYCAL", + [], + [placeholder], + [Instruction.from_delay(Delay(Expression.from_number(complex(0.5)), [], [placeholder]))], + [], + ) + + calibration_copy = copy(calibration) + assert calibration_copy == calibration + + calibration_deepcopy = deepcopy(calibration) + assert calibration_deepcopy != calibration + + assert calibration_deepcopy.qubits == calibration_deepcopy.instructions[0].to_delay().qubits diff --git a/quil-py/test/instructions/test_gate.py b/quil-py/test/instructions/test_gate.py new file mode 100644 index 00000000..358fc5d0 --- /dev/null +++ b/quil-py/test/instructions/test_gate.py @@ -0,0 +1,10 @@ +from quil.expression import Expression +from quil.instructions import PauliGate, PauliTerm + + +class TestPauliTerm: + def test_new(self): + pt = PauliTerm([(PauliGate.X, "a")], Expression.new_pi()) + assert pt.arguments == [(PauliGate.X, "a")] + pt.arguments = [(PauliGate.Y, "b")] + assert pt.arguments == [(PauliGate.Y, "b")] diff --git a/quil-py/test/program/__snapshots__/test_program.ambr b/quil-py/test/program/__snapshots__/test_program.ambr new file mode 100644 index 00000000..79a305ea --- /dev/null +++ b/quil-py/test/program/__snapshots__/test_program.ambr @@ -0,0 +1,12 @@ +# name: test_filter_instructions + ''' + DECLARE foo REAL[1] + DEFGATE BAR AS MATRIX: + 0, 1 + 1, 0 + + H 1 + CNOT 2 3 + + ''' +# --- diff --git a/quil-py/test/program/test_program.py b/quil-py/test/program/test_program.py new file mode 100644 index 00000000..86f81179 --- /dev/null +++ b/quil-py/test/program/test_program.py @@ -0,0 +1,188 @@ +import pickle +from typing import Optional + +import pytest +from syrupy.assertion import SnapshotAssertion + +from quil.instructions import Gate, Instruction, Jump, Qubit, QubitPlaceholder, Target, TargetPlaceholder +from quil.program import Program + + +def test_pickle(): + program = Program.parse( + """ +DECLARE ro BIT[2] +H 0 +CNOT 0 1 +MEASURE 0 ro[0] +MEASURE 1 ro[1] +""" + ) + pickled = pickle.dumps(program) + unpickled = pickle.loads(pickled) + assert program == unpickled + + +def test_custom_resolver(): + qubit_placeholder = QubitPlaceholder() + + def qubit_resolver(qubit: QubitPlaceholder) -> Optional[int]: + return {qubit_placeholder: 9}.get(qubit) + + target_placeholder = TargetPlaceholder("base-target") + + def target_resolver(target: TargetPlaceholder) -> Optional[str]: + print("resolving target", target) + return {target_placeholder: "test"}.get(target) + + program = Program() + program.add_instructions( + [ + Instruction.from_gate(Gate("H", [], [Qubit.from_placeholder(qubit_placeholder)], [])), + Instruction.from_jump(Jump(Target.from_placeholder(target_placeholder))), + ] + ) + + with pytest.raises(ValueError): + program.to_quil() + + program.resolve_placeholders_with_custom_resolvers(target_resolver=target_resolver, qubit_resolver=qubit_resolver) + + print(program.to_quil_or_debug()) + + assert program.to_quil() == "H 9\nJUMP @test\n" + + +def test_single_block_control_flow_analysis(): + program = Program.parse( + """ +DECLARE ro BIT[2] +H 0 +CNOT 0 1 +MEASURE 0 ro[0] +MEASURE 1 ro[1] +""" + ) + assert program.get_used_qubits() == {Qubit.from_fixed(0), Qubit.from_fixed(1)} + cfg = program.control_flow_graph() + blocks = cfg.basic_blocks() + assert len(blocks) == 1 + assert blocks[0].terminator() is None + block_program = Program() + block_program.add_instructions(blocks[0].instructions()) + assert ( + block_program.to_quil() + == """H 0 +CNOT 0 1 +MEASURE 0 ro[0] +MEASURE 1 ro[1] +""" + ) + + +def test_multi_block_control_flow_analysis(): + program = Program.parse( + """ +DECLARE ro BIT[1] +LABEL @start +MEASURE 0 ro[0] +JUMP-UNLESS @end ro[0] +X 0 +JUMP @start +LABEL @end +HALT +""" + ) + assert program.get_used_qubits() == {Qubit.from_fixed(0)} + cfg = program.control_flow_graph() + + assert cfg.has_dynamic_control_flow() + + blocks = cfg.basic_blocks() + assert len(blocks) == 3 + + +def test_basic_block_fixed_schedule(): + """Test that a simple, realistic program can be scheduled as expected.""" + program = Program.parse( + """DEFFRAME 0 "flux_tx_cz": + TEST: 1 + +DEFFRAME 1 "flux_tx_iswap": + TEST: 1 + +DEFFRAME 1 "flux_tx_cz": + TEST: 1 + +DEFFRAME 1 "flux_tx_iswap": + TEST: 1 + +DEFFRAME 2 "flux_tx_cz": + TEST: 1 + +DEFFRAME 2 "flux_tx_iswap": + TEST: 1 + +DEFFRAME 3 "flux_tx_cz": + TEST: 1 + +DEFFRAME 3 "flux_tx_iswap": + TEST: 1 + +# Simplified version +DEFCAL CZ q0 q1: + FENCE q0 q1 + SET-PHASE q0 "flux_tx_cz" 0.0 + SET-PHASE q1 "flux_tx_iswap" 0.0 + NONBLOCKING PULSE q0 "flux_tx_cz" erf_square(duration: 6.000000000000001e-08) + NONBLOCKING PULSE q1 "flux_tx_iswap" erf_square(duration: 6.000000000000001e-08) + SHIFT-PHASE q0 "flux_tx_cz" 1.0 + SHIFT-PHASE q1 "flux_tx_iswap" 1.0 + FENCE q0 q1 + +CZ 0 1 +CZ 2 3 +CZ 0 2 +""" + ) + cfg = program.control_flow_graph() + blocks = cfg.basic_blocks() + assert len(blocks) == 1 + block = blocks[0] + + schedule = block.as_schedule_seconds(program) + items = schedule.items() + + # One for each CZ + assert len(items) == 3 + + items = {item.instruction_index: item for item in items} + + # The first CZ should start at 0 + assert items[0].time_span.start == 0.0 + + # The first CZ should start and end at the same times + assert items[0].time_span.start == items[1].time_span.start + assert items[0].time_span.duration == items[1].time_span.duration + + # The third CZ should start when the first and second ones end and be of the same duration + assert items[0].time_span.start + items[0].time_span.duration == items[2].time_span.start + assert items[0].time_span.duration == items[2].time_span.duration + + +def test_filter_instructions(snapshot: SnapshotAssertion): + input = """DECLARE foo REAL[1] +DEFFRAME 1 "rx": +\tHARDWARE-OBJECT: "hardware" +DEFCAL I 1: +\tDELAY 0 1 +DEFGATE BAR AS MATRIX: +\t0, 1 +\t1, 0 + +H 1 +CNOT 2 3 +""" + program = Program.parse(input) + program_without_quil_t = program.filter_instructions(lambda instruction: not instruction.is_quil_t()) + assert program_without_quil_t.to_quil() == snapshot diff --git a/quil-py/test/test_eq.py b/quil-py/test/test_eq.py new file mode 100644 index 00000000..9f8f8a61 --- /dev/null +++ b/quil-py/test/test_eq.py @@ -0,0 +1,38 @@ +from copy import deepcopy + +import numpy as np + +from quil.expression import Expression +from quil.instructions import Gate, Instruction, Qubit +from quil.program import Program + + +def test_instruction_eq(): + pi_expr = Expression.from_number(complex(np.pi, 0)) + + rx = Gate("RX", [pi_expr], [Qubit.from_fixed(0)], []) + rx_copy = deepcopy(rx) + ry = Gate("RY", [pi_expr], [Qubit.from_fixed(0)], []) + assert rx == rx_copy + assert not (rx != rx_copy) + assert rx != ry + assert not (rx == ry) + + rx_inst = Instruction.from_gate(rx) + rx_inst_copy = deepcopy(rx_inst) + ry_inst = Instruction.from_gate(ry) + assert rx_inst == rx_inst_copy + assert not (rx_inst != rx_inst_copy) + assert rx_inst != ry_inst + assert not (rx_inst == ry_inst) + + +def test_program_eq(): + p1 = Program.parse("DECLARE ro BIT\nRX(pi) 0") + p1_copy = deepcopy(p1) + p2 = Program.parse("DECLARE theta BIT\nRX(pi) 0") + + assert p1 == p1_copy + assert not (p1 != p1_copy) + assert p1 != p2 + assert not (p1 == p2) diff --git a/quil-rs/CHANGELOG.md b/quil-rs/CHANGELOG.md new file mode 100644 index 00000000..1bb9b543 --- /dev/null +++ b/quil-rs/CHANGELOG.md @@ -0,0 +1,1589 @@ +## 0.26.1 + +### Features + +- add waveform templates (#369) + +### Fixes + +- Parsing programs with integers that overflow a u64 will no longer panic; instead, they will raise an error. (#372) + +## 0.26.1-rc.1 + +### Features + +- add waveform templates (#369) + +### Fixes + +- Parsing programs with integers that overflow a u64 will no longer panic; instead, they will raise an error. (#372) + +## 0.26.1-rc.0 + +### Features + +- add waveform templates (#369) + +## 0.26.0 + +### Breaking Changes + +- reduce number of classical instruction edges in InstructionBlock::graph + +## 0.26.0-rc.0 + +### Breaking Changes + +- reduce number of classical instruction edges in InstructionBlock::graph + +## 0.25.1 + +### Features + +- Support constructing ControlFlowGraph and BasicBlocks. (#359) + +## 0.25.1-rc.0 + +### Features + +- Support constructing ControlFlowGraph and BasicBlocks. (#359) + +## 0.25.0 + +### Breaking Changes + +- Program instruction iteration and serialization is deterministic. (#355) + +### Fixes + +- Program equality is sensitive to the order of calibration instructions. (#357) + +## 0.25.0-rc.0 + +### Breaking Changes + +- Program instruction iteration and serialization is deterministic. (#355) + +### Fixes + +- Program equality is sensitive to the order of calibration instructions. (#357) + +## 0.24.1-rc.0 + +### Fixes + +- Program equality is sensitive to the order of calibration instructions. (#357) + +## 0.24.0 + +### Breaking Changes + +- CalibrationSet's and Program's will be considered equal if they contain the same set of calibrations, regardless of order. (#352) + +## 0.24.0-rc.0 + +### Breaking Changes + +- CalibrationSet's and Program's will be considered equal if they contain the same set of calibrations, regardless of order. (#352) + +## 0.23.0 + +### Breaking Changes + +- #334: program scheduling and analysis utilities (#336) + +### Fixes + +- Revert "match exactly one qubit for DELAYs without frame specifier" (#342) + +## 0.23.0-rc.1 + +### Breaking Changes + +- #334: program scheduling and analysis utilities (#336) + +### Fixes + +- Revert "match exactly one qubit for DELAYs without frame specifier" (#342) + +## 0.23.0-rc.0 + +### Breaking Changes + +- #334: program scheduling and analysis utilities (#336) + +## 0.22.6 + +### Fixes + +- include separators between DEFCIRCUIT parameters (#338) + +## 0.22.6-rc.0 + +### Fixes + +- include separators between DEFCIRCUIT parameters (#338) + +## 0.22.5 + +### Fixes + +- `is_quil_t()` now correctly returns false for WAIT instructions (#331) + +## 0.22.5-rc.0 + +### Fixes + +- `is_quil_t()` now correctly returns false for WAIT instructions (#331) + +## 0.22.4 + +### Fixes + +- The `wrap_in_loop` method now applies the end target to the program (#329) + +## 0.22.4-rc.0 + +### Fixes + +- The `wrap_in_loop` method now applies the end target to the program (#329) + +## 0.22.3 + +### Features + +- Add methods for identifying Quil-T instructions and filtering instructions from `Program`s (#323) +- Add `Program.wrap_in_loop()` method (#321) +- Add methods for identifying Quil-T instructions and filtering instructions from Programs + +## 0.22.3-rc.1 + +### Features + +- Add methods for identifying Quil-T instructions and filtering instructions from `Program`s (#323) +- Add `Program.wrap_in_loop()` method (#321) +- Add methods for identifying Quil-T instructions and filtering instructions from Programs + +## 0.22.3-rc.0 + +### Features + +- Add `Program.wrap_in_loop()` method (#321) +- Add methods for identifying Quil-T instructions and filtering instructions from Programs + +## 0.22.2 + +### Features + +- impl FromStr for FrameIdentifier (#312) + +### Fixes + +- use internal QuotedString wrapper to quote Quil strings correctly (#317) + +## 0.22.2-rc.1 + +### Features + +- impl FromStr for FrameIdentifier (#312) + +### Fixes + +- use internal QuotedString wrapper to quote Quil strings correctly (#317) + +## 0.22.2-rc.0 + +### Features + +- impl FromStr for FrameIdentifier (#312) + +## 0.22.1 + +### Fixes + +- misc instruction memory accesses (#304) + +## 0.22.1-rc.1 + +### Fixes + +- misc instruction memory accesses (#304) + +## 0.22.1-rc.0 + +### Fixes + +- misc instruction memory accesses (#304) + +## 0.22.0 + +### Breaking Changes + +- Program now has a gate_definitions property that stores all DEFGATEs in a program. These instructions will no longer appear in body_instructions. (#306) + +## 0.22.0-rc.0 + +### Breaking Changes + +- Program now has a gate_definitions property that stores all DEFGATEs in a program. These instructions will no longer appear in body_instructions. (#306) + +## 0.21.7 + +### Fixes + +- match exactly one qubit for DELAYs without frame specifier (#300) + +## 0.21.7-rc.0 + +### Fixes + +- match exactly one qubit for DELAYs without frame specifier (#300) + +## 0.21.6 + +### Fixes + +- trigger release + +## 0.21.6-rc.0 + +### Fixes + +- trigger release + +## 0.21.5 + +### Fixes + +- implement PartialOrd correctly for types implementing Ord (#295) +- no percent symbol in variable qubit outputs + +## 0.21.5-rc.1 + +### Fixes + +- implement PartialOrd correctly for types implementing Ord (#295) +- no percent symbol in variable qubit outputs + +## 0.21.5-rc.0 + +### Fixes + +- no percent symbol in variable qubit outputs + +## 0.21.4 + +### Features + +- Make in-place addition of Program more efficient (#290) + +## 0.21.4-rc.0 + +### Features + +- Make in-place addition of Program more efficient (#290) + +## 0.21.3 + +### Fixes + +- Implement not equal comparisons (#289) + +## 0.21.3-rc.0 + +### Fixes + +- Implement not equal comparisons (#289) + +## 0.21.2 + +### Fixes + +- Allow whitespace to delimit matrix specifications, better support parsing (#286) + +## 0.21.2-rc.0 + +### Fixes + +- Allow whitespace to delimit matrix specifications, better support parsing (#286) + +## 0.21.1 + +### Features + +- Add get_qubits method to Instruction + +## 0.21.1-rc.0 + +### Features + +- Add get_qubits method to Instruction + +## 0.21.0 + +### Breaking Changes + +- Support for Qubit and Target Placeholdres have been added. Converting programs and instructions to a string has been removed and replaced with a fallible to_quil() method. The `Label` struct has been repurposed to support `Label` instructions specifically. The `Target` enum has been added to express `@targets` as part of an instruction. (#266) +- Decouple expression hashing and equality (#277) + +### Features + +- MemoryReference implements Ord (#275) + +## 0.21.0-rc.1 + +### Breaking Changes + +- Support for Qubit and Target Placeholdres have been added. Converting programs and instructions to a string has been removed and replaced with a fallible to_quil() method. The `Label` struct has been repurposed to support `Label` instructions specifically. The `Target` enum has been added to express `@targets` as part of an instruction. (#266) +- Decouple expression hashing and equality (#277) + +### Features + +- MemoryReference implements Ord (#275) + +## 0.21.0-rc.0 + +### Breaking Changes + +- Decouple expression hashing and equality (#277) + +### Features + +- MemoryReference implements Ord (#275) + +## 0.20.1-rc.1 + +### Features + +- MemoryReference implements Ord (#275) + +## 0.20.1-rc.0 + +### Features + +- MemoryReference implements Ord (#275) + +## 0.20.0 + +### Breaking Changes + +- allow overriding Instruction getters (#260) + +### Fixes + +- infix expression parenthesization (#262) +- escape Quil strings for display (#258) + +## 0.20.0-rc.2 + +### Breaking Changes + +- allow overriding Instruction getters (#260) + +### Fixes + +- infix expression parenthesization (#262) +- escape Quil strings for display (#258) + +## 0.20.0-rc.1 + +### Breaking Changes + +- allow overriding Instruction getters (#260) + +### Fixes + +- infix expression parenthesization (#262) +- escape Quil strings for display (#258) + +## 0.20.0-rc.0 + +### Breaking Changes + +- allow overriding Instruction getters (#260) + +### Fixes + +- infix expression parenthesization (#262) +- escape Quil strings for display (#258) + +## 0.19.1-rc.1 + +### Fixes + +- infix expression parenthesization (#262) +- escape Quil strings for display (#258) + +## 0.19.1-rc.0 + +### Fixes + +- escape Quil strings for display (#258) + +## 0.19.0 + +### Breaking Changes + +- This release is identical to 0.18.0. An error in our CI caused a continuity error with our published releases. (#254) + +## 0.19.0-rc.0 + +### Breaking Changes + +- This release is identical to 0.18.0. An error in our CI caused a continuity error with our published releases. (#254) + +## 0.18.0 + +### Breaking Changes + +- When adding two Programs, the resulting Program will have a correct used qubit cache. (#249) +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- wasm-bindgen cargo feature +- Program::into_instructions (#242) +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- associate & commute multiplication & addition in expression simplification (#245) +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- 'simplify' pi expression to floating-point form (#240) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.19.0-rc.1 + +### Breaking Changes + +- When adding two Programs, the resulting Program will have a correct used qubit cache. (#249) +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- wasm-bindgen cargo feature +- Program::into_instructions (#242) +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- associate & commute multiplication & addition in expression simplification (#245) +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- 'simplify' pi expression to floating-point form (#240) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0 + +### Breaking Changes + +- When adding two Programs, the resulting Program will have a correct used qubit cache. (#249) +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- wasm-bindgen cargo feature +- Program::into_instructions (#242) +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- associate & commute multiplication & addition in expression simplification (#245) +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- 'simplify' pi expression to floating-point form (#240) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.19.0-rc.0 + +### Breaking Changes + +- When adding two Programs, the resulting Program will have a correct used qubit cache. (#249) +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- wasm-bindgen cargo feature +- Program::into_instructions (#242) +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- associate & commute multiplication & addition in expression simplification (#245) +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- 'simplify' pi expression to floating-point form (#240) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- wasm-bindgen cargo feature +- Program::into_instructions (#242) +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- associate & commute multiplication & addition in expression simplification (#245) +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- 'simplify' pi expression to floating-point form (#240) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0-rc.14 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- wasm-bindgen cargo feature +- Program::into_instructions (#242) +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- associate & commute multiplication & addition in expression simplification (#245) +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- 'simplify' pi expression to floating-point form (#240) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0-rc.13 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- Program::into_instructions (#242) +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- associate & commute multiplication & addition in expression simplification (#245) +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- 'simplify' pi expression to floating-point form (#240) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0-rc.12 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- Program::into_instructions (#242) +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- Correct precedence rules are followed when matching measure calibrations. (#243) +- Expand all analog control instructions (#238) +- 'simplify' pi expression to floating-point form (#240) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0-rc.11 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- Program::into_instructions (#242) +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- Expand all analog control instructions (#238) +- 'simplify' pi expression to floating-point form (#240) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0-rc.10 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- Program::into_instructions (#242) +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- 'simplify' pi expression to floating-point form (#240) +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0-rc.9 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- Program::into_instructions (#242) +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0-rc.8 + +### Breaking Changes + +- cache used qubits on Program (#234) +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0-rc.7 + +### Breaking Changes + +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- add clone_without_body_instructions to Program (#236) +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0-rc.6 + +### Breaking Changes + +- Program::get_frames_for_instruction return type +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0-rc.5 + +### Breaking Changes + +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- build and return the unitary of a program (#213) +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0-rc.4 + +### Breaking Changes + +- more optimizations (#233) +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.18.0-rc.3 + +### Breaking Changes + +- optimize clones and collections (#228) +- Expression parameter strings are now delimited by a comma. `get_expression_parameter_string` and `get_string_parameter_string` have been removed from the instruction module. (#214) + +### Features + +- Most instruction types are now hashable. + +### Fixes + +- documentation typo +- The destination and source are no longer flipped when parsing CONVERT instructions. (#226) +- BinaryLogic now has it's own to string implementation (#222) + +## 0.17.0 + +### Breaking Changes + +- The `Infix`, `Prefix`, and `FunctionCall` variants of `Expression` are no longer struct variants. They now are tuple variants with an inner `InfixExpression`, `PrefixExpression`, and `FunctionCallExpression`, respectively. +- The `GateType` and `GateSpecification` enums now include a `PauliSum` variant. +- `program::error::ProgramError` has been renamed to `ParseProgramError` +- `Program` methods that return a result now use `program::ProgramError` as the error type. +- `Program.to_headers()` has been removed. This has affected two other methods: + - `Program.to_string()` is now implemented via `std::fmt::Display` and doesn't take an argument to include "headers", they are always included in the string. + - `Program.to_instructions()` no longer takes an argument to include headers, what were called "headers" are always included. +- `CalibrationSet::get_match_for_gate` now takes a single `Gate` as a parameter. + +### Features + +- `PauliSum` `GateSpecification`s are now supported. +- All instruction types now have a `new` constructor, which perform extra validation, if appropriate. +- The `Gate` type now has methods for applying `DAGGER`, `CONTROLLED`, and `FORKED` modifiers. +- `SWAP-PHASES` is now supported. +- `WAIT` is now supported. +- `DECLARE` with offset and sharing is now supported. +- `Program`s can now be appended with the `+` operator. +- `get` and `insert` methods have been added to `FrameSet` +- `CalibrationSet` now has getter methods for `calibrations` and `measure_calibrations`. +- `CalibrationSet` now has a `get_match_for_measurement` method. +- `Program` now has a `dagger` method for applying `DAGGER` modifiers to all gates in a program. + +### Fixes + +- Casing is now ignored when parsing function call expressions. +- Parameters now include the leading `%` when printed with `std::fmt::Display`. +- `DEFCAL MEASURE` can now be parsed with a variable qubit and an address, instead of just one or the other. + + +## 0.16.0 + +### Breaking Changes + +- introduce ExecutionDependency::Scheduled (#186) +- empty commit so knope calculates current version + +### Fixes + +- The parser now follows the correct precedence rules for ungrouped infix expressions (#207) +- The imaginary part of a complex number will now always be formatted as a floating point number. (#205) +- mark additional instructions as scheduled (#203) +- parse SWAP-PHASES (#200) +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) + +## 0.16.0-rc.6 + +### Breaking Changes + +- introduce ExecutionDependency::Scheduled (#186) +- empty commit so knope calculates current version + +### Fixes + +- The parser now follows the correct precedence rules for ungrouped infix expressions (#207) +- The imaginary part of a complex number will now always be formatted as a floating point number. (#205) +- mark additional instructions as scheduled (#203) +- parse SWAP-PHASES (#200) +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) + +## 0.16.0-rc.5 + +### Breaking Changes + +- introduce ExecutionDependency::Scheduled (#186) +- empty commit so knope calculates current version + +### Fixes + +- The imaginary part of a complex number will now always be formatted as a floating point number. (#205) +- mark additional instructions as scheduled (#203) +- parse SWAP-PHASES (#200) +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) + +## 0.16.0-rc.4 + +### Breaking Changes + +- introduce ExecutionDependency::Scheduled (#186) +- empty commit so knope calculates current version + +### Fixes + +- mark additional instructions as scheduled (#203) +- parse SWAP-PHASES (#200) +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) + +## 0.16.0-rc.3 + +### Breaking Changes + +- introduce ExecutionDependency::Scheduled (#186) +- empty commit so knope calculates current version + +### Fixes + +- parse SWAP-PHASES (#200) +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) + +## 0.16.0-rc.2 + +### Breaking Changes + +- introduce ExecutionDependency::Scheduled (#186) +- empty commit so knope calculates current version + +### Fixes + +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) + +## 0.16.0-rc.1 + +### Breaking Changes + +- empty commit so knope calculates current version + +### Fixes + +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) + +## 0.10.0-rc.2 + +### Breaking Changes + +- empty commit to force version bump +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- RESET frame computation +- Program.into_simplified +- Support Expression arithmetic operations (#126) +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) +- update snapshots +- Waveforms w/o params need no parens +- support escaped double quotes and backslashes in strings (#120) +- make dynamic error Sync as well (#131) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.10.0-rc.1 + +### Breaking Changes + +- empty commit to force version bump +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- RESET frame computation +- Program.into_simplified +- Support Expression arithmetic operations (#126) +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) +- update snapshots +- Waveforms w/o params need no parens +- support escaped double quotes and backslashes in strings (#120) +- make dynamic error Sync as well (#131) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.10.0-rc.0 + +### Breaking Changes + +- empty commit to force version bump +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- RESET frame computation +- Program.into_simplified +- Support Expression arithmetic operations (#126) +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- allow for variable qubit in DEFCAL MEASURE +- correctly expand delays (#142) +- update snapshots +- Waveforms w/o params need no parens +- support escaped double quotes and backslashes in strings (#120) +- make dynamic error Sync as well (#131) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.16.0-rc.0 + +### Breaking Changes + +- empty commit to force version bump +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- RESET frame computation +- Program.into_simplified +- Support Expression arithmetic operations (#126) +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- correctly expand delays (#142) +- update snapshots +- Waveforms w/o params need no parens +- support escaped double quotes and backslashes in strings (#120) +- make dynamic error Sync as well (#131) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.15.0 + +### Breaking Changes + +- empty commit to force version bump + +## 0.10.0 + +### Breaking Changes + +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- RESET frame computation +- Program.into_simplified +- Support Expression arithmetic operations (#126) +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- update snapshots +- Waveforms w/o params need no parens +- support escaped double quotes and backslashes in strings (#120) +- make dynamic error Sync as well (#131) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.10.0-rc.2 + +### Breaking Changes + +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- RESET frame computation +- Program.into_simplified +- Support Expression arithmetic operations (#126) +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- update snapshots +- Waveforms w/o params need no parens +- support escaped double quotes and backslashes in strings (#120) +- make dynamic error Sync as well (#131) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.10.0-rc.1 + +### Breaking Changes + +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- RESET frame computation +- Program.into_simplified +- Support Expression arithmetic operations (#126) +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- support escaped double quotes and backslashes in strings (#120) +- make dynamic error Sync as well (#131) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.10.0-rc.0 + +### Breaking Changes + +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- Support Expression arithmetic operations (#126) +- Program.into_simplified +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- make dynamic error Sync as well (#131) +- support escaped double quotes and backslashes in strings (#120) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.15.0-rc.0 + +### Breaking Changes + +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- Support Expression arithmetic operations (#126) +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- make dynamic error Sync as well (#131) +- support escaped double quotes and backslashes in strings (#120) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.14.2 + +### Features + +- Support Expression arithmetic operations (#126) + +### Fixes + +- make dynamic error Sync as well (#131) +- support escaped double quotes and backslashes in strings (#120) + +## 0.14.2-rc.0 + +### Features + +- Support Expression arithmetic operations (#126) + +### Fixes + +- make dynamic error Sync as well (#131) +- support escaped double quotes and backslashes in strings (#120) + +## 0.10.0-rc.1 + +### Breaking Changes + +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- Support Expression arithmetic operations (#126) +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- make dynamic error Sync as well (#131) +- support escaped double quotes and backslashes in strings (#120) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.10.0-rc.0 + +### Breaking Changes + +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- make dynamic error Sync as well (#131) +- support escaped double quotes and backslashes in strings (#120) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.10.0-rc.1 + +### Breaking Changes + +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- make dynamic error Sync as well (#131) +- support escaped double quotes and backslashes in strings (#120) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.10.0-rc.0 + +### Breaking Changes + +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- make dynamic error Sync as well (#131) +- support escaped double quotes and backslashes in strings (#120) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.11.0-rc.0 + +### Breaking Changes + +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- support INCLUDE +- support CONVERT +- support NOP +- impl FromStr for MemoryReference + +### Fixes + +- support escaped double quotes and backslashes in strings (#120) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.10.0 + +### Breaking Changes + +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- impl FromStr for MemoryReference + +### Fixes + +- support escaped double quotes and backslashes in strings (#120) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) + +## 0.15.0-rc.0 + +### Breaking Changes + +- genericize parsing errors and remove error Strings +- fix all compilation errors from error refactor + +### Features + +- impl FromStr for MemoryReference + +### Fixes + +- support escaped double quotes and backslashes in strings (#120) +- fix performance regression (#113) +- do not get line/column info for tokens except on error +- require dynamic error to by Send (#108) +- bump thiserror version and update import name (#103) +- identifier parser (#100) +- test cases with rstest +- test cases should not violate the spec +- remove a `dbg!` statement left over from #88 +- use structured error +- update node version and dependencies for semantic-release (#84) +- update semantic-release version as per dependabot suggestion (#83) +- DEFCAL MEASURE serialization +- test roundtrip of program->string->program +- linting +- Instruction used/blocked frames calculation (#74) diff --git a/quil-rs/Cargo.toml b/quil-rs/Cargo.toml new file mode 100644 index 00000000..c1796bbb --- /dev/null +++ b/quil-rs/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "quil-rs" +description = "Rust tooling for Quil (Quantum Instruction Language)" +version = "0.26.1" +edition = "2021" +rust-version = "1.70" +license = "Apache-2.0" +repository = "https://github.com/rigetti/quil-rs" +keywords = ["Quil", "Quantum", "Rigetti"] +categories = ["parser-implementations", "science", "compilers", "emulators"] + +[dependencies] +approx = { version = "0.5.1", features = ["num-complex"] } +dot-writer = { version = "0.1.2", optional = true } +itertools = "0.12.1" +lexical = "6.1.1" +ndarray.workspace = true +nom = "7.1.1" +nom_locate = "4.0.0" +num-complex = "0.4.0" +once_cell = "1.17.1" +petgraph = "0.6.4" +regex = "1.7.1" +serde = { version = "1.0.125", features = ["derive"] } +strum.workspace = true +thiserror = "1.0.56" +indexmap.workspace = true +statrs = "0.16.0" + +[dev-dependencies] +clap = { version = "4.3.19", features = ["derive", "string"] } +criterion = { version = "0.5.1", features = ["html_reports"] } +insta = "1.37.0" +petgraph = "0.6.2" +proptest = "1.0.0" +proptest-derive = "0.3.0" +rand = "0.8.5" +rasciigraph = "0.2.0" +rstest = "0.18.2" + +# These are described in the crate README.md +[features] +graphviz-dot = ["dot-writer"] +wasm-bindgen = [] + +[[bench]] +name = "parser" +harness = false + +[[bench]] +name = "get_frames_for_instruction" +harness = false + +[[bench]] +name = "scheduled_program_from_program" +harness = false + +[[bench]] +name = "simplification" +harness = false + +[[example]] +name = "generate_test_expressions" diff --git a/quil-rs/README.md b/quil-rs/README.md new file mode 100644 index 00000000..7782486b --- /dev/null +++ b/quil-rs/README.md @@ -0,0 +1,27 @@ +# Quil Parser & Program Builder + +This library is the implementation of the [Quil spec](https://github.com/quil-lang/quil) in Rust. + +It serves three purposes: + +1. Parse Quil programs from strings, and output programs to strings +2. Manipulate Quil programs within Rust +3. Construct a dependency graph among program instructions + +It should be considered unstable until the release of v1.0. + +## Crate Features + +| Feature | Description | | | | +|--------------|--------------------------------------------------------------------|---|---|---| +| graphviz-dot | Enable plotting `ScheduledProgram`s in Graphviz dotfile format. | | | | +| wasm-bindgen | Enable compilation to `wasm32-unknown-unknown` with `wasm-bindgen` | | | | + + +## Testing + +When testing this crate, you should run with the `--all-features` flag to ensure all tests are executed. + +```sh +cargo test --all-features +``` \ No newline at end of file diff --git a/quil-rs/benches/corpus.rs b/quil-rs/benches/corpus.rs new file mode 100644 index 00000000..8a990453 --- /dev/null +++ b/quil-rs/benches/corpus.rs @@ -0,0 +1,59 @@ +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, + str::FromStr, +}; + +pub struct QuilBenchConfig { + pub name: String, + pub program: String, +} + +fn bench_config_from_file(path: &Path) -> Option { + if !path.is_file() { + return None; + } + + let program = fs::read_to_string(path).expect("failed to read quil program file"); + let name = path + .file_name() + .expect("path should have file name component") + .to_str() + .expect("filename should be valid") + .to_string(); + + if quil_rs::Program::from_str(&program).is_ok() { + Some(QuilBenchConfig { name, program }) + } else { + None + } +} + +pub fn from_corpus() -> Vec { + const PATH_SRC: &str = "benches/quilc/tests/good-test-files"; + const SAMPLE_CALIBRATIONS: &str = "benches/sample-calibrations.quil"; + + // collect valid quil programs + let corpus_dir = Path::new(PATH_SRC); + if !corpus_dir.exists() { + init_submodules() + } + + let dir = fs::read_dir(corpus_dir).expect("failed to locate quil corpus directory"); + + dir.filter_map(Result::ok) + .filter_map(|entry| bench_config_from_file(&entry.path())) + .chain(bench_config_from_file(&PathBuf::from(SAMPLE_CALIBRATIONS))) + .collect() +} + +// in the event someone wants to run the benchmarks locally, this will download the corpus of quil used +fn init_submodules() { + Command::new("git") + .args(["submodule", "update", "--init", "--recursive"]) + .spawn() + .expect("failed to spawn git process") + .wait_with_output() + .expect("failed to init submodules, verify `git` is installed"); +} diff --git a/quil-rs/benches/get_frames_for_instruction.rs b/quil-rs/benches/get_frames_for_instruction.rs new file mode 100644 index 00000000..47207e40 --- /dev/null +++ b/quil-rs/benches/get_frames_for_instruction.rs @@ -0,0 +1,30 @@ +mod corpus; + +use std::str::FromStr; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +fn benchmark_quil_corpus(c: &mut Criterion) { + corpus::from_corpus().iter().for_each(|cfg| { + c.bench_function(&cfg.name, |b| { + b.iter_batched( + || { + quil_rs::Program::from_str(&cfg.program) + .expect("program should parse successfully") + }, + |prog| { + for instruction in prog.body_instructions() { + for _ in 0..50 { + let frames = prog.get_frames_for_instruction(instruction); + black_box(frames); + } + } + }, + criterion::BatchSize::SmallInput, + ) + }); + }) +} + +criterion_group!(benches, benchmark_quil_corpus); +criterion_main!(benches); diff --git a/quil-rs/benches/parser.rs b/quil-rs/benches/parser.rs new file mode 100644 index 00000000..a65e2971 --- /dev/null +++ b/quil-rs/benches/parser.rs @@ -0,0 +1,34 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use std::{fs, str::FromStr}; + +mod corpus; + +fn benchmark_sample_calibration(c: &mut Criterion) { + let input = fs::read_to_string(concat!( + env!("CARGO_MANIFEST_DIR"), + "/benches/sample-calibrations.quil" + )) + .expect("benches/sample-calibrations.quil should exist"); + + let mut group = c.benchmark_group("calibration file"); + group.sample_size(100); + group.bench_function("Sample calibration file", |b| { + b.iter(|| { + let _ = quil_rs::Program::from_str(&input); + }) + }); + group.finish(); +} + +fn benchmark_quil_corpus(c: &mut Criterion) { + corpus::from_corpus().iter().for_each(|cfg| { + c.bench_function(&cfg.name, |b| { + b.iter(|| { + let _ = quil_rs::Program::from_str(&cfg.program); + }) + }); + }) +} + +criterion_group!(benches, benchmark_sample_calibration, benchmark_quil_corpus); +criterion_main!(benches); diff --git a/quil-rs/benches/quilc b/quil-rs/benches/quilc new file mode 160000 index 00000000..5c114260 --- /dev/null +++ b/quil-rs/benches/quilc @@ -0,0 +1 @@ +Subproject commit 5c1142609691a817ec70442c103df3693b29d51a diff --git a/benches/sample-calibrations.quil b/quil-rs/benches/sample-calibrations.quil similarity index 100% rename from benches/sample-calibrations.quil rename to quil-rs/benches/sample-calibrations.quil diff --git a/quil-rs/benches/scheduled_program_from_program.rs b/quil-rs/benches/scheduled_program_from_program.rs new file mode 100644 index 00000000..ced3c17f --- /dev/null +++ b/quil-rs/benches/scheduled_program_from_program.rs @@ -0,0 +1,41 @@ +use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; +use quil_rs::{instruction::InstructionHandler, program::scheduling::ScheduledProgram}; +use std::str::FromStr; + +mod corpus; + +fn benchmark_quil_corpus(c: &mut Criterion) { + let mut group = c.benchmark_group("ScheduleProgram::from_program"); + corpus::from_corpus() + .iter() + .filter(|cfg| { + // Ignore any programs that would fail to schedule + match quil_rs::Program::from_str(&cfg.program) { + Err(_) => false, + Ok(program) => { + ScheduledProgram::from_program(&program, &mut InstructionHandler::default()) + .is_ok() + } + } + }) + .for_each(|cfg| { + group.bench_function(&cfg.name, |b| { + b.iter_batched( + || quil_rs::Program::from_str(&cfg.program).unwrap(), + |program| { + let prog = ScheduledProgram::from_program( + &program, + &mut InstructionHandler::default(), + ) + .expect("scheduling should not fail"); + black_box(prog); + }, + BatchSize::SmallInput, + ) + }); + }); + group.finish(); +} + +criterion_group!(benches, benchmark_quil_corpus); +criterion_main!(benches); diff --git a/quil-rs/benches/simplification.rs b/quil-rs/benches/simplification.rs new file mode 100644 index 00000000..21b0a266 --- /dev/null +++ b/quil-rs/benches/simplification.rs @@ -0,0 +1,32 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use once_cell::sync::Lazy; +use quil_rs::expression::Expression; +use std::str::FromStr; + +static EXPRESSIONS: Lazy> = Lazy::new(|| { + include_str!("test_expressions.txt") + .lines() + .map(|line| { + let (name, expr) = line + .split_once('\t') + .expect("these lines are designed this way"); + ( + name.to_string(), + Expression::from_str(expr).expect("these are valid expressions"), + ) + }) + .collect() +}); + +fn simplify(e: Expression) -> Expression { + e.into_simplified() +} + +fn benchmark_expression_simplification(c: &mut Criterion) { + EXPRESSIONS.iter().for_each(|(n, e)| { + c.bench_function(n, |b| b.iter(|| black_box(simplify(e.clone())))); + }) +} + +criterion_group!(benches, benchmark_expression_simplification); +criterion_main!(benches); diff --git a/quil-rs/benches/test_expressions.txt b/quil-rs/benches/test_expressions.txt new file mode 100644 index 00000000..12655096 --- /dev/null +++ b/quil-rs/benches/test_expressions.txt @@ -0,0 +1,25 @@ +173a84262702db6a ((-((sqrt((sqrt((((((pi - (%rPB2U)) - ((eWKiD8ANs[1882173003229244072])))^((pi - (WjkI[369047809853268171]))+((%bdog))))))))))+(sin((exp((-(sqrt((cis(((-(0.753927761290558+0.1274779608318326i))+(sin((%d)))))))))))))))) +6ae5a08a8b51b932 (-(cos((cis((exp((cos(((((sin((-(%qnAW)))))*((sin(((%mZzO7xPqrZ)*(0.6197126024920141+0.2886860102719164i))))))+(exp((-(sqrt(((%J)^(0.6921564511878662+0.8877963133794042i))))))))))))))))) +b95e5dd696057e90 ((cis(((((((exp(((cis((%SW2C)))))))^(sqrt((-((exp((0.7537659690216602+0.49142263960966326i)))^(-(qTZhOmRjQt[427535028116528873]))))))) - ((exp((-((exp((d8mfLMXiq5[3073035939270987126])))+(pi/(0.5549356048833326+0.12936044939206137i)))))))) - (sqrt(((((cis(((0.002713445404557535+0.9878853810537357i))))^((-(0.47470600733698387+0.4947901352540276i)))))^(exp((sin(((cis((V[9038248854277023090])))+((%gxj4IUjWB) - pi)))))))))))))*(sin((sin((cos((sqrt((exp((((-((%cEfSPSJ39q)*pi))) - ((-(-(UvHc9Newr[10005632133741682971])))^(((KNtCU9ko[13880715020190861872]))+(sin(pi)))))))))))))))) +6a46faa89d3ea55c (((sin((((cis((((((0.92064563662856+0.43157363974399454i)^(%KX)) - ((0.8762270701804121+0.5845807911819807i)/pi)))^(cos((-(sqrt(pi)))))))) - (-((((sin((%DYqbF3Gfj8))))^(cis((sin((0.4517991317936275+0.746116208710985i))))))^(-(cis(((%phS994ym)*(HH27BQW[3690035158974706091]))))))))/(((sqrt((-(-(sqrt((0.6396417345674997+0.628813218126556i)))))))))))) - (((-(cis(((sin(((sqrt(pi))*(sqrt((0.7500675509808279+0.8915297015014972i)))))) - ((sin((pi)))*(-(pi*(0.6855254969943321+0.08521304194050594i))))))))^(((((-(cis((0.9374288148478345+0.7630735470283602i)))) - ((cos(pi))*((%gkgVvbKu))))^((-((0.044659540999317016+0.49279907343392637i)*(M3Nii6jRLy[12090585854645165350])))+((pi+(0.8519673924010185+0.1506986097284787i))*((0.471197256178162+0.9789166863558301i) - pi))))+(exp(((exp((exp((eCT[13883025930559259510]))))))))) - (sin((-(((-(%x))+(-pi))+(sin((-(WA[1308525191597856651])))))))))) - (((-(((((0.0645045072856788+0.6420665566203357i)*(%luSQmO))/(sin((WNkNKwBzw[18039914013060603199])))) - ((cos(pi)))))) - (exp(((((-pi)*((0.8158412286232288+0.2032374018778399i))) - (cos((cis((%nfSvZ))))))*(sqrt(((sin(pi))*(cis(pi)))))))))^(sin(((-((exp((pi+(0.5457839604144421+0.6512268017099812i))))*(cis(((%F0lK)*(%EX)))))) - (sqrt((((-(%cUX))) - (-(cis(pi))))))))))))*(-((sin(((((((-(xBg[11652666198817847336]))*((%umNMJyu)^(0.24051690298434913+0.000014412075094671906i))))^(sqrt(((exp((kAvEAUwbD[4369169028651854617]))) - (sqrt((%GKH9A9WB)))))))) - (sqrt((cis((-(sqrt((cos((0.8473623783779789+0.28320799668285424i))))))))))))) - (cos((((cis(((((0.19907089460923733+0.5038917364770757i))*(sqrt((0.5661714209252255+0.7122364474752835i))))+((cis((%Z2WVDo)))+(-(0.5405336515667549+0.5442873071265375i))))))+(cos(((cos((cos((0.15474200027834828+0.37149390595577125i))))))))))))))) +ccf9d5a9e9ef2f9a (cis(((-((-(cis((-(sin((exp((sqrt(pi))))))))))^((sqrt((((sqrt((sin((v6[3182543564664158505])))))) - (sqrt((cos((cos(pi))))))))))))+(cos(((sqrt((((((-pi))*((-(%k5Zu)) - (cis((f0HwsuQ[8150390985608142612]))))))/((-(sqrt(((0.6103183304637813+0.5200831249052977i)))))*(((-(0.13568666037355248+0.04118523255678652i)) - (pi/(B0jxNhknM[2221661361985295311])))/(sin((-pi))))))))^(cis((((-(-((%IB))))*(cis((cis((exp((%t))))))))^((sin(((exp((%UTiLqu3))))))^(((pi^(%G8WT2Gq3))+(pi/(ml[4201561654349347631])))^((-(UXCQDp[612863040070733852])) - (pi))))))))))))) +aefe048e6e1f5eb (exp(((cos((exp((-(-((((pi*(k[465286205247376638]))/((0.3174975383738109+0.9682542830468266i)+(jZKCVDURI[334027890093199391])))/(cos(((bx[11245358153961754805]))))))))))))/(sqrt(((exp(((-(sqrt((-(-(%zfGP1V9))))))*(exp((((sqrt((cP2RGaC[11767783883774225937]))))^(cos((cos((%uzT)))))))))))+(-(sin(((exp((-((%NXU2b)*(0.635655871882054+0.5370557180686911i)))))*(-((exp(pi)) - ((0.6140105294222962+0.9859324792107319i) - (TkH8hknqYH[9720297976666116159])))))))))))))) +7e06a2fa4aa5d2ad (-((-(-((-((cis((-(cos((tZ0mylR79[7150444051172536776])))))) - (-(((c6s[8599200276775860208])*pi)*((E2D7fK[14869207805702654621])+(%eUbRKl6))))))+(sin(((((sin((RNKq6E71[17945555329159622189])))/(cos((aknpXvTpD[810862309246240139]))))/(-((0.413886254158631+0.127668151192735i)*(0.9627497467768105+0.14574187945577377i)))))))))))) +4255789c77f8510c (((sin((sin(((cos((cis((sqrt(((-pi)+((GVXD[9861590576122095072])+(XsjuF[10263815755378945136]))))))))))))))*((((cis(((exp((sin((pi))))))))^(((((pi/(0.33650080879011934+0.14032724091262294i))+(cos((%bZ)))))^(-(-(pi*(0.3534215094990122+0.8632422581156614i)))))*((-(cis((-pi))))+(((exp(pi))+(pi))^((cis(pi)) - ((wtCDXiff[5555231427868552452])+(0.45420805676715537+0.6238722357347042i)))))))/((cis(((((exp((0.2330471763715365+0.48932422313180013i)))*((%oF1iB)))))))+((-(sin(((cos(pi)) - (exp((0.9831518067860007+0.5198904281242142i)))))))))))) - (exp(((sqrt(((((((cos((%agTMyL))) - (exp((HcIc[15441049492044018357]))))^(sin(((0.6983273324523946+0.7686923871376249i)))))+(-(-(pi - (%wVV6AC))))))))))))) +d13b5352621f31a9 ((((cos((sin(((((cis(((%aBt68y47) - (0.160043738887494+0.573043458825749i))))))/(((((%M8IuUNs7C)/pi)^(sqrt((0.4039710724295982+0.17609620832137995i)))) - ((cos(pi))))^((((%C) - pi)) - (((J3CrgQ0l[17971742956304328371])/pi)))))))))+(cis((-(((sqrt((-((f[4019548587206009467])^pi)))))/(-(((exp((%uIx)))/((%ys)/(0.6545740718334008+0.7166408315434312i))) - (((%VAzzNhOx)) - ((%qlDjpcEyD))))))))))) - (((((sin((-(-(-(sqrt(pi)))))))))))) +672850a70e7d62bf (cos(((sqrt((-(((sqrt(((((0.358689139561708+0.2586107271313056i))/(pi*(0.3133002181744047+0.6104523642018599i)))*(((%kvo7Iv)))))))^(sqrt((exp(((cis(((%t)))))))))))))^((((sqrt((-(cos((((0.5466610602133077+0.8217318438575381i)^(CAoNhb[13774545133413118114])) - ((%s)/(pz[9631395094204365524])))))))))*(cos(((cos(((sqrt(((0.36296867264971633+0.7135075096700164i)*(BFRu[7107931267165044489]))))))) - (((((%eCCjb9Z8s0))))*(cis(((-(%A9r3usXr))/((0.21774033420993455+0.1845794754880753i))))))))))+((-((cos((cis(((cis((%iwqeS)))*((0.3885829287800405+0.6630794448782495i)))))))))))))) +f5a4743f24c2661d (-(cis((sqrt(((((cis((cos(((-(p98jhTBdy[18298030677387700618])) - ((0.5032979008155419+0.16350821383308356i)))))))*(sin((((-(%ec7mwt)))+((pi/(oxrkC[3995347605319301968]))+((0.6368531065316847+0.39159861922619554i) - (%v)))))))))))))) +9b02301d06a8da1e ((((cis((((((exp((cos((%Yp3F5VrOA)))))))))))^(cos((exp((sqrt((sqrt((sqrt((cis(((0.5688097218774495+0.10856233610108934i)/pi))))))))))))))*(cis((cos((((((cos(((0.6936877775957795+0.9084807599097233i)))))*(exp((cos((cos(pi))))))))))))))+((exp((cos(((sin((-(sqrt((-((%eGZ089) - (0.4379999993398974+0.13664683711836123i)))))))) - (((sin((((0.6846372818108821+0.38270677480021986i)))))^((sqrt((-(%Yp6Bs))))))))))))+(-((-(cis((((sqrt(((0.6838729133807456+0.11115438289295387i)*(0.9483709686822589+0.46114155714735905i))))/(cos((sqrt((J330ylV[7824979434847205634]))))))/(-(exp(((%wZOfwX1yw)*(F[5269248406977949135]))))))))))))) +3aa3e832dcaad282 ((((((cos((sqrt((((exp((0.1629497386154144+0.14219085443807356i)))^((0.5134864448983544+0.47238119981043214i)+(GFdzg[17394551960862523925])))*(cos((exp((0.4366769113841067+0.46598062868056955i))))))))))/(cis((((((%LX87RiE8)/(%b4hPJwlhCg)) - (sqrt(pi)))^((sqrt((0.7803547560996079+0.7377873483662124i))))) - ((exp((cis(pi))))))))) - ((-((-((exp((0.5667349141446941+0.5265969772809208i)))))))^(cis(((-(cis((-pi))))*(cis((sqrt(((%HKlC)))))))))))*((sqrt((-(-((-(-(%unhpX3jsvz))) - (((mAcyT[17514086350856911374])+(uQzbXokcw[2723789627297440769]))^(sin((0.860221105436476+0.5666323518191785i)))))))))))/((-(cos(((((sin(((%OSODfiEmTr)/(b700cxcdY3[12041442043203787753]))))*(-(-pi))))*((sqrt((((ID[16335168471156511696])/(%kxBk))))) - ((sin(((%W9tQLSUQlV)^(W7am30[8268980426966750818]))))))))))^(((((sqrt((-((uS7S48FVwf[9349193621500174523]))))) - (((cos(pi)))*(-(-pi))))^(cis(((-((A2h[3071441582424427260])*pi))/((exp((LfsR[12563115380044466196]))))))))) - (sqrt(((sin(((cos((pi^(0.0848860777184991+0.2924767255006151i)))) - ((sin((%i7dXQDTt)))))))+((((-(%z))*(cis((Sgz3ZqWUZ[18122185756497468076]))))+((sqrt((%hYLJESWW)))/(sin((fZF[10017546020179431506])))))+(-((sin(pi)))))))))))^(((cos((-(((-(((%GTlwln0lXA)*(0.8831642247345625+0.9434901623547256i)) - (-(lWcXB0X[16065758993579774743]))))/(-(((qlMpAfRe17[1388274470032112328])+(O2fvVnUg[1731378245285406419]))+((%hYVQ10j)*(%Y5)))))/(cos((sin((-(cos((wh4u4BINC[5222386030459614138]))))))))))))^(-((sqrt((exp((-(((0.7955226736168272+0.16384525946094708i))))))))/(((-(((z1fF[8215535681322055254]))+(pi))))))))/(((cis((-(((-(-pi)) - (exp(((dSK[15724216789636247512])/(0.15725717438460807+0.09972084371750678i)))))+(sin(((sqrt((0.19035111998668108+0.2478022661135898i)))+(exp((0.8766275362204793+0.015780347151170382i))))))))))/((sin((sqrt(((exp((sin((0.3913180928125084+0.28864375408651033i)))))+((pi)*(pi^(0.3457848855979051+0.7598742950473052i))))))))+((cis((((sin((Mkx[7074715892971515078])))*(cos((%GALF0rS))))^((exp((tBoUu7yII[6566864008156228876])))))))))) - (((-((cis((((0.41777446141756625+0.19863699052848727i)/(%rU)))))^((sin(((%nDGxK)/pi)))/(((CvTyhQQF9[4151709818573479692]))))))^((sin(((((0.45591928103952983+0.7085848923098182i)*(s9G[18365718805072619376])) - (-pi)))))/(((((GhFSGh[11790346093455586545])+pi)^(sqrt((0.8628591988225915+0.17411566096578346i))))) - ((sqrt((sin((%xmLg4U)))))))))+(((sqrt((cos((sqrt(((%ZWBm)^pi)))))))^(cis(((((rx[8750905303196024406])/(%MNJXJ)) - (cis(pi)))/((pi) - ((dZzJ[10096107892042606281])/(iVjCDIz[11911969965074533386])))))))))))) +f1e09d640a457a42 (cis((((cis(((cis((cos((sqrt((((uxfHDDA8[16527273025155490429]) - pi)+((0.5370815238643659+0.39300324736597636i)))))))))/((((((0.028396672711154203+0.2849895451407497i))^(exp((0.35821322437134484+0.8220394420204237i))))))*((cos(((cos(pi))/(cos((%tq6sFi8NS))))))))))) - (cos((cos((-(sin(((-((KO[980422015806673180])^pi))+(((NWB[7859408064946359605]))+(exp((0.11970022039458017+0.553234597202123i)))))))))))))^(cos((exp((cos(((cos((sin((cos((cis((q8XOLQ[16963474779466710702])))))))))^(exp((sin((-(-(%v2H))))))))))))))))) +8cd674d715aa4851 (-(exp((-(cis(((((cis((exp((sin((%HzXH7e)))))))+(-(((VJxGrQQhCT[9853197500199777691]))^(pi)))))+(sin(((cis((-(exp(pi))))))))))))))) +e7bc32adf73664c1 (exp(((-(-((((cos((((0.6778753455458385+0.4909571308957468i)^(%NI35RleUU4)) - (pi/(%NoK9L)))))*(sin((((%EJ3SmFalG3))*(pi*pi)))))) - ((cis((cos(((-(%pZ))/((%FuFekC8hxf)+(mcdiXGZnAy[3341838834063681415])))))))/((((-pi)+(-pi))))))))+(-(-(sin(((cos((-(-(-pi))))))))))))) +278dfd687df88063 (-((((((cos(((((ZjKIQ0[7399316316025589543]))))))) - (exp((((-(sin(pi)))))))))*((sin((-(-(((sqrt((%LGjC))))*((cos((%X8CfdloIj4)))^((tmLPgmQwJt[4857197057396407686])*pi)))))))*(sqrt((-((-((pi)+(-(qd[118917028751255374]))))*(sqrt(((cis(pi))*(-(Sn3aDbH9T[4426826324789048539]))))))))))) - ((cis((cos((-((cos((exp((-pi)))))))))))))) +6aee9b77f8fc93cf (cis((-(-((sin((cis((cis((cis((cos(((%If9iQDRfA)/(0.4012244003091906+0.2459199718357935i))))))))))))*((-((((-(W[7988081346118122030]))+(cis(pi))))))^(-(-(exp(((cis((%gi8Q1sZwI)))^(-(cE4gtyz[323910736213143785]))))))))))))) +c5b5ba2f4772bc1d (sqrt(((-(cis((sin(((((cos((sin((NVsVisDwb3[11077116287693261923])))))*(-(-(0.46714143939711394+0.5341103914278651i))))*(-(cos(((0.8569281151525258+0.10998811736182768i)+(%gFhd))))))))))))))) +c488bd0db1d7d9e2 (sin((-(sin((sqrt((((exp((-((exp((%x)))))))/((-(cos((sqrt((%Wd))))))/(-(cos(((xrcO[17774985176446845760])*(e8gpxS57o[10656085304832498775]))))))) - ((exp(((cos(((F[13274900918626792304]))))^((-pi)+(cos((0.9218146862539831+0.31641396958015766i)))))))))))))))) +3178bc01eaba628d ((exp(((((exp(((-(-((0.10502050190673018+0.3656962248403598i)^(0.5536165687704481+0.7549382334872303i))))*(((pi)+((%UgAlaunp6)/(0.3414633278627124+0.4722668492879226i)))/(sin(((0.6798567690826945+0.6352833502131923i)/pi))))))) - (((-(-(pi+(0.42592870903969227+0.5606251870998288i))))+((-((eeEruR8Ki[16872487332663907589])))))/(cos((sin((((pGRKlM[12356375288122602056]))/(pi)))))))))+((cos(((-(exp(((cis((%LdbLGCsV)))))))^((sqrt((-(pi^pi)))) - (-((sin((%GV))) - (pi)))))))*(((((-(cos(pi)))))^(cos((cos((-(sin((HnYLSqE[7352395821518689462])))))))))^(-((exp(((sqrt(pi)) - ((%tQiiyqwHnl) - (TFBjx5uf2[14344269862164022574]))))))))))))) +357edca7c6536916 ((-((-(cos(((cis((cos(((cos((%W))))))))+(((sin((pi)))*(((0.392812143557292+0.043223185416233934i)+(0.8118424135036463+0.6706971792090328i))+(-(0.01009197121554506+0.4863644326067531i))))))))))) - (sqrt(((cis(((cis(((exp(((cis((%Evq6aktR)))))))))))))))) +68200c1f5d6bea3f (sqrt((-((cos((-((-(-(exp((-pi))))) - (sin((-((pi*(YTtcj3ttbn[513822240809584536]))))))))))*(((sqrt(((((cos((0.21204505980980648+0.3960231417050756i))))*(((ohxIR[13055741761788244191])/(%wiQCvOtZz))))^(cis((sqrt(((%NbVH)+(biLe6N7[5876163470347147383]))))))))))))))) +10f2f80eeca1ff92 (sin((cis(((-((-(exp((sqrt((sqrt((sin((l2dsnzY7W[1231534077448402696])))))))))) - (cis((-((cos(((0.9736766344146526+0.6543805063486613i)/pi)))*(sqrt(((0.18929549193516337+0.6525988684023386i))))))))))))))) +c4a1cb961aee409c (sqrt((cos(((sqrt((sin((-(exp((-((sin((XArl[1664237423311617999])))^(cos(pi)))))))))))))))) diff --git a/quil-rs/examples/generate_test_expressions.rs b/quil-rs/examples/generate_test_expressions.rs new file mode 100644 index 00000000..66df0477 --- /dev/null +++ b/quil-rs/examples/generate_test_expressions.rs @@ -0,0 +1,214 @@ +//! Generate a file full of expressions for benchmarking simplification. +//! +//! Expressions will be written to a specified file as "\t", where is the hex +//! value corresponding to the hashed version of the expression, and is an overly +//! parenthesized string version of the expression to ensure consistent parsing. + +use clap::Parser; +use quil_rs::{ + expression::{ + Expression, ExpressionFunction, FunctionCallExpression, InfixExpression, InfixOperator, + PrefixExpression, PrefixOperator, + }, + instruction::MemoryReference, + quil::Quil, + reserved::ReservedToken, +}; +use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; +use std::{ + collections::hash_map::DefaultHasher, + fs::File, + hash::{Hash, Hasher}, + io::{BufWriter, Write}, + path::{Path, PathBuf}, + str::FromStr, +}; + +/// Default output path for writing the test expressions. +fn get_default_output_path() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("benches") + .join("test_expressions.txt") +} + +/// Generate a file full of expressions for benchmarking simplification. +#[derive(Parser)] +struct Args { + /// Output path + #[arg(short, long, default_value=get_default_output_path().into_os_string())] + output_path: PathBuf, + /// Number of Expressions to generate + #[arg(short, long, default_value_t = 25)] + number_of_expressions: u64, + /// Maximum depth of expressions + #[arg(short, long, default_value_t = 10)] + maximum_depth: u64, + /// Seed for PRNG (default from random.org) + #[arg(short, long, default_value_t = 27586845)] + seed: u64, +} + +/// Parenthesized version of [`Expression::to_string()`] to ensure consistent re-parsing. +fn parenthesized(expression: &Expression) -> String { + use Expression::*; + match expression { + Address(memory_reference) => format!("({memory_reference})"), + FunctionCall(FunctionCallExpression { + function, + expression, + }) => format!("({function}({}))", parenthesized(expression)), + Infix(InfixExpression { + left, + operator, + right, + }) => format!( + "({}{}{})", + parenthesized(left), + operator, + parenthesized(right) + ), + Number(_) => format!("({})", expression.to_quil_or_debug()), + PiConstant => "pi".to_string(), + Prefix(PrefixExpression { + operator, + expression, + }) => format!("({}{})", operator, parenthesized(expression)), + Variable(identifier) => format!("(%{identifier})"), + } +} + +/// Build a random [`Expression`] +fn build(rng: &mut impl Rng, depth: u64) -> Expression { + if depth == 0 { + match rng.gen_range(0..4) { + 0 => addr(rng), + 1 => number(rng), + 2 => Expression::PiConstant, + 3 => var(rng), + _ => unreachable!(), + } + } else { + let d = depth - 1; + match rng.gen_range(0..3) { + 0 => func(rng, d), + 1 => infix(rng, d), + 2 => prefix(rng, d), + _ => unreachable!(), + } + } +} + +/// Random usable name. +fn name(rng: &mut impl Rng) -> String { + let len = rng.gen_range(1..=10); + let mut name = rng + .sample_iter(&Alphanumeric) + .take(len) + .map(char::from) + .collect::(); + while ReservedToken::from_str(&name).is_ok() + || name.to_lowercase() == "nan" + || name + .chars() + .next() + .map(|c| c.is_ascii_digit()) + .unwrap_or(false) + { + name = rng + .sample_iter(&Alphanumeric) + .take(len) + .map(char::from) + .collect(); + } + name +} + +/// Random address. +fn addr(rng: &mut impl Rng) -> Expression { + Expression::Address(MemoryReference { + name: name(rng), + index: rng.gen(), + }) +} + +/// Random complex number. +fn number(rng: &mut impl Rng) -> Expression { + Expression::Number(num_complex::Complex:: { + re: rng.gen(), + im: rng.gen(), + }) +} + +/// Random variable. +fn var(rng: &mut impl Rng) -> Expression { + Expression::Variable(name(rng)) +} + +/// Random function. +fn func(rng: &mut impl Rng, depth: u64) -> Expression { + let function = match rng.gen_range(0..5) { + 0 => ExpressionFunction::Cis, + 1 => ExpressionFunction::Cosine, + 2 => ExpressionFunction::Exponent, + 3 => ExpressionFunction::Sine, + 4 => ExpressionFunction::SquareRoot, + _ => unreachable!(), + }; + Expression::FunctionCall(FunctionCallExpression { + function, + expression: build(rng, depth).into(), + }) +} + +/// Random infix expression. +fn infix(rng: &mut impl Rng, depth: u64) -> Expression { + let operator = match rng.gen_range(0..5) { + 0 => InfixOperator::Caret, + 1 => InfixOperator::Plus, + 2 => InfixOperator::Minus, + 3 => InfixOperator::Slash, + 4 => InfixOperator::Star, + _ => unreachable!(), + }; + Expression::Infix(InfixExpression { + left: build(rng, depth).into(), + operator, + right: build(rng, depth).into(), + }) +} + +/// Random prefix expression. +fn prefix(rng: &mut impl Rng, depth: u64) -> Expression { + let operator = if rng.gen() { + PrefixOperator::Plus + } else { + PrefixOperator::Minus + }; + Expression::Prefix(PrefixExpression { + operator, + expression: build(rng, depth).into(), + }) +} + +/// Hash a hashable thing to a number. +fn hash(t: &T) -> u64 { + let mut s = DefaultHasher::new(); + t.hash(&mut s); + s.finish() +} + +/// Generate a file full of expressions for benchmarking simplification. +fn main() -> Result<(), String> { + let args = Args::parse(); + let mut rng = StdRng::seed_from_u64(args.seed); + let file = File::create(args.output_path) + .map_err(|e| format!("Error in creating output file: {e:?}"))?; + let mut buf = BufWriter::new(file); + for _ in 0..args.number_of_expressions { + let e = build(&mut rng, args.maximum_depth); + let h = hash(&e); + writeln!(buf, "{:x}\t{}", h, parenthesized(&e),) + .map_err(|e| format!("Error in writing to output file: {e:?}"))?; + } + Ok(()) +} diff --git a/src/expression.rs b/quil-rs/src/expression/mod.rs similarity index 56% rename from src/expression.rs rename to quil-rs/src/expression/mod.rs index ee86c679..eadbe1b6 100644 --- a/src/expression.rs +++ b/quil-rs/src/expression/mod.rs @@ -12,132 +12,167 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::{ + hash::hash_f64, + imag, + instruction::MemoryReference, + parser::{lex, parse_expression, ParseError}, + program::{disallow_leftover, ParseProgramError}, + quil::Quil, + real, +}; use lexical::{format, to_string_with_options, WriteFloatOptions}; use nom_locate::LocatedSpan; use num_complex::Complex64; -use std::collections::{hash_map::DefaultHasher, HashMap}; -use std::f64::consts::PI; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::num::NonZeroI32; -use std::ops::{ - Add, AddAssign, BitXor, BitXorAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign, +use once_cell::sync::Lazy; +use std::{ + collections::HashMap, + f64::consts::PI, + fmt, + hash::{Hash, Hasher}, + num::NonZeroI32, + ops::{Add, AddAssign, BitXor, BitXorAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}, + str::FromStr, }; -use std::str::FromStr; #[cfg(test)] use proptest_derive::Arbitrary; -use crate::parser::{lex, parse_expression, ParseError}; -use crate::program::{disallow_leftover, ProgramError}; -use crate::{imag, instruction::MemoryReference, real}; +mod simplification; /// The different possible types of errors that could occur during expression evaluation. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] pub enum EvaluationError { - /// There wasn't enough information to completely evaluate an expression. + #[error("There wasn't enough information to completely evaluate the expression.")] Incomplete, - /// An operation expected a real number but received a complex one. + #[error("The operation expected a real number but received a complex one.")] NumberNotReal, - /// An operation expected a number but received a different type of expression. + #[error("The operation expected a number but received a different type of expression.")] NotANumber, } #[derive(Clone, Debug)] pub enum Expression { Address(MemoryReference), - FunctionCall { - function: ExpressionFunction, - expression: Box, - }, - Infix { - left: Box, - operator: InfixOperator, - right: Box, - }, - Number(num_complex::Complex64), + FunctionCall(FunctionCallExpression), + Infix(InfixExpression), + Number(Complex64), PiConstant, - Prefix { - operator: PrefixOperator, - expression: Box, - }, + Prefix(PrefixExpression), Variable(String), } -/// Hash value helper: turn a hashable thing into a u64. -fn hash_to_u64(t: &T) -> u64 { - let mut s = DefaultHasher::new(); - t.hash(&mut s); - s.finish() +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct FunctionCallExpression { + pub function: ExpressionFunction, + pub expression: Box, +} + +impl FunctionCallExpression { + pub fn new(function: ExpressionFunction, expression: Box) -> Self { + Self { + function, + expression, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct InfixExpression { + pub left: Box, + pub operator: InfixOperator, + pub right: Box, +} + +impl InfixExpression { + pub fn new(left: Box, operator: InfixOperator, right: Box) -> Self { + Self { + left, + operator, + right, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct PrefixExpression { + pub operator: PrefixOperator, + pub expression: Box, +} + +impl PrefixExpression { + pub fn new(operator: PrefixOperator, expression: Box) -> Self { + Self { + operator, + expression, + } + } +} + +impl PartialEq for Expression { + // Implemented by hand since we can't derive with f64s hidden inside. + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Address(left), Self::Address(right)) => left == right, + (Self::Infix(left), Self::Infix(right)) => left == right, + (Self::Number(left), Self::Number(right)) => left == right, + (Self::Prefix(left), Self::Prefix(right)) => left == right, + (Self::FunctionCall(left), Self::FunctionCall(right)) => left == right, + (Self::Variable(left), Self::Variable(right)) => left == right, + (Self::PiConstant, Self::PiConstant) => true, + _ => false, + } + } } +// Implemented by hand since we can't derive with f64s hidden inside. +impl Eq for Expression {} + impl Hash for Expression { // Implemented by hand since we can't derive with f64s hidden inside. - // Also to understand when things should be the same, like with commutativity (`1 + 2 == 2 + 1`). - // See https://github.com/rigetti/quil-rust/issues/27 fn hash(&self, state: &mut H) { - use std::cmp::{max_by_key, min_by_key}; - use Expression::*; match self { - Address(m) => { + Self::Address(m) => { "Address".hash(state); m.hash(state); } - FunctionCall { + Self::FunctionCall(FunctionCallExpression { function, expression, - } => { + }) => { "FunctionCall".hash(state); function.hash(state); expression.hash(state); } - Infix { + Self::Infix(InfixExpression { left, operator, right, - } => { + }) => { "Infix".hash(state); operator.hash(state); - match operator { - InfixOperator::Plus | InfixOperator::Star => { - // commutative, so put left & right in decreasing order by hash value - let (a, b) = ( - min_by_key(left, right, hash_to_u64), - max_by_key(left, right, hash_to_u64), - ); - a.hash(state); - b.hash(state); - } - _ => { - left.hash(state); - right.hash(state); - } - } + left.hash(state); + right.hash(state); } - Number(n) => { + Self::Number(n) => { "Number".hash(state); // Skip zero values (akin to `format_complex`). - // Also, since f64 isn't hashable, use the u64 binary representation. - // The docs claim this is rather portable: https://doc.rust-lang.org/std/primitive.f64.html#method.to_bits if n.re.abs() > 0f64 { - n.re.to_bits().hash(state) + hash_f64(n.re, state) } if n.im.abs() > 0f64 { - n.im.to_bits().hash(state) + hash_f64(n.im, state) } } - PiConstant => { + Self::PiConstant => { "PiConstant".hash(state); } - Prefix { - operator, - expression, - } => { + Self::Prefix(p) => { "Prefix".hash(state); - operator.hash(state); - expression.hash(state); + p.operator.hash(state); + p.expression.hash(state); } - Variable(v) => { + Self::Variable(v) => { "Variable".hash(state); v.hash(state); } @@ -145,31 +180,23 @@ impl Hash for Expression { } } -impl PartialEq for Expression { - // Partial equality by hash value - fn eq(&self, other: &Self) -> bool { - hash_to_u64(self) == hash_to_u64(other) - } -} - -impl Eq for Expression {} - macro_rules! impl_expr_op { ($name:ident, $name_assign:ident, $function:ident, $function_assign:ident, $operator:ident) => { impl $name for Expression { type Output = Self; fn $function(self, other: Self) -> Self { - Expression::Infix { + Expression::Infix(InfixExpression { left: Box::new(self), operator: InfixOperator::$operator, right: Box::new(other), - } + }) } } impl $name_assign for Expression { fn $function_assign(&mut self, other: Self) { - let result = self.clone().$function(other); - *self = result; + // Move out of self to avoid potentially cloning a large value + let temp = ::std::mem::replace(self, Self::PiConstant); + *self = temp.$function(other); } } }; @@ -182,11 +209,7 @@ impl_expr_op!(Mul, MulAssign, mul, mul_assign, Star); impl_expr_op!(Div, DivAssign, div, div_assign, Slash); /// Compute the result of an infix expression where both operands are complex. -fn calculate_infix( - left: &num_complex::Complex64, - operator: &InfixOperator, - right: &num_complex::Complex64, -) -> num_complex::Complex64 { +fn calculate_infix(left: &Complex64, operator: &InfixOperator, right: &Complex64) -> Complex64 { use InfixOperator::*; match operator { Caret => left.powc(*right), @@ -198,10 +221,7 @@ fn calculate_infix( } /// Compute the result of a Quil-defined expression function where the operand is complex. -fn calculate_function( - function: &ExpressionFunction, - argument: &num_complex::Complex64, -) -> num_complex::Complex64 { +fn calculate_function(function: &ExpressionFunction, argument: &Complex64) -> Complex64 { use ExpressionFunction::*; match function { Sine => argument.sin(), @@ -234,47 +254,12 @@ impl Expression { /// assert_eq!(expression, Expression::Number(Complex64::from(3.0))); /// ``` pub fn simplify(&mut self) { - use Expression::*; - match self { - FunctionCall { - function, - expression, - } => { - expression.simplify(); - if let Number(number) = expression.as_ref() { - *self = Number(calculate_function(function, number)); - } + Expression::Address(_) | Expression::Number(_) | Expression::Variable(_) => {} + Expression::PiConstant => { + *self = Expression::Number(Complex64::from(PI)); } - Infix { - left, - operator: _, - right, - } => { - left.simplify(); - right.simplify(); - } - Prefix { - operator, - expression, - } => { - use PrefixOperator::*; - expression.simplify(); - - // Avoid potentially expensive clone - // Cannot directly swap `expression` with `self` because that causes - // a double mutable borrow. - if let Plus = operator { - let mut temp = Expression::PiConstant; - std::mem::swap(expression.as_mut(), &mut temp); - std::mem::swap(self, &mut temp); - } - } - Variable(_) | Address(_) | PiConstant | Number(_) => {} - }; - - if let Ok(number) = self.evaluate(&HashMap::new(), &HashMap::new()) { - *self = Number(number); + _ => *self = simplification::run(self), } } @@ -321,32 +306,32 @@ impl Expression { /// ``` pub fn evaluate( &self, - variables: &HashMap, + variables: &HashMap, memory_references: &HashMap<&str, Vec>, - ) -> Result { + ) -> Result { use Expression::*; match self { - FunctionCall { + FunctionCall(FunctionCallExpression { function, expression, - } => { + }) => { let evaluated = expression.evaluate(variables, memory_references)?; Ok(calculate_function(function, &evaluated)) } - Infix { + Infix(InfixExpression { left, operator, right, - } => { + }) => { let left_evaluated = left.evaluate(variables, memory_references)?; let right_evaluated = right.evaluate(variables, memory_references)?; Ok(calculate_infix(&left_evaluated, operator, &right_evaluated)) } - Prefix { + Prefix(PrefixExpression { operator, expression, - } => { + }) => { use PrefixOperator::*; let value = expression.evaluate(variables, memory_references)?; if matches!(operator, Minus) { @@ -395,33 +380,33 @@ impl Expression { use Expression::*; match self { - FunctionCall { + FunctionCall(FunctionCallExpression { function, expression, - } => FunctionCall { + }) => Expression::FunctionCall(FunctionCallExpression { function, expression: expression.substitute_variables(variable_values).into(), - }, - Infix { + }), + Infix(InfixExpression { left, operator, right, - } => { + }) => { let left = left.substitute_variables(variable_values).into(); let right = right.substitute_variables(variable_values).into(); - Infix { + Infix(InfixExpression { left, operator, right, - } + }) } - Prefix { + Prefix(PrefixExpression { operator, expression, - } => Prefix { + }) => Prefix(PrefixExpression { operator, expression: expression.substitute_variables(variable_values).into(), - }, + }), Variable(identifier) => match variable_values.get(identifier.as_str()) { Some(value) => value.clone(), None => Variable(identifier), @@ -443,7 +428,7 @@ impl Expression { } impl FromStr for Expression { - type Err = ProgramError; + type Err = ParseProgramError; fn from_str(s: &str) -> Result { let input = LocatedSpan::new(s); @@ -452,6 +437,24 @@ impl FromStr for Expression { } } +static FORMAT_REAL_OPTIONS: Lazy = Lazy::new(|| { + WriteFloatOptions::builder() + .negative_exponent_break(NonZeroI32::new(-5)) + .positive_exponent_break(NonZeroI32::new(15)) + .trim_floats(true) + .build() + .expect("options are valid") +}); + +static FORMAT_IMAGINARY_OPTIONS: Lazy = Lazy::new(|| { + WriteFloatOptions::builder() + .negative_exponent_break(NonZeroI32::new(-5)) + .positive_exponent_break(NonZeroI32::new(15)) + .trim_floats(false) // Per the quil spec, the imaginary part of a complex number is always a floating point number + .build() + .expect("options are valid") +}); + /// Format a num_complex::Complex64 value in a way that omits the real or imaginary part when /// reasonable. That is: /// @@ -461,73 +464,122 @@ impl FromStr for Expression { #[inline(always)] fn format_complex(value: &Complex64) -> String { const FORMAT: u128 = format::STANDARD; - // Safety: - // This uses `build_unchecked`, which is safe as long as `is_valid` is true, and - // `is_valid` must be true because we don't change the default values for: - // - the exponent string, - // - the decimal point, - // - the NaN string, - // - the ∞ string, - // - or the minimum or maximum significant digits. - // Of what we _do_ change: - // - negative_exponent_break is < 0, - // - positive_exponent_break is > 0, - // - and trim floats can only take a bool and both branches are safe. - // As of version 6.1.1 of lexical, this means `OPTIONS.is_valid()` must be true. However, we - // still `assert!` it just to be "safe." - const OPTIONS: WriteFloatOptions = unsafe { - let options = WriteFloatOptions::builder() - .negative_exponent_break(NonZeroI32::new(-5)) - .positive_exponent_break(NonZeroI32::new(15)) - .trim_floats(true) - .build_unchecked(); - assert!(options.is_valid()); - options - }; if value.re == 0f64 && value.im == 0f64 { "0".to_owned() } else if value.im == 0f64 { - to_string_with_options::<_, FORMAT>(value.re, &OPTIONS) + to_string_with_options::<_, FORMAT>(value.re, &FORMAT_REAL_OPTIONS) } else if value.re == 0f64 { - to_string_with_options::<_, FORMAT>(value.im, &OPTIONS) + "i" + to_string_with_options::<_, FORMAT>(value.im, &FORMAT_IMAGINARY_OPTIONS) + "i" } else { - let mut out = to_string_with_options::<_, FORMAT>(value.re, &OPTIONS); + let mut out = to_string_with_options::<_, FORMAT>(value.re, &FORMAT_REAL_OPTIONS); if value.im > 0f64 { out.push('+') } - out.push_str(&to_string_with_options::<_, FORMAT>(value.im, &OPTIONS)); + out.push_str(&to_string_with_options::<_, FORMAT>( + value.im, + &FORMAT_IMAGINARY_OPTIONS, + )); out.push('i'); out } } -impl fmt::Display for Expression { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl Quil for Expression { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> Result<(), crate::quil::ToQuilError> { use Expression::*; match self { - Address(memory_reference) => write!(f, "{}", memory_reference), - FunctionCall { + Address(memory_reference) => memory_reference.write(f, fall_back_to_debug), + FunctionCall(FunctionCallExpression { function, expression, - } => write!(f, "{}({})", function, expression), - Infix { + }) => { + write!(f, "{function}(")?; + expression.write(f, fall_back_to_debug)?; + write!(f, ")")?; + Ok(()) + } + Infix(InfixExpression { left, operator, right, - } => write!(f, "({}{}{})", left, operator, right), - Number(value) => write!(f, "{}", format_complex(value)), - PiConstant => write!(f, "pi"), - Prefix { + }) => { + format_inner_expression(f, fall_back_to_debug, left)?; + write!(f, "{}", operator)?; + format_inner_expression(f, fall_back_to_debug, right) + } + Number(value) => write!(f, "{}", format_complex(value)).map_err(Into::into), + PiConstant => write!(f, "pi").map_err(Into::into), + Prefix(PrefixExpression { operator, expression, - } => write!(f, "({}{})", operator, expression), - Variable(identifier) => write!(f, "%{}", identifier), + }) => { + write!(f, "{}", operator)?; + format_inner_expression(f, fall_back_to_debug, expression) + } + Variable(identifier) => write!(f, "%{}", identifier).map_err(Into::into), + } + } +} + +/// Utility function to wrap infix expressions that are part of an expression in parentheses, so +/// that correct precedence rules are enforced. +fn format_inner_expression( + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + expression: &Expression, +) -> crate::quil::ToQuilResult<()> { + match expression { + Expression::Infix(InfixExpression { + left, + operator, + right, + }) => { + write!(f, "(")?; + format_inner_expression(f, fall_back_to_debug, left)?; + write!(f, "{operator}")?; + format_inner_expression(f, fall_back_to_debug, right)?; + write!(f, ")")?; + Ok(()) } + _ => expression.write(f, fall_back_to_debug), + } +} + +#[cfg(test)] +mod test { + use crate::{ + expression::{ + Expression, InfixExpression, InfixOperator, PrefixExpression, PrefixOperator, + }, + quil::Quil, + real, + }; + + #[test] + fn formats_nested_expression() { + let expression = Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: Box::new(Expression::Number(real!(3f64))), + })), + operator: InfixOperator::Star, + right: Box::new(Expression::Infix(InfixExpression { + left: Box::new(Expression::PiConstant), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(2f64))), + })), + }); + + assert_eq!(expression.to_quil_or_debug(), "-3*(pi/2)"); } } /// A function defined within Quil syntax. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(test, derive(Arbitrary))] pub enum ExpressionFunction { Cis, @@ -554,7 +606,7 @@ impl fmt::Display for ExpressionFunction { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(test, derive(Arbitrary))] pub enum PrefixOperator { Plus, @@ -568,14 +620,15 @@ impl fmt::Display for PrefixOperator { f, "{}", match self { - Plus => "+", + // NOTE: prefix Plus does nothing but cause parsing issues + Plus => "", Minus => "-", } ) } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(test, derive(Arbitrary))] pub enum InfixOperator { Caret, @@ -594,7 +647,8 @@ impl fmt::Display for InfixOperator { match self { Caret => "^", Plus => "+", - Minus => "-", + // NOTE: spaces included to distinguish from hyphenated identifiers + Minus => " - ", Slash => "/", Star => "*", } @@ -603,18 +657,23 @@ impl fmt::Display for InfixOperator { } #[cfg(test)] +// This lint should be re-enabled once this proptest issue is resolved +// https://github.com/proptest-rs/proptest/issues/364 +#[allow(clippy::arc_with_non_send_sync)] mod tests { - use std::collections::HashSet; - - use num_complex::Complex64; + use super::*; + use crate::reserved::ReservedToken; use proptest::prelude::*; + use std::collections::hash_map::DefaultHasher; + use std::collections::HashSet; - use crate::{ - expression::{EvaluationError, Expression, ExpressionFunction}, - real, - }; - - use super::*; + /// Hash value helper: turn a hashable thing into a u64. + #[inline] + fn hash_to_u64(t: &T) -> u64 { + let mut s = DefaultHasher::new(); + t.hash(&mut s); + s.finish() + } #[test] fn simplify_and_evaluate() { @@ -650,10 +709,10 @@ mod tests { evaluated: Ok(one), }, TestCase { - expression: Expression::Prefix { + expression: Expression::Prefix(PrefixExpression { operator: PrefixOperator::Minus, expression: Box::new(Number(real!(1f64))), - }, + }), variables: &empty_variables, memory_references: &empty_memory, simplified: Number(real!(-1f64)), @@ -674,10 +733,10 @@ mod tests { evaluated: Ok(real!(110f64)), }, TestCase { - expression: Expression::FunctionCall { + expression: Expression::FunctionCall(FunctionCallExpression { function: ExpressionFunction::Sine, expression: Box::new(Expression::Number(real!(PI / 2f64))), - }, + }), variables: &variables, memory_references: &empty_memory, simplified: Number(real!(1f64)), @@ -703,86 +762,113 @@ mod tests { } } + /// Parenthesized version of [`Expression::to_string()`] + fn parenthesized(expression: &Expression) -> String { + use Expression::*; + match expression { + Address(memory_reference) => memory_reference.to_quil_or_debug(), + FunctionCall(FunctionCallExpression { + function, + expression, + }) => format!("({function}({}))", parenthesized(expression)), + Infix(InfixExpression { + left, + operator, + right, + }) => format!( + "({}{}{})", + parenthesized(left), + operator, + parenthesized(right) + ), + Number(value) => format!("({})", format_complex(value)), + PiConstant => "pi".to_string(), + Prefix(PrefixExpression { + operator, + expression, + }) => format!("({}{})", operator, parenthesized(expression)), + Variable(identifier) => format!("(%{identifier})"), + } + } + + // Better behaved than the auto-derived version for names + fn arb_name() -> impl Strategy { + r"[a-z][a-zA-Z0-9]{1,10}".prop_filter("Exclude reserved tokens", |t| { + ReservedToken::from_str(t).is_err() && !t.to_lowercase().starts_with("nan") + }) + } + + // Better behaved than the auto-derived version re: names & indices + fn arb_memory_reference() -> impl Strategy { + (arb_name(), (u64::MIN..u32::MAX as u64)) + .prop_map(|(name, index)| MemoryReference { name, index }) + } + + // Better behaved than the auto-derived version via arbitrary floats + fn arb_complex64() -> impl Strategy { + let tau = std::f64::consts::TAU; + ((-tau..tau), (-tau..tau)).prop_map(|(re, im)| Complex64 { re, im }) + } + /// Generate an arbitrary Expression for a property test. /// See https://docs.rs/proptest/1.0.0/proptest/prelude/trait.Strategy.html#method.prop_recursive fn arb_expr() -> impl Strategy { use Expression::*; let leaf = prop_oneof![ - any::().prop_map(Address), - (any::(), any::()) - .prop_map(|(re, im)| Number(num_complex::Complex64::new(re, im))), + arb_memory_reference().prop_map(Address), + arb_complex64().prop_map(Number), Just(PiConstant), - ".*".prop_map(Variable), + arb_name().prop_map(Variable), ]; (leaf).prop_recursive( 4, // No more than 4 branch levels deep 64, // Target around 64 total nodes - 2, // Each "collection" is up to 2 elements + 16, // Each "collection" is up to 16 elements |expr| { prop_oneof![ (any::(), expr.clone()).prop_map(|(function, e)| { - FunctionCall { + Expression::FunctionCall(FunctionCallExpression { function, expression: Box::new(e), - } + }) }), (expr.clone(), any::(), expr.clone()).prop_map( - |(l, operator, r)| Infix { + |(l, operator, r)| Infix(InfixExpression { left: Box::new(l), operator, right: Box::new(r) - } + }) ), - (any::(), expr).prop_map(|(operator, e)| Prefix { - operator, + (expr).prop_map(|e| Prefix(PrefixExpression { + operator: PrefixOperator::Minus, expression: Box::new(e) - }) + })) ] }, ) } - fn arb_complex64() -> impl Strategy { - any::<(f64, f64)>().prop_map(|(re, im)| Complex64::new(re, im)) - } - proptest! { #[test] fn eq(a in any::(), b in any::()) { - let first = Expression::Infix { + let first = Expression::Infix (InfixExpression { left: Box::new(Expression::Number(real!(a))), operator: InfixOperator::Plus, right: Box::new(Expression::Number(real!(b))), - }; - let matching = first.clone(); + } ); let differing = Expression::Number(real!(a + b)); - prop_assert_eq!(&first, &matching); + prop_assert_eq!(&first, &first); prop_assert_ne!(&first, &differing); } - #[test] - fn eq_commutative(a in any::(), b in any::()) { - let first = Expression::Infix{ - left: Box::new(Expression::Number(real!(a))), - operator: InfixOperator::Plus, - right: Box::new(Expression::Number(real!(b))), - }; - let second = Expression::Infix{ - left: Box::new(Expression::Number(real!(b))), - operator: InfixOperator::Plus, - right: Box::new(Expression::Number(real!(a))), - }; - prop_assert_eq!(first, second); - } - #[test] fn hash(a in any::(), b in any::()) { - let first = Expression::Infix { + let first = Expression::Infix (InfixExpression { left: Box::new(Expression::Number(real!(a))), operator: InfixOperator::Plus, right: Box::new(Expression::Number(real!(b))), - }; + }); let matching = first.clone(); let differing = Expression::Number(real!(a + b)); let mut set = HashSet::new(); @@ -791,36 +877,9 @@ mod tests { assert!(!set.contains(&differing)) } - #[test] - fn hash_commutative(a in any::(), b in any::()) { - let first = Expression::Infix{ - left: Box::new(Expression::Number(real!(a))), - operator: InfixOperator::Plus, - right: Box::new(Expression::Number(real!(b))), - }; - let second = Expression::Infix{ - left: Box::new(Expression::Number(real!(b))), - operator: InfixOperator::Plus, - right: Box::new(Expression::Number(real!(a))), - }; - let mut set = HashSet::new(); - set.insert(first); - assert!(set.contains(&second)); - } - #[test] fn eq_iff_hash_eq(x in arb_expr(), y in arb_expr()) { - let h_x = { - let mut s = DefaultHasher::new(); - x.hash(&mut s); - s.finish() - }; - let h_y = { - let mut s = DefaultHasher::new(); - y.hash(&mut s); - s.finish() - }; - prop_assert_eq!(x == y, h_x == h_y); + prop_assert_eq!(x == y, hash_to_u64(&x) == hash_to_u64(&y)); } #[test] @@ -847,60 +906,143 @@ mod tests { fn complexes_are_parseable_as_expressions(value in arb_complex64()) { let parsed = Expression::from_str(&format_complex(&value)); assert!(parsed.is_ok()); - assert_eq!(Expression::Number(value), parsed.unwrap().into_simplified()); + let simple = parsed.unwrap().into_simplified(); + assert_eq!(Expression::Number(value), simple); } #[test] fn exponentiation_works_as_expected(left in arb_expr(), right in arb_expr()) { - let expected = Expression::Infix { left: Box::new(left.clone()), operator: InfixOperator::Caret, right: Box::new(right.clone()) }; - prop_assert_eq!(left.clone() ^ right.clone(), expected.clone()); - let mut x = left.clone(); - x ^= right.clone(); + let expected = Expression::Infix (InfixExpression { left: Box::new(left.clone()), operator: InfixOperator::Caret, right: Box::new(right.clone()) } ); + prop_assert_eq!(left ^ right, expected); + } + + #[test] + fn in_place_exponentiation_works_as_expected(left in arb_expr(), right in arb_expr()) { + let expected = Expression::Infix (InfixExpression { left: Box::new(left.clone()), operator: InfixOperator::Caret, right: Box::new(right.clone()) } ); + let mut x = left; + x ^= right; prop_assert_eq!(x, expected); } #[test] fn addition_works_as_expected(left in arb_expr(), right in arb_expr()) { - let expected = Expression::Infix { left: Box::new(left.clone()), operator: InfixOperator::Plus, right: Box::new(right.clone()) }; - prop_assert_eq!(left.clone() + right.clone(), expected.clone()); - let mut x = left.clone(); - x += right.clone(); + let expected = Expression::Infix (InfixExpression { left: Box::new(left.clone()), operator: InfixOperator::Plus, right: Box::new(right.clone()) } ); + prop_assert_eq!(left + right, expected); + } + + #[test] + fn in_place_addition_works_as_expected(left in arb_expr(), right in arb_expr()) { + let expected = Expression::Infix (InfixExpression { left: Box::new(left.clone()), operator: InfixOperator::Plus, right: Box::new(right.clone()) } ); + let mut x = left; + x += right; prop_assert_eq!(x, expected); } #[test] fn subtraction_works_as_expected(left in arb_expr(), right in arb_expr()) { - let expected = Expression::Infix { left: Box::new(left.clone()), operator: InfixOperator::Minus, right: Box::new(right.clone()) }; - prop_assert_eq!(left.clone() - right.clone(), expected.clone()); - let mut x = left.clone(); - x -= right.clone(); + let expected = Expression::Infix (InfixExpression { left: Box::new(left.clone()), operator: InfixOperator::Minus, right: Box::new(right.clone()) } ); + prop_assert_eq!(left - right, expected); + } + + #[test] + fn in_place_subtraction_works_as_expected(left in arb_expr(), right in arb_expr()) { + let expected = Expression::Infix (InfixExpression { left: Box::new(left.clone()), operator: InfixOperator::Minus, right: Box::new(right.clone()) } ); + let mut x = left; + x -= right; prop_assert_eq!(x, expected); } #[test] fn multiplication_works_as_expected(left in arb_expr(), right in arb_expr()) { - let expected = Expression::Infix { left: Box::new(left.clone()), operator: InfixOperator::Star, right: Box::new(right.clone()) }; - prop_assert_eq!(left.clone() * right.clone(), expected.clone()); - let mut x = left.clone(); - x *= right.clone(); + let expected = Expression::Infix (InfixExpression { left: Box::new(left.clone()), operator: InfixOperator::Star, right: Box::new(right.clone()) } ); + prop_assert_eq!(left * right, expected); + } + + #[test] + fn in_place_multiplication_works_as_expected(left in arb_expr(), right in arb_expr()) { + let expected = Expression::Infix (InfixExpression { left: Box::new(left.clone()), operator: InfixOperator::Star, right: Box::new(right.clone()) } ); + let mut x = left; + x *= right; prop_assert_eq!(x, expected); } #[test] fn division_works_as_expected(left in arb_expr(), right in arb_expr()) { - let expected = Expression::Infix { left: Box::new(left.clone()), operator: InfixOperator::Slash, right: Box::new(right.clone()) }; - prop_assert_eq!(left.clone() / right.clone(), expected.clone()); - let mut x = left.clone(); - x /= right.clone(); + let expected = Expression::Infix (InfixExpression { left: Box::new(left.clone()), operator: InfixOperator::Slash, right: Box::new(right.clone()) } ); + prop_assert_eq!(left / right, expected); + } + + #[test] + fn in_place_division_works_as_expected(left in arb_expr(), right in arb_expr()) { + let expected = Expression::Infix (InfixExpression { left: Box::new(left.clone()), operator: InfixOperator::Slash, right: Box::new(right.clone()) } ); + let mut x = left; + x /= right; prop_assert_eq!(x, expected); } + // Redundant clone: clippy does not correctly introspect the prop_assert_eq! macro + #[allow(clippy::redundant_clone)] + #[test] + fn round_trip(e in arb_expr()) { + let simple_e = e.clone().into_simplified(); + let s = parenthesized(&e); + let p = Expression::from_str(&s); + prop_assert!(p.is_ok()); + let p = p.unwrap(); + let simple_p = p.clone().into_simplified(); + prop_assert_eq!( + simple_p.clone(), + simple_e.clone(), + "Simplified expressions should be equal:\nparenthesized {p} ({p:?}) extracted from {s} simplified to {simple_p}\nvs original {e} ({e:?}) simplified to {simple_e}", + p=p.to_quil_or_debug(), + s=s, + e=e.to_quil_or_debug(), + simple_p=simple_p.to_quil_or_debug(), + simple_e=simple_e.to_quil_or_debug() + ); + } + + } + + /// Assert that certain selected expressions are parsed and re-written to string + /// in exactly the same way. + #[test] + fn specific_round_trip_tests() { + for input in &[ + "-1*(phases+phases[1])", + "(-1*(phases+phases[1]))+(-1*(phases+phases[1]))", + ] { + let parsed = Expression::from_str(input); + let parsed = parsed.unwrap(); + let restring = parsed.to_quil_or_debug(); + assert_eq!(input, &restring); + } + } + #[test] + fn specific_simplification_tests() { + for (input, expected) in [ + ("pi", Expression::Number(PI.into())), + ("pi/2", Expression::Number((PI / 2.0).into())), + ("pi * pi", Expression::Number((PI.powi(2)).into())), + ( + "(a[0]*2*pi)/6.283185307179586", + Expression::Address(MemoryReference { + name: String::from("a"), + index: 0, + }), + ), + ] { + assert_eq!( + Expression::from_str(input).unwrap().into_simplified(), + expected + ) + } } #[test] fn specific_to_real_tests() { - for (input, expected) in vec![ + for (input, expected) in [ (Expression::PiConstant, Ok(PI)), (Expression::Number(Complex64 { re: 1.0, im: 0.0 }), Ok(1.0)), ( @@ -922,6 +1064,8 @@ mod tests { (Complex64::new(0.0, 0.0), "0"), (Complex64::new(-0.0, 0.0), "0"), (Complex64::new(-0.0, -0.0), "0"), + (Complex64::new(0.0, 1.0), "1.0i"), + (Complex64::new(1.0, -1.0), "1-1.0i"), (Complex64::new(1.234, 0.0), "1.234"), (Complex64::new(0.0, 1.234), "1.234i"), (Complex64::new(-1.234, 0.0), "-1.234"), @@ -930,7 +1074,7 @@ mod tests { (Complex64::new(-1.234, 5.678), "-1.234+5.678i"), (Complex64::new(1.234, -5.678), "1.234-5.678i"), (Complex64::new(-1.234, -5.678), "-1.234-5.678i"), - (Complex64::new(1e100, 2e-100), "1e100+2e-100i"), + (Complex64::new(1e100, 2e-100), "1e100+2.0e-100i"), ] { assert_eq!(format_complex(x), *s); } diff --git a/quil-rs/src/expression/simplification/by_hand.rs b/quil-rs/src/expression/simplification/by_hand.rs new file mode 100644 index 00000000..75e49637 --- /dev/null +++ b/quil-rs/src/expression/simplification/by_hand.rs @@ -0,0 +1,868 @@ +/// Complex machinery for simplifying [`Expression`]s. +use crate::expression::{ + imag, real, Expression, ExpressionFunction, FunctionCallExpression, InfixExpression, + InfixOperator, PrefixExpression, PrefixOperator, +}; +use std::cmp::min_by_key; + +/// Simplify an [`Expression`]. +pub(super) fn run(expression: &Expression) -> Expression { + simplify(expression, LIMIT) +} + +/// Keep stack sizes under control +/// +/// Note(@genos): If this limit is allowed to be too large (100, in local testing on my laptop), +/// the recursive nature of `simplify` and friends (below) will build up large callstacks and then +/// crash with an "I've overflowed my stack" error. Except for exceedingly large expressions +/// (`the_big_one` test case in `mod.rs`, for example), a larger limit here doesn't seem to be of +/// practical value in anecdotal testing. +const LIMIT: u64 = 10; + +/// Recursively simplify an [`Expression`] by hand, breaking into cases to make things more +/// manageable. +fn simplify(e: &Expression, limit: u64) -> Expression { + if limit == 0 { + // bail + e.clone() + } else { + match e { + Expression::Address(_) | Expression::Number(_) | Expression::Variable(_) => e.clone(), + Expression::FunctionCall(FunctionCallExpression { + function, + expression, + }) => simplify_function_call(*function, expression, limit - 1), + Expression::PiConstant => Expression::Number(std::f64::consts::PI.into()), + Expression::Infix(InfixExpression { + left, + operator, + right, + }) => simplify_infix(left, *operator, right, limit - 1), + Expression::Prefix(PrefixExpression { + operator, + expression, + }) => simplify_prefix(*operator, expression, limit - 1), + } + } +} + +const PI: num_complex::Complex64 = real!(std::f64::consts::PI); +const ZERO: num_complex::Complex64 = real!(0.0); +const ONE: num_complex::Complex64 = real!(1.0); +const TWO: num_complex::Complex64 = real!(2.0); + +/// Simplify a function call inside an `Expression`, terminating the recursion if `limit` has reached zero. +fn simplify_function_call(func: ExpressionFunction, expr: &Expression, limit: u64) -> Expression { + if limit == 0 { + // bail + Expression::FunctionCall(FunctionCallExpression { + function: func, + expression: expr.clone().into(), + }) + } else { + // Evaluate numbers and π + // Pass through otherwise + match (func, simplify(expr, limit - 1)) { + (ExpressionFunction::Cis, Expression::Number(x)) => { + // num_complex::Complex64::cis only accepts f64 + Expression::Number(x.cos() + imag!(1.0) * x.sin()) + } + (ExpressionFunction::Cis, Expression::PiConstant) => Expression::Number(-ONE), + (ExpressionFunction::Cosine, Expression::Number(x)) => Expression::Number(x.cos()), + (ExpressionFunction::Cosine, Expression::PiConstant) => Expression::Number(-ONE), + (ExpressionFunction::Exponent, Expression::Number(x)) => Expression::Number(x.exp()), + (ExpressionFunction::Exponent, Expression::PiConstant) => Expression::Number(PI.exp()), + (ExpressionFunction::Sine, Expression::Number(x)) => Expression::Number(x.sin()), + (ExpressionFunction::Sine, Expression::PiConstant) => Expression::Number(PI.sin()), + (ExpressionFunction::SquareRoot, Expression::Number(x)) => Expression::Number(x.sqrt()), + (ExpressionFunction::SquareRoot, Expression::PiConstant) => { + Expression::Number(PI.sqrt()) + } + (function, expression) => Expression::FunctionCall(FunctionCallExpression { + function, + expression: expression.into(), + }), + } + } +} + +#[inline] +fn is_zero(x: num_complex::Complex64) -> bool { + x.norm() < 1e-10 +} + +#[inline] +fn is_one(x: num_complex::Complex64) -> bool { + is_zero(x - 1.0) +} + +/// Helper: in simplification, we'll bias towards smaller expressions +fn size(expr: &Expression) -> usize { + match expr { + Expression::Address(_) + | Expression::Number(_) + | Expression::PiConstant + | Expression::Variable(_) => 1, + Expression::FunctionCall(FunctionCallExpression { + function: _, + expression, + }) => 1 + size(expression), + Expression::Infix(InfixExpression { + left, + operator: _, + right, + }) => 1 + size(left) + size(right), + Expression::Prefix(PrefixExpression { + operator: _, + expression, + }) => 1 + size(expression), + } +} + +// It's verbose to go alone! Take this. +macro_rules! infix { + ($left:expr, $op:expr, $right:expr) => { + Expression::Infix(InfixExpression { + left: $left.into(), + operator: $op, + right: $right.into(), + }) + }; +} +macro_rules! add { + ($left:expr, $right:expr) => { + infix!($left, InfixOperator::Plus, $right) + }; +} +macro_rules! sub { + ($left:expr, $right:expr) => { + infix!($left, InfixOperator::Minus, $right) + }; +} +macro_rules! mul { + ($left:expr, $right:expr) => { + infix!($left, InfixOperator::Star, $right) + }; +} +macro_rules! div { + ($left:expr, $right:expr) => { + infix!($left, InfixOperator::Slash, $right) + }; +} + +/// Check if both arguments are of the form "something * x" for the _same_ x. +fn mul_matches(left_ax: &Expression, right_ax: &Expression) -> bool { + match (left_ax, right_ax) { + ( + Expression::Infix(InfixExpression { + left: ref ll, + operator: InfixOperator::Star, + right: ref lr, + }), + Expression::Infix(InfixExpression { + left: ref rl, + operator: InfixOperator::Star, + right: ref rr, + }), + ) => ll == rl || ll == rr || lr == rl || lr == rr, + _ => false, + } +} + +/// Simplify an infix expression inside an `Expression`, terminating the recursion if `limit` has reached +/// zero. +fn simplify_infix(l: &Expression, op: InfixOperator, r: &Expression, limit: u64) -> Expression { + if limit == 0 { + // bail + Expression::Infix(InfixExpression { + left: l.clone().into(), + operator: op, + right: r.clone().into(), + }) + } else { + // There are … many cases here + match (simplify(l, limit - 1), op, simplify(r, limit - 1)) { + //---------------------------------------------------------------- + // First: only diving one deep, pattern matching on the operation + // (Constant folding and cancellations, mostly) + //---------------------------------------------------------------- + + // Addition and Subtraction + + // Adding with zero + (Expression::Number(x), InfixOperator::Plus, other) + | (other, InfixOperator::Plus, Expression::Number(x)) + if is_zero(x) => + { + other + } + // Adding numbers or π + (Expression::Number(x), InfixOperator::Plus, Expression::Number(y)) => { + Expression::Number(x + y) + } + (Expression::Number(x), InfixOperator::Plus, Expression::PiConstant) + | (Expression::PiConstant, InfixOperator::Plus, Expression::Number(x)) => { + Expression::Number(PI + x) + } + (Expression::PiConstant, InfixOperator::Plus, Expression::PiConstant) => { + Expression::Number(2.0 * PI) + } + + // Subtracting with zero + (Expression::Number(x), InfixOperator::Minus, right) if is_zero(x) => { + simplify_prefix(PrefixOperator::Minus, &right, limit - 1) + } + (left, InfixOperator::Minus, Expression::Number(y)) if is_zero(y) => left, + // Subtracting self + (left, InfixOperator::Minus, right) if left == right => Expression::Number(ZERO), + // Subtracting numbers or π (π - π already covered) + (Expression::Number(x), InfixOperator::Minus, Expression::Number(y)) => { + Expression::Number(x - y) + } + (Expression::Number(x), InfixOperator::Minus, Expression::PiConstant) => { + Expression::Number(x - PI) + } + (Expression::PiConstant, InfixOperator::Minus, Expression::Number(y)) => { + Expression::Number(PI - y) + } + + // Multiplication and Division + + // Multiplication with zero + (Expression::Number(x), InfixOperator::Star, _) + | (_, InfixOperator::Star, Expression::Number(x)) + if is_zero(x) => + { + Expression::Number(ZERO) + } + // Multiplication with one + (Expression::Number(x), InfixOperator::Star, other) + | (other, InfixOperator::Star, Expression::Number(x)) + if is_one(x) => + { + other + } + // Multiplying with numbers or π + (Expression::Number(x), InfixOperator::Star, Expression::Number(y)) => { + Expression::Number(x * y) + } + (Expression::Number(x), InfixOperator::Star, Expression::PiConstant) + | (Expression::PiConstant, InfixOperator::Star, Expression::Number(x)) => { + Expression::Number(PI * x) + } + (Expression::PiConstant, InfixOperator::Star, Expression::PiConstant) => { + Expression::Number(PI * PI) + } + + // Division with zero + (Expression::Number(x), InfixOperator::Slash, _) if is_zero(x) => { + Expression::Number(ZERO) + } + (_, InfixOperator::Slash, Expression::Number(y)) if is_zero(y) => { + Expression::Number(real!(f64::NAN)) + } + // Division with one + (left, InfixOperator::Slash, Expression::Number(y)) if is_one(y) => left, + // Division with self + (left, InfixOperator::Slash, right) if left == right => Expression::Number(ONE), + // Division with numbers or π (π / π already covered) + (Expression::Number(x), InfixOperator::Slash, Expression::Number(y)) => { + Expression::Number(x / y) + } + (Expression::Number(x), InfixOperator::Slash, Expression::PiConstant) => { + Expression::Number(x / PI) + } + (Expression::PiConstant, InfixOperator::Slash, Expression::Number(y)) => { + Expression::Number(PI / y) + } + + // Exponentiation + + // Exponentiation with zero + (Expression::Number(x), InfixOperator::Caret, _) if is_zero(x) => { + Expression::Number(ZERO) + } + (_, InfixOperator::Caret, Expression::Number(y)) if is_zero(y) => { + Expression::Number(ONE) + } + // Exponentiation with one + (Expression::Number(x), InfixOperator::Caret, _) if is_one(x) => { + Expression::Number(ONE) + } + (left, InfixOperator::Caret, Expression::Number(y)) if is_one(y) => left, + // Exponentiation with numbers or π + (Expression::Number(x), InfixOperator::Caret, Expression::Number(y)) => { + Expression::Number(x.powc(y)) + } + (Expression::Number(x), InfixOperator::Caret, Expression::PiConstant) => { + Expression::Number(x.powc(PI)) + } + (Expression::PiConstant, InfixOperator::Caret, Expression::Number(y)) => { + Expression::Number(PI.powc(y)) + } + (Expression::PiConstant, InfixOperator::Caret, Expression::PiConstant) => { + Expression::Number(PI.powc(PI)) + } + + //---------------------------------------------------------------- + // Second: dealing with negation in subexpressions + //---------------------------------------------------------------- + + // Addition with negation + // a + (-b) = (-b) + a = a - b + ( + ref other, + InfixOperator::Plus, + Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + ref expression, + }), + ) + | ( + Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + ref expression, + }), + InfixOperator::Plus, + ref other, + ) => simplify_infix(other, InfixOperator::Minus, expression, limit - 1), + + // Subtraction with negation + + // a - (-b) = a + b + ( + ref left, + InfixOperator::Minus, + Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + ref expression, + }), + ) => simplify_infix(left, InfixOperator::Plus, expression, limit - 1), + + // -expression - right = smaller of [(-expression) - right, -(expression + right)] + ( + ref left @ Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + ref expression, + }), + InfixOperator::Minus, + ref right, + ) => { + let original = sub!(left.clone(), right.clone()); + let new = simplify_prefix( + PrefixOperator::Minus, + &simplify_infix(expression, InfixOperator::Plus, right, limit - 1), + limit - 1, + ); + min_by_key(original, new, size) + } + + // Multiplication with negation + + // Double negative: (-a) * (-b) = a * b + ( + Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: ref a, + }), + InfixOperator::Star, + Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: ref b, + }), + ) => simplify_infix(a, InfixOperator::Star, b, limit - 1), + + // a * (-b) = (-a) * b, pick the shorter + ( + ref left, + InfixOperator::Star, + ref right @ Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + ref expression, + }), + ) => { + let original = mul!(left.clone(), right.clone()); + let neg_left = simplify_prefix(PrefixOperator::Minus, left, limit - 1); + let new = simplify_infix(&neg_left, InfixOperator::Star, expression, limit - 1); + min_by_key(original, new, size) + } + // (-a) * b = a * (-b), pick the shorter + ( + ref left @ Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + ref expression, + }), + InfixOperator::Star, + ref right, + ) => { + let original = mul!(left.clone(), right.clone()); + let neg_right = simplify_prefix(PrefixOperator::Minus, right, limit - 1); + let new = simplify_infix(expression, InfixOperator::Star, &neg_right, limit - 1); + min_by_key(original, new, size) + } + + // Division with negation + + // Double negative: (-a) / (-b) = a / b + ( + Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: ref a, + }), + InfixOperator::Slash, + Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: ref b, + }), + ) => simplify_infix(a, InfixOperator::Slash, b, limit - 1), + + // (-a) / a = a / (-a) = -1 + ( + ref other, + InfixOperator::Slash, + Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + ref expression, + }), + ) + | ( + Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + ref expression, + }), + InfixOperator::Slash, + ref other, + ) if *other == **expression => Expression::Number(-ONE), + + // a / (-b) = (-a) / b, pick the shorter + ( + ref left, + InfixOperator::Slash, + ref right @ Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + ref expression, + }), + ) => { + let original = div!(left.clone(), right.clone()); + let neg_left = simplify_prefix(PrefixOperator::Minus, left, limit - 1); + let new = simplify_infix(&neg_left, InfixOperator::Slash, expression, limit - 1); + min_by_key(original, new, size) + } + + // (-a) / b = a / (-b), pick the shorter + ( + ref left @ Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + ref expression, + }), + InfixOperator::Slash, + ref right, + ) => { + let original = div!(left.clone(), right.clone()); + let neg_right = simplify_prefix(PrefixOperator::Minus, right, limit - 1); + let new = simplify_infix(expression, InfixOperator::Slash, &neg_right, limit - 1); + min_by_key(original, new, size) + } + + //---------------------------------------------------------------- + // Third: Affine relationships + //---------------------------------------------------------------- + + // (a1 * x + b1) + (a2 * x + b2) = (a1 + a2) * x + (b1 + b2) + // + // Apologies for this one; I couldn't get the compiler to let me match two levels deep in a + // recursive data type, and `if let` in a match guard isn't stabilized. + ( + Expression::Infix(InfixExpression { + left: ref left_ax, + operator: InfixOperator::Plus, + right: ref left_b, + }), + InfixOperator::Plus, + Expression::Infix(InfixExpression { + left: ref right_ax, + operator: InfixOperator::Plus, + right: ref right_b, + }), + ) if mul_matches(left_ax, right_ax) => { + let &Expression::Infix(InfixExpression { + left: ref ll, + operator: InfixOperator::Star, + right: ref lr, + }) = &**left_ax + else { + unreachable!("This is handled by mul_matches") + }; + let &Expression::Infix(InfixExpression { + left: ref rl, + operator: InfixOperator::Star, + right: ref rr, + }) = &**right_ax + else { + unreachable!("This is handled by mul_matches") + }; + let (left_a, right_a, x) = if **ll == **rl { + (lr, rr, ll) + } else if **ll == **rr { + (lr, rl, ll) + } else if **lr == **rl { + (ll, rr, lr) + } else { + (ll, rl, rr) + }; + simplify_infix( + &simplify_infix( + &simplify_infix(left_a, InfixOperator::Plus, right_a, limit - 1), + InfixOperator::Star, + x, + limit - 1, + ), + InfixOperator::Plus, + &simplify_infix(left_b, InfixOperator::Plus, right_b, limit - 1), + limit - 1, + ) + } + + // (a1 * x) + (a2 * x) = (a1 + a2) * x + ( + Expression::Infix(InfixExpression { + left: ref left_a, + operator: InfixOperator::Star, + right: ref left_x, + }), + InfixOperator::Plus, + Expression::Infix(InfixExpression { + left: ref right_a, + operator: InfixOperator::Star, + right: ref right_x, + }), + ) if left_x == right_x => simplify_infix( + &simplify_infix(left_a, InfixOperator::Plus, right_a, limit - 1), + InfixOperator::Star, + left_x, + limit - 1, + ), + + // (x + b1) + (x + b2) = x + (b1 + b2) + ( + Expression::Infix(InfixExpression { + left: ref left_x, + operator: InfixOperator::Plus, + right: ref left_b, + }), + InfixOperator::Plus, + Expression::Infix(InfixExpression { + left: ref right_x, + operator: InfixOperator::Plus, + right: ref right_b, + }), + ) if left_x == right_x => simplify_infix( + &simplify_infix( + &Expression::Number(TWO), + InfixOperator::Star, + left_x, + limit - 1, + ), + InfixOperator::Plus, + &simplify_infix(left_b, InfixOperator::Plus, right_b, limit - 1), + limit - 1, + ), + + //---------------------------------------------------------------- + // Fourth: commutation, association, distribution + //---------------------------------------------------------------- + + // Addition associative, right: a + (b + c) = (a + b) + c, pick the shorter + ( + ref a, + InfixOperator::Plus, + ref right @ Expression::Infix(InfixExpression { + left: ref b, + operator: InfixOperator::Plus, + right: ref c, + }), + ) => { + let original = add!(a.clone(), right.clone()); + let new_ab = simplify_infix(a, InfixOperator::Plus, b, limit - 1); + let new = simplify_infix(&new_ab, InfixOperator::Plus, c, limit - 1); + min_by_key(original, new, size) + } + + // Addition associative, left: (a + b) + c = a + (b + c), pick the shorter + ( + ref left @ Expression::Infix(InfixExpression { + left: ref a, + operator: InfixOperator::Plus, + right: ref b, + }), + InfixOperator::Plus, + ref c, + ) => { + let original = add!(left.clone(), c.clone()); + let bc = simplify_infix(b, InfixOperator::Plus, c, limit - 1); + let new = simplify_infix(a, InfixOperator::Plus, &bc, limit - 1); + min_by_key(original, new, size) + } + + // Multiplication associative, right: a * (b * c) = (a * b) * c, pick the shorter + ( + ref a, + InfixOperator::Star, + ref right @ Expression::Infix(InfixExpression { + left: ref b, + operator: InfixOperator::Star, + right: ref c, + }), + ) => { + let original = mul!(a.clone(), right.clone()); + let ab = simplify_infix(a, InfixOperator::Star, b, limit - 1); + let new = simplify_infix(&ab, InfixOperator::Star, c, limit - 1); + min_by_key(original, new, size) + } + + // Multiplication associative, left: (a * b) * c = a * (b * c), pick the shorter + ( + ref left @ Expression::Infix(InfixExpression { + left: ref a, + operator: InfixOperator::Star, + right: ref b, + }), + InfixOperator::Star, + ref c, + ) => { + let original = mul!(left.clone(), c.clone()); + let bc = simplify_infix(b, InfixOperator::Star, c, limit - 1); + let new = simplify_infix(a, InfixOperator::Star, &bc, limit - 1); + min_by_key(original, new, size) + } + + // Subtraction "associative" (not really), right: a - (b - c) = (a + c) - b + ( + ref a, + InfixOperator::Minus, + ref right @ Expression::Infix(InfixExpression { + left: ref b, + operator: InfixOperator::Minus, + right: ref c, + }), + ) => { + let original = sub!(a.clone(), right.clone()); + let ac = simplify_infix(a, InfixOperator::Plus, c, limit - 1); + let new = simplify_infix(&ac, InfixOperator::Minus, b, limit - 1); + min_by_key(original, new, size) + } + + // Division "associative" (not really), right: a / (b / c) = (a * c) / b + ( + ref a, + InfixOperator::Slash, + ref right @ Expression::Infix(InfixExpression { + left: ref b, + operator: InfixOperator::Slash, + right: ref c, + }), + ) => { + let original = div!(a.clone(), right.clone()); + let ac = simplify_infix(a, InfixOperator::Star, c, limit - 1); + let new = simplify_infix(&ac, InfixOperator::Slash, b, limit - 1); + min_by_key(original, new, size) + } + + // Division "associative" (not really), left: (a / b) / c = a / (b * c) + ( + ref left @ Expression::Infix(InfixExpression { + left: ref a, + operator: InfixOperator::Slash, + right: ref b, + }), + InfixOperator::Slash, + ref c, + ) => { + let original = div!(left.clone(), c.clone()); + let bc = simplify_infix(b, InfixOperator::Star, c, limit - 1); + let new = simplify_infix(a, InfixOperator::Slash, &bc, limit - 1); + min_by_key(original, new, size) + } + + // Right distribution: a * (b + c) = (a * b) + (a * c) + ( + ref a, + InfixOperator::Star, + ref right @ Expression::Infix(InfixExpression { + left: ref b, + operator: InfixOperator::Plus, + right: ref c, + }), + ) => { + let original = mul!(a.clone(), right.clone()); + let ab = simplify_infix(a, InfixOperator::Star, b, limit - 1); + let ac = simplify_infix(a, InfixOperator::Star, c, limit - 1); + let new = simplify_infix(&ab, InfixOperator::Plus, &ac, limit - 1); + min_by_key(original, new, size) + } + + // Left distribution: (a + b) * c = (a * c) + (a * b) + ( + ref left @ Expression::Infix(InfixExpression { + left: ref a, + operator: InfixOperator::Plus, + right: ref b, + }), + InfixOperator::Star, + ref c, + ) => { + let original = mul!(left.clone(), c.clone()); + let ac = simplify_infix(a, InfixOperator::Star, c, limit - 1); + let bc = simplify_infix(b, InfixOperator::Star, c, limit - 1); + let new = simplify_infix(&ac, InfixOperator::Plus, &bc, limit - 1); + min_by_key(original, new, size) + } + + //---------------------------------------------------------------- + // Fifth: other parenthesis manipulation + //---------------------------------------------------------------- + + // Mul inside Div on left with cancellation + ( + Expression::Infix(InfixExpression { + left: ref same_1, + operator: InfixOperator::Star, + right: ref other, + }), + InfixOperator::Slash, + ref same_2, + ) + | ( + Expression::Infix(InfixExpression { + left: ref other, + operator: InfixOperator::Star, + right: ref same_1, + }), + InfixOperator::Slash, + ref same_2, + ) if **same_1 == *same_2 => simplify(other, limit - 1), + + // Mul inside Div on right with cancellation + ( + ref same_1, + InfixOperator::Slash, + Expression::Infix(InfixExpression { + left: ref same_2, + operator: InfixOperator::Star, + right: ref other, + }), + ) + | ( + ref same_1, + InfixOperator::Slash, + Expression::Infix(InfixExpression { + left: ref other, + operator: InfixOperator::Star, + right: ref same_2, + }), + ) if *same_1 == **same_2 => simplify_infix( + &Expression::Number(ONE), + InfixOperator::Slash, + other, + limit - 1, + ), + + // Mul inside Div on left + ( + ref numerator @ Expression::Infix(InfixExpression { + left: ref multiplier, + operator: InfixOperator::Star, + right: ref multiplicand, + }), + InfixOperator::Slash, + ref denominator, + ) => { + let original = div!(numerator.clone(), denominator.clone()); + let new_multiplicand = + simplify_infix(multiplicand, InfixOperator::Slash, denominator, limit - 1); + let new = simplify_infix( + multiplier, + InfixOperator::Star, + &new_multiplicand, + limit - 1, + ); + min_by_key(original, new, size) + } + + // Mul inside Div on right + ( + ref numerator, + InfixOperator::Slash, + ref denominator @ Expression::Infix(InfixExpression { + left: ref multiplier, + operator: InfixOperator::Star, + right: ref multiplicand, + }), + ) => { + let original = div!(numerator.clone(), denominator.clone()); + let new_multiplier = + simplify_infix(numerator, InfixOperator::Slash, multiplier, limit - 1); + let new = simplify_infix( + &new_multiplier, + InfixOperator::Star, + multiplicand, + limit - 1, + ); + min_by_key(original, new, size) + } + + // Div inside Mul with cancellation + ( + Expression::Infix(InfixExpression { + left: ref other, + operator: InfixOperator::Slash, + right: ref same_1, + }), + InfixOperator::Star, + ref same_2, + ) + | ( + ref same_2, + InfixOperator::Star, + Expression::Infix(InfixExpression { + left: ref other, + operator: InfixOperator::Slash, + right: ref same_1, + }), + ) if **same_1 == *same_2 => simplify(other, limit - 1), + + //---------------------------------------------------------------- + // Sixth: catch-all if no other patterns match + //---------------------------------------------------------------- + (left, operator, right) => Expression::Infix(InfixExpression { + left: left.into(), + operator, + right: right.into(), + }), + } + } +} + +/// Simplify a prefix expression inside an `Expression`, terminating the recursion if `limit` has reached zero. +fn simplify_prefix(op: PrefixOperator, expr: &Expression, limit: u64) -> Expression { + if limit == 0 { + // bail + Expression::Prefix(PrefixExpression { + operator: op, + expression: expr.clone().into(), + }) + } else { + // Remove + + // Push - into numbers & π + // Pass through otherwise + match (op, simplify(expr, limit - 1)) { + (PrefixOperator::Plus, expression) => expression, + (PrefixOperator::Minus, Expression::Number(x)) => Expression::Number(-x), + (PrefixOperator::Minus, Expression::PiConstant) => Expression::Number(-PI), + (operator, expression) => Expression::Prefix(PrefixExpression { + operator, + expression: expression.into(), + }), + } + } +} diff --git a/quil-rs/src/expression/simplification/mod.rs b/quil-rs/src/expression/simplification/mod.rs new file mode 100644 index 00000000..205a8494 --- /dev/null +++ b/quil-rs/src/expression/simplification/mod.rs @@ -0,0 +1,345 @@ +use crate::expression::Expression; + +mod by_hand; + +/// Simplify an [`Expression`]. +pub(super) fn run(expression: &Expression) -> Expression { + by_hand::run(expression) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + macro_rules! test_simplify { + ($name:ident, $input:expr, $expected:expr) => { + #[test] + fn $name() { + let parsed_input = Expression::from_str($input) + .unwrap_or_else(|error| panic!("Parsing input `{}` failed: {error}", $input)); + let parsed_expected = Expression::from_str($expected).unwrap_or_else(|error| { + panic!( + "Parsing expected expression `{}` failed: {error}", + $expected + ) + }); + let computed = run(&parsed_input); + assert_eq!(parsed_expected, computed); + } + }; + } + + test_simplify! { + function_cis, + "cis(0)", + "1" + } + + test_simplify! { + function_cos, + "cis(0)", + "1" + } + + test_simplify! { + function_exp, + "exp(1)", + "2.718281828459045" + } + + test_simplify! { + function_sin, + "sin(0)", + "0" + } + + test_simplify! { + function_sqrt, + "sqrt(9)", + "3" + } + + test_simplify! { + infix_add_0_r, + "x + 0", + "x" + } + + test_simplify! { + infix_add_0_l, + "0 + x", + "x" + } + + test_simplify! { + infix_add, + "1 + 2", + "3" + } + + test_simplify! { + infix_sub_0_r, + "x - 0", + "x" + } + + test_simplify! { + infix_sub_self, + "x - x", + "0" + } + + test_simplify! { + infix_mul_0_r, + "x * 0", + "0" + } + + test_simplify! { + infix_mul_0_l, + "0 * x", + "0" + } + + test_simplify! { + infix_mul_1_r, + "x * 1", + "x" + } + + test_simplify! { + infix_mul_1_l, + "1 * x", + "x" + } + + test_simplify! { + infix_div_0_l, + "0 / x", + "0" + } + + test_simplify! { + infix_div_1_r, + "x / 1", + "x" + } + + test_simplify! { + infix_div_self, + "x / x", + "1" + } + + test_simplify! { + infix_exp_0_r, + "0^x", + "0" + } + + test_simplify! { + infix_exp_0_l, + "x^0", + "1" + } + + test_simplify! { + infix_sub_neg, + "x - (-y)", + "x + y" + } + + test_simplify! { + infix_mul_double_neg, + "(-x) * (-y)", + "x * y" + } + + test_simplify! { + infix_div_double_neg, + "(-x) / (-y)", + "x / y" + } + + test_simplify! { + infix_affine_full, + "(a1 * x + b1) + (a2 * x + b2)", + "(a1 + a2) * x + (b1 + b2)" + } + + test_simplify! { + infix_affine_coeffs, + "(a1 * x) + (a2 * x)", + "(a1 + a2) * x" + } + + test_simplify! { + infix_affine_constants, + "(x + b1) + (x + b2)", + "(2 * x) + (b1 + b2)" + } + + test_simplify! { + infix_mul_div_ll, + "(y * x) / x", + "y" + } + + test_simplify! { + infix_mul_div_lr, + "(x * y) / x", + "y" + } + + test_simplify! { + infix_mul_div_rl, + "x / (y * x)", + "1 / y" + } + + test_simplify! { + infix_mul_div_rr, + "x / (x * y)", + "1 / y" + } + + test_simplify! { + infix_div_mul_l, + "(x / y) * y", + "x" + } + + test_simplify! { + infix_div_mul_r, + "y * (x / y)", + "x" + } + + test_simplify! { + docstring_example, + "cos(2 * pi) + 2", + "3" + } + + test_simplify! { + issue_208_1, + "0 * theta[0]", + "0" + } + + test_simplify! { + issue_208_2, + "theta[0] / 1", + "theta[0]" + } + + test_simplify! { + issue_208_3, + "(theta[0] * 5) / 5", + "theta" + } + + test_simplify! { + memory_ref, + "theta[0]", + "theta[0]" + } + + test_simplify! { + var, + "%foo", + "%foo" + } + + test_simplify! { + prefix_neg, + "-(-1)", + "1" + } + + test_simplify! { + sub_neg, + "2 - (-1)", + "3" + } + + test_simplify! { + neg_sub, + "-(1 - 2)", + "1" + } + + test_simplify! { + fold_constant_mul, + "2 * pi", + "6.283185307179586" + } + + test_simplify! { + fold_constant_mul_div, + "(2 * pi) / 6.283185307179586", + "1" + } + + test_simplify! { + fold_constant_mul_div_2, + "2 * (pi / 6.283185307179586)", + "1" + } + + test_simplify! { + fold_constant_mul_div_with_ref, + "((a[0] * 2) * pi) / 6.283185307179586", + "a[0]" + } + + test_simplify! { + fold_constant_mul_div_with_ref_2, + "a[0] * (2 * pi) / 6.283185307179586", + "a[0]" + } + + test_simplify! { + fold_constant_mul_div_with_ref_3, + "a[0] * (2 * (pi / 6.283185307179586))", + "a[0]" + } + + test_simplify! { + affine, + "(2 * x[0] + 3) + (4 * x[0] + 5)", + "6 * x[0] + 8" + } + + test_simplify! { + affine_2, + "2 * x[0] + (4 * x[0] + 5)", + "6 * x[0] + 5" + } + + test_simplify! { + affine_3, + "2 * x[0] + 4 * x[0]", + "6 * x[0]" + } + + test_simplify! { + affine_4, + "(x[0] + 3) + (x[0] + 5)", + "2 * x[0] + 8" + } + + test_simplify! { + double_subtraction, + "3 - 2 - 1", + "0" + } + + // TODO doesn't fully simplify in a reasonable amount of recursion + // test_simplify! { + // the_big_one, + // "(6.283185307179586*(-((-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(-((1827.142690137572+-(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702) - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+1830.4305845069357))+(2404.366183299857+(-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)) - 2473.4667568746527)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+(1827.142690137572+-(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)) - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)+1830.4305845069357))+2997.220957806505) - ((1827.142690137572+-(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702) - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+1830.4305845069357))+(2404.366183299857+(-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)) - 2473.4667568746527)+3082.921997445349)+-((-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+(-((-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(-((1827.142690137572+-(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702) - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+1830.4305845069357))+(2404.366183299857+(-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)) - 2473.4667568746527)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+(1827.142690137572+-(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)) - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)+1830.4305845069357))+2997.220957806505) - ((1827.142690137572+-(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702) - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+1830.4305845069357))+(2404.366183299857+(-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)) - 2473.4667568746527)+3082.921997445349)+(1827.142690137572+-(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702) - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+1830.4305845069357))+-((1827.142690137572+-(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702) - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+1830.4305845069357))+(2404.366183299857+(-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)) - 2473.4667568746527))+3552.7822825370968) - ((-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(-((1827.142690137572+-(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702) - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+1830.4305845069357))+(2404.366183299857+(-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)) - 2473.4667568746527)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+(1827.142690137572+-(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)) - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)+1830.4305845069357))+2997.220957806505) - ((1827.142690137572+-(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702) - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+1830.4305845069357))+(2404.366183299857+(-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)) - 2473.4667568746527)+3082.921997445349)+(((1827.142690137572+-(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702) - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - ((-3.141592653589793+gamma[0]*-1.3670709112264738)/6.283185307179586+1293.2884354900702)+1830.4305845069357))+(2404.366183299857+(-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+(1292.2571023206997 - (-3.141592653589793+(gamma[0]*-1.3670709112264738)/6.283185307179586)+1293.2884354900702)) - 2473.4667568746527)+3654.308518679512+(-3.141592653589793+gamma[0]*-1.4598346220303238)/6.283185307179586)))+0.4345210910722077)/6.283185307179586", + // "-0.637964476122525*gamma[0] - 14553.9199845484" + // } +} diff --git a/quil-rs/src/hash.rs b/quil-rs/src/hash.rs new file mode 100644 index 00000000..2a8221ad --- /dev/null +++ b/quil-rs/src/hash.rs @@ -0,0 +1,35 @@ +use std::hash::{Hash, Hasher}; + +/// Hashes a f64 using its u64 representation. +/// +/// Notes: +/// * This function hashes +0.0 and -0.0 to the same value. +/// * The [documentation](https://doc.rust-lang.org/std/primitive.f64.html#method.to_bits) claims +/// that this is generally portable in practice. +#[inline] +pub(crate) fn hash_f64(value: f64, state: &mut H) { + // +0.0 and -0.0 have different bits, so we use the bit form of +0.0 for both, that way + // hash(+0.0) == hash(-0.0) + if value == 0.0f64 { + 0.0f64.to_bits().hash(state) + } else { + value.to_bits().hash(state) + } +} + +#[cfg(test)] +mod test { + use super::hash_f64; + use std::{collections::hash_map::DefaultHasher, hash::Hasher}; + + fn get_f64_hash(float: f64) -> u64 { + let mut hasher = DefaultHasher::new(); + hash_f64(float, &mut hasher); + hasher.finish() + } + + #[test] + fn test_hash_f64_zero() { + assert_eq!(get_f64_hash(0.0), get_f64_hash(-0.0)) + } +} diff --git a/quil-rs/src/instruction/calibration.rs b/quil-rs/src/instruction/calibration.rs new file mode 100644 index 00000000..88b5f3e0 --- /dev/null +++ b/quil-rs/src/instruction/calibration.rs @@ -0,0 +1,184 @@ +use crate::{ + instruction::{ + write_expression_parameter_string, write_instruction_block, Expression, GateModifier, + Instruction, Qubit, + }, + quil::Quil, + validation::identifier::{validate_identifier, IdentifierValidationError}, +}; + +use super::write_qubit_parameters; + +pub trait CalibrationSignature { + type Signature<'a> + where + Self: 'a; + + fn signature(&self) -> Self::Signature<'_>; + fn has_signature(&self, signature: &Self::Signature<'_>) -> bool; +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Calibration { + pub instructions: Vec, + pub modifiers: Vec, + pub name: String, + pub parameters: Vec, + pub qubits: Vec, +} + +impl Calibration { + /// Builds a new calibration definition. + /// + /// # Errors + /// + /// Returns an error if the given name isn't a valid Quil identifier. + pub fn new( + name: &str, + parameters: Vec, + qubits: Vec, + instructions: Vec, + modifiers: Vec, + ) -> Result { + validate_identifier(name)?; + Ok(Self { + instructions, + modifiers, + name: name.to_string(), + parameters, + qubits, + }) + } +} + +impl Quil for Calibration { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "DEFCAL {}", self.name)?; + write_expression_parameter_string(f, fall_back_to_debug, &self.parameters)?; + write_qubit_parameters(f, fall_back_to_debug, &self.qubits)?; + write!(f, ":")?; + for instruction in &self.instructions { + write!(f, "\n\t")?; + instruction.write(f, fall_back_to_debug)?; + } + Ok(()) + } +} + +impl CalibrationSignature for Calibration { + type Signature<'a> = (&'a str, &'a [Expression], &'a [Qubit]); + + fn signature(&self) -> Self::Signature<'_> { + ( + self.name.as_str(), + self.parameters.as_slice(), + self.qubits.as_slice(), + ) + } + + fn has_signature(&self, signature: &Self::Signature<'_>) -> bool { + let (name, parameters, qubits) = signature; + self.name == *name && self.parameters == *parameters && self.qubits == *qubits + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct MeasureCalibrationDefinition { + pub qubit: Option, + pub parameter: String, + pub instructions: Vec, +} + +impl MeasureCalibrationDefinition { + pub fn new(qubit: Option, parameter: String, instructions: Vec) -> Self { + Self { + qubit, + parameter, + instructions, + } + } +} + +impl CalibrationSignature for MeasureCalibrationDefinition { + type Signature<'a> = (Option<&'a Qubit>, &'a str); + + fn signature(&self) -> Self::Signature<'_> { + (self.qubit.as_ref(), self.parameter.as_str()) + } + + fn has_signature(&self, signature: &Self::Signature<'_>) -> bool { + let (qubit, parameter) = signature; + self.qubit.as_ref() == *qubit && self.parameter == *parameter + } +} + +impl Quil for MeasureCalibrationDefinition { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "DEFCAL MEASURE")?; + if let Some(qubit) = &self.qubit { + write!(f, " ")?; + qubit.write(f, fall_back_to_debug)?; + } + + writeln!(f, " {}:", self.parameter,)?; + + write_instruction_block(f, fall_back_to_debug, &self.instructions)?; + writeln!(f)?; + Ok(()) + } +} + +#[cfg(test)] +mod test_measure_calibration_definition { + use super::MeasureCalibrationDefinition; + use crate::expression::Expression; + use crate::instruction::{Gate, Instruction, Qubit}; + use crate::quil::Quil; + use insta::assert_snapshot; + use rstest::rstest; + + #[rstest] + #[case( + "With Fixed Qubit", + MeasureCalibrationDefinition { + qubit: Some(Qubit::Fixed(0)), + parameter: "theta".to_string(), + instructions: vec![Instruction::Gate(Gate { + name: "X".to_string(), + parameters: vec![Expression::Variable("theta".to_string())], + qubits: vec![Qubit::Fixed(0)], + modifiers: vec![], + + })]}, + )] + #[case( + "With Variable Qubit", + MeasureCalibrationDefinition { + qubit: Some(Qubit::Variable("q".to_string())), + parameter: "theta".to_string(), + instructions: vec![Instruction::Gate(Gate { + name: "X".to_string(), + parameters: vec![Expression::Variable("theta".to_string())], + qubits: vec![Qubit::Variable("q".to_string())], + modifiers: vec![], + })]}, + )] + fn test_display( + #[case] description: &str, + #[case] measure_cal_def: MeasureCalibrationDefinition, + ) { + insta::with_settings!({ + snapshot_suffix => description, + }, { + assert_snapshot!(measure_cal_def.to_quil_or_debug()) + }) + } +} diff --git a/quil-rs/src/instruction/circuit.rs b/quil-rs/src/instruction/circuit.rs new file mode 100644 index 00000000..90e4ccba --- /dev/null +++ b/quil-rs/src/instruction/circuit.rs @@ -0,0 +1,190 @@ +use crate::quil::Quil; + +use super::Instruction; + +#[derive(Clone, Debug, PartialEq)] +pub struct CircuitDefinition { + pub name: String, + pub parameters: Vec, + // These cannot be fixed qubits and thus are not typed as `Qubit` + pub qubit_variables: Vec, + pub instructions: Vec, +} + +impl CircuitDefinition { + pub fn new( + name: String, + parameters: Vec, + qubit_variables: Vec, + instructions: Vec, + ) -> Self { + Self { + name, + parameters, + qubit_variables, + instructions, + } + } +} + +impl Quil for CircuitDefinition { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> Result<(), crate::quil::ToQuilError> { + write!(writer, "DEFCIRCUIT {}", self.name)?; + if !self.parameters.is_empty() { + write!(writer, "(")?; + let mut iter = self.parameters.iter(); + if let Some(p) = iter.next() { + write!(writer, "%{}", p)?; + } + for p in iter { + write!(writer, ", %{}", p)?; + } + write!(writer, ")")?; + } + for qubit_variable in &self.qubit_variables { + write!(writer, " {}", qubit_variable)?; + } + writeln!(writer, ":")?; + for instruction in &self.instructions { + let lines = match fall_back_to_debug { + true => instruction.to_quil_or_debug(), + false => instruction.to_quil()?, + }; + for line in lines.split('\n') { + writeln!(writer, "\t{line}")?; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod test_circuit_definition { + use crate::expression::Expression; + use crate::instruction::{Gate, Instruction, Qubit}; + use crate::quil::Quil; + + use super::CircuitDefinition; + + use insta::assert_snapshot; + use rstest::rstest; + + #[rstest] + #[case( + "CircuitDefinition No Params", + CircuitDefinition { + name: "BELL".to_owned(), + parameters: vec![], + qubit_variables: vec!["a".to_owned(), "b".to_owned()], + instructions: vec![ + Instruction::Gate(Gate { + name: "H".to_owned(), + parameters: vec![], + qubits: vec![Qubit::Variable("a".to_owned())], + modifiers: vec![], + }), + Instruction::Gate(Gate { + name: "CNOT".to_owned(), + parameters: vec![], + qubits: vec![ + Qubit::Variable("a".to_owned()), + Qubit::Variable("b".to_owned()) + ], + modifiers: vec![], + }) + ] + } + )] + #[case( + "CircuitDefinition With Params", + CircuitDefinition { + name: "BELL".to_owned(), + parameters: vec!["a".to_owned(), "b".to_owned()], + qubit_variables: vec!["a".to_owned(), "b".to_owned()], + instructions: vec![ + Instruction::Gate(Gate { + name: "RZ".to_owned(), + parameters: vec![Expression::Variable("a".to_owned())], + qubits: vec![Qubit::Variable("a".to_owned())], + modifiers: vec![], + }), + Instruction::Gate(Gate { + name: "RZ".to_owned(), + parameters: vec![Expression::Variable("b".to_owned())], + qubits: vec![Qubit::Variable("a".to_owned())], + modifiers: vec![], + }), + Instruction::Gate(Gate { + name: "RX".to_owned(), + parameters: vec![Expression::Variable("a".to_owned())], + qubits: vec![Qubit::Variable("a".to_owned())], + modifiers: vec![], + }), + Instruction::Gate(Gate { + name: "RZ".to_owned(), + parameters: vec![Expression::Variable("a".to_owned())], + qubits: vec![Qubit::Variable("a".to_owned())], + modifiers: vec![], + }), + Instruction::Gate(Gate { + name: "CNOT".to_owned(), + parameters: vec![], + qubits: vec![ + Qubit::Variable("a".to_owned()), + Qubit::Variable("b".to_owned()) + ], + modifiers: vec![], + }) + ] + } + )] + #[case( + "CircuitDefinition With Single Param", + CircuitDefinition { + name: "BELL".to_owned(), + parameters: vec!["a".to_owned()], + qubit_variables: vec!["a".to_owned(), "b".to_owned()], + instructions: vec![ + Instruction::Gate(Gate { + name: "RZ".to_owned(), + parameters: vec![Expression::Variable("a".to_owned())], + qubits: vec![Qubit::Variable("a".to_owned())], + modifiers: vec![], + }), + Instruction::Gate(Gate { + name: "RX".to_owned(), + parameters: vec![Expression::Variable("a".to_owned())], + qubits: vec![Qubit::Variable("a".to_owned())], + modifiers: vec![], + }), + Instruction::Gate(Gate { + name: "RZ".to_owned(), + parameters: vec![Expression::Variable("a".to_owned())], + qubits: vec![Qubit::Variable("a".to_owned())], + modifiers: vec![], + }), + Instruction::Gate(Gate { + name: "CNOT".to_owned(), + parameters: vec![], + qubits: vec![ + Qubit::Variable("a".to_owned()), + Qubit::Variable("b".to_owned()) + ], + modifiers: vec![], + }) + ] + } + )] + fn test_display(#[case] description: &str, #[case] circuit_def: CircuitDefinition) { + insta::with_settings!({ + snapshot_suffix => description, + }, { + assert_snapshot!(circuit_def.to_quil_or_debug()) + }) + } +} diff --git a/quil-rs/src/instruction/classical.rs b/quil-rs/src/instruction/classical.rs new file mode 100644 index 00000000..f1bd18b4 --- /dev/null +++ b/quil-rs/src/instruction/classical.rs @@ -0,0 +1,381 @@ +use crate::{hash::hash_f64, quil::Quil}; + +use super::MemoryReference; + +#[derive(Clone, Debug, Hash, PartialEq)] +pub struct Arithmetic { + pub operator: ArithmeticOperator, + pub destination: ArithmeticOperand, + pub source: ArithmeticOperand, +} + +impl Arithmetic { + pub fn new( + operator: ArithmeticOperator, + destination: ArithmeticOperand, + source: ArithmeticOperand, + ) -> Self { + Self { + operator, + destination, + source, + } + } +} + +impl Quil for Arithmetic { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + self.operator.write(f, fall_back_to_debug)?; + write!(f, " ")?; + self.destination.write(f, fall_back_to_debug)?; + write!(f, " ")?; + self.source.write(f, fall_back_to_debug) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ArithmeticOperand { + LiteralInteger(i64), + LiteralReal(f64), + MemoryReference(MemoryReference), +} + +impl std::hash::Hash for ArithmeticOperand { + fn hash(&self, state: &mut H) { + match self { + Self::LiteralInteger(operand) => operand.hash(state), + Self::LiteralReal(operand) => hash_f64(*operand, state), + Self::MemoryReference(operand) => operand.hash(state), + } + } +} + +impl Quil for ArithmeticOperand { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match &self { + ArithmeticOperand::LiteralInteger(value) => write!(f, "{value}").map_err(Into::into), + ArithmeticOperand::LiteralReal(value) => write!(f, "{value}").map_err(Into::into), + ArithmeticOperand::MemoryReference(value) => value.write(f, fall_back_to_debug), + } + } +} + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum ArithmeticOperator { + Add, + Subtract, + Divide, + Multiply, +} + +impl Quil for ArithmeticOperator { + fn write( + &self, + f: &mut impl std::fmt::Write, + _fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match &self { + ArithmeticOperator::Add => write!(f, "ADD"), + ArithmeticOperator::Subtract => write!(f, "SUB"), + ArithmeticOperator::Divide => write!(f, "DIV"), + ArithmeticOperator::Multiply => write!(f, "MUL"), + } + .map_err(Into::into) + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub enum BinaryOperand { + LiteralInteger(i64), + MemoryReference(MemoryReference), +} + +impl Quil for BinaryOperand { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match &self { + BinaryOperand::LiteralInteger(value) => write!(f, "{value}").map_err(Into::into), + BinaryOperand::MemoryReference(value) => value.write(f, fall_back_to_debug), + } + } +} + +pub type BinaryOperands = (MemoryReference, BinaryOperand); + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum BinaryOperator { + And, + Ior, + Xor, +} + +impl Quil for BinaryOperator { + fn write( + &self, + f: &mut impl std::fmt::Write, + _fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match &self { + BinaryOperator::And => write!(f, "AND"), + BinaryOperator::Ior => write!(f, "IOR"), + BinaryOperator::Xor => write!(f, "XOR"), + } + .map_err(Into::into) + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct BinaryLogic { + pub operator: BinaryOperator, + pub operands: BinaryOperands, +} + +impl Quil for BinaryLogic { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + self.operator.write(f, fall_back_to_debug)?; + write!(f, " ")?; + self.operands.0.write(f, fall_back_to_debug)?; + write!(f, " ")?; + self.operands.1.write(f, fall_back_to_debug)?; + Ok(()) + } +} + +impl BinaryLogic { + pub fn new(operator: BinaryOperator, operands: BinaryOperands) -> Self { + Self { operator, operands } + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct Convert { + pub destination: MemoryReference, + pub source: MemoryReference, +} + +impl Convert { + pub fn new(destination: MemoryReference, source: MemoryReference) -> Self { + Self { + destination, + source, + } + } +} + +impl Quil for Convert { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "CONVERT ")?; + self.destination.write(f, fall_back_to_debug)?; + write!(f, " ")?; + self.source.write(f, fall_back_to_debug)?; + Ok(()) + } +} + +#[derive(Clone, Debug, Hash, PartialEq)] +pub struct Move { + pub destination: MemoryReference, + pub source: ArithmeticOperand, +} + +impl Move { + pub fn new(destination: MemoryReference, source: ArithmeticOperand) -> Self { + Self { + destination, + source, + } + } +} + +impl Quil for Move { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "MOVE ")?; + self.destination.write(f, fall_back_to_debug)?; + write!(f, " ")?; + self.source.write(f, fall_back_to_debug)?; + Ok(()) + } +} + +#[derive(Clone, Debug, Hash, PartialEq)] +pub struct Exchange { + pub left: MemoryReference, + pub right: MemoryReference, +} + +impl Quil for Exchange { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "EXCHANGE ")?; + self.left.write(f, fall_back_to_debug)?; + write!(f, " ")?; + self.right.write(f, fall_back_to_debug)?; + Ok(()) + } +} + +impl Exchange { + pub fn new(left: MemoryReference, right: MemoryReference) -> Self { + Self { left, right } + } +} + +#[derive(Clone, Debug, Hash, PartialEq)] +pub struct Comparison { + pub operator: ComparisonOperator, + pub operands: (MemoryReference, MemoryReference, ComparisonOperand), +} + +impl Comparison { + pub fn new( + operator: ComparisonOperator, + operands: (MemoryReference, MemoryReference, ComparisonOperand), + ) -> Self { + Self { operator, operands } + } +} + +impl Quil for Comparison { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + self.operator.write(f, fall_back_to_debug)?; + write!(f, " ")?; + self.operands.0.write(f, fall_back_to_debug)?; + write!(f, " ")?; + self.operands.1.write(f, fall_back_to_debug)?; + write!(f, " ")?; + self.operands.2.write(f, fall_back_to_debug)?; + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ComparisonOperand { + LiteralInteger(i64), + LiteralReal(f64), + MemoryReference(MemoryReference), +} + +impl Quil for ComparisonOperand { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match &self { + ComparisonOperand::LiteralInteger(value) => write!(f, "{value}").map_err(Into::into), + ComparisonOperand::LiteralReal(value) => write!(f, "{value}").map_err(Into::into), + ComparisonOperand::MemoryReference(value) => value.write(f, fall_back_to_debug), + } + } +} + +impl std::hash::Hash for ComparisonOperand { + fn hash(&self, state: &mut H) { + match self { + ComparisonOperand::LiteralInteger(operand) => operand.hash(state), + ComparisonOperand::LiteralReal(operand) => hash_f64(*operand, state), + ComparisonOperand::MemoryReference(operand) => operand.hash(state), + } + } +} + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum ComparisonOperator { + Equal, + GreaterThanOrEqual, + GreaterThan, + LessThanOrEqual, + LessThan, +} + +impl Quil for ComparisonOperator { + fn write( + &self, + f: &mut impl std::fmt::Write, + _fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match &self { + ComparisonOperator::Equal => write!(f, "EQ"), + ComparisonOperator::GreaterThanOrEqual => write!(f, "GE"), + ComparisonOperator::GreaterThan => write!(f, "GT"), + ComparisonOperator::LessThanOrEqual => write!(f, "LE"), + ComparisonOperator::LessThan => write!(f, "LT"), + } + .map_err(Into::into) + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct UnaryLogic { + pub operator: UnaryOperator, + pub operand: MemoryReference, +} + +impl UnaryLogic { + pub fn new(operator: UnaryOperator, operand: MemoryReference) -> Self { + Self { operator, operand } + } +} + +impl Quil for UnaryLogic { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + self.operator.write(f, fall_back_to_debug)?; + write!(f, " ")?; + self.operand.write(f, fall_back_to_debug)?; + Ok(()) + } +} + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum UnaryOperator { + Neg, + Not, +} + +impl Quil for UnaryOperator { + fn write( + &self, + f: &mut impl std::fmt::Write, + _fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match &self { + UnaryOperator::Neg => write!(f, "NEG"), + UnaryOperator::Not => write!(f, "NOT"), + } + .map_err(Into::into) + } +} diff --git a/quil-rs/src/instruction/control_flow.rs b/quil-rs/src/instruction/control_flow.rs new file mode 100644 index 00000000..40200f75 --- /dev/null +++ b/quil-rs/src/instruction/control_flow.rs @@ -0,0 +1,211 @@ +use std::sync::Arc; + +use super::MemoryReference; +use crate::quil::{Quil, ToQuilError}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Label { + pub target: Target, +} + +impl Label { + pub fn new(target: Target) -> Self { + Label { target } + } +} + +impl Quil for Label { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(writer, "LABEL ")?; + self.target.write(writer, fall_back_to_debug) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, strum::EnumTryAs)] +pub enum Target { + Fixed(String), + Placeholder(TargetPlaceholder), +} + +impl Target { + pub(crate) fn resolve_placeholder(&mut self, resolver: R) + where + R: Fn(&TargetPlaceholder) -> Option, + { + if let Target::Placeholder(placeholder) = self { + if let Some(resolved) = resolver(placeholder) { + *self = Target::Fixed(resolved); + } + } + } +} + +impl Quil for Target { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match self { + Target::Fixed(label) => write!(writer, "@{}", label).map_err(Into::into), + Target::Placeholder(_) => { + if fall_back_to_debug { + write!(writer, "@{:?}", self).map_err(Into::into) + } else { + Err(ToQuilError::UnresolvedLabelPlaceholder) + } + } + } + } +} + +type TargetPlaceholderInner = Arc; + +/// An opaque placeholder for a label whose index may be assigned +/// at a later time. +#[derive(Clone, Debug, Eq)] +pub struct TargetPlaceholder(TargetPlaceholderInner); + +impl TargetPlaceholder { + pub fn new(base_label: String) -> Self { + Self(Arc::new(base_label)) + } + + pub fn as_inner(&self) -> &str { + &self.0 + } + + fn address(&self) -> usize { + &*self.0 as *const _ as usize + } +} + +impl std::hash::Hash for TargetPlaceholder { + fn hash(&self, state: &mut H) { + self.address().hash(state); + } +} + +impl PartialOrd for TargetPlaceholder { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for TargetPlaceholder { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.address().cmp(&other.address()) + } +} + +impl PartialEq for TargetPlaceholder { + fn eq(&self, other: &Self) -> bool { + Arc::::ptr_eq(&self.0, &other.0) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Jump { + pub target: Target, +} + +impl Quil for Jump { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> Result<(), crate::quil::ToQuilError> { + write!(writer, "JUMP ")?; + self.target.write(writer, fall_back_to_debug)?; + Ok(()) + } +} + +impl Jump { + pub fn new(target: Target) -> Self { + Self { target } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct JumpWhen { + pub target: Target, + pub condition: MemoryReference, +} + +impl JumpWhen { + pub fn new(target: Target, condition: MemoryReference) -> Self { + Self { target, condition } + } +} + +impl Quil for JumpWhen { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> Result<(), crate::quil::ToQuilError> { + write!(writer, "JUMP-WHEN ")?; + self.target.write(writer, fall_back_to_debug)?; + write!(writer, " {}", self.condition)?; + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct JumpUnless { + pub target: Target, + pub condition: MemoryReference, +} + +impl JumpUnless { + pub fn new(target: Target, condition: MemoryReference) -> Self { + Self { target, condition } + } +} + +impl Quil for JumpUnless { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> Result<(), crate::quil::ToQuilError> { + write!(writer, "JUMP-UNLESS ")?; + self.target.write(writer, fall_back_to_debug)?; + write!(writer, " {}", self.condition)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + + #[test] + fn resolve_placeholder() { + let mut label = Target::Placeholder(TargetPlaceholder::new("base".to_string())); + label.resolve_placeholder(|_| Some("test".to_string())); + assert_eq!(label, Target::Fixed("test".to_string())) + } + + #[rstest] + #[case(Target::Fixed(String::from("test")), Ok("@test"), "@test")] + #[case( + Target::Placeholder(TargetPlaceholder::new(String::from("test-placeholder"))), + Err(ToQuilError::UnresolvedLabelPlaceholder), + "@Placeholder(TargetPlaceholder(\"test-placeholder\"))" + )] + fn quil_format( + #[case] input: Target, + #[case] expected_quil: crate::quil::ToQuilResult<&str>, + #[case] expected_debug: &str, + ) { + assert_eq!(input.to_quil(), expected_quil.map(|s| s.to_string())); + assert_eq!(input.to_quil_or_debug(), expected_debug); + } +} diff --git a/quil-rs/src/instruction/declaration.rs b/quil-rs/src/instruction/declaration.rs new file mode 100644 index 00000000..fea888b5 --- /dev/null +++ b/quil-rs/src/instruction/declaration.rs @@ -0,0 +1,291 @@ +use std::str::FromStr; + +use nom_locate::LocatedSpan; + +use crate::{ + parser::{common::parse_memory_reference, lex, ParseError}, + program::{disallow_leftover, SyntaxError}, + quil::Quil, +}; + +use super::ArithmeticOperand; + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum ScalarType { + Bit, + Integer, + Octet, + Real, +} + +impl Quil for ScalarType { + fn write( + &self, + f: &mut impl std::fmt::Write, + _fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + use ScalarType::*; + write!( + f, + "{}", + match self { + Bit => "BIT", + Integer => "INTEGER", + Octet => "OCTET", + Real => "REAL", + } + ) + .map_err(Into::into) + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct Vector { + pub data_type: ScalarType, + pub length: u64, +} + +impl Vector { + pub fn new(data_type: ScalarType, length: u64) -> Self { + Self { data_type, length } + } +} + +impl Quil for Vector { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + self.data_type.write(f, fall_back_to_debug)?; + write!(f, "[{}]", self.length).map_err(Into::into) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Sharing { + pub name: String, + pub offsets: Vec, +} + +impl Sharing { + pub fn new(name: String, offsets: Vec) -> Self { + Self { name, offsets } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Offset { + pub offset: u64, + pub data_type: ScalarType, +} + +impl Offset { + pub fn new(offset: u64, data_type: ScalarType) -> Self { + Self { offset, data_type } + } +} + +impl Quil for Offset { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "{} ", self.offset)?; + self.data_type.write(f, fall_back_to_debug) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Declaration { + pub name: String, + pub size: Vector, + pub sharing: Option, +} + +impl Declaration { + pub fn new(name: String, size: Vector, sharing: Option) -> Self { + Self { + name, + size, + sharing, + } + } +} + +impl Quil for Declaration { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "DECLARE {} ", self.name)?; + self.size.write(f, fall_back_to_debug)?; + if let Some(shared) = &self.sharing { + write!(f, " SHARING {}", shared.name)?; + if !shared.offsets.is_empty() { + write!(f, " OFFSET")?; + for offset in shared.offsets.iter() { + write!(f, " ")?; + offset.write(f, fall_back_to_debug)?; + } + } + } + Ok(()) + } +} + +#[cfg(test)] +mod test_declaration { + use super::{Declaration, Offset, ScalarType, Sharing, Vector}; + use crate::quil::Quil; + use insta::assert_snapshot; + use rstest::rstest; + + #[rstest] + #[case( + "Basic Declaration", + Declaration{ + name: "ro".to_string(), + size: Vector{data_type: ScalarType::Bit, length: 1}, + sharing: None, + + } + )] + #[case( + "Shared Declaration", + Declaration{ + name: "ro".to_string(), + size: Vector{data_type: ScalarType::Integer, length: 2}, + sharing: Some(Sharing{name: "foo".to_string(), offsets: vec![]}) + } + )] + #[case( + "Shared Declaration with Offsets", + Declaration{ + name: "ro".to_string(), + size: Vector{data_type: ScalarType::Real, length: 3}, + sharing: Some(Sharing{ + name: "bar".to_string(), + offsets: vec![ + Offset{offset: 4, data_type: ScalarType::Bit}, + Offset{offset: 5, data_type: ScalarType::Bit} + ]}) + } + )] + fn test_display(#[case] description: &str, #[case] declaration: Declaration) { + insta::with_settings!({ + snapshot_suffix => description, + }, { + assert_snapshot!(declaration.to_quil_or_debug()) + }) + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct MemoryReference { + pub name: String, + pub index: u64, +} + +impl MemoryReference { + pub fn new(name: String, index: u64) -> Self { + Self { name, index } + } +} + +impl Quil for MemoryReference { + fn write( + &self, + f: &mut impl std::fmt::Write, + _fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + if self.index == 0 { + write!(f, "{}", self.name)?; + return Ok(()); + } + + write!(f, "{}[{}]", self.name, self.index).map_err(Into::into) + } +} + +impl std::fmt::Display for MemoryReference { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}[{}]", self.name, self.index) + } +} + +impl FromStr for MemoryReference { + type Err = SyntaxError; + + fn from_str(s: &str) -> Result { + let input = LocatedSpan::new(s); + let tokens = lex(input)?; + disallow_leftover( + parse_memory_reference(&tokens).map_err(ParseError::from_nom_internal_err), + ) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Load { + pub destination: MemoryReference, + pub source: String, + pub offset: MemoryReference, +} + +impl Load { + pub fn new(destination: MemoryReference, source: String, offset: MemoryReference) -> Self { + Self { + destination, + source, + offset, + } + } +} + +impl Quil for Load { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "LOAD ")?; + self.destination.write(f, fall_back_to_debug)?; + write!(f, " {} ", self.source)?; + self.offset.write(f, fall_back_to_debug)?; + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Hash)] +pub struct Store { + pub destination: String, + pub offset: MemoryReference, + pub source: ArithmeticOperand, +} + +impl Store { + pub fn new(destination: String, offset: MemoryReference, source: ArithmeticOperand) -> Self { + Self { + destination, + offset, + source, + } + } +} + +impl Quil for Store { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "STORE {} ", self.destination)?; + self.offset.write(f, fall_back_to_debug)?; + write!(f, " ")?; + self.source.write(f, fall_back_to_debug)?; + Ok(()) + } +} diff --git a/quil-rs/src/instruction/frame.rs b/quil-rs/src/instruction/frame.rs new file mode 100644 index 00000000..e9ced8a2 --- /dev/null +++ b/quil-rs/src/instruction/frame.rs @@ -0,0 +1,391 @@ +use std::str::FromStr; + +use indexmap::IndexMap; +use nom_locate::LocatedSpan; + +use super::{MemoryReference, Qubit, QuotedString, WaveformInvocation}; +use crate::{ + expression::Expression, + parser::{common::parse_frame_identifier, lex, ParseError}, + program::{disallow_leftover, SyntaxError}, + quil::Quil, +}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, strum::EnumTryAs)] +pub enum AttributeValue { + String(String), + Expression(Expression), +} + +impl Quil for AttributeValue { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + use AttributeValue::*; + match self { + String(value) => write!(f, "{}", QuotedString(value)).map_err(Into::into), + Expression(value) => value.write(f, fall_back_to_debug), + } + } +} + +pub type FrameAttributes = IndexMap; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FrameDefinition { + pub identifier: FrameIdentifier, + pub attributes: FrameAttributes, +} + +impl FrameDefinition { + pub fn new(identifier: FrameIdentifier, attributes: FrameAttributes) -> Self { + Self { + identifier, + attributes, + } + } +} + +impl Quil for FrameDefinition { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(writer, "DEFFRAME ")?; + self.identifier.write(writer, fall_back_to_debug)?; + write!(writer, ":")?; + for (key, value) in &self.attributes { + write!(writer, "\n\t{}: ", key)?; + value.write(writer, fall_back_to_debug)?; + } + + Ok(()) + } +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct FrameIdentifier { + pub name: String, + pub qubits: Vec, +} + +impl FrameIdentifier { + pub fn new(name: String, qubits: Vec) -> Self { + Self { name, qubits } + } +} + +impl Quil for FrameIdentifier { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> std::result::Result<(), crate::quil::ToQuilError> { + for qubit in &self.qubits { + qubit.write(writer, fall_back_to_debug)?; + write!(writer, " ")?; + } + write!(writer, "{}", QuotedString(&self.name)).map_err(Into::into) + } +} + +impl FromStr for FrameIdentifier { + type Err = SyntaxError; + + fn from_str(s: &str) -> Result { + let input = LocatedSpan::new(s); + let tokens = lex(input)?; + disallow_leftover( + parse_frame_identifier(&tokens).map_err(ParseError::from_nom_internal_err), + ) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Capture { + pub blocking: bool, + pub frame: FrameIdentifier, + pub memory_reference: MemoryReference, + pub waveform: WaveformInvocation, +} + +impl Capture { + pub fn new( + blocking: bool, + frame: FrameIdentifier, + memory_reference: MemoryReference, + waveform: WaveformInvocation, + ) -> Self { + Self { + blocking, + frame, + memory_reference, + waveform, + } + } +} + +impl Quil for Capture { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> Result<(), crate::quil::ToQuilError> { + { + if self.blocking { + write!(writer, "CAPTURE ")?; + } else { + write!(writer, "NONBLOCKING CAPTURE ")?; + } + + self.frame.write(writer, fall_back_to_debug)?; + write!(writer, " ")?; + self.waveform.write(writer, fall_back_to_debug)?; + write!(writer, " ")?; + self.memory_reference.write(writer, fall_back_to_debug)?; + Ok(()) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Pulse { + pub blocking: bool, + pub frame: FrameIdentifier, + pub waveform: WaveformInvocation, +} + +impl Pulse { + pub fn new(blocking: bool, frame: FrameIdentifier, waveform: WaveformInvocation) -> Self { + Self { + blocking, + frame, + waveform, + } + } +} + +impl Quil for Pulse { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + { + if self.blocking { + write!(writer, "PULSE ")?; + } else { + write!(writer, "NONBLOCKING PULSE ")?; + } + self.frame.write(writer, fall_back_to_debug)?; + write!(writer, " ")?; + self.waveform.write(writer, fall_back_to_debug)?; + Ok(()) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct RawCapture { + pub blocking: bool, + pub frame: FrameIdentifier, + pub duration: Expression, + pub memory_reference: MemoryReference, +} + +impl RawCapture { + pub fn new( + blocking: bool, + frame: FrameIdentifier, + duration: Expression, + memory_reference: MemoryReference, + ) -> Self { + Self { + blocking, + frame, + duration, + memory_reference, + } + } +} + +impl Quil for RawCapture { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + { + if self.blocking { + write!(writer, "RAW-CAPTURE ")?; + } else { + write!(writer, "NONBLOCKING RAW-CAPTURE ")?; + } + self.frame.write(writer, fall_back_to_debug)?; + write!(writer, " ")?; + self.duration.write(writer, fall_back_to_debug)?; + write!(writer, " ")?; + self.memory_reference.write(writer, fall_back_to_debug)?; + Ok(()) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SetFrequency { + pub frame: FrameIdentifier, + pub frequency: Expression, +} + +impl SetFrequency { + pub fn new(frame: FrameIdentifier, frequency: Expression) -> Self { + Self { frame, frequency } + } +} + +impl Quil for SetFrequency { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(writer, "SET-FREQUENCY ")?; + self.frame.write(writer, fall_back_to_debug)?; + write!(writer, " ")?; + self.frequency.write(writer, fall_back_to_debug)?; + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SetPhase { + pub frame: FrameIdentifier, + pub phase: Expression, +} + +impl SetPhase { + pub fn new(frame: FrameIdentifier, phase: Expression) -> Self { + Self { frame, phase } + } +} + +impl Quil for SetPhase { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(writer, "SET-PHASE ")?; + self.frame.write(writer, fall_back_to_debug)?; + write!(writer, " ")?; + self.phase.write(writer, fall_back_to_debug)?; + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SetScale { + pub frame: FrameIdentifier, + pub scale: Expression, +} + +impl SetScale { + pub fn new(frame: FrameIdentifier, scale: Expression) -> Self { + Self { frame, scale } + } +} + +impl Quil for SetScale { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(writer, "SET-SCALE ")?; + self.frame.write(writer, fall_back_to_debug)?; + write!(writer, " ")?; + self.scale.write(writer, fall_back_to_debug)?; + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ShiftFrequency { + pub frame: FrameIdentifier, + pub frequency: Expression, +} + +impl ShiftFrequency { + pub fn new(frame: FrameIdentifier, frequency: Expression) -> Self { + Self { frame, frequency } + } +} + +impl Quil for ShiftFrequency { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(writer, "SHIFT-FREQUENCY ")?; + self.frame.write(writer, fall_back_to_debug)?; + write!(writer, " ")?; + self.frequency.write(writer, fall_back_to_debug)?; + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ShiftPhase { + pub frame: FrameIdentifier, + pub phase: Expression, +} + +impl ShiftPhase { + pub fn new(frame: FrameIdentifier, phase: Expression) -> Self { + Self { frame, phase } + } +} + +impl Quil for ShiftPhase { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(writer, "SHIFT-PHASE ")?; + self.frame.write(writer, fall_back_to_debug)?; + write!(writer, " ")?; + self.phase.write(writer, fall_back_to_debug)?; + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SwapPhases { + pub frame_1: FrameIdentifier, + pub frame_2: FrameIdentifier, +} + +impl SwapPhases { + pub fn new(frame_1: FrameIdentifier, frame_2: FrameIdentifier) -> Self { + Self { frame_1, frame_2 } + } +} + +impl Quil for SwapPhases { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(writer, "SWAP-PHASES ")?; + self.frame_1.write(writer, fall_back_to_debug)?; + write!(writer, " ")?; + self.frame_2.write(writer, fall_back_to_debug)?; + Ok(()) + } +} diff --git a/quil-rs/src/instruction/gate.rs b/quil-rs/src/instruction/gate.rs new file mode 100644 index 00000000..ed78b653 --- /dev/null +++ b/quil-rs/src/instruction/gate.rs @@ -0,0 +1,1116 @@ +use crate::{ + expression::Expression, + imag, + instruction::{write_expression_parameter_string, write_parameter_string, write_qubits, Qubit}, + parser::{gate::parse_gate, lex, ParseError}, + program::{disallow_leftover, SyntaxError}, + quil::{write_join_quil, Quil}, + real, + validation::identifier::{ + validate_identifier, validate_user_identifier, IdentifierValidationError, + }, +}; +use ndarray::{array, linalg::kron, Array2}; +use nom_locate::LocatedSpan; +use num_complex::Complex64; +use once_cell::sync::Lazy; +use std::{ + cmp::Ordering, + collections::{HashMap, HashSet}, + str::FromStr, +}; + +/// A struct encapsulating all the properties of a Quil Quantum Gate. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Gate { + pub name: String, + pub parameters: Vec, + pub qubits: Vec, + pub modifiers: Vec, +} + +/// An enum of all the possible modifiers on a quil [`Gate`] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum GateModifier { + /// The `CONTROLLED` modifier makes the gate take an extra [`Qubit`] parameter as a control + /// qubit. + Controlled, + /// The `DAGGER` modifier does a complex-conjugate transpose on the [`Gate`]. + Dagger, + /// The `FORKED` modifier allows an alternate set of parameters to be used based on the state + /// of a qubit. + Forked, +} + +#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)] +pub enum GateError { + #[error("invalid name: {0}")] + InvalidIdentifier(#[from] IdentifierValidationError), + + #[error("a gate must operate on 1 or more qubits")] + EmptyQubits, + + #[error("expected {expected} parameters, but got {actual}")] + ForkedParameterLength { expected: usize, actual: usize }, + + #[error("expected the number of Pauli term arguments, {actual}, to match the length of the Pauli word, {expected}")] + PauliTermArgumentLength { expected: usize, actual: usize }, + + #[error("the Pauli term arguments {mismatches:?}, are not in the defined argument list: {expected_arguments:?}")] + PauliSumArgumentMismatch { + mismatches: Vec, + expected_arguments: Vec, + }, + + #[error("unknown gate `{name}` to turn into {} matrix ", if *parameterized { "parameterized" } else { "constant" })] + UndefinedGate { name: String, parameterized: bool }, + + #[error("expected {expected} parameters, was given {actual}")] + MatrixArgumentLength { expected: usize, actual: usize }, + + #[error( + "cannot produce a matrix for a gate `{name}` with non-constant parameters {parameters:?}" + )] + MatrixNonConstantParams { + name: String, + parameters: Vec, + }, + + #[error("cannot produce a matrix for gate `{name}` with variable qubit {qubit}", qubit=.qubit.to_quil_or_debug())] + MatrixVariableQubit { name: String, qubit: Qubit }, + + #[error("forked gate `{name}` has an odd number of parameters: {parameters:?}")] + ForkedGateOddNumParams { + name: String, + parameters: Vec, + }, + + #[error("cannot produce a matrix for a gate `{name}` with unresolved qubit placeholders")] + UnresolvedQubitPlaceholder { name: String }, +} + +/// Matrix version of a gate. +pub type Matrix = Array2; + +impl Gate { + /// Build a new gate + /// + /// # Errors + /// + /// Returns an error if the given name isn't a valid Quil identifier or if no qubits are given. + pub fn new( + name: &str, + parameters: Vec, + qubits: Vec, + modifiers: Vec, + ) -> Result { + if qubits.is_empty() { + return Err(GateError::EmptyQubits); + } + + validate_identifier(name).map_err(GateError::InvalidIdentifier)?; + + Ok(Self { + name: name.to_string(), + parameters, + qubits, + modifiers, + }) + } + + /// Apply a DAGGER modifier to the gate + pub fn dagger(mut self) -> Self { + self.modifiers.insert(0, GateModifier::Dagger); + self + } + + /// Apply a CONTROLLED modifier to the gate + pub fn controlled(mut self, control_qubit: Qubit) -> Self { + self.qubits.insert(0, control_qubit); + self.modifiers.insert(0, GateModifier::Controlled); + self + } + + /// Apply a FORKED modifier to the gate + /// + /// # Errors + /// + /// Returns an error if the number of provided alternate parameters don't + /// equal the number of existing parameters. + pub fn forked( + mut self, + fork_qubit: Qubit, + alt_params: Vec, + ) -> Result { + if alt_params.len() != self.parameters.len() { + return Err(GateError::ForkedParameterLength { + expected: self.parameters.len(), + actual: alt_params.len(), + }); + } + self.modifiers.insert(0, GateModifier::Forked); + self.qubits.insert(0, fork_qubit); + self.parameters.extend(alt_params); + Ok(self) + } + + /// Lift a Gate to the full `n_qubits`-qubit Hilbert space. + /// + /// # Errors + /// + /// Returns an error if any of the parameters of this gate are non-constant, if any of the + /// qubits are variable, if the name of this gate is unknown, or if there are an unexpected + /// number of parameters. + pub fn to_unitary(&mut self, n_qubits: u64) -> Result { + let qubits = self + .qubits + .iter() + .map(|q| match q { + Qubit::Variable(_) => Err(GateError::MatrixVariableQubit { + name: self.name.clone(), + qubit: q.clone(), + }), + Qubit::Placeholder(_) => Err(GateError::UnresolvedQubitPlaceholder { + name: self.name.clone(), + }), + Qubit::Fixed(i) => Ok(*i), + }) + .collect::, _>>()?; + Ok(lifted_gate_matrix(&gate_matrix(self)?, &qubits, n_qubits)) + } +} + +impl FromStr for Gate { + type Err = SyntaxError; + + fn from_str(s: &str) -> Result { + let input = LocatedSpan::new(s); + let tokens = lex(input)?; + disallow_leftover(parse_gate(&tokens).map_err(ParseError::from_nom_internal_err)) + } +} + +/// Lift a unitary matrix to act on the specified qubits in a full `n_qubits`-qubit Hilbert +/// space. +/// +/// For 1-qubit gates, this is easy and can be achieved with appropriate kronning of identity +/// matrices. For 2-qubit gates acting on adjacent qubit indices, it is also easy. However, for a +/// multiqubit gate acting on non-adjactent qubit indices, we must first apply a permutation matrix +/// to make the qubits adjacent and then apply the inverse permutation. +fn lifted_gate_matrix(matrix: &Matrix, qubits: &[u64], n_qubits: u64) -> Matrix { + let (perm, start) = permutation_arbitrary(qubits, n_qubits); + let v = qubit_adjacent_lifted_gate(start, matrix, n_qubits); + perm.t().mapv(|c| c.conj()).dot(&v.dot(&perm)) +} + +/// Recursively handle a gate, with all modifiers. +/// +/// The main source of complexity is in handling FORKED gates. Given a gate with modifiers, such as +/// `FORKED CONTROLLED FORKED RX(a,b,c,d) 0 1 2 3`, we get a tree, as in +/// +/// ```text +/// +/// FORKED CONTROLLED FORKED RX(a,b,c,d) 0 1 2 3 +/// / \ +/// CONTROLLED FORKED RX(a,b) 1 2 3 CONTROLLED FORKED RX(c,d) 1 2 3 +/// | | +/// FORKED RX(a,b) 2 3 FORKED RX(c,d) 2 3 +/// / \ / \ +/// RX(a) 3 RX(b) 3 RX(c) 3 RX(d) 3 +/// ``` +fn gate_matrix(gate: &mut Gate) -> Result { + static ZERO: Lazy = + Lazy::new(|| array![[real!(1.0), real!(0.0)], [real!(0.0), real!(0.0)]]); + static ONE: Lazy = + Lazy::new(|| array![[real!(0.0), real!(0.0)], [real!(0.0), real!(1.0)]]); + if let Some(modifier) = gate.modifiers.pop() { + match modifier { + GateModifier::Controlled => { + gate.qubits = gate.qubits[1..].to_vec(); + let matrix = gate_matrix(gate)?; + Ok(kron(&ZERO, &Array2::eye(matrix.shape()[0])) + kron(&ONE, &matrix)) + } + GateModifier::Dagger => gate_matrix(gate).map(|g| g.t().mapv(|c| c.conj())), + GateModifier::Forked => { + let param_index = gate.parameters.len(); + if param_index & 1 != 0 { + Err(GateError::ForkedGateOddNumParams { + name: gate.name.clone(), + parameters: gate.parameters.clone(), + }) + } else { + // Some mutability dancing to keep the borrow checker happy + gate.qubits = gate.qubits[1..].to_vec(); + let (p0, p1) = gate.parameters[..].split_at(param_index / 2); + let mut child0 = gate.clone(); + child0.parameters = p0.to_vec(); + let mat0 = gate_matrix(&mut child0)?; + gate.parameters = p1.to_vec(); + let mat1 = gate_matrix(gate)?; + Ok(kron(&ZERO, &mat0) + kron(&ONE, &mat1)) + } + } + } + } else if gate.parameters.is_empty() { + CONSTANT_GATE_MATRICES + .get(&gate.name) + .cloned() + .ok_or_else(|| GateError::UndefinedGate { + name: gate.name.clone(), + parameterized: false, + }) + } else { + match gate.parameters.len() { + 1 => { + if let Expression::Number(x) = gate.parameters[0].clone().into_simplified() { + PARAMETERIZED_GATE_MATRICES + .get(&gate.name) + .map(|f| f(x)) + .ok_or_else(|| GateError::UndefinedGate { + name: gate.name.clone(), + parameterized: true, + }) + } else { + Err(GateError::MatrixNonConstantParams { + name: gate.name.clone(), + parameters: gate.parameters.clone(), + }) + } + } + actual => Err(GateError::MatrixArgumentLength { + expected: 1, + actual, + }), + } + } +} + +/// Generate the permutation matrix that permutes an arbitrary number of single-particle Hilbert +/// spaces into adjacent positions. +/// +/// +/// Transposes the qubit indices in the order they are passed to a contiguous region in the +/// complete Hilbert space, in increasing qubit index order (preserving the order they are passed +/// in). +/// +/// Gates are usually defined as `GATE 0 1 2`, with such an argument ordering dictating the layout +/// of the matrix corresponding to GATE. If such an instruction is given, actual qubits (0, 1, 2) +/// need to be swapped into the positions (2, 1, 0), because the lifting operation taking the 8 x 8 +/// matrix of GATE is done in the little-endian (reverse) addressed qubit space. +/// +/// For example, suppose I have a Quil command CCNOT 20 15 10. The median of the qubit indices is +/// 15 - hence, we permute qubits [20, 15, 10] into the final map [16, 15, 14] to minimize the +/// number of swaps needed, and so we can directly operate with the final CCNOT, when lifted from +/// indices [16, 15, 14] to the complete Hilbert space. +/// +/// Notes: assumes qubit indices are unique. +/// +/// Done in preparation for arbitrary gate application on adjacent qubits. +fn permutation_arbitrary(qubit_inds: &[u64], n_qubits: u64) -> (Matrix, u64) { + // Begin construction of permutation + let mut perm = Array2::eye(2usize.pow(n_qubits as u32)); + // First, sort the list and find the median. + let mut sorted_inds = qubit_inds.to_vec(); + sorted_inds.sort(); + let med_i = qubit_inds.len() / 2; + let med = sorted_inds[med_i]; + // The starting position of all specified Hilbert spaces begins at the qubit at (median - + // med_i) + let start = med - med_i as u64; + if qubit_inds.len() > 1 { + // Array of final indices the arguments are mapped to, from high index to low index, left to + // right ordering + let final_map = (start..start + qubit_inds.len() as u64) + .rev() + .collect::>(); + + // Note that the lifting operation takes a k-qubit gate operating on the qubits i+k-1, i+k-2, + // ... i (left to right). two_swap_helper can be used to build the permutation matrix by + // filling out the final map by sweeping over the qubit_inds from left to right and back again, + // swapping qubits into position. we loop over the qubit_inds until the final mapping matches + // the argument. + let mut qubit_arr = (0..n_qubits).collect::>(); // Current qubit indexing + + let mut made_it = false; + let mut right = true; + while !made_it { + let array = if right { + (0..qubit_inds.len()).collect::>() + } else { + (0..qubit_inds.len()).rev().collect() + }; + + for i in array { + let j = qubit_arr + .iter() + .position(|&q| q == qubit_inds[i]) + .expect("These arrays cover the same range."); + let pmod = two_swap_helper(j as u64, final_map[i], n_qubits, &mut qubit_arr); + perm = pmod.dot(&perm); + if (final_map[final_map.len() - 1]..final_map[0] + 1) + .rev() + .zip(qubit_inds) + .all(|(f, &q)| qubit_arr[f as usize] == q) + { + made_it = true; + break; + } + } + right = !right; + } + } + (perm, start) +} + +/// Generate the permutation matrix that permutes two single-particle Hilbert spaces into adjacent +/// positions. +/// +/// ALWAYS swaps j TO k. Recall that Hilbert spaces are ordered in decreasing qubit index order. +/// Hence, j > k implies that j is to the left of k. +/// +/// End results: +/// j == k: nothing happens +/// j > k: Swap j right to k, until j at ind (k) and k at ind (k+1). +/// j < k: Swap j left to k, until j at ind (k) and k at ind (k-1). +/// +/// Done in preparation for arbitrary 2-qubit gate application on ADJACENT qubits. +fn two_swap_helper(j: u64, k: u64, n_qubits: u64, qubit_map: &mut [u64]) -> Matrix { + let mut perm = Array2::eye(2usize.pow(n_qubits as u32)); + let swap = CONSTANT_GATE_MATRICES + .get("SWAP") + .expect("Key should exist by design."); + match Ord::cmp(&j, &k) { + Ordering::Equal => {} + Ordering::Greater => { + // swap j right to k, until j at ind (k) and k at ind (k+1) + for i in (k + 1..=j).rev() { + perm = qubit_adjacent_lifted_gate(i - 1, swap, n_qubits).dot(&perm); + qubit_map.swap(i as usize, (i - 1) as usize); + } + } + Ordering::Less => { + // swap j left to k, until j at ind (k) and k at ind (k-1) + for i in j..k { + perm = qubit_adjacent_lifted_gate(i, swap, n_qubits).dot(&perm); + qubit_map.swap(i as usize, (i + 1) as usize); + } + } + } + perm +} + +/// Lifts input k-qubit gate on adjacent qubits starting from qubit i to complete Hilbert space of +/// dimension 2 ** `num_qubits`. +/// +/// Ex: 1-qubit gate, lifts from qubit i +/// Ex: 2-qubit gate, lifts from qubits (i+1, i) +/// Ex: 3-qubit gate, lifts from qubits (i+2, i+1, i), operating in that order +/// +/// In general, this takes a k-qubit gate (2D matrix 2^k x 2^k) and lifts it to the complete +/// Hilbert space of dim 2^num_qubits, as defined by the right-to-left tensor product (1) in +/// arXiv:1608.03355. +/// +/// Developer note: Quil and the QVM like qubits to be ordered such that qubit 0 is on the right. +/// Therefore, in `qubit_adjacent_lifted_gate`, `lifted_pauli`, and `lifted_state_operator`, we +/// build up the lifted matrix by performing the kronecker product from right to left. +/// +/// Note that while the qubits are addressed in decreasing order, starting with num_qubit - 1 on +/// the left and ending with qubit 0 on the right (in a little-endian fashion), gates are still +/// lifted to apply on qubits in increasing index (right-to-left) order. +fn qubit_adjacent_lifted_gate(i: u64, matrix: &Matrix, n_qubits: u64) -> Matrix { + let bottom_matrix = Array2::eye(2usize.pow(i as u32)); + let gate_size = (matrix.shape()[0] as f64).log2().floor() as u64; + let top_qubits = n_qubits - i - gate_size; + let top_matrix = Array2::eye(2usize.pow(top_qubits as u32)); + kron(&top_matrix, &kron(matrix, &bottom_matrix)) +} + +/// Gates matrices that don't use any parameters. +/// +/// https://github.com/quil-lang/quil/blob/master/spec/Quil.md#standard-gates +static CONSTANT_GATE_MATRICES: Lazy> = Lazy::new(|| { + let _0 = real!(0.0); + let _1 = real!(1.0); + let _i = imag!(1.0); + let _1_sqrt_2 = real!(std::f64::consts::FRAC_1_SQRT_2); + HashMap::from([ + ("I".to_string(), Array2::eye(2)), + ("X".to_string(), array![[_0, _1], [_1, _0]]), + ("Y".to_string(), array![[_0, -_i], [_i, _0]]), + ("Z".to_string(), array![[_1, _0], [_0, -_1]]), + ("H".to_string(), array![[_1, _1], [_1, -_1]] * _1_sqrt_2), + ( + "CNOT".to_string(), + array![ + [_1, _0, _0, _0], + [_0, _1, _0, _0], + [_0, _0, _0, _1], + [_0, _0, _1, _0] + ], + ), + ( + "CCNOT".to_string(), + array![ + [_1, _0, _0, _0, _0, _0, _0, _0], + [_0, _1, _0, _0, _0, _0, _0, _0], + [_0, _0, _1, _0, _0, _0, _0, _0], + [_0, _0, _0, _1, _0, _0, _0, _0], + [_0, _0, _0, _0, _1, _0, _0, _0], + [_0, _0, _0, _0, _0, _1, _0, _0], + [_0, _0, _0, _0, _0, _0, _0, _1], + [_0, _0, _0, _0, _0, _0, _1, _0], + ], + ), + ("S".to_string(), array![[_1, _0], [_0, _i]]), + ( + "T".to_string(), + array![[_1, _0], [_0, Complex64::cis(std::f64::consts::FRAC_PI_4)]], + ), + ("CZ".to_string(), { + let mut cz = Array2::eye(4); + cz[[3, 3]] = -_1; + cz + }), + ( + "SWAP".to_string(), + array![ + [_1, _0, _0, _0], + [_0, _0, _1, _0], + [_0, _1, _0, _0], + [_0, _0, _0, _1], + ], + ), + ( + "CSWAP".to_string(), + array![ + [_1, _0, _0, _0, _0, _0, _0, _0], + [_0, _1, _0, _0, _0, _0, _0, _0], + [_0, _0, _1, _0, _0, _0, _0, _0], + [_0, _0, _0, _1, _0, _0, _0, _0], + [_0, _0, _0, _0, _1, _0, _0, _0], + [_0, _0, _0, _0, _0, _0, _1, _0], + [_0, _0, _0, _0, _0, _1, _0, _0], + [_0, _0, _0, _0, _0, _0, _0, _1], + ], + ), + ( + "ISWAP".to_string(), + array![ + [_1, _0, _0, _0], + [_0, _0, _i, _0], + [_0, _i, _0, _0], + [_0, _0, _0, _1], + ], + ), + ]) +}); + +type ParameterizedMatrix = fn(Complex64) -> Matrix; + +/// Gates matrices that use parameters. +/// +/// https://github.com/quil-lang/quil/blob/master/spec/Quil.md#standard-gates +static PARAMETERIZED_GATE_MATRICES: Lazy> = Lazy::new(|| { + // Unfortunately, Complex::cis takes a _float_ argument. + HashMap::from([ + ( + "RX".to_string(), + (|theta: Complex64| { + let _i = imag!(1.0); + let t = theta / 2.0; + array![[t.cos(), -_i * t.sin()], [-_i * t.sin(), t.cos()]] + }) as ParameterizedMatrix, + ), + ( + "RY".to_string(), + (|theta: Complex64| { + let t = theta / 2.0; + array![[t.cos(), -t.sin()], [t.sin(), t.cos()]] + }) as ParameterizedMatrix, + ), + ( + "RZ".to_string(), + (|theta: Complex64| { + let t = theta / 2.0; + array![[t.cos(), -t.sin()], [t.sin(), t.cos()]] + }) as ParameterizedMatrix, + ), + ( + "PHASE".to_string(), + (|alpha: Complex64| { + let mut p = Array2::eye(2); + p[[1, 1]] = alpha.cos() + imag!(1.0) * alpha.sin(); + p + }) as ParameterizedMatrix, + ), + ( + "CPHASE00".to_string(), + (|alpha: Complex64| { + let mut p = Array2::eye(4); + p[[0, 0]] = alpha.cos() + imag!(1.0) * alpha.sin(); + p + }) as ParameterizedMatrix, + ), + ( + "CPHASE01".to_string(), + (|alpha: Complex64| { + let mut p = Array2::eye(4); + p[[1, 1]] = alpha.cos() + imag!(1.0) * alpha.sin(); + p + }) as ParameterizedMatrix, + ), + ( + "CPHASE10".to_string(), + (|alpha: Complex64| { + let mut p = Array2::eye(4); + p[[2, 2]] = alpha.cos() + imag!(1.0) * alpha.sin(); + p + }) as ParameterizedMatrix, + ), + ( + "CPHASE".to_string(), + (|alpha: Complex64| { + let mut p = Array2::eye(4); + p[[3, 3]] = alpha.cos() + imag!(1.0) * alpha.sin(); + p + }) as ParameterizedMatrix, + ), + ( + "PSWAP".to_string(), + (|theta: Complex64| { + let (_0, _1, _c) = (real!(0.0), real!(1.0), theta.cos() + theta); + array![ + [_1, _0, _0, _0], + [_0, _0, _c, _0], + [_0, _c, _0, _0], + [_0, _0, _0, _1], + ] + }) as ParameterizedMatrix, + ), + ]) +}); + +impl Quil for Gate { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + for modifier in &self.modifiers { + modifier.write(f, fall_back_to_debug)?; + write!(f, " ")?; + } + + write!(f, "{}", self.name)?; + write_expression_parameter_string(f, fall_back_to_debug, &self.parameters)?; + write_qubits(f, fall_back_to_debug, &self.qubits) + } +} + +impl Quil for GateModifier { + fn write( + &self, + f: &mut impl std::fmt::Write, + _fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match self { + Self::Controlled => write!(f, "CONTROLLED"), + Self::Dagger => write!(f, "DAGGER"), + Self::Forked => write!(f, "FORKED"), + } + .map_err(Into::into) + } +} + +#[cfg(test)] +mod test_gate_into_matrix { + use super::{ + lifted_gate_matrix, permutation_arbitrary, qubit_adjacent_lifted_gate, two_swap_helper, + Expression::Number, Gate, GateModifier::*, Matrix, ParameterizedMatrix, Qubit::Fixed, + CONSTANT_GATE_MATRICES, PARAMETERIZED_GATE_MATRICES, + }; + use crate::{imag, real}; + use approx::assert_abs_diff_eq; + use ndarray::{array, linalg::kron, Array2}; + use num_complex::Complex64; + use once_cell::sync::Lazy; + use rstest::rstest; + + static _0: Complex64 = real!(0.0); + static _1: Complex64 = real!(1.0); + static _I: Complex64 = imag!(1.0); + static PI: Complex64 = real!(std::f64::consts::PI); + static PI_4: Complex64 = real!(std::f64::consts::FRAC_PI_4); + static SWAP: Lazy = Lazy::new(|| CONSTANT_GATE_MATRICES.get("SWAP").cloned().unwrap()); + static X: Lazy = Lazy::new(|| array![[_0, _1], [_1, _0]]); + static P0: Lazy = Lazy::new(|| array![[_1, _0], [_0, _0]]); + static P1: Lazy = Lazy::new(|| array![[_0, _0], [_0, _1]]); + static CNOT: Lazy = Lazy::new(|| CONSTANT_GATE_MATRICES.get("CNOT").cloned().unwrap()); + static ISWAP: Lazy = + Lazy::new(|| CONSTANT_GATE_MATRICES.get("ISWAP").cloned().unwrap()); + static H: Lazy = Lazy::new(|| CONSTANT_GATE_MATRICES.get("H").cloned().unwrap()); + static RZ: Lazy = + Lazy::new(|| PARAMETERIZED_GATE_MATRICES.get("RZ").cloned().unwrap()); + static CCNOT: Lazy = + Lazy::new(|| CONSTANT_GATE_MATRICES.get("CCNOT").cloned().unwrap()); + static CZ: Lazy = Lazy::new(|| CONSTANT_GATE_MATRICES.get("CZ").cloned().unwrap()); + + #[rstest] + #[case(0, 2, &SWAP)] + #[case(0, 3, &kron(&Array2::eye(2), &SWAP))] + #[case(0, 4, &kron(&Array2::eye(4), &SWAP))] + #[case(1, 3, &kron(&SWAP, &Array2::eye(2)))] + #[case(1, 4, &kron(&Array2::eye(2), &kron(&SWAP, &Array2::eye(2))))] + #[case(2, 4, &kron(&Array2::eye(1), &kron(&SWAP, &Array2::eye(4))))] + #[case(8, 10, &kron(&Array2::eye(1), &kron(&SWAP, &Array2::eye(2usize.pow(8)))))] + fn test_qubit_adjacent_lifted_gate( + #[case] i: u64, + #[case] n_qubits: u64, + #[case] expected: &Matrix, + ) { + let result = qubit_adjacent_lifted_gate(i, &SWAP, n_qubits); + assert_abs_diff_eq!(result, expected); + } + + // test cases via pyquil.simulation.tools.two_swap_helper + #[rstest] + #[case(0, 0, 2, &mut[0, 1], &[0, 1], &Array2::eye(4))] + #[case(0, 1, 2, &mut[0, 1], &[1, 0], &array![[_1, _0, _0, _0], + [_0, _0, _1, _0], + [_0, _1, _0, _0], + [_0, _0, _0, _1]])] + #[case(0, 1, 2, &mut[1, 0], &[0, 1], &array![[_1, _0, _0, _0], + [_0, _0, _1, _0], + [_0, _1, _0, _0], + [_0, _0, _0, _1]])] + #[case(1, 0, 2, &mut[0, 1], &[1, 0], &array![[_1, _0, _0, _0], + [_0, _0, _1, _0], + [_0, _1, _0, _0], + [_0, _0, _0, _1]])] + #[case(1, 0, 2, &mut[1, 0], &[0, 1], &array![[_1, _0, _0, _0], + [_0, _0, _1, _0], + [_0, _1, _0, _0], + [_0, _0, _0, _1]])] + #[case(0, 1, 3, &mut[0, 1, 2], &[1, 0, 2], &array![[_1, _0, _0, _0, _0, _0, _0, _0], + [_0, _0, _1, _0, _0, _0, _0, _0], + [_0, _1, _0, _0, _0, _0, _0, _0], + [_0, _0, _0, _1, _0, _0, _0, _0], + [_0, _0, _0, _0, _1, _0, _0, _0], + [_0, _0, _0, _0, _0, _0, _1, _0], + [_0, _0, _0, _0, _0, _1, _0, _0], + [_0, _0, _0, _0, _0, _0, _0, _1]])] + + fn test_two_swap_helper( + #[case] j: u64, + #[case] k: u64, + #[case] n_qubits: u64, + #[case] qubit_map: &mut [u64], + #[case] expected_qubit_map: &[u64], + #[case] expected_matrix: &Matrix, + ) { + let result = two_swap_helper(j, k, n_qubits, qubit_map); + assert_eq!(qubit_map, expected_qubit_map); + assert_abs_diff_eq!(result, expected_matrix); + } + + // test cases via pyquil.simulation.tools.permutation_arbitrary + #[rstest] + #[case(&[0], 1, 0, &Array2::eye(2))] + #[case(&[0, 1], 2, 0, &array![[_1, _0, _0, _0], + [_0, _0, _1, _0], + [_0, _1, _0, _0], + [_0, _0, _0, _1]])] + #[case(&[1, 0], 2, 0, &Array2::eye(4))] + #[case(&[0, 2], 3, 1, &array![[_1, _0, _0, _0, _0, _0, _0, _0], + [_0, _0, _1, _0, _0, _0, _0, _0], + [_0, _0, _0, _0, _1, _0, _0, _0], + [_0, _0, _0, _0, _0, _0, _1, _0], + [_0, _1, _0, _0, _0, _0, _0, _0], + [_0, _0, _0, _1, _0, _0, _0, _0], + [_0, _0, _0, _0, _0, _1, _0, _0], + [_0, _0, _0, _0, _0, _0, _0, _1]])] + #[case(&[1, 2], 3, 1, &array![[_1, _0, _0, _0, _0, _0, _0, _0], + [_0, _1, _0, _0, _0, _0, _0, _0], + [_0, _0, _0, _0, _1, _0, _0, _0], + [_0, _0, _0, _0, _0, _1, _0, _0], + [_0, _0, _1, _0, _0, _0, _0, _0], + [_0, _0, _0, _1, _0, _0, _0, _0], + [_0, _0, _0, _0, _0, _0, _1, _0], + [_0, _0, _0, _0, _0, _0, _0, _1]])] + #[case(&[0, 1, 2], 3, 0, &array![[_1, _0, _0, _0, _0, _0, _0, _0], + [_0, _0, _0, _0, _1, _0, _0, _0], + [_0, _0, _1, _0, _0, _0, _0, _0], + [_0, _0, _0, _0, _0, _0, _1, _0], + [_0, _1, _0, _0, _0, _0, _0, _0], + [_0, _0, _0, _0, _0, _1, _0, _0], + [_0, _0, _0, _1, _0, _0, _0, _0], + [_0, _0, _0, _0, _0, _0, _0, _1]])] + fn test_permutation_arbitrary( + #[case] qubit_inds: &[u64], + #[case] n_qubits: u64, + #[case] expected_start: u64, + #[case] expected_matrix: &Matrix, + ) { + let (result_matrix, result_start) = permutation_arbitrary(qubit_inds, n_qubits); + assert_abs_diff_eq!(result_matrix, expected_matrix); + assert_eq!(result_start, expected_start); + } + + #[rstest] + #[case(&CNOT, &mut [1, 0], 2, &(kron(&P0, &Array2::eye(2)) + kron(&P1, &X)))] + #[case(&CNOT, &mut [0, 1], 2, &(kron(&Array2::eye(2), &P0) + kron(&X, &P1)))] + #[case(&CNOT, &mut [2, 1], 3, &(kron(&CNOT, &Array2::eye(2))))] + #[case(&ISWAP, &mut [0, 1], 3, &kron(&Array2::eye(2), &ISWAP))] + #[case(&ISWAP, &mut [1, 0], 3, &kron(&Array2::eye(2), &ISWAP))] + #[case(&ISWAP, &mut [1, 2], 4, &kron(&Array2::eye(2), &kron(&ISWAP, &Array2::eye(2))))] + #[case(&ISWAP, &mut [3, 2], 4, &kron(&ISWAP, &Array2::eye(4)))] + #[case(&ISWAP, &mut [2, 3], 4, &kron(&ISWAP, &Array2::eye(4)))] + #[case(&H, &mut [0], 4, &kron(&Array2::eye(8), &H))] + #[case(&H, &mut [1], 4, &kron(&Array2::eye(4), &kron(&H, &Array2::eye(2))))] + #[case(&H, &mut [2], 4, &kron(&Array2::eye(2), &kron(&H, &Array2::eye(4))))] + #[case(&H, &mut [3], 4, &kron(&H, &Array2::eye(8)))] + #[case(&H, &mut [0], 5, &kron(&Array2::eye(16), &H))] + #[case(&H, &mut [1], 5, &kron(&Array2::eye(8), &kron(&H, &Array2::eye(2))))] + #[case(&H, &mut [2], 5, &kron(&Array2::eye(4), &kron(&H, &Array2::eye(4))))] + #[case(&H, &mut [3], 5, &kron(&Array2::eye(2), &kron(&H, &Array2::eye(8))))] + #[case(&H, &mut [4], 5, &kron(&H, &Array2::eye(16)))] + fn test_lifted_gate_matrix( + #[case] matrix: &Matrix, + #[case] indices: &mut [u64], + #[case] n_qubits: u64, + #[case] expected: &Matrix, + ) { + assert_abs_diff_eq!(lifted_gate_matrix(matrix, indices, n_qubits), expected); + } + + #[rstest] + #[case(&mut Gate::new("H", vec![], vec![Fixed(0)], vec![]).unwrap(), 4, &kron(&Array2::eye(8), &H))] + #[case(&mut Gate::new("RZ", vec![Number(PI_4)], vec![Fixed(0)], vec![Dagger]).unwrap(), 1, &RZ(-PI_4))] + #[case(&mut Gate::new("X", vec![], vec![Fixed(0)], vec![Dagger]).unwrap().controlled(Fixed(1)), 2, &CNOT)] + #[case( + &mut Gate::new("X", vec![], vec![Fixed(0)], vec![]).unwrap().dagger().controlled(Fixed(1)).dagger().dagger().controlled(Fixed(2)), + 3, + &CCNOT + )] + #[case( + &mut Gate::new("PHASE", vec![Number(_0)], vec![Fixed(1)], vec![]).unwrap().forked(Fixed(0), vec![Number(PI)]).unwrap(), + 2, + &lifted_gate_matrix(&CZ, &[0, 1], 2) + )] + fn test_to_unitary(#[case] gate: &mut Gate, #[case] n_qubits: u64, #[case] expected: &Matrix) { + let result = gate.to_unitary(n_qubits); + assert!(result.is_ok()); + assert_abs_diff_eq!(result.as_ref().unwrap(), expected); + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, strum::Display, strum::EnumString)] +#[strum(serialize_all = "UPPERCASE")] +pub enum PauliGate { + I, + X, + Y, + Z, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct PauliTerm { + pub arguments: Vec<(PauliGate, String)>, + pub expression: Expression, +} + +impl PauliTerm { + pub fn new(arguments: Vec<(PauliGate, String)>, expression: Expression) -> Self { + Self { + arguments, + expression, + } + } + + pub(crate) fn word(&self) -> impl Iterator { + self.arguments.iter().map(|(gate, _)| gate) + } + + pub(crate) fn arguments(&self) -> impl Iterator { + self.arguments.iter().map(|(_, argument)| argument) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct PauliSum { + pub arguments: Vec, + pub terms: Vec, +} + +impl PauliSum { + pub fn new(arguments: Vec, terms: Vec) -> Result { + let diff = terms + .iter() + .flat_map(|t| t.arguments()) + .collect::>() + .difference(&arguments.iter().collect::>()) + .copied() + .collect::>(); + + if !diff.is_empty() { + return Err(GateError::PauliSumArgumentMismatch { + mismatches: diff.into_iter().cloned().collect(), + expected_arguments: arguments, + }); + } + + Ok(Self { arguments, terms }) + } +} + +/// An enum representing a the specification of a [`GateDefinition`] for a given [`GateType`] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum GateSpecification { + /// A matrix of [`Expression`]s representing a unitary operation for a [`GateType::Matrix`]. + Matrix(Vec>), + /// A vector of integers that defines the permutation used for a [`GateType::Permutation`] + Permutation(Vec), + /// A Hermitian operator specified as a Pauli sum, a sum of combinations of Pauli operators, + /// used for a [`GateType::PauliSum`] + PauliSum(PauliSum), +} + +impl Quil for GateSpecification { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match self { + GateSpecification::Matrix(matrix) => { + for row in matrix { + write!(f, "\t")?; + write_join_quil(f, fall_back_to_debug, row.iter(), ", ", "")?; + writeln!(f)?; + } + } + GateSpecification::Permutation(permutation) => { + write!(f, "\t")?; + if let Some(i) = permutation.first() { + write!(f, "{i}")?; + } + for i in permutation.iter().skip(1) { + write!(f, ", {i}")?; + } + writeln!(f)?; + } + GateSpecification::PauliSum(pauli_sum) => { + for term in &pauli_sum.terms { + write!(f, "\t")?; + for word in term.word() { + write!(f, "{word}")?; + } + write!(f, "(")?; + term.expression.write(f, fall_back_to_debug)?; + write!(f, ")")?; + for argument in term.arguments() { + write!(f, " {argument}")?; + } + writeln!(f)?; + } + } + } + Ok(()) + } +} + +/// A struct encapsulating a quil Gate Definition +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct GateDefinition { + pub name: String, + pub parameters: Vec, + pub specification: GateSpecification, +} + +impl GateDefinition { + pub fn new( + name: String, + parameters: Vec, + specification: GateSpecification, + ) -> Result { + validate_user_identifier(&name)?; + Ok(Self { + name, + parameters, + specification, + }) + } +} + +impl Quil for GateDefinition { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "DEFGATE {}", self.name,)?; + write_parameter_string(f, &self.parameters)?; + match &self.specification { + GateSpecification::Matrix(_) => writeln!(f, " AS MATRIX:")?, + GateSpecification::Permutation(_) => writeln!(f, " AS PERMUTATION:")?, + GateSpecification::PauliSum(sum) => { + for arg in &sum.arguments { + write!(f, " {arg}")?; + } + writeln!(f, " AS PAULI-SUM:")? + } + } + self.specification.write(f, fall_back_to_debug)?; + Ok(()) + } +} + +#[cfg(test)] +mod test_gate_definition { + use super::{GateDefinition, GateSpecification, PauliGate, PauliSum, PauliTerm}; + use crate::expression::{ + Expression, ExpressionFunction, FunctionCallExpression, InfixExpression, InfixOperator, + PrefixExpression, PrefixOperator, + }; + use crate::quil::Quil; + use crate::{imag, real}; + use insta::assert_snapshot; + use rstest::rstest; + + #[rstest] + #[case( + "Permutation GateDefinition", + GateDefinition{ + name: "PermGate".to_string(), + parameters: vec![], + specification: GateSpecification::Permutation(vec![0, 1, 2, 3, 4, 5, 7, 6]), + + } + )] + #[case( + "Parameterized GateDefinition", + GateDefinition{ + name: "ParamGate".to_string(), + parameters: vec!["theta".to_string()], + specification: GateSpecification::Matrix(vec![ + vec![ + Expression::FunctionCall(FunctionCallExpression { + function: crate::expression::ExpressionFunction::Cosine, + expression: Box::new(Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(2.0))), + })), + }), + Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: Box::new(Expression::Number(imag!(1f64))) + })), + operator: InfixOperator::Star, + right: Box::new(Expression::FunctionCall(FunctionCallExpression { + function: ExpressionFunction::Sine, + expression: Box::new(Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(2.0))), + })), + })), + }) + ], + vec![ + Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: Box::new(Expression::Number(imag!(1f64))) + })), + operator: InfixOperator::Star, + right: Box::new(Expression::FunctionCall(FunctionCallExpression { + function: ExpressionFunction::Sine, + expression: Box::new(Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(2.0))), + })), + })), + }), + Expression::FunctionCall(FunctionCallExpression { + function: crate::expression::ExpressionFunction::Cosine, + expression: Box::new(Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(2.0))), + })), + }), + ], + ]), + + } + )] + #[case( + "Pauli Sum GateDefinition", + GateDefinition{ + name: "PauliSumGate".to_string(), + parameters: vec!["theta".to_string()], + specification: GateSpecification::PauliSum(PauliSum{arguments: vec!["p".to_string(), "q".to_string()], terms: vec![ + PauliTerm { + arguments: vec![(PauliGate::Z, "p".to_string()), (PauliGate::Z, "q".to_string())], + expression: Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: Box::new(Expression::Variable("theta".to_string())) + })), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(4.0))) + }), + }, + PauliTerm { + arguments: vec![(PauliGate::Y, "p".to_string())], + expression: Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(4.0))) + }), + }, + PauliTerm { + arguments: vec![(PauliGate::X, "q".to_string())], + expression: Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(4.0))) + }), + }, + ]}) + } + )] + fn test_display(#[case] description: &str, #[case] gate_def: GateDefinition) { + insta::with_settings!({ + snapshot_suffix => description, + }, { + assert_snapshot!(gate_def.to_quil_or_debug()) + }) + } +} + +/// The type of a [`GateDefinition`] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum GateType { + Matrix, + Permutation, + PauliSum, +} + +impl Quil for GateType { + fn write( + &self, + f: &mut impl std::fmt::Write, + _fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match self { + Self::Matrix => write!(f, "MATRIX"), + Self::Permutation => write!(f, "PERMUTATION"), + Self::PauliSum => write!(f, "PAULI-SUM"), + } + .map_err(Into::into) + } +} diff --git a/quil-rs/src/instruction/measurement.rs b/quil-rs/src/instruction/measurement.rs new file mode 100644 index 00000000..9d39e7e8 --- /dev/null +++ b/quil-rs/src/instruction/measurement.rs @@ -0,0 +1,32 @@ +use crate::quil::Quil; + +use super::{MemoryReference, Qubit}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Measurement { + pub qubit: Qubit, + pub target: Option, +} + +impl Measurement { + pub fn new(qubit: Qubit, target: Option) -> Self { + Self { qubit, target } + } +} + +impl Quil for Measurement { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> Result<(), crate::quil::ToQuilError> { + write!(writer, "MEASURE ")?; + self.qubit.write(writer, fall_back_to_debug)?; + if let Some(target) = &self.target { + write!(writer, " ")?; + target.write(writer, fall_back_to_debug)?; + } + + Ok(()) + } +} diff --git a/quil-rs/src/instruction/mod.rs b/quil-rs/src/instruction/mod.rs new file mode 100644 index 00000000..851b8c6e --- /dev/null +++ b/quil-rs/src/instruction/mod.rs @@ -0,0 +1,1042 @@ +// Copyright 2021 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. + +use std::collections::HashSet; +use std::fmt; + +use crate::expression::Expression; +#[cfg(test)] +use crate::parser::lex; +use crate::program::frame::{FrameMatchCondition, FrameMatchConditions}; +use crate::program::{MatchedFrames, MemoryAccesses}; +use crate::quil::{write_join_quil, Quil, ToQuilResult}; +use crate::Program; + +#[cfg(test)] +use nom_locate::LocatedSpan; + +mod calibration; +mod circuit; +mod classical; +mod control_flow; +mod declaration; +mod frame; +mod gate; +mod measurement; +mod pragma; +mod qubit; +mod reset; +mod timing; +mod waveform; + +pub use self::calibration::{Calibration, CalibrationSignature, MeasureCalibrationDefinition}; +pub use self::circuit::CircuitDefinition; +pub use self::classical::{ + Arithmetic, ArithmeticOperand, ArithmeticOperator, BinaryLogic, BinaryOperand, BinaryOperands, + BinaryOperator, Comparison, ComparisonOperand, ComparisonOperator, Convert, Exchange, Move, + UnaryLogic, UnaryOperator, +}; +pub use self::control_flow::{Jump, JumpUnless, JumpWhen, Label, Target, TargetPlaceholder}; +pub use self::declaration::{ + Declaration, Load, MemoryReference, Offset, ScalarType, Sharing, Store, Vector, +}; +pub use self::frame::{ + AttributeValue, Capture, FrameAttributes, FrameDefinition, FrameIdentifier, Pulse, RawCapture, + SetFrequency, SetPhase, SetScale, ShiftFrequency, ShiftPhase, SwapPhases, +}; +pub use self::gate::{ + Gate, GateDefinition, GateError, GateModifier, GateSpecification, GateType, Matrix, PauliGate, + PauliSum, PauliTerm, +}; +pub use self::measurement::Measurement; +pub use self::pragma::{Include, Pragma, PragmaArgument}; +pub use self::qubit::{Qubit, QubitPlaceholder}; +pub use self::reset::Reset; +pub use self::timing::{Delay, Fence}; +pub use self::waveform::{Waveform, WaveformDefinition, WaveformInvocation, WaveformParameters}; + +#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)] +pub enum ValidationError { + #[error(transparent)] + GateError(#[from] GateError), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Instruction { + Arithmetic(Arithmetic), + BinaryLogic(BinaryLogic), + CalibrationDefinition(Calibration), + Capture(Capture), + CircuitDefinition(CircuitDefinition), + Convert(Convert), + Comparison(Comparison), + Declaration(Declaration), + Delay(Delay), + Exchange(Exchange), + Fence(Fence), + FrameDefinition(FrameDefinition), + Gate(Gate), + GateDefinition(GateDefinition), + Halt, + Include(Include), + Jump(Jump), + JumpUnless(JumpUnless), + JumpWhen(JumpWhen), + Label(Label), + Load(Load), + MeasureCalibrationDefinition(MeasureCalibrationDefinition), + Measurement(Measurement), + Move(Move), + Nop, + Pragma(Pragma), + Pulse(Pulse), + RawCapture(RawCapture), + Reset(Reset), + SetFrequency(SetFrequency), + SetPhase(SetPhase), + SetScale(SetScale), + ShiftFrequency(ShiftFrequency), + ShiftPhase(ShiftPhase), + Store(Store), + SwapPhases(SwapPhases), + UnaryLogic(UnaryLogic), + WaveformDefinition(WaveformDefinition), + Wait, +} + +#[derive(Clone, Debug)] +pub enum InstructionRole { + ClassicalCompute, + ControlFlow, + ProgramComposition, + RFControl, +} + +impl From<&Instruction> for InstructionRole { + fn from(instruction: &Instruction) -> Self { + match instruction { + Instruction::CalibrationDefinition(_) + | Instruction::CircuitDefinition(_) + | Instruction::Declaration(_) + | Instruction::FrameDefinition(_) + | Instruction::Gate(_) + | Instruction::GateDefinition(_) + | Instruction::Include(_) + | Instruction::Label(_) + | Instruction::MeasureCalibrationDefinition(_) + | Instruction::Measurement(_) + | Instruction::WaveformDefinition(_) => InstructionRole::ProgramComposition, + Instruction::Reset(_) + | Instruction::Capture(_) + | Instruction::Delay(_) + | Instruction::Fence(_) + | Instruction::Pulse(_) + | Instruction::RawCapture(_) + | Instruction::SetFrequency(_) + | Instruction::SetPhase(_) + | Instruction::SetScale(_) + | Instruction::ShiftFrequency(_) + | Instruction::ShiftPhase(_) + | Instruction::SwapPhases(_) => InstructionRole::RFControl, + Instruction::Arithmetic(_) + | Instruction::Comparison(_) + | Instruction::Convert(_) + | Instruction::BinaryLogic(_) + | Instruction::UnaryLogic(_) + | Instruction::Move(_) + | Instruction::Exchange(_) + | Instruction::Load(_) + | Instruction::Nop + | Instruction::Pragma(_) + | Instruction::Store(_) => InstructionRole::ClassicalCompute, + Instruction::Halt + | Instruction::Jump(_) + | Instruction::JumpWhen(_) + | Instruction::JumpUnless(_) + | Instruction::Wait => InstructionRole::ControlFlow, + } + } +} + +pub fn write_instruction_block<'i, I, Q>( + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + values: I, +) -> crate::quil::ToQuilResult<()> +where + I: IntoIterator, + Q: Quil + 'i, +{ + write_join_quil(f, fall_back_to_debug, values, "\n", "\t") +} + +pub(crate) fn write_join( + f: &mut impl std::fmt::Write, + values: &[impl std::fmt::Display], + separator: &str, + prefix: &str, +) -> std::fmt::Result { + let mut iter = values.iter(); + if let Some(first) = iter.next() { + write!(f, "{prefix}{first}")?; + + for value in iter { + write!(f, "{separator}{prefix}{value}")?; + } + } + Ok(()) +} + +pub fn format_integer_vector(values: &[u64]) -> String { + values + .iter() + .map(|q| format!("{q}")) + .collect::>() + .join(" ") +} + +/// Write a list of qubits, with each prefixed by a space (including the first) +fn write_qubits( + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + qubits: &[Qubit], +) -> crate::quil::ToQuilResult<()> { + for qubit in qubits { + write!(f, " ")?; + qubit.write(f, fall_back_to_debug)?; + } + Ok(()) +} + +/// Write qubits as a Quil parameter list, where all are prefixed with ` `. +fn write_qubit_parameters( + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + qubits: &[Qubit], +) -> ToQuilResult<()> { + for qubit in qubits.iter() { + write!(f, " ")?; + qubit.write(f, fall_back_to_debug)?; + } + Ok(()) +} + +fn write_expression_parameter_string( + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + parameters: &[Expression], +) -> crate::quil::ToQuilResult<()> { + if parameters.is_empty() { + return Ok(()); + } + + write!(f, "(")?; + write_join_quil(f, fall_back_to_debug, parameters, ", ", "")?; + write!(f, ")")?; + Ok(()) +} + +fn write_parameter_string(f: &mut impl std::fmt::Write, parameters: &[String]) -> fmt::Result { + if parameters.is_empty() { + return Ok(()); + } + + write!(f, "(")?; + write_join(f, parameters, ", ", "%")?; + write!(f, ")") +} + +impl Quil for Instruction { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> Result<(), crate::quil::ToQuilError> { + match self { + Instruction::Arithmetic(arithmetic) => arithmetic.write(f, fall_back_to_debug), + Instruction::CalibrationDefinition(calibration) => { + calibration.write(f, fall_back_to_debug) + } + Instruction::Capture(capture) => capture.write(f, fall_back_to_debug), + Instruction::CircuitDefinition(circuit) => circuit.write(f, fall_back_to_debug), + Instruction::Convert(convert) => convert.write(f, fall_back_to_debug), + Instruction::Declaration(declaration) => declaration.write(f, fall_back_to_debug), + Instruction::Delay(delay) => delay.write(f, fall_back_to_debug), + Instruction::Fence(fence) => fence.write(f, fall_back_to_debug), + Instruction::FrameDefinition(frame_definition) => { + frame_definition.write(f, fall_back_to_debug) + } + Instruction::Gate(gate) => gate.write(f, fall_back_to_debug), + Instruction::GateDefinition(gate_definition) => { + gate_definition.write(f, fall_back_to_debug) + } + Instruction::Include(include) => include.write(f, fall_back_to_debug), + Instruction::MeasureCalibrationDefinition(measure_calibration) => { + measure_calibration.write(f, fall_back_to_debug) + } + Instruction::Measurement(measurement) => measurement.write(f, fall_back_to_debug), + Instruction::Move(r#move) => r#move.write(f, fall_back_to_debug), + Instruction::Exchange(exchange) => exchange.write(f, fall_back_to_debug), + Instruction::Load(load) => load.write(f, fall_back_to_debug), + Instruction::Store(store) => store.write(f, fall_back_to_debug), + Instruction::Pulse(pulse) => pulse.write(f, fall_back_to_debug), + Instruction::Pragma(pragma) => pragma.write(f, fall_back_to_debug), + Instruction::RawCapture(raw_capture) => raw_capture.write(f, fall_back_to_debug), + Instruction::Reset(reset) => reset.write(f, fall_back_to_debug), + Instruction::SetFrequency(set_frequency) => set_frequency.write(f, fall_back_to_debug), + Instruction::SetPhase(set_phase) => set_phase.write(f, fall_back_to_debug), + Instruction::SetScale(set_scale) => set_scale.write(f, fall_back_to_debug), + Instruction::ShiftFrequency(shift_frequency) => { + shift_frequency.write(f, fall_back_to_debug) + } + Instruction::ShiftPhase(shift_phase) => shift_phase.write(f, fall_back_to_debug), + Instruction::SwapPhases(swap_phases) => swap_phases.write(f, fall_back_to_debug), + Instruction::WaveformDefinition(waveform_definition) => { + waveform_definition.write(f, fall_back_to_debug) + } + Instruction::Halt => write!(f, "HALT").map_err(Into::into), + Instruction::Nop => write!(f, "NOP").map_err(Into::into), + Instruction::Wait => write!(f, "WAIT").map_err(Into::into), + Instruction::Jump(jump) => jump.write(f, fall_back_to_debug), + Instruction::JumpUnless(jump) => jump.write(f, fall_back_to_debug), + Instruction::JumpWhen(jump) => jump.write(f, fall_back_to_debug), + Instruction::Label(label) => label.write(f, fall_back_to_debug), + Instruction::Comparison(comparison) => comparison.write(f, fall_back_to_debug), + Instruction::BinaryLogic(binary_logic) => binary_logic.write(f, fall_back_to_debug), + Instruction::UnaryLogic(unary_logic) => unary_logic.write(f, fall_back_to_debug), + } + } +} + +pub(crate) struct QuotedString(pub(crate) S); + +impl fmt::Display for QuotedString +where + S: AsRef, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "\"")?; + for c in self.0.as_ref().chars() { + match c { + '"' => write!(f, "\\\"")?, + '\\' => write!(f, "\\\\")?, + c => write!(f, "{}", c)?, + } + } + write!(f, "\"") + } +} + +#[cfg(test)] +mod test_instruction_display { + use crate::{instruction::PragmaArgument, quil::Quil}; + + use super::{Instruction, Pragma}; + + #[test] + fn pragma() { + assert_eq!( + Instruction::Pragma(Pragma { + name: String::from("INITIAL_REWIRING"), + arguments: vec![], + data: Some(String::from("PARTIAL")), + }) + .to_quil() + .unwrap(), + "PRAGMA INITIAL_REWIRING \"PARTIAL\"" + ); + assert_eq!( + Instruction::Pragma(Pragma { + name: String::from("LOAD-MEMORY"), + arguments: vec![PragmaArgument::Identifier("q0".to_string())], + data: Some(String::from("addr")), + }) + .to_quil() + .unwrap(), + "PRAGMA LOAD-MEMORY q0 \"addr\"" + ); + assert_eq!( + Instruction::Pragma(Pragma { + name: String::from("PRESERVE_BLOCK"), + arguments: vec![], + data: None, + }) + .to_quil() + .unwrap(), + "PRAGMA PRESERVE_BLOCK" + ); + } +} + +impl Instruction { + /// Apply the provided closure to this instruction, mutating any `Expression`s within. + /// Does not affect instructions without `Expression`s within. + /// Does not traverse or mutate instructions nested within blocks (such as + /// within `DEFCAL`). + /// + /// # Example + /// + /// ```rust + /// use std::mem::replace; + /// use std::str::FromStr; + /// use quil_rs::{expression::Expression, Program, quil::Quil}; + /// + /// + /// let program = Program::from_str("SHIFT-PHASE 0 \"rf\" 2*2").unwrap(); + /// let mut instructions = program.to_instructions(); + /// instructions.iter_mut().for_each(|inst| inst.apply_to_expressions(Expression::simplify)); + /// + /// assert_eq!(instructions[0].to_quil().unwrap(), String::from("SHIFT-PHASE 0 \"rf\" 4")) + /// + /// ``` + pub fn apply_to_expressions(&mut self, mut closure: impl FnMut(&mut Expression)) { + match self { + Instruction::CalibrationDefinition(Calibration { parameters, .. }) + | Instruction::Gate(Gate { parameters, .. }) => { + parameters.iter_mut().for_each(closure); + } + Instruction::Capture(Capture { waveform, .. }) + | Instruction::Pulse(Pulse { waveform, .. }) => { + waveform.parameters.values_mut().for_each(closure); + } + Instruction::Delay(Delay { duration, .. }) + | Instruction::RawCapture(RawCapture { duration, .. }) => { + closure(duration); + } + Instruction::FrameDefinition(FrameDefinition { attributes, .. }) => { + for value in attributes.values_mut() { + if let AttributeValue::Expression(expression) = value { + closure(expression); + } + } + } + Instruction::SetFrequency(SetFrequency { + frequency: expression, + .. + }) + | Instruction::SetPhase(SetPhase { + phase: expression, .. + }) + | Instruction::SetScale(SetScale { + scale: expression, .. + }) + | Instruction::ShiftFrequency(ShiftFrequency { + frequency: expression, + .. + }) + | Instruction::ShiftPhase(ShiftPhase { + phase: expression, .. + }) => { + closure(expression); + } + Instruction::WaveformDefinition(WaveformDefinition { definition, .. }) => { + definition.matrix.iter_mut().for_each(closure); + } + Instruction::GateDefinition(GateDefinition { + specification: GateSpecification::Matrix(matrix), + .. + }) => { + for row in matrix { + for cell in row { + closure(cell); + } + } + } + _ => {} + } + } + + pub(crate) fn get_frame_match_condition<'a>( + &'a self, + qubits_available: &'a HashSet, + ) -> Option> { + match self { + Instruction::Pulse(Pulse { + blocking, frame, .. + }) + | Instruction::Capture(Capture { + blocking, frame, .. + }) + | Instruction::RawCapture(RawCapture { + blocking, frame, .. + }) => Some(FrameMatchConditions { + blocked: blocking + .then(|| FrameMatchCondition::AnyOfQubits(frame.qubits.iter().collect())), + used: Some(FrameMatchCondition::Specific(frame)), + }), + Instruction::Delay(Delay { + frame_names, + qubits, + .. + }) => Some(FrameMatchConditions { + used: Some(if frame_names.is_empty() { + FrameMatchCondition::ExactQubits(qubits.iter().collect()) + } else { + FrameMatchCondition::And(vec![ + FrameMatchCondition::ExactQubits(qubits.iter().collect()), + FrameMatchCondition::AnyOfNames( + frame_names.iter().map(String::as_str).collect(), + ), + ]) + }), + blocked: None, + }), + Instruction::Fence(Fence { qubits }) => Some(FrameMatchConditions { + used: None, + blocked: Some(if qubits.is_empty() { + FrameMatchCondition::All + } else { + FrameMatchCondition::AnyOfQubits(qubits.iter().collect()) + }), + }), + Instruction::Reset(Reset { qubit }) => { + let qubits = match qubit { + Some(qubit) => { + let mut set = HashSet::new(); + set.insert(qubit); + set + } + None => qubits_available.iter().collect(), + }; + + Some(FrameMatchConditions { + used: Some(FrameMatchCondition::ExactQubits(qubits.clone())), + blocked: Some(FrameMatchCondition::AnyOfQubits(qubits)), + }) + } + Instruction::SetFrequency(SetFrequency { frame, .. }) + | Instruction::SetPhase(SetPhase { frame, .. }) + | Instruction::SetScale(SetScale { frame, .. }) + | Instruction::ShiftFrequency(ShiftFrequency { frame, .. }) + | Instruction::ShiftPhase(ShiftPhase { frame, .. }) => Some(FrameMatchConditions { + used: Some(FrameMatchCondition::Specific(frame)), + blocked: None, + }), + Instruction::SwapPhases(SwapPhases { frame_1, frame_2 }) => { + Some(FrameMatchConditions { + used: Some(FrameMatchCondition::Or(vec![ + FrameMatchCondition::Specific(frame_1), + FrameMatchCondition::Specific(frame_2), + ])), + blocked: None, + }) + } + Instruction::Arithmetic(_) + | Instruction::BinaryLogic(_) + | Instruction::CalibrationDefinition(_) + | Instruction::CircuitDefinition(_) + | Instruction::Comparison(_) + | Instruction::Convert(_) + | Instruction::Declaration(_) + | Instruction::Exchange(_) + | Instruction::FrameDefinition(_) + | Instruction::Gate(_) + | Instruction::GateDefinition(_) + | Instruction::Halt + | Instruction::Include(_) + | Instruction::Jump(_) + | Instruction::JumpUnless(_) + | Instruction::JumpWhen(_) + | Instruction::Label(_) + | Instruction::Load(_) + | Instruction::MeasureCalibrationDefinition(_) + | Instruction::Measurement(_) + | Instruction::Move(_) + | Instruction::Nop + | Instruction::Pragma(_) + | Instruction::Store(_) + | Instruction::UnaryLogic(_) + | Instruction::WaveformDefinition(_) + | Instruction::Wait => None, + } + } + + /// Return immutable references to the [`Qubit`]s contained within an instruction + #[allow(dead_code)] + pub fn get_qubits(&self) -> Vec<&Qubit> { + match self { + Instruction::Gate(gate) => gate.qubits.iter().collect(), + Instruction::Measurement(measurement) => vec![&measurement.qubit], + Instruction::Reset(reset) => match &reset.qubit { + Some(qubit) => vec![qubit], + None => vec![], + }, + Instruction::Delay(delay) => delay.qubits.iter().collect(), + Instruction::Fence(fence) => fence.qubits.iter().collect(), + Instruction::Capture(capture) => capture.frame.qubits.iter().collect(), + Instruction::Pulse(pulse) => pulse.frame.qubits.iter().collect(), + Instruction::RawCapture(raw_capture) => raw_capture.frame.qubits.iter().collect(), + _ => vec![], + } + } + + /// Return mutable references to the [`Qubit`]s contained within an instruction + pub fn get_qubits_mut(&mut self) -> Vec<&mut Qubit> { + match self { + Instruction::Gate(gate) => gate.qubits.iter_mut().collect(), + Instruction::CalibrationDefinition(calibration) => calibration + .qubits + .iter_mut() + .chain( + calibration + .instructions + .iter_mut() + .flat_map(|inst| inst.get_qubits_mut()), + ) + .collect(), + Instruction::MeasureCalibrationDefinition(measurement) => measurement + .qubit + .iter_mut() + .chain( + measurement + .instructions + .iter_mut() + .flat_map(|inst| inst.get_qubits_mut()), + ) + .collect(), + Instruction::Measurement(measurement) => vec![&mut measurement.qubit], + Instruction::Reset(reset) => match &mut reset.qubit { + Some(qubit) => vec![qubit], + None => vec![], + }, + Instruction::Delay(delay) => delay.qubits.iter_mut().collect(), + Instruction::Fence(fence) => fence.qubits.iter_mut().collect(), + Instruction::Capture(capture) => capture.frame.qubits.iter_mut().collect(), + Instruction::Pulse(pulse) => pulse.frame.qubits.iter_mut().collect(), + Instruction::RawCapture(raw_capture) => raw_capture.frame.qubits.iter_mut().collect(), + _ => vec![], + } + } + + /// Return the waveform _directly_ invoked by the instruction, if any. + /// + /// Note: this does not expand calibrations or other instructions which may + /// indirectly cause a waveform to be invoked. + pub(crate) fn get_waveform_invocation(&self) -> Option<&WaveformInvocation> { + match self { + Instruction::Capture(Capture { waveform, .. }) => Some(waveform), + Instruction::Pulse(Pulse { waveform, .. }) => Some(waveform), + _ => None, + } + } + + #[cfg(test)] + /// Parse a single instruction from an input string. Returns an error if the input fails to parse, + /// or if there is input left over after parsing. + pub(crate) fn parse(input: &str) -> Result { + use crate::parser::instruction::parse_instruction; + + let input = LocatedSpan::new(input); + let lexed = lex(input).map_err(|err| err.to_string())?; + let (_, instruction) = + nom::combinator::all_consuming(parse_instruction)(&lexed).map_err(|e| e.to_string())?; + Ok(instruction) + } + + /// Returns true if the instruction is a Quil-T instruction. + pub fn is_quil_t(&self) -> bool { + match self { + Instruction::Capture(_) + | Instruction::CalibrationDefinition(_) + | Instruction::Delay(_) + | Instruction::Fence(_) + | Instruction::FrameDefinition(_) + | Instruction::MeasureCalibrationDefinition(_) + | Instruction::Pulse(_) + | Instruction::RawCapture(_) + | Instruction::SetFrequency(_) + | Instruction::SetPhase(_) + | Instruction::SetScale(_) + | Instruction::ShiftFrequency(_) + | Instruction::ShiftPhase(_) + | Instruction::SwapPhases(_) + | Instruction::WaveformDefinition(_) => true, + Instruction::Arithmetic(_) + | Instruction::BinaryLogic(_) + | Instruction::CircuitDefinition(_) + | Instruction::Convert(_) + | Instruction::Comparison(_) + | Instruction::Declaration(_) + | Instruction::Exchange(_) + | Instruction::Gate(_) + | Instruction::GateDefinition(_) + | Instruction::Halt + | Instruction::Include(_) + | Instruction::Jump(_) + | Instruction::JumpUnless(_) + | Instruction::JumpWhen(_) + | Instruction::Label(_) + | Instruction::Load(_) + | Instruction::Measurement(_) + | Instruction::Move(_) + | Instruction::Nop + | Instruction::Pragma(_) + | Instruction::Reset(_) + | Instruction::Store(_) + | Instruction::Wait + | Instruction::UnaryLogic(_) => false, + } + } + + /// Per the Quil-T spec, whether this instruction's timing within the pulse + /// program must be precisely controlled so as to begin exactly on the end of + /// the latest preceding timed instruction + pub fn is_scheduled(&self) -> bool { + match self { + Instruction::Capture(_) + | Instruction::Delay(_) + | Instruction::Fence(_) + | Instruction::Pulse(_) + | Instruction::RawCapture(_) + | Instruction::SetFrequency(_) + | Instruction::SetPhase(_) + | Instruction::SetScale(_) + | Instruction::ShiftFrequency(_) + | Instruction::ShiftPhase(_) + | Instruction::SwapPhases(_) + | Instruction::Wait => true, + Instruction::Arithmetic(_) + | Instruction::BinaryLogic(_) + | Instruction::CalibrationDefinition(_) + | Instruction::CircuitDefinition(_) + | Instruction::Convert(_) + | Instruction::Comparison(_) + | Instruction::Declaration(_) + | Instruction::Exchange(_) + | Instruction::FrameDefinition(_) + | Instruction::Gate(_) + | Instruction::GateDefinition(_) + | Instruction::Halt + | Instruction::Include(_) + | Instruction::Jump(_) + | Instruction::JumpUnless(_) + | Instruction::JumpWhen(_) + | Instruction::Label(_) + | Instruction::Load(_) + | Instruction::MeasureCalibrationDefinition(_) + | Instruction::Measurement(_) + | Instruction::Move(_) + | Instruction::Nop + | Instruction::Pragma(_) + | Instruction::Reset(_) + | Instruction::Store(_) + | Instruction::UnaryLogic(_) + | Instruction::WaveformDefinition(_) => false, + } + } + + pub(crate) fn resolve_placeholders(&mut self, target_resolver: TR, qubit_resolver: QR) + where + TR: Fn(&TargetPlaceholder) -> Option, + QR: Fn(&QubitPlaceholder) -> Option, + { + match self { + Instruction::Label(label) => { + label.target.resolve_placeholder(target_resolver); + } + Instruction::Jump(jump) => { + jump.target.resolve_placeholder(target_resolver); + } + Instruction::JumpWhen(jump_when) => { + jump_when.target.resolve_placeholder(target_resolver); + } + Instruction::JumpUnless(jump_unless) => { + jump_unless.target.resolve_placeholder(target_resolver); + } + other => { + for qubit in other.get_qubits_mut() { + qubit.resolve_placeholder(&qubit_resolver); + } + } + } + } +} + +/// Trait signature for a function or closure that returns an optional override for whether +/// an instruction should be scheduled. +pub trait GetIsScheduledFnMut: FnMut(&Instruction) -> Option {} +impl GetIsScheduledFnMut for F where F: FnMut(&Instruction) -> Option {} + +/// Trait signature for a function or closure that returns an optional override for an +/// instruction's [`InstructionRole`]. +pub trait GetRoleForInstructionFnMut: FnMut(&Instruction) -> Option {} +impl GetRoleForInstructionFnMut for F where F: FnMut(&Instruction) -> Option {} + +/// Trait signature for a function or closure that returns an optional override for an +/// instruction's [`MatchedFrames`]. +pub trait GetMatchingFramesFnMut: + for<'a> FnMut(&'a Instruction, &'a Program) -> Option>> +{ +} +impl GetMatchingFramesFnMut for F where + F: for<'a> FnMut(&'a Instruction, &'a Program) -> Option>> +{ +} + +/// Trait signature for a function or closure that returns an optional override for an +/// instruction's [`MemoryAccesses`]. +pub trait GetMemoryAccessesFnMut: FnMut(&Instruction) -> Option {} +impl GetMemoryAccessesFnMut for F where F: FnMut(&Instruction) -> Option {} + +/// A struct that allows setting optional overrides for key [`Instruction`] methods. +/// +/// A common use case for this is to support custom `PRAGMA` instructions, which are treated as +/// classical style no-ops by default. +#[derive(Default)] +pub struct InstructionHandler { + get_is_scheduled: Option>, + get_role_for_instruction: Option>, + get_matching_frames: Option>, + get_memory_accesses: Option>, +} + +impl InstructionHandler { + /// Set an override function for whether an instruction is scheduled. + /// + /// If the provided function returns `None`, a default will be used. + /// See also [`InstructionHandler::is_scheduled`]. + pub fn set_is_scheduled(mut self, f: F) -> Self + where + F: GetIsScheduledFnMut + 'static, + { + self.get_is_scheduled = Some(Box::new(f)); + self + } + + /// Set an override function for determining an instruction's [`InstructionRole`]. + /// + /// If the provided function returns `None`, a default will be used. + /// See also [`InstructionHandler::role_for_instruction`]. + pub fn set_role_for_instruction(mut self, f: F) -> Self + where + F: GetRoleForInstructionFnMut + 'static, + { + self.get_role_for_instruction = Some(Box::new(f)); + self + } + + /// Set an override function for determining an instruction's [`MatchedFrames`]. + /// + /// If the provided function returns `None`, a default will be used. + /// See also [`InstructionHandler::get_matching_frames`]. + pub fn set_matching_frames(mut self, f: F) -> Self + where + F: GetMatchingFramesFnMut + 'static, + { + self.get_matching_frames = Some(Box::new(f)); + self + } + + /// Set an override function for determining an instruction's [`MemoryAccesses`]. + /// + /// If the provided function returns `None`, a default will be used. + /// See also [`InstructionHandler::get_memory_accesses`]. + pub fn set_memory_accesses(mut self, f: F) -> Self + where + F: GetMemoryAccessesFnMut + 'static, + { + self.get_memory_accesses = Some(Box::new(f)); + self + } + + /// Determine whether the given instruction is scheduled. + /// + /// This uses the return value of the override function, if set and returns `Some`. If not set + /// or the function returns `None`, defaults to the return value of + /// [`Instruction::is_scheduled`]. + pub fn is_scheduled(&mut self, instruction: &Instruction) -> bool { + self.get_is_scheduled + .as_mut() + .and_then(|f| f(instruction)) + .unwrap_or_else(|| instruction.is_scheduled()) + } + + /// Determine the [`InstructionRole`] for the given instruction. + /// + /// This uses the return value of the override function, if set and returns `Some`. If not set + /// or the function returns `None`, defaults to the return value of + /// [`InstructionRole::from`]. + pub fn role_for_instruction(&mut self, instruction: &Instruction) -> InstructionRole { + self.get_role_for_instruction + .as_mut() + .and_then(|f| f(instruction)) + .unwrap_or_else(|| InstructionRole::from(instruction)) + } + + /// Determine the [`MatchedFrames`] for the given instruction. + /// + /// This uses the return value of the override function, if set and returns `Some`. If not set + /// or the function returns `None`, defaults to the return value of + /// [`Program::get_frames_for_instruction`]. + pub fn matching_frames<'a>( + &mut self, + instruction: &'a Instruction, + program: &'a Program, + ) -> Option> { + self.get_matching_frames + .as_mut() + .and_then(|f| f(instruction, program)) + .unwrap_or_else(|| program.get_frames_for_instruction(instruction)) + } + + /// Determine the [`MemoryAccesses`] for the given instruction. + /// + /// This uses the return value of the override function, if set and returns `Some`. If not set + /// or the function returns `None`, defaults to the return value of + /// [`Instruction::get_memory_accesses`]. + pub fn memory_accesses(&mut self, instruction: &Instruction) -> MemoryAccesses { + self.get_memory_accesses + .as_mut() + .and_then(|f| f(instruction)) + .unwrap_or_else(|| instruction.get_memory_accesses()) + } +} + +#[cfg(test)] +mod tests { + use rstest::*; + use std::str::FromStr; + + use crate::{expression::Expression, Program}; + + use super::MemoryReference; + + #[test] + fn apply_to_expressions() { + let mut program = Program::from_str( + "DECLARE ro BIT +SET-PHASE 0 \"rf\" pi/2 +RX(2) 0", + ) + .unwrap(); + let closure = |expr: &mut Expression| *expr = Expression::Variable(String::from("a")); + program.for_each_body_instruction(|instruction| { + instruction.apply_to_expressions(closure); + }); + + let expected_program = Program::from_str( + "DECLARE ro BIT +SET-PHASE 0 \"rf\" %a +RX(%a) 0", + ) + .unwrap(); + + assert_eq!(expected_program, program); + } + + #[rstest(input, expected, + case("_", MemoryReference { name: "_".to_string(), index: 0 }), + case("a", MemoryReference { name: "a".to_string(), index: 0 }), + case("a---b", MemoryReference { name: "a---b".to_string(), index: 0 }), + case("_a_b_", MemoryReference { name: "_a_b_".to_string(), index: 0 }), + case("a-2_b-2", MemoryReference { name: "a-2_b-2".to_string(), index: 0 }), + case("_[0]", MemoryReference { name: "_".to_string(), index: 0 }), + case("a[1]", MemoryReference { name: "a".to_string(), index: 1 }), + case("a---b[2]", MemoryReference { name: "a---b".to_string(), index: 2 }), + case("_a_b_[3]", MemoryReference { name: "_a_b_".to_string(), index: 3 }), + case("a-2_b-2[4]", MemoryReference { name: "a-2_b-2".to_string(), index: 4 }), + )] + fn it_parses_memory_reference_from_str(input: &str, expected: MemoryReference) { + assert_eq!(MemoryReference::from_str(input), Ok(expected)); + } + + #[rstest( + input, + case(""), + case("[0]"), + case("a[-1]"), + case("2a[2]"), + case("-a"), + case("NOT[3]"), + case("a a"), + case("a[5] a[5]"), + case("DECLARE a[6]") + )] + fn it_fails_to_parse_memory_reference_from_str(input: &str) { + assert!(MemoryReference::from_str(input).is_err()); + } + + mod placeholders { + use std::collections::HashMap; + + use crate::instruction::{Label, Qubit, QubitPlaceholder, Target, TargetPlaceholder}; + + #[allow(clippy::redundant_clone)] + #[test] + fn target() { + let placeholder_1 = TargetPlaceholder::new(String::from("label")); + let placeholder_2 = TargetPlaceholder::new(String::from("label")); + let placeholder_3 = TargetPlaceholder::new(String::from("other")); + + assert_eq!(placeholder_1, placeholder_1); + assert_eq!(placeholder_1, placeholder_1.clone()); + assert_eq!(placeholder_1.clone(), placeholder_1.clone()); + assert_ne!(placeholder_1, placeholder_2); + assert_ne!(placeholder_2, placeholder_3); + assert_ne!(placeholder_1, placeholder_3); + } + + #[test] + fn target_resolution() { + let placeholder_1 = TargetPlaceholder::new(String::from("label")); + let placeholder_2 = TargetPlaceholder::new(String::from("label")); + + let resolver = HashMap::from([(placeholder_1.clone(), String::from("label_1"))]); + + let mut label_1 = Label { + target: Target::Placeholder(placeholder_1), + }; + label_1 + .target + .resolve_placeholder(|k| resolver.get(k).cloned()); + assert_eq!(label_1.target, Target::Fixed(String::from("label_1"))); + + let mut label_2 = Label { + target: Target::Placeholder(placeholder_2.clone()), + }; + label_2 + .target + .resolve_placeholder(|k| resolver.get(k).cloned()); + assert_eq!(label_2.target, Target::Placeholder(placeholder_2)); + } + + #[allow(clippy::redundant_clone)] + #[test] + fn qubit() { + let placeholder_1 = QubitPlaceholder::default(); + let placeholder_2 = QubitPlaceholder::default(); + + assert_eq!(placeholder_1, placeholder_1); + assert_eq!(placeholder_1, placeholder_1.clone()); + assert_eq!(placeholder_1.clone(), placeholder_1.clone()); + assert_ne!(placeholder_1, placeholder_2); + } + + #[test] + fn qubit_resolution() { + let placeholder_1 = QubitPlaceholder::default(); + let placeholder_2 = QubitPlaceholder::default(); + + let resolver = HashMap::from([(placeholder_1.clone(), 1)]); + + let mut qubit_1 = Qubit::Placeholder(placeholder_1); + qubit_1.resolve_placeholder(|k| resolver.get(k).copied()); + assert_eq!(qubit_1, Qubit::Fixed(1)); + + let mut qubit_2 = Qubit::Placeholder(placeholder_2.clone()); + qubit_2.resolve_placeholder(|k| resolver.get(k).copied()); + assert_eq!(qubit_2, Qubit::Placeholder(placeholder_2)); + } + } +} diff --git a/quil-rs/src/instruction/pragma.rs b/quil-rs/src/instruction/pragma.rs new file mode 100644 index 00000000..d24ffbd0 --- /dev/null +++ b/quil-rs/src/instruction/pragma.rs @@ -0,0 +1,79 @@ +use crate::quil::Quil; + +use super::QuotedString; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Pragma { + pub name: String, + pub arguments: Vec, + pub data: Option, +} + +impl Pragma { + pub fn new(name: String, arguments: Vec, data: Option) -> Self { + Self { + name, + arguments, + data, + } + } +} + +impl Quil for Pragma { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "PRAGMA {}", self.name)?; + for arg in &self.arguments { + write!(f, " ")?; + arg.write(f, fall_back_to_debug)?; + } + if let Some(data) = &self.data { + write!(f, " {}", QuotedString(data))?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum PragmaArgument { + Identifier(String), + Integer(u64), +} + +impl Quil for PragmaArgument { + fn write( + &self, + f: &mut impl std::fmt::Write, + _fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match self { + PragmaArgument::Identifier(i) => write!(f, "{i}"), + PragmaArgument::Integer(i) => write!(f, "{i}"), + } + .map_err(Into::into) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Include { + pub filename: String, +} + +impl Quil for Include { + fn write( + &self, + f: &mut impl std::fmt::Write, + _fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, r#"INCLUDE {}"#, QuotedString(&self.filename)).map_err(Into::into) + } +} + +impl Include { + pub fn new(filename: String) -> Self { + Self { filename } + } +} diff --git a/quil-rs/src/instruction/qubit.rs b/quil-rs/src/instruction/qubit.rs new file mode 100644 index 00000000..8239ec77 --- /dev/null +++ b/quil-rs/src/instruction/qubit.rs @@ -0,0 +1,130 @@ +use std::sync::Arc; + +use crate::quil::{Quil, ToQuilError}; + +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, strum::EnumTryAs)] +pub enum Qubit { + Fixed(u64), + Placeholder(QubitPlaceholder), + Variable(String), +} + +impl Qubit { + pub(crate) fn resolve_placeholder(&mut self, resolver: R) + where + R: Fn(&QubitPlaceholder) -> Option, + { + if let Qubit::Placeholder(placeholder) = self { + if let Some(resolved) = resolver(placeholder) { + *self = Qubit::Fixed(resolved); + } + } + } +} + +impl Quil for Qubit { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> std::result::Result<(), crate::quil::ToQuilError> { + use Qubit::*; + match self { + Fixed(value) => write!(writer, "{value}").map_err(Into::into), + Placeholder(_) => { + if fall_back_to_debug { + write!(writer, "{:?}", self).map_err(Into::into) + } else { + Err(ToQuilError::UnresolvedQubitPlaceholder) + } + } + Variable(value) => write!(writer, "{value}").map_err(Into::into), + } + } +} + +type QubitPlaceholderInner = Arc<()>; + +/// An opaque placeholder for a qubit whose index may be assigned +/// at a later time. +#[derive(Clone, Eq)] +pub struct QubitPlaceholder(QubitPlaceholderInner); + +impl QubitPlaceholder { + fn address(&self) -> usize { + &*self.0 as *const _ as usize + } +} + +impl Default for QubitPlaceholder { + fn default() -> Self { + Self(Arc::new(())) + } +} + +impl std::fmt::Debug for QubitPlaceholder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "QubitPlaceholder({:#X})", self.address()) + } +} + +impl std::hash::Hash for QubitPlaceholder { + fn hash(&self, state: &mut H) { + self.address().hash(state); + } +} + +impl PartialEq for QubitPlaceholder { + #[allow(clippy::ptr_eq)] + fn eq(&self, other: &Self) -> bool { + self.address().eq(&other.address()) + } +} + +impl PartialOrd for QubitPlaceholder { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for QubitPlaceholder { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.address().cmp(&other.address()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use regex::Regex; + use rstest::rstest; + + #[test] + fn resolve_placeholder() { + let mut qubit = Qubit::Placeholder(QubitPlaceholder::default()); + qubit.resolve_placeholder(|_| Some(0)); + assert_eq!(qubit, Qubit::Fixed(0)); + } + + #[rstest] + #[case(Qubit::Fixed(0), Ok("0"), "0")] + #[case( + Qubit::Variable("q".to_string()), + Ok("q"), + "q" + )] + #[case( + Qubit::Placeholder(QubitPlaceholder::default()), + Err(ToQuilError::UnresolvedQubitPlaceholder), + r"Placeholder\(QubitPlaceholder\(0x[0-9,A-Z]+\)\)" + )] + fn quil_format( + #[case] input: Qubit, + #[case] expected_quil: crate::quil::ToQuilResult<&str>, + #[case] expected_debug: &str, + ) { + assert_eq!(input.to_quil(), expected_quil.map(|s| s.to_string())); + let re = Regex::new(expected_debug).unwrap(); + assert!(re.is_match(&input.to_quil_or_debug())); + } +} diff --git a/quil-rs/src/instruction/reset.rs b/quil-rs/src/instruction/reset.rs new file mode 100644 index 00000000..229f1bff --- /dev/null +++ b/quil-rs/src/instruction/reset.rs @@ -0,0 +1,30 @@ +use crate::quil::Quil; + +use super::Qubit; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Reset { + pub qubit: Option, +} + +impl Reset { + pub fn new(qubit: Option) -> Self { + Self { qubit } + } +} + +impl Quil for Reset { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + match &self.qubit { + Some(qubit) => { + write!(writer, "RESET ")?; + qubit.write(writer, fall_back_to_debug) + } + None => write!(writer, "RESET").map_err(Into::into), + } + } +} diff --git a/quil-rs/src/instruction/snapshots/quil_rs__instruction__calibration__test_measure_calibration_definition__display@With Fixed Qubit.snap b/quil-rs/src/instruction/snapshots/quil_rs__instruction__calibration__test_measure_calibration_definition__display@With Fixed Qubit.snap new file mode 100644 index 00000000..7b47807f --- /dev/null +++ b/quil-rs/src/instruction/snapshots/quil_rs__instruction__calibration__test_measure_calibration_definition__display@With Fixed Qubit.snap @@ -0,0 +1,7 @@ +--- +source: quil-rs/src/instruction/calibration.rs +expression: measure_cal_def.to_string() +--- +DEFCAL MEASURE 0 theta: + X(%theta) 0 + diff --git a/quil-rs/src/instruction/snapshots/quil_rs__instruction__calibration__test_measure_calibration_definition__display@With Variable Qubit.snap b/quil-rs/src/instruction/snapshots/quil_rs__instruction__calibration__test_measure_calibration_definition__display@With Variable Qubit.snap new file mode 100644 index 00000000..225b5df1 --- /dev/null +++ b/quil-rs/src/instruction/snapshots/quil_rs__instruction__calibration__test_measure_calibration_definition__display@With Variable Qubit.snap @@ -0,0 +1,7 @@ +--- +source: quil-rs/src/instruction/calibration.rs +expression: measure_cal_def.to_string() +--- +DEFCAL MEASURE q theta: + X(%theta) q + diff --git a/quil-rs/src/instruction/snapshots/quil_rs__instruction__circuit__test_circuit_definition__display@CircuitDefinition No Params.snap b/quil-rs/src/instruction/snapshots/quil_rs__instruction__circuit__test_circuit_definition__display@CircuitDefinition No Params.snap new file mode 100644 index 00000000..8fba7c5d --- /dev/null +++ b/quil-rs/src/instruction/snapshots/quil_rs__instruction__circuit__test_circuit_definition__display@CircuitDefinition No Params.snap @@ -0,0 +1,8 @@ +--- +source: quil-rs/src/instruction/circuit.rs +expression: circuit_def.to_string() +--- +DEFCIRCUIT BELL a b: + H a + CNOT a b + diff --git a/quil-rs/src/instruction/snapshots/quil_rs__instruction__circuit__test_circuit_definition__display@CircuitDefinition With Params.snap b/quil-rs/src/instruction/snapshots/quil_rs__instruction__circuit__test_circuit_definition__display@CircuitDefinition With Params.snap new file mode 100644 index 00000000..8d61c97d --- /dev/null +++ b/quil-rs/src/instruction/snapshots/quil_rs__instruction__circuit__test_circuit_definition__display@CircuitDefinition With Params.snap @@ -0,0 +1,11 @@ +--- +source: quil-rs/src/instruction/circuit.rs +expression: circuit_def.to_string() +--- +DEFCIRCUIT BELL(%a, %b) a b: + RZ(%a) a + RZ(%b) a + RX(%a) a + RZ(%a) a + CNOT a b + diff --git a/quil-rs/src/instruction/snapshots/quil_rs__instruction__circuit__test_circuit_definition__display@CircuitDefinition With Single Param.snap b/quil-rs/src/instruction/snapshots/quil_rs__instruction__circuit__test_circuit_definition__display@CircuitDefinition With Single Param.snap new file mode 100644 index 00000000..74a75f4f --- /dev/null +++ b/quil-rs/src/instruction/snapshots/quil_rs__instruction__circuit__test_circuit_definition__display@CircuitDefinition With Single Param.snap @@ -0,0 +1,10 @@ +--- +source: quil-rs/src/instruction/circuit.rs +expression: circuit_def.to_quil_or_debug() +--- +DEFCIRCUIT BELL(%a) a b: + RZ(%a) a + RX(%a) a + RZ(%a) a + CNOT a b + diff --git a/quil-rs/src/instruction/snapshots/quil_rs__instruction__declaration__test_declaration__display@Basic Declaration.snap b/quil-rs/src/instruction/snapshots/quil_rs__instruction__declaration__test_declaration__display@Basic Declaration.snap new file mode 100644 index 00000000..bc0cba7f --- /dev/null +++ b/quil-rs/src/instruction/snapshots/quil_rs__instruction__declaration__test_declaration__display@Basic Declaration.snap @@ -0,0 +1,5 @@ +--- +source: quil-rs/src/instruction/declaration.rs +expression: declaration.to_string() +--- +DECLARE ro BIT[1] diff --git a/quil-rs/src/instruction/snapshots/quil_rs__instruction__declaration__test_declaration__display@Shared Declaration with Offsets.snap b/quil-rs/src/instruction/snapshots/quil_rs__instruction__declaration__test_declaration__display@Shared Declaration with Offsets.snap new file mode 100644 index 00000000..949915ce --- /dev/null +++ b/quil-rs/src/instruction/snapshots/quil_rs__instruction__declaration__test_declaration__display@Shared Declaration with Offsets.snap @@ -0,0 +1,5 @@ +--- +source: quil-rs/src/instruction/declaration.rs +expression: declaration.to_string() +--- +DECLARE ro REAL[3] SHARING bar OFFSET 4 BIT 5 BIT diff --git a/quil-rs/src/instruction/snapshots/quil_rs__instruction__declaration__test_declaration__display@Shared Declaration.snap b/quil-rs/src/instruction/snapshots/quil_rs__instruction__declaration__test_declaration__display@Shared Declaration.snap new file mode 100644 index 00000000..08b79e1a --- /dev/null +++ b/quil-rs/src/instruction/snapshots/quil_rs__instruction__declaration__test_declaration__display@Shared Declaration.snap @@ -0,0 +1,5 @@ +--- +source: quil-rs/src/instruction/declaration.rs +expression: declaration.to_string() +--- +DECLARE ro INTEGER[2] SHARING foo diff --git a/quil-rs/src/instruction/snapshots/quil_rs__instruction__gate__test_gate_definition__display@Parameterized GateDefinition.snap b/quil-rs/src/instruction/snapshots/quil_rs__instruction__gate__test_gate_definition__display@Parameterized GateDefinition.snap new file mode 100644 index 00000000..a3fade6c --- /dev/null +++ b/quil-rs/src/instruction/snapshots/quil_rs__instruction__gate__test_gate_definition__display@Parameterized GateDefinition.snap @@ -0,0 +1,8 @@ +--- +source: quil-rs/src/instruction/gate.rs +expression: gate_def.to_string() +--- +DEFGATE ParamGate(%theta) AS MATRIX: + cos(%theta/2), -1.0i*sin(%theta/2) + -1.0i*sin(%theta/2), cos(%theta/2) + diff --git a/quil-rs/src/instruction/snapshots/quil_rs__instruction__gate__test_gate_definition__display@Pauli Sum GateDefinition.snap b/quil-rs/src/instruction/snapshots/quil_rs__instruction__gate__test_gate_definition__display@Pauli Sum GateDefinition.snap new file mode 100644 index 00000000..2196c663 --- /dev/null +++ b/quil-rs/src/instruction/snapshots/quil_rs__instruction__gate__test_gate_definition__display@Pauli Sum GateDefinition.snap @@ -0,0 +1,9 @@ +--- +source: quil-rs/src/instruction/gate.rs +expression: gate_def.to_string() +--- +DEFGATE PauliSumGate(%theta) p q AS PAULI-SUM: + ZZ(-%theta/4) p q + Y(%theta/4) p + X(%theta/4) q + diff --git a/quil-rs/src/instruction/snapshots/quil_rs__instruction__gate__test_gate_definition__display@Permutation GateDefinition.snap b/quil-rs/src/instruction/snapshots/quil_rs__instruction__gate__test_gate_definition__display@Permutation GateDefinition.snap new file mode 100644 index 00000000..f5b3794d --- /dev/null +++ b/quil-rs/src/instruction/snapshots/quil_rs__instruction__gate__test_gate_definition__display@Permutation GateDefinition.snap @@ -0,0 +1,7 @@ +--- +source: quil-rs/src/instruction/gate.rs +expression: gate_def.to_string() +--- +DEFGATE PermGate AS PERMUTATION: + 0, 1, 2, 3, 4, 5, 7, 6 + diff --git a/quil-rs/src/instruction/snapshots/quil_rs__instruction__waveform__test_waveform_definition__display@Simple WaveformDefinition.snap b/quil-rs/src/instruction/snapshots/quil_rs__instruction__waveform__test_waveform_definition__display@Simple WaveformDefinition.snap new file mode 100644 index 00000000..c8e614a1 --- /dev/null +++ b/quil-rs/src/instruction/snapshots/quil_rs__instruction__waveform__test_waveform_definition__display@Simple WaveformDefinition.snap @@ -0,0 +1,6 @@ +--- +source: quil-rs/src/instruction/waveform.rs +expression: waveform_def.to_string() +--- +DEFWAVEFORM WF(%theta): + 0, 0, 0.00027685415721916584 diff --git a/quil-rs/src/instruction/timing.rs b/quil-rs/src/instruction/timing.rs new file mode 100644 index 00000000..0e72948f --- /dev/null +++ b/quil-rs/src/instruction/timing.rs @@ -0,0 +1,64 @@ +use super::Qubit; +use crate::{expression::Expression, quil::Quil}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Delay { + pub duration: Expression, + pub frame_names: Vec, + pub qubits: Vec, +} + +impl Delay { + pub fn new(duration: Expression, frame_names: Vec, qubits: Vec) -> Self { + Self { + duration, + frame_names, + qubits, + } + } +} + +impl Quil for Delay { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(writer, "DELAY")?; + for qubit in &self.qubits { + write!(writer, " ")?; + qubit.write(writer, fall_back_to_debug)?; + } + for frame_name in &self.frame_names { + write!(writer, " \"{}\"", frame_name)?; + } + write!(writer, " ",)?; + self.duration.write(writer, fall_back_to_debug) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Fence { + pub qubits: Vec, +} + +impl Quil for Fence { + fn write( + &self, + writer: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> Result<(), crate::quil::ToQuilError> { + write!(writer, "FENCE")?; + for qubit in &self.qubits { + write!(writer, " ")?; + qubit.write(writer, fall_back_to_debug)?; + } + Ok(()) + } +} + +impl Fence { + pub fn new(qubits: Vec) -> Self { + Self { qubits } + } +} diff --git a/quil-rs/src/instruction/waveform.rs b/quil-rs/src/instruction/waveform.rs new file mode 100644 index 00000000..9c85cf4c --- /dev/null +++ b/quil-rs/src/instruction/waveform.rs @@ -0,0 +1,141 @@ +use indexmap::IndexMap; + +use crate::{ + expression::Expression, + quil::{write_join_quil, Quil}, +}; + +use super::write_parameter_string; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Waveform { + pub matrix: Vec, + pub parameters: Vec, +} + +impl Waveform { + pub fn new(matrix: Vec, parameters: Vec) -> Self { + Self { matrix, parameters } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct WaveformDefinition { + pub name: String, + pub definition: Waveform, +} + +impl WaveformDefinition { + pub fn new(name: String, definition: Waveform) -> Self { + Self { name, definition } + } +} + +impl Quil for WaveformDefinition { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "DEFWAVEFORM {}", self.name)?; + write_parameter_string(f, &self.definition.parameters)?; + write!(f, ":\n\t")?; + write_join_quil(f, fall_back_to_debug, &self.definition.matrix, ", ", "") + .map_err(Into::into) + } +} + +#[cfg(test)] +mod test_waveform_definition { + use super::{Waveform, WaveformDefinition}; + use crate::expression::Expression; + use crate::quil::Quil; + use crate::real; + + use insta::assert_snapshot; + use rstest::rstest; + + #[rstest] + #[case( + "Simple WaveformDefinition", + WaveformDefinition { + name: "WF".to_string(), + definition: Waveform { + matrix: vec![ + Expression::Number(real!(0.0)), + Expression::Number(real!(0.0)), + Expression::Number(real!(0.00027685415721916584)) + ], + parameters: vec!["theta".to_string()] + } + } + )] + fn test_display(#[case] description: &str, #[case] waveform_def: WaveformDefinition) { + insta::with_settings!({ + snapshot_suffix => description, + }, { + assert_snapshot!(waveform_def.to_quil_or_debug()) + }) + } +} + +pub type WaveformParameters = IndexMap; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WaveformInvocation { + pub name: String, + pub parameters: WaveformParameters, +} + +impl WaveformInvocation { + pub fn new(name: String, parameters: WaveformParameters) -> Self { + Self { name, parameters } + } +} + +impl Quil for WaveformInvocation { + fn write( + &self, + f: &mut impl std::fmt::Write, + _fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + let mut key_value_pairs = self + .parameters + .iter() + .collect::>(); + + key_value_pairs.sort_by(|(k1, _), (k2, _)| k1.cmp(k2)); + + if key_value_pairs.is_empty() { + write!(f, "{}", self.name,)?; + } else { + write!( + f, + "{}({})", + self.name, + key_value_pairs + .iter() + .map(|(k, v)| format!("{k}: {}", v.to_quil_or_debug())) + .collect::>() + .join(", ") + )?; + } + Ok(()) + } +} + +#[cfg(test)] +mod waveform_invocation_tests { + use super::WaveformParameters; + + use crate::{instruction::WaveformInvocation, quil::Quil}; + + #[test] + fn format_no_parameters() { + let invocation = WaveformInvocation { + name: "CZ".into(), + parameters: WaveformParameters::new(), + }; + assert_eq!(invocation.to_quil_or_debug(), "CZ".to_string()); + } +} diff --git a/src/lib.rs b/quil-rs/src/lib.rs similarity index 95% rename from src/lib.rs rename to quil-rs/src/lib.rs index 1a6b4585..f7da6adf 100644 --- a/src/lib.rs +++ b/quil-rs/src/lib.rs @@ -37,9 +37,15 @@ //! [serializer]: crate::program::Program#method.to_string pub mod expression; +mod hash; pub mod instruction; mod macros; pub(crate) mod parser; pub mod program; +pub mod quil; +pub mod reserved; +pub mod units; +pub mod validation; +pub mod waveform; pub use program::Program; diff --git a/src/macros.rs b/quil-rs/src/macros.rs similarity index 100% rename from src/macros.rs rename to quil-rs/src/macros.rs diff --git a/src/parser/command.rs b/quil-rs/src/parser/command.rs similarity index 76% rename from src/parser/command.rs rename to quil-rs/src/parser/command.rs index 254478ca..42a89918 100644 --- a/src/parser/command.rs +++ b/quil-rs/src/parser/command.rs @@ -1,50 +1,34 @@ -// Copyright 2021 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. - -use nom::{ - branch::alt, - combinator::{map, opt}, - multi::{many0, many1, separated_list0, separated_list1}, - sequence::{delimited, pair, preceded, tuple}, -}; - -use crate::{ - instruction::{Convert, GateSpecification, GateType, Include, PragmaArgument}, - parser::common::parse_variable_qubit, -}; +use nom::branch::alt; +use nom::combinator::{map, map_res, opt}; +use nom::multi::{many0, many1, separated_list0, separated_list1}; +use nom::sequence::{delimited, pair, preceded, tuple}; +use crate::expression::Expression; use crate::instruction::{ Arithmetic, ArithmeticOperand, ArithmeticOperator, BinaryLogic, BinaryOperator, Calibration, - Capture, CircuitDefinition, Comparison, ComparisonOperator, Declaration, Delay, Exchange, - Fence, FrameDefinition, GateDefinition, Instruction, Jump, JumpUnless, JumpWhen, Label, Load, - MeasureCalibrationDefinition, Measurement, Move, Pragma, Pulse, RawCapture, Reset, - SetFrequency, SetPhase, SetScale, ShiftFrequency, ShiftPhase, Store, UnaryLogic, UnaryOperator, - Waveform, WaveformDefinition, + Capture, CircuitDefinition, Comparison, ComparisonOperator, Convert, Declaration, Delay, + Exchange, Fence, FrameDefinition, GateDefinition, GateSpecification, GateType, Include, + Instruction, Jump, JumpUnless, JumpWhen, Label, Load, MeasureCalibrationDefinition, + Measurement, Move, PauliSum, Pragma, PragmaArgument, Pulse, Qubit, RawCapture, Reset, + SetFrequency, SetPhase, SetScale, ShiftFrequency, ShiftPhase, Store, SwapPhases, Target, + UnaryLogic, UnaryOperator, ValidationError, Waveform, WaveformDefinition, }; + use crate::parser::instruction::parse_block; use crate::parser::InternalParserResult; -use crate::token; +use crate::quil::Quil; +use crate::{real, token}; +use super::common::parse_variable_qubit; use super::{ common::{ parse_arithmetic_operand, parse_binary_logic_operand, parse_comparison_operand, parse_frame_attribute, parse_frame_identifier, parse_gate_modifier, parse_matrix, - parse_memory_reference, parse_permutation, parse_qubit, parse_vector, - parse_waveform_invocation, parse_waveform_name, + parse_memory_reference, parse_pauli_terms, parse_permutation, parse_qubit, parse_sharing, + parse_vector, parse_waveform_invocation, parse_waveform_name, }, expression::parse_expression, - ParserInput, + InternalParseError, ParserErrorKind, ParserInput, }; /// Parse an arithmetic instruction of the form `destination source`. @@ -122,12 +106,14 @@ pub(crate) fn parse_logical_unary( pub(crate) fn parse_declare<'a>(input: ParserInput<'a>) -> InternalParserResult<'a, Instruction> { let (input, name) = token!(Identifier(v))(input)?; let (input, size) = parse_vector(input)?; + let (input, sharing) = parse_sharing(input)?; + Ok(( input, Instruction::Declaration(Declaration { name, - sharing: None, size, + sharing, }), )) } @@ -159,7 +145,13 @@ pub(crate) fn parse_capture( pub(crate) fn parse_convert(input: ParserInput) -> InternalParserResult { let (input, to) = parse_memory_reference(input)?; let (input, from) = parse_memory_reference(input)?; - Ok((input, Instruction::Convert(Convert { from, to }))) + Ok(( + input, + Instruction::Convert(Convert { + destination: to, + source: from, + }), + )) } /// Parse the contents of a `DEFCAL` instruction (including `DEFCAL MEASURE`), @@ -192,11 +184,11 @@ pub(crate) fn parse_defcal_gate<'a>( Ok(( input, Instruction::CalibrationDefinition(Calibration { - instructions, - modifiers, name, parameters, qubits, + instructions, + modifiers, }), )) } @@ -208,7 +200,7 @@ pub(crate) fn parse_defcal_measure<'a>( let (input, params) = pair(parse_qubit, opt(token!(Identifier(v))))(input)?; let (qubit, destination) = match params { (qubit, Some(destination)) => (Some(qubit), destination), - (destination, None) => (None, destination.to_string()), + (destination, None) => (None, destination.to_quil_or_debug()), }; let (input, _) = token!(Colon)(input)?; let (input, instructions) = parse_block(input)?; @@ -227,7 +219,7 @@ pub(crate) fn parse_defframe<'a>(input: ParserInput<'a>) -> InternalParserResult let (input, identifier) = parse_frame_identifier(input)?; let (input, _) = token!(Colon)(input)?; let (input, attribute_pairs) = many1(parse_frame_attribute)(input)?; - let attributes = attribute_pairs.iter().cloned().collect(); + let attributes = attribute_pairs.into_iter().collect(); Ok(( input, @@ -246,19 +238,29 @@ pub(crate) fn parse_defgate<'a>(input: ParserInput<'a>) -> InternalParserResult< separated_list1(token!(Comma), token!(Variable(v))), token!(RParenthesis), ))(input)?; + let (input, mut arguments) = opt(many0(token!(Identifier(v))))(input)?; let (input, gate_type) = opt(preceded( token!(As), alt(( map(token!(Matrix), |()| GateType::Matrix), map(token!(Permutation), |()| GateType::Permutation), + map(token!(PauliSum), |()| GateType::PauliSum), )), ))(input)?; + let (input, _) = token!(Colon)(input)?; let gate_type = gate_type.unwrap_or(GateType::Matrix); let (input, specification) = match gate_type { GateType::Matrix => map(parse_matrix, GateSpecification::Matrix)(input)?, GateType::Permutation => map(parse_permutation, GateSpecification::Permutation)(input)?, + GateType::PauliSum => map_res(parse_pauli_terms, |terms| { + Ok(GateSpecification::PauliSum( + PauliSum::new(arguments.take().unwrap_or_default(), terms) + .map_err(ValidationError::from) + .map_err(|e| InternalParseError::from_kind(input, ParserErrorKind::from(e)))?, + )) + })(input)?, }; Ok(( @@ -322,9 +324,19 @@ pub(crate) fn parse_defcircuit<'a>( /// Parse the contents of a `DELAY` instruction. pub(crate) fn parse_delay<'a>(input: ParserInput<'a>) -> InternalParserResult<'a, Instruction> { - let (input, qubits) = many0(parse_qubit)(input)?; + let (input, mut qubits) = many0(parse_qubit)(input)?; let (input, frame_names) = many0(token!(String(v)))(input)?; - let (input, duration) = parse_expression(input)?; + // If there is no intervening frame name and the delay is an integer, it will have been parsed + // as a qubit. We check for and correct that condition here. + let (input, duration) = parse_expression(input).or_else(|e| { + if let Some(Qubit::Fixed(index)) = qubits.last() { + let duration = *index as f64; + qubits.pop(); + Ok((input, Expression::Number(real!(duration)))) + } else { + Err(e) + } + })?; Ok(( input, @@ -341,13 +353,7 @@ pub(crate) fn parse_exchange(input: ParserInput) -> InternalParserResult InternalParserResult(input: ParserInput<'a>) -> InternalParserResult<'a, Instruction> { - let (input, target) = token!(Label(v))(input)?; - Ok((input, Instruction::Jump(Jump { target }))) + let (input, target) = token!(Target(v))(input)?; + Ok(( + input, + Instruction::Jump(Jump { + target: Target::Fixed(target), + }), + )) } /// Parse the contents of a `JUMP-WHEN` instruction. pub(crate) fn parse_jump_when<'a>(input: ParserInput<'a>) -> InternalParserResult<'a, Instruction> { - let (input, target) = token!(Label(v))(input)?; + let (input, target) = token!(Target(v))(input)?; let (input, condition) = parse_memory_reference(input)?; - Ok((input, Instruction::JumpWhen(JumpWhen { target, condition }))) + Ok(( + input, + Instruction::JumpWhen(JumpWhen { + target: Target::Fixed(target), + condition, + }), + )) } /// Parse the contents of a `JUMP-UNLESS` instruction. pub(crate) fn parse_jump_unless<'a>( input: ParserInput<'a>, ) -> InternalParserResult<'a, Instruction> { - let (input, target) = token!(Label(v))(input)?; + let (input, target) = token!(Target(v))(input)?; let (input, condition) = parse_memory_reference(input)?; Ok(( input, - Instruction::JumpUnless(JumpUnless { target, condition }), + Instruction::JumpUnless(JumpUnless { + target: Target::Fixed(target), + condition, + }), )) } /// Parse the contents of a `DECLARE` instruction. pub(crate) fn parse_label<'a>(input: ParserInput<'a>) -> InternalParserResult<'a, Instruction> { - let (input, name) = token!(Label(v))(input)?; - Ok((input, Instruction::Label(Label(name)))) + let (input, name) = token!(Target(v))(input)?; + Ok(( + input, + Instruction::Label(Label { + target: Target::Fixed(name), + }), + )) } /// Parse the contents of a `MOVE` instruction. pub(crate) fn parse_move(input: ParserInput) -> InternalParserResult { - let (input, destination) = parse_arithmetic_operand(input)?; + let (input, destination) = parse_memory_reference(input)?; let (input, source) = parse_arithmetic_operand(input)?; Ok(( input, @@ -539,6 +564,17 @@ pub(crate) fn parse_shift_phase(input: ParserInput) -> InternalParserResult InternalParserResult { + let (input, frame_1) = parse_frame_identifier(input)?; + let (input, frame_2) = parse_frame_identifier(input)?; + + Ok(( + input, + Instruction::SwapPhases(SwapPhases { frame_1, frame_2 }), + )) +} + /// Parse the contents of a `MEASURE` instruction. pub(crate) fn parse_measurement(input: ParserInput) -> InternalParserResult { let (input, qubit) = parse_qubit(input)?; @@ -561,8 +597,14 @@ pub(crate) fn parse_include<'a>(input: ParserInput<'a>) -> InternalParserResult< #[cfg(test)] mod tests { - use crate::expression::{Expression, ExpressionFunction, InfixOperator, PrefixOperator}; - use crate::instruction::{GateDefinition, GateSpecification, PragmaArgument}; + use crate::expression::{ + Expression, ExpressionFunction, FunctionCallExpression, InfixExpression, InfixOperator, + PrefixExpression, PrefixOperator, + }; + use crate::instruction::{ + GateDefinition, GateSpecification, Offset, PauliGate, PauliSum, PauliTerm, PragmaArgument, + Sharing, + }; use crate::parser::lexer::lex; use crate::{imag, real}; use crate::{ @@ -581,11 +623,11 @@ mod tests { "ro BIT", Instruction::Declaration(Declaration { name: "ro".to_owned(), - sharing: None, size: Vector { data_type: ScalarType::Bit, length: 1 - } + }, + sharing: None, }) ); @@ -595,11 +637,54 @@ mod tests { "ro INTEGER[5]", Instruction::Declaration(Declaration { name: "ro".to_owned(), - sharing: None, size: Vector { data_type: ScalarType::Integer, length: 5 - } + }, + sharing: None, + }) + ); + + make_test!( + declare_instruction_sharing, + parse_declare, + "ro REAL[1] SHARING bar", + Instruction::Declaration(Declaration { + name: "ro".to_owned(), + size: Vector { + data_type: ScalarType::Real, + length: 1, + }, + sharing: Some(Sharing { + name: "bar".to_string(), + offsets: vec![] + }) + }) + ); + + make_test!( + declare_instruction_sharing_offsets, + parse_declare, + "ro REAL[1] SHARING bar OFFSET 2 BIT 3 INTEGER", + Instruction::Declaration(Declaration { + name: "ro".to_owned(), + size: Vector { + data_type: ScalarType::Real, + length: 1, + }, + sharing: Some(Sharing { + name: "bar".to_string(), + offsets: vec![ + Offset { + offset: 2, + data_type: ScalarType::Bit + }, + Offset { + offset: 3, + data_type: ScalarType::Integer + } + ], + }) }) ); @@ -778,27 +863,27 @@ mod tests { 1/sqrt(2), -1/sqrt(2)"#, { // 1/sqrt(2) - let expression = Expression::Infix { + let expression = Expression::Infix(InfixExpression { left: Box::new(Expression::Number(real!(1.0))), operator: InfixOperator::Slash, - right: Box::new(Expression::FunctionCall { + right: Box::new(Expression::FunctionCall(FunctionCallExpression { function: crate::expression::ExpressionFunction::SquareRoot, expression: Box::new(Expression::Number(real!(2.0))), - }), - }; + })), + }); // -1/sqrt(2) - let negative_expression = Expression::Infix { - left: Box::new(Expression::Prefix { + let negative_expression = Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { operator: PrefixOperator::Minus, expression: Box::new(Expression::Number(real!(1.0))), - }), + })), operator: InfixOperator::Slash, - right: Box::new(Expression::FunctionCall { + right: Box::new(Expression::FunctionCall(FunctionCallExpression { function: crate::expression::ExpressionFunction::SquareRoot, expression: Box::new(Expression::Number(real!(2.0))), - }), - }; + })), + }); Instruction::GateDefinition(GateDefinition { name: "H".to_string(), @@ -822,54 +907,54 @@ mod tests { parameters: vec!["theta".to_string()], specification: GateSpecification::Matrix(vec![ vec![ - Expression::FunctionCall { + Expression::FunctionCall(FunctionCallExpression { function: crate::expression::ExpressionFunction::Cosine, - expression: Box::new(Expression::Infix { + expression: Box::new(Expression::Infix(InfixExpression { left: Box::new(Expression::Variable("theta".to_string())), operator: InfixOperator::Slash, right: Box::new(Expression::Number(real!(2.0))), - }), - }, - Expression::Infix { - left: Box::new(Expression::Prefix { + })), + }), + Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { operator: PrefixOperator::Minus, expression: Box::new(Expression::Number(imag!(1f64))) - }), + })), operator: InfixOperator::Star, - right: Box::new(Expression::FunctionCall { + right: Box::new(Expression::FunctionCall(FunctionCallExpression { function: ExpressionFunction::Sine, - expression: Box::new(Expression::Infix { + expression: Box::new(Expression::Infix(InfixExpression { left: Box::new(Expression::Variable("theta".to_string())), operator: InfixOperator::Slash, right: Box::new(Expression::Number(real!(2.0))), - }), - }), - } + })), + })), + }) ], vec![ - Expression::Infix { - left: Box::new(Expression::Prefix { + Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { operator: PrefixOperator::Minus, expression: Box::new(Expression::Number(imag!(1f64))) - }), + })), operator: InfixOperator::Star, - right: Box::new(Expression::FunctionCall { + right: Box::new(Expression::FunctionCall(FunctionCallExpression { function: ExpressionFunction::Sine, - expression: Box::new(Expression::Infix { + expression: Box::new(Expression::Infix(InfixExpression { left: Box::new(Expression::Variable("theta".to_string())), operator: InfixOperator::Slash, right: Box::new(Expression::Number(real!(2.0))), - }), - }), - }, - Expression::FunctionCall { + })), + })), + }), + Expression::FunctionCall(FunctionCallExpression { function: crate::expression::ExpressionFunction::Cosine, - expression: Box::new(Expression::Infix { + expression: Box::new(Expression::Infix(InfixExpression { left: Box::new(Expression::Variable("theta".to_string())), operator: InfixOperator::Slash, right: Box::new(Expression::Number(real!(2.0))), - }), - }, + })), + }), ], ]), }) @@ -886,4 +971,52 @@ mod tests { specification: GateSpecification::Permutation(vec![0, 1, 2, 3, 4, 5, 7, 6]), }) ); + + make_test!( + defgate_pauli_sum, + parse_defgate, + r#"PauliSumGate(%theta) p q AS PAULI-SUM: + ZZ((-%theta)/4) p q + Y(%theta/4) p + X(%theta/4) q"#, + Instruction::GateDefinition(GateDefinition { + name: "PauliSumGate".to_string(), + parameters: vec!["theta".to_string()], + specification: GateSpecification::PauliSum(PauliSum { + arguments: vec!["p".to_string(), "q".to_string()], + terms: vec![ + PauliTerm { + arguments: vec![ + (PauliGate::Z, "p".to_string()), + (PauliGate::Z, "q".to_string()) + ], + expression: Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: Box::new(Expression::Variable("theta".to_string())) + })), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(4.0))) + }), + }, + PauliTerm { + arguments: vec![(PauliGate::Y, "p".to_string())], + expression: Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(4.0))) + }), + }, + PauliTerm { + arguments: vec![(PauliGate::X, "q".to_string())], + expression: Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(4.0))) + }), + }, + ] + }) + }) + ); } diff --git a/src/parser/common.rs b/quil-rs/src/parser/common.rs similarity index 63% rename from src/parser/common.rs rename to quil-rs/src/parser/common.rs index 8fc583f0..a85dd845 100644 --- a/src/parser/common.rs +++ b/quil-rs/src/parser/common.rs @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::str::FromStr; use nom::{ branch::alt, - combinator::{cut, map, opt, value}, + combinator::{cut, map, map_res, opt, value}, multi::{many0, many1, separated_list0, separated_list1}, - sequence::{delimited, preceded, tuple}, + sequence::{delimited, pair, preceded, tuple}, }; use crate::{ @@ -26,7 +26,8 @@ use crate::{ expression::Expression, instruction::{ ArithmeticOperand, AttributeValue, BinaryOperand, ComparisonOperand, FrameIdentifier, - GateModifier, MemoryReference, Qubit, ScalarType, Vector, WaveformInvocation, + GateModifier, MemoryReference, Offset, PauliGate, PauliTerm, Qubit, ScalarType, Sharing, + Vector, WaveformInvocation, WaveformParameters, }, parser::lexer::Operator, token, @@ -177,7 +178,10 @@ pub(crate) fn parse_matrix<'a>( token!(NewLine), preceded( token!(Indentation), - separated_list0(token!(Comma), parse_expression), + separated_list0( + pair(token!(Comma), many0(token!(Indentation))), + parse_expression, + ), ), ), )(input) @@ -194,6 +198,71 @@ pub(crate) fn parse_permutation<'a>(input: ParserInput<'a>) -> InternalParserRes )(input) } +/// Parse a [`crate::instruction::PauliWord`]. These are a special kind of a [`Token::Identifier`] +/// used in Pauli sum `DEFGATE` specifications where each identifier is made up of one or more +/// `I`, `X`, `Y`, or `Z` characters (e.g. `X`, `YY`). +fn parse_pauli_word<'a>(input: ParserInput<'a>) -> InternalParserResult<'a, Vec> { + map_res(token!(Identifier(v)), |words| { + let mut pauli_words: Vec = Vec::new(); + for word in words.split("") { + if word.is_empty() { + // split("") separates every character, including empty spaces at the beginning and + // end of string. + continue; + } + pauli_words.push(PauliGate::from_str(word).map_err(|_| { + InternalParseError::from_kind( + input, + ParserErrorKind::ExpectedCharacter { + expected: "(I,X,Y,Z)".to_string(), + actual: word.to_string(), + }, + ) + })?) + } + Ok(pauli_words) + })(input) +} + +pub(crate) fn parse_pauli_term<'a>(input: ParserInput<'a>) -> InternalParserResult<'a, PauliTerm> { + map_res( + tuple(( + parse_pauli_word, + delimited(token!(LParenthesis), parse_expression, token!(RParenthesis)), + many1(token!(Identifier(i))), + )), + |(word, expression, arguments)| { + if word.len() != arguments.len() { + Err(InternalParseError::from_kind( + input, + ParserErrorKind::PauliTermArgumentMismatch { + word_length: word.len(), + num_args: arguments.len(), + }, + )) + } else { + Ok(PauliTerm::new( + word.into_iter().zip(arguments).collect(), + expression, + )) + } + }, + )(input) +} + +/// Parse Pauli sum representation of a `DEFGATE` specification. +pub(crate) fn parse_pauli_terms<'a>( + input: ParserInput<'a>, +) -> InternalParserResult<'a, Vec> { + preceded( + token!(NewLine), + separated_list1( + token!(NewLine), + preceded(token!(Indentation), parse_pauli_term), + ), + )(input) +} + /// Parse a reference to a memory location, such as `ro[5]`, with optional brackets /// (i.e, `ro` allowed). pub(crate) fn parse_memory_reference<'a>( @@ -239,7 +308,7 @@ pub(crate) fn parse_waveform_invocation<'a>( token!(RParenthesis), ))(input)?; let parameter_tuples = parameter_tuples.unwrap_or_default(); - let parameters: HashMap<_, _> = parameter_tuples.into_iter().collect(); + let parameters: WaveformParameters = parameter_tuples.into_iter().collect(); Ok((input, WaveformInvocation { name, parameters })) } @@ -279,16 +348,46 @@ pub(crate) fn parse_variable_qubit(input: ParserInput) -> InternalParserResult(input: ParserInput<'a>) -> InternalParserResult<'a, Vector> { - let (input, data_type_token) = token!(DataType(v))(input)?; - - let data_type = match data_type_token { +fn match_data_type_token(token: DataType) -> ScalarType { + match token { DataType::Bit => ScalarType::Bit, DataType::Integer => ScalarType::Integer, DataType::Real => ScalarType::Real, DataType::Octet => ScalarType::Octet, - }; + } +} + +/// Parse an optional shared memory region and its offsets from a DECLARE statement. +pub(crate) fn parse_sharing<'a>( + input: ParserInput<'a>, +) -> InternalParserResult<'a, Option> { + let (input, sharing) = opt(preceded( + token!(Sharing), + pair( + token!(Identifier(v)), + opt(preceded( + token!(Offset), + many1(map( + pair(token!(Integer(v)), token!(DataType(v))), + |(offset, data_type)| Offset { + offset, + data_type: match_data_type_token(data_type), + }, + )), + )), + ), + ))(input)?; + let sharing = sharing.map(|(name, offsets)| Sharing { + name, + offsets: offsets.unwrap_or_default(), + }); + Ok((input, sharing)) +} + +/// Parse a "vector" which is an integer index, such as `[0]` +pub(crate) fn parse_vector<'a>(input: ParserInput<'a>) -> InternalParserResult<'a, Vector> { + let (input, data_type_token) = token!(DataType(v))(input)?; + let data_type = match_data_type_token(data_type_token); let (input, length) = opt(delimited( token!(LBracket), @@ -308,7 +407,7 @@ pub(crate) fn parse_waveform_name<'a>(input: ParserInput<'a>) -> InternalParserR let (input, name_extension) = opt(tuple((token!(Operator(Slash)), token!(Identifier(v)))))(input)?; if let Some((_, extension)) = name_extension { - name = format!("{}/{}", name, extension); + name = format!("{name}/{extension}"); } Ok((input, name)) } @@ -372,17 +471,77 @@ mod describe_skip_newlines_and_comments { } #[cfg(test)] -mod tests { +pub mod tests { use crate::{ - expression::Expression, - instruction::MemoryReference, - parser::{common::parse_permutation, lex}, + expression::{ + Expression, InfixExpression, InfixOperator, PrefixExpression, PrefixOperator, + }, + instruction::{MemoryReference, PauliGate, PauliTerm, WaveformParameters}, + parser::lex, real, }; use nom_locate::LocatedSpan; - use super::{parse_matrix, parse_waveform_invocation}; + use super::{parse_matrix, parse_pauli_terms, parse_permutation, parse_waveform_invocation}; + + /// A Quil program which uses all available instructions. + pub const KITCHEN_SINK_QUIL: &str = "DECLARE ro BIT[1] +DEFGATE HADAMARD AS MATRIX: +\t(1/sqrt(2)),(1/sqrt(2)) +\t(1/sqrt(2)),((-1)/sqrt(2)) + +DEFGATE RX(%theta) AS MATRIX: +\tcos((%theta/2)),((-1i)*sin((%theta/2))) +\t((-1i)*sin((%theta/2))),cos((%theta/2)) + +DEFGATE Name AS PERMUTATION: +\t1, 0 + +DEFCIRCUIT SIMPLE: +\tX 0 +\tX 1 + +RX 0 +CZ 0 1 +MEASURE 0 ro[0] +RESET 0 +RESET +CAPTURE 0 \"out\" my_waveform() iq[0] +DEFCAL X 0: +\tPULSE 0 \"xy\" my_waveform() + +DEFCAL RX(%theta) 0: +\tPULSE 0 \"xy\" my_waveform() + +DEFCAL MEASURE 0 dest: +\tDECLARE iq REAL[2] +\tCAPTURE 0 \"out\" flat(duration: 1000000, iqs: (2+3i)) iq[0] + +DEFFRAME 0 \"xy\": +\tSAMPLE-RATE: 3000 + +DEFFRAME 0 \"xy\": +\tDIRECTION: \"rx\" +\tCENTER-FREQUENCY: 1000 +\tHARDWARE-OBJECT: \"some object\" +\tINITIAL-FREQUENCY: 2000 +\tSAMPLE-RATE: 3000 + +DELAY 0 100 +DELAY 0 \"xy\" 100000000 +FENCE 0 +FENCE 0 1 +PULSE 0 \"xy\" my_waveform() +PULSE 0 1 \"cz\" my_parametrized_waveform(a: 1) +RAW-CAPTURE 0 \"out\" 200000000 iqs[0] +SET-FREQUENCY 0 \"xy\" 5400000000 +SET-PHASE 0 \"xy\" pi +SET-SCALE 0 \"xy\" pi +SHIFT-FREQUENCY 0 \"ro\" 6100000000 +SHIFT-PHASE 0 \"xy\" (-pi) +SHIFT-PHASE 0 \"xy\" (%theta*(2/pi)) +SWAP-PHASES 2 3 \"xy\" 3 4 \"xy\""; #[test] fn waveform_invocation() { @@ -391,8 +550,7 @@ mod tests { let (remainder, waveform) = parse_waveform_invocation(&lexed).unwrap(); assert!( remainder.is_empty(), - "expected remainder to be empty, got {:?}", - remainder + "expected remainder to be empty, got {remainder:?}" ); assert_eq!( waveform.parameters, @@ -408,7 +566,7 @@ mod tests { ) ] .into_iter() - .collect() + .collect::() ) } @@ -419,12 +577,29 @@ mod tests { let (remainder, matrix) = parse_matrix(&lexed).unwrap(); assert!( remainder.is_empty(), - "expected remainder to be empty, got {:?}", - remainder + "expected remainder to be empty, got {remainder:?}" ); assert_eq!(matrix.len(), 2); } + #[test] + fn test_parse_matrix_inline_whitespace() { + let input = LocatedSpan::new( + " + cis(%alpha), 0, 0, 0 + 0, 1, 0, 0 + 0, 0, 1, 0 + 0, 0, 0, 1", + ); + let lexed = lex(input).unwrap(); + let (remainder, matrix) = parse_matrix(&lexed).unwrap(); + assert!( + remainder.is_empty(), + "expected remainder to be empty, got {remainder:?}" + ); + assert_eq!(matrix.len(), 4); + } + #[test] fn test_parse_permutation() { let input = LocatedSpan::new("\n\t0, 1, 2, 3, 4, 5, 7, 6"); @@ -432,8 +607,7 @@ mod tests { let (remainder, permutation) = parse_permutation(&lexed).unwrap(); assert!( remainder.is_empty(), - "expected remainder to be empty, got {:?}", - remainder + "expected remainder to be empty, got {remainder:?}" ); assert_eq!(permutation, vec![0, 1, 2, 3, 4, 5, 7, 6]); @@ -443,4 +617,54 @@ mod tests { assert!(!remainder.is_empty(), "multiline permutations are invalid"); assert_eq!(permutation, vec![0, 1, 2, 3, 4, 5, 7, 6]); } + + #[test] + fn test_parse_pauli_terms() { + let input = LocatedSpan::new("\n\tZZ((-%theta)/4) p q\n\tY(%theta/4) p\n\tX(%theta/4) q"); + let lexed = lex(input).unwrap(); + let (remainder, pauli_terms) = parse_pauli_terms(&lexed).unwrap(); + assert!(remainder.is_empty()); + + let expected_pauli_terms = vec![ + PauliTerm { + arguments: vec![ + (PauliGate::Z, "p".to_string()), + (PauliGate::Z, "q".to_string()), + ], + expression: Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: Box::new(Expression::Variable("theta".to_string())), + })), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(4.0))), + }), + }, + PauliTerm { + arguments: vec![(PauliGate::Y, "p".to_string())], + expression: Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(4.0))), + }), + }, + PauliTerm { + arguments: vec![(PauliGate::X, "q".to_string())], + expression: Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(4.0))), + }), + }, + ]; + + assert_eq!(pauli_terms, expected_pauli_terms); + + let input = LocatedSpan::new("\n\tZZ((-%theta)/4) p\n"); + let lexed = lex(input).unwrap(); + assert!( + parse_pauli_terms(&lexed).is_err(), + "length of pauli word must match number of arguments" + ) + } } diff --git a/src/parser/error/error.rs b/quil-rs/src/parser/error/error.rs similarity index 99% rename from src/parser/error/error.rs rename to quil-rs/src/parser/error/error.rs index 6b56bdc9..5e4d22b0 100644 --- a/src/parser/error/error.rs +++ b/quil-rs/src/parser/error/error.rs @@ -147,7 +147,7 @@ where )?; if f.alternate() { if let Some(previous) = &self.previous { - write!(f, "\n\tcause: {}", previous)?; + write!(f, "\n\tcause: {previous}")?; } } Ok(()) diff --git a/src/parser/error/input.rs b/quil-rs/src/parser/error/input.rs similarity index 96% rename from src/parser/error/input.rs rename to quil-rs/src/parser/error/input.rs index eb559323..4e54bea3 100644 --- a/src/parser/error/input.rs +++ b/quil-rs/src/parser/error/input.rs @@ -40,9 +40,9 @@ impl ErrorInput for LexInput<'_> { std::str::from_utf8(self.get_line_beginning()) .map(|s| { if s.len() == self.len() { - format!("\"{}\"", s) + format!("\"{s}\"") } else { - format!("\"{}\"...", s) + format!("\"{s}\"...") } }) .unwrap_or_default() diff --git a/src/parser/error/internal.rs b/quil-rs/src/parser/error/internal.rs similarity index 100% rename from src/parser/error/internal.rs rename to quil-rs/src/parser/error/internal.rs diff --git a/src/parser/error/kind.rs b/quil-rs/src/parser/error/kind.rs similarity index 100% rename from src/parser/error/kind.rs rename to quil-rs/src/parser/error/kind.rs diff --git a/src/parser/error/mod.rs b/quil-rs/src/parser/error/mod.rs similarity index 83% rename from src/parser/error/mod.rs rename to quil-rs/src/parser/error/mod.rs index 7c7a7a04..f277bd17 100644 --- a/src/parser/error/mod.rs +++ b/quil-rs/src/parser/error/mod.rs @@ -43,6 +43,10 @@ pub enum ParserErrorKind { #[error("expected {expected}, found {actual:?}")] ExpectedToken { actual: Token, expected: String }, + /// Got an unexpected character + #[error("expected {expected}, found {actual}")] + ExpectedCharacter { actual: String, expected: String }, + /// Tried to parse a kind of command and couldn't #[error("failed to parse arguments for {command}")] InvalidCommand { command: Command }, @@ -62,4 +66,12 @@ pub enum ParserErrorKind { /// Literals specified in the input cannot be supported without loss of precision #[error("using this literal will result in loss of precision")] UnsupportedPrecision, + + #[error("invalid quil: {0}")] + InvalidQuil(#[from] crate::instruction::ValidationError), + + #[error( + "expected a Pauli term with a word length of {word_length} to match the number of arguments, {num_args}" + )] + PauliTermArgumentMismatch { word_length: usize, num_args: usize }, } diff --git a/src/parser/expression.rs b/quil-rs/src/parser/expression.rs similarity index 76% rename from src/parser/expression.rs rename to quil-rs/src/parser/expression.rs index 0129d066..52cc1dd6 100644 --- a/src/parser/expression.rs +++ b/quil-rs/src/parser/expression.rs @@ -14,6 +14,7 @@ use nom::combinator::opt; +use crate::expression::{FunctionCallExpression, InfixExpression, PrefixExpression}; use crate::parser::InternalParserResult; use crate::{ expected_token, @@ -32,16 +33,14 @@ enum Precedence { Lowest, Sum, Product, + Exponentiation, Call, } impl From<&Token> for Precedence { fn from(token: &Token) -> Self { match token { - Token::Operator(Operator::Plus) | Token::Operator(Operator::Minus) => Precedence::Sum, - Token::Operator(Operator::Star) | Token::Operator(Operator::Slash) => { - Precedence::Product - } + Token::Operator(operator) => Self::from(operator), // TODO: Is this used? Token::LParenthesis => Precedence::Call, _ => Precedence::Lowest, @@ -49,6 +48,16 @@ impl From<&Token> for Precedence { } } +impl From<&Operator> for Precedence { + fn from(operator: &Operator) -> Self { + match operator { + Operator::Plus | Operator::Minus => Precedence::Sum, + Operator::Star | Operator::Slash => Precedence::Product, + Operator::Caret => Precedence::Exponentiation, + } + } +} + fn get_precedence(input: ParserInput) -> Precedence { match super::first_token(input) { Some(v) => Precedence::from(v), @@ -92,10 +101,10 @@ fn parse(input: ParserInput, precedence: Precedence) -> InternalParserResult precedence { @@ -132,10 +141,10 @@ fn parse_function_call<'a>( let (input, _) = token!(RParenthesis)(input)?; Ok(( input, - Expression::FunctionCall { + Expression::FunctionCall(FunctionCallExpression { function, expression: Box::new(expression), - }, + }), )) } @@ -154,7 +163,7 @@ fn parse_expression_identifier(input: ParserInput) -> InternalParserResult unexpected_eof!(input), - Some((Token::Identifier(ident), remainder)) => match ident.as_str() { + Some((Token::Identifier(ident), remainder)) => match ident.to_lowercase().as_str() { "cis" => parse_function_call(remainder, ExpressionFunction::Cis), "cos" => parse_function_call(remainder, ExpressionFunction::Cosine), "exp" => parse_function_call(remainder, ExpressionFunction::Exponent), @@ -200,13 +209,13 @@ fn parse_infix(input: ParserInput, left: Expression) -> InternalParserResult InfixOperator::Slash, Operator::Star => InfixOperator::Star, }; - let precedence = get_precedence(remainder); + let precedence = Precedence::from(token_operator); let (remainder, right) = parse(remainder, precedence)?; - let infix_expression = Expression::Infix { + let infix_expression = Expression::Infix(InfixExpression { left: Box::new(left), operator: expression_operator, right: Box::new(right), - }; + }); Ok((remainder, infix_expression)) } Some((other_token, _)) => expected_token!(input, other_token, "infix operator".to_owned()), @@ -226,11 +235,14 @@ fn parse_prefix(input: ParserInput) -> InternalParserResult { #[cfg(test)] mod tests { - use crate::{expression::PrefixOperator, parser::lexer::lex}; - use crate::{ - expression::{Expression, ExpressionFunction, InfixOperator}, - imag, real, + use crate::expression::{ + Expression, ExpressionFunction, FunctionCallExpression, InfixExpression, InfixOperator, + PrefixExpression, PrefixOperator, }; + use crate::instruction::MemoryReference; + use crate::parser::lexer::lex; + use crate::quil::Quil; + use crate::{imag, real}; use nom_locate::LocatedSpan; @@ -267,11 +279,14 @@ mod tests { let cases = vec![ "pi", "sin(pi)", - "(1+(2*3))", - "((1+2)*3)", + "1+(2*3)", + "(1+2)*3", "%theta", "cis(%theta)", - "(%a+%b)", + "%a+%b", + "(pi/2)+(1*theta)", + "(pi/2)+(1*theta[1])", + "3 - -2", ]; for case in cases { @@ -279,7 +294,7 @@ mod tests { let tokens = lex(input).unwrap(); let (remainder, parsed) = parse_expression(&tokens).unwrap(); assert_eq!(remainder.len(), 0); - assert_eq!(parsed.to_string(), case); + assert_eq!(parsed.to_quil_or_debug(), case); } } @@ -287,70 +302,109 @@ mod tests { function_call, parse_expression, "sin(1)", - Expression::FunctionCall { + Expression::FunctionCall(FunctionCallExpression { function: ExpressionFunction::Sine, expression: Box::new(Expression::Number(real!(1f64))), - } + }) ); test!( nested_function_call, parse_expression, "sin(sin(1))", - Expression::FunctionCall { + Expression::FunctionCall(FunctionCallExpression { function: ExpressionFunction::Sine, - expression: Box::new(Expression::FunctionCall { + expression: Box::new(Expression::FunctionCall(FunctionCallExpression { function: ExpressionFunction::Sine, expression: Box::new(Expression::Number(real!(1f64))), - }), - } + })), + }) ); test!( simple_infix, parse_expression, "1+2", - Expression::Infix { + Expression::Infix(InfixExpression { left: Box::new(Expression::Number(real!(1f64))), operator: InfixOperator::Plus, right: Box::new(Expression::Number(real!(2f64))), - } + }) ); test!( infix_with_function_call, parse_expression, "-i*sin(%theta/2)", - Expression::Infix { - left: Box::new(Expression::Prefix { + Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { operator: PrefixOperator::Minus, expression: Box::new(Expression::Number(imag!(1f64))), - }), + })), operator: InfixOperator::Star, - right: Box::new(Expression::FunctionCall { + right: Box::new(Expression::FunctionCall(FunctionCallExpression { function: ExpressionFunction::Sine, - expression: Box::new(Expression::Infix { + expression: Box::new(Expression::Infix(InfixExpression { left: Box::new(Expression::Variable("theta".to_owned())), operator: InfixOperator::Slash, right: Box::new(Expression::Number(real!(2f64))), - }), - }), - } + })), + })), + }) ); test!( infix_parenthesized, parse_expression, "(1+2i)*%a", - Expression::Infix { - left: Box::new(Expression::Infix { + Expression::Infix(InfixExpression { + left: Box::new(Expression::Infix(InfixExpression { left: Box::new(Expression::Number(real!(1f64))), operator: InfixOperator::Plus, right: Box::new(Expression::Number(imag!(2f64))), - }), + })), operator: InfixOperator::Star, right: Box::new(Expression::Variable("a".to_owned())), - } + }) + ); + + test!( + infix_with_infix_operands_implicit_precedence, + parse_expression, + "pi/2 + 2*theta[0]", + Expression::Infix(InfixExpression { + left: Box::new(Expression::Infix(InfixExpression { + left: Box::new(Expression::PiConstant), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(2f64))), + })), + operator: InfixOperator::Plus, + right: Box::new(Expression::Infix(InfixExpression { + left: Box::new(Expression::Number(real!(2f64))), + operator: InfixOperator::Star, + right: Box::new(Expression::Address(MemoryReference { + name: "theta".to_string(), + index: 0, + })), + })), + }) + ); + + test!( + infix_minus_negative, + parse_expression, + "-3 - -2", + Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: Box::new(Expression::Number(real!(3f64))), + })), + operator: InfixOperator::Minus, + right: Box::new(Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: Box::new(Expression::Number(real!(2f64))) + })) + }) ); #[test] @@ -358,46 +412,46 @@ mod tests { let cases = vec![ ( "1 + ( 2 + 3 )", - Expression::Infix { + Expression::Infix(InfixExpression { left: Box::new(Expression::Number(real!(1f64))), operator: InfixOperator::Plus, - right: Box::new(Expression::Infix { + right: Box::new(Expression::Infix(InfixExpression { left: Box::new(Expression::Number(real!(2f64))), operator: InfixOperator::Plus, right: Box::new(Expression::Number(real!(3f64))), - }), - }, + })), + }), ), ( "1+(2+3)", - Expression::Infix { + Expression::Infix(InfixExpression { left: Box::new(Expression::Number(real!(1f64))), operator: InfixOperator::Plus, - right: Box::new(Expression::Infix { + right: Box::new(Expression::Infix(InfixExpression { left: Box::new(Expression::Number(real!(2f64))), operator: InfixOperator::Plus, right: Box::new(Expression::Number(real!(3f64))), - }), - }, + })), + }), ), ( "(1+2)+3", - Expression::Infix { - left: Box::new(Expression::Infix { + Expression::Infix(InfixExpression { + left: Box::new(Expression::Infix(InfixExpression { left: Box::new(Expression::Number(real!(1f64))), operator: InfixOperator::Plus, right: Box::new(Expression::Number(real!(2f64))), - }), + })), operator: InfixOperator::Plus, right: Box::new(Expression::Number(real!(3f64))), - }, + }), ), ( "(((cos(((pi))))))", - Expression::FunctionCall { + Expression::FunctionCall(FunctionCallExpression { function: ExpressionFunction::Cosine, expression: Box::new(Expression::PiConstant), - }, + }), ), ]; diff --git a/src/parser/gate.rs b/quil-rs/src/parser/gate.rs similarity index 100% rename from src/parser/gate.rs rename to quil-rs/src/parser/gate.rs diff --git a/src/parser/instruction.rs b/quil-rs/src/parser/instruction.rs similarity index 83% rename from src/parser/instruction.rs rename to quil-rs/src/parser/instruction.rs index fa8bc832..a3ce5f14 100644 --- a/src/parser/instruction.rs +++ b/quil-rs/src/parser/instruction.rs @@ -89,8 +89,10 @@ pub(crate) fn parse_instruction(input: ParserInput) -> InternalParserResult command::parse_set_scale(remainder), Command::ShiftFrequency => command::parse_shift_frequency(remainder), Command::ShiftPhase => command::parse_shift_phase(remainder), + Command::SwapPhases => command::parse_swap_phases(remainder), Command::Store => command::parse_store(remainder), Command::Sub => command::parse_arithmetic(ArithmeticOperator::Subtract, remainder), + Command::Wait => Ok((remainder, Instruction::Wait)), Command::Xor => command::parse_logical_binary(BinaryOperator::Xor, remainder), } .map_err(|err| { @@ -111,7 +113,9 @@ pub(crate) fn parse_instruction(input: ParserInput) -> InternalParserResult todo!(), }, - Some((Token::Identifier(_), _)) | Some((Token::Modifier(_), _)) => map(gate::parse_gate, Instruction::Gate)(input), + Some((Token::Identifier(_), _)) | Some((Token::Modifier(_), _)) => { + map(gate::parse_gate, Instruction::Gate)(input) + } Some((_, _)) => Err(nom::Err::Failure(InternalParseError::from_kind( &input[..1], ParserErrorKind::NotACommandOrGate, @@ -146,21 +150,27 @@ pub(crate) fn parse_block_instruction<'a>( #[cfg(test)] mod tests { - use std::collections::HashMap; use std::str::FromStr; use nom_locate::LocatedSpan; + use num_complex::Complex; - use crate::expression::{Expression, InfixOperator, PrefixOperator}; + use crate::expression::{ + Expression, ExpressionFunction, FunctionCallExpression, InfixExpression, InfixOperator, + PrefixExpression, PrefixOperator, + }; use crate::instruction::{ Arithmetic, ArithmeticOperand, ArithmeticOperator, AttributeValue, BinaryLogic, BinaryOperand, BinaryOperator, Calibration, Capture, Comparison, ComparisonOperand, ComparisonOperator, Convert, FrameDefinition, FrameIdentifier, Gate, GateDefinition, GateSpecification, Include, Instruction, Jump, JumpWhen, Label, MemoryReference, Move, Pulse, Qubit, RawCapture, Reset, SetFrequency, SetPhase, SetScale, ShiftFrequency, - ShiftPhase, UnaryLogic, UnaryOperator, Waveform, WaveformDefinition, WaveformInvocation, + ShiftPhase, SwapPhases, Target, UnaryLogic, UnaryOperator, Waveform, WaveformDefinition, + WaveformInvocation, WaveformParameters, }; + use crate::parser::common::tests::KITCHEN_SINK_QUIL; use crate::parser::lexer::lex; + use crate::quil::Quil; use crate::{make_test, real, Program}; use super::parse_instructions; @@ -463,7 +473,7 @@ mod tests { }, waveform: WaveformInvocation { name: "my_custom_waveform".to_owned(), - parameters: HashMap::new() + parameters: WaveformParameters::new() }, memory_reference: MemoryReference { name: "ro".to_owned(), @@ -532,7 +542,7 @@ mod tests { "DEFFRAME 0 \"ro_rx\":\n\tDIRECTION: \"rx\"\n\n# (Pdb) settings.gates[GateID(name=\"x180\", targets=(0,))]\n\n", vec![Instruction::FrameDefinition(FrameDefinition { identifier: FrameIdentifier { name: "ro_rx".to_owned(), qubits: vec![Qubit::Fixed(0)] }, - attributes: [("DIRECTION".to_owned(), AttributeValue::String("rx".to_owned()))].iter().cloned().collect() + attributes: [("DIRECTION".to_owned(), AttributeValue::String("rx".to_owned()))].into_iter().collect() })]); make_test!( @@ -577,8 +587,7 @@ mod tests { waveform: WaveformInvocation { name: "custom_waveform".to_owned(), parameters: [("a".to_owned(), Expression::Number(crate::real![1f64]))] - .iter() - .cloned() + .into_iter() .collect() } })] @@ -598,8 +607,7 @@ mod tests { "INITIAL-FREQUENCY".to_owned(), AttributeValue::Expression(Expression::Number(crate::real![2e9])) )] - .iter() - .cloned() + .into_iter() .collect() })] ); @@ -609,12 +617,14 @@ mod tests { parse_instructions, "LABEL @hello\nJUMP @hello\nJUMP-WHEN @hello ro", vec![ - Instruction::Label(Label("hello".to_owned())), + Instruction::Label(Label { + target: Target::Fixed("hello".to_owned()) + }), Instruction::Jump(Jump { - target: "hello".to_owned() + target: Target::Fixed("hello".to_owned()) }), Instruction::JumpWhen(JumpWhen { - target: "hello".to_owned(), + target: Target::Fixed("hello".to_owned()), condition: MemoryReference { name: "ro".to_owned(), index: 0 @@ -636,7 +646,7 @@ mod tests { }, waveform: WaveformInvocation { name: "custom".to_owned(), - parameters: HashMap::new() + parameters: WaveformParameters::new() } }), Instruction::Pulse(Pulse { @@ -647,7 +657,7 @@ mod tests { }, waveform: WaveformInvocation { name: "custom".to_owned(), - parameters: HashMap::new() + parameters: WaveformParameters::new() } }), Instruction::Pulse(Pulse { @@ -671,10 +681,10 @@ mod tests { parse_instructions, "MOVE a 1.0", vec![Instruction::Move(Move { - destination: ArithmeticOperand::MemoryReference(MemoryReference { + destination: MemoryReference { name: "a".to_owned(), index: 0 - }), + }, source: ArithmeticOperand::LiteralReal(1.0) })] ); @@ -717,43 +727,105 @@ mod tests { parameters: vec![], specification: GateSpecification::Matrix(vec![ vec![ - Expression::Infix { + Expression::Infix(InfixExpression { left: Box::new(Expression::Number(real!(1.0))), operator: InfixOperator::Slash, - right: Box::new(Expression::FunctionCall { + right: Box::new(Expression::FunctionCall(FunctionCallExpression { function: crate::expression::ExpressionFunction::SquareRoot, expression: Box::new(Expression::Number(real!(2.0))), - }), - }, - Expression::Infix { + })), + }), + Expression::Infix(InfixExpression { left: Box::new(Expression::Number(real!(1.0))), operator: InfixOperator::Slash, - right: Box::new(Expression::FunctionCall { + right: Box::new(Expression::FunctionCall(FunctionCallExpression { function: crate::expression::ExpressionFunction::SquareRoot, expression: Box::new(Expression::Number(real!(2.0))), - }), - }, + })), + }), ], vec![ - Expression::Infix { + Expression::Infix(InfixExpression { left: Box::new(Expression::Number(real!(1.0))), operator: InfixOperator::Slash, - right: Box::new(Expression::FunctionCall { + right: Box::new(Expression::FunctionCall(FunctionCallExpression { function: crate::expression::ExpressionFunction::SquareRoot, expression: Box::new(Expression::Number(real!(2.0))), - }), - }, - Expression::Infix { - left: Box::new(Expression::Prefix { + })), + }), + Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { operator: PrefixOperator::Minus, expression: Box::new(Expression::Number(real!(1.0))), - }), + })), operator: InfixOperator::Slash, - right: Box::new(Expression::FunctionCall { + right: Box::new(Expression::FunctionCall(FunctionCallExpression { function: crate::expression::ExpressionFunction::SquareRoot, expression: Box::new(Expression::Number(real!(2.0))), - }), - }, + })), + }), + ], + ]), + })] + ); + + make_test!( + gate_definition_with_params, + parse_instructions, + "DEFGATE RX(%theta):\n\tCOS(%theta/2), -i*SIN(%theta/2)\n\t-i*SIN(%theta/2), COS(%theta/2)\n", + vec![Instruction::GateDefinition(GateDefinition { + name: "RX".to_string(), + parameters: vec!["theta".to_string()], + specification: GateSpecification::Matrix(vec![ + vec![ + Expression::FunctionCall(FunctionCallExpression { + function: ExpressionFunction::Cosine, + expression: Box::new(Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(Complex { re: 2.0, im: 0.0 })), + })) + }), + Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: Box::new(Expression::Number(Complex { re: 0.0, im: 1.0 })), + })), + operator: InfixOperator::Star, + right: Box::new(Expression::FunctionCall(FunctionCallExpression { + function: ExpressionFunction::Sine, + expression: Box::new(Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(Complex { re: 2.0, im: 0.0 })), + })) + })) + }), + ], + vec![ + Expression::Infix(InfixExpression { + left: Box::new(Expression::Prefix(PrefixExpression { + operator: PrefixOperator::Minus, + expression: Box::new(Expression::Number(Complex { re: 0.0, im: 1.0 })) + })), + operator: InfixOperator::Star, + right: Box::new(Expression::FunctionCall(FunctionCallExpression { + function: ExpressionFunction::Sine, + expression: Box::new(Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(Complex { re: 2.0, im: 0.0 })) + })) + })) + }), + Expression::FunctionCall(FunctionCallExpression { + function: ExpressionFunction::Cosine, + expression: Box::new(Expression::Infix(InfixExpression { + left: Box::new(Expression::Variable("theta".to_string())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(Complex { re: 2.0, im: 0.0 })), + })) + }), ], ]), })] @@ -764,14 +836,14 @@ mod tests { parse_instructions, "CONVERT theta unadjusted-theta[1]", vec![Instruction::Convert(Convert { - from: MemoryReference { - name: "unadjusted-theta".to_string(), - index: 1 - }, - to: MemoryReference { + destination: MemoryReference { name: "theta".to_string(), index: 0 }, + source: MemoryReference { + name: "unadjusted-theta".to_string(), + index: 1 + }, })] ); @@ -810,8 +882,40 @@ mod tests { }), }), ]; + assert_eq!(parsed, expected); assert_eq!(remainder.len(), 0); + } + + #[test] + fn parse_swap_phases() { + let input = + LocatedSpan::new(r#"SWAP-PHASES 0 "rf" 1 "rf"; SWAP-PHASES 1 2 3 "rf" 4 5 6 "rf""#); + let tokens = lex(input).unwrap(); + let (remainder, parsed) = parse_instructions(&tokens).unwrap(); + let expected = vec![ + Instruction::SwapPhases(SwapPhases { + frame_1: FrameIdentifier { + name: String::from("rf"), + qubits: vec![Qubit::Fixed(0)], + }, + frame_2: FrameIdentifier { + name: String::from("rf"), + qubits: vec![Qubit::Fixed(1)], + }, + }), + Instruction::SwapPhases(SwapPhases { + frame_1: FrameIdentifier { + name: String::from("rf"), + qubits: vec![Qubit::Fixed(1), Qubit::Fixed(2), Qubit::Fixed(3)], + }, + frame_2: FrameIdentifier { + name: String::from("rf"), + qubits: vec![Qubit::Fixed(4), Qubit::Fixed(5), Qubit::Fixed(6)], + }, + }), + ]; assert_eq!(parsed, expected); + assert_eq!(remainder.len(), 0); } #[test] @@ -838,8 +942,8 @@ mod tests { }), }), ]; - assert_eq!(remainder.len(), 0); assert_eq!(parsed, expected); + assert_eq!(remainder.len(), 0); } #[test] @@ -866,8 +970,8 @@ mod tests { }), }), ]; - assert_eq!(remainder.len(), 0); assert_eq!(parsed, expected); + assert_eq!(remainder.len(), 0); } #[test] @@ -895,8 +999,8 @@ mod tests { }), }), ]; - assert_eq!(remainder.len(), 0); assert_eq!(parsed, expected); + assert_eq!(remainder.len(), 0); } #[test] @@ -923,8 +1027,14 @@ mod tests { }), }), ]; - assert_eq!(remainder.len(), 0); assert_eq!(parsed, expected); + assert_eq!(remainder.len(), 0); + } + + /// Test that an entire sample program can be parsed without failure. + #[test] + fn kitchen_sink() { + Program::from_str(KITCHEN_SINK_QUIL).unwrap(); } /// Assert that when a program is converted to a string, the conversion of @@ -936,14 +1046,15 @@ mod tests { r#"DEFCAL MEASURE 0 dest: DECLARE iq REAL[2] CAPTURE 0 "out" flat(duration: 1.0, iqs: (2.0+3.0i)) iq[0]"#, + r#"LABEL @target +JUMP @target"#, ]; for input in inputs { let program = Program::from_str(input).unwrap(); - let output = program.to_string(true); + let output = program.to_quil().unwrap(); let roundtrip = Program::from_str(&output).unwrap(); - - assert_eq!(program, roundtrip); + assert_eq!(output, roundtrip.to_quil().unwrap()); } } } diff --git a/src/parser/lexer/error.rs b/quil-rs/src/parser/lexer/error.rs similarity index 93% rename from src/parser/lexer/error.rs rename to quil-rs/src/parser/lexer/error.rs index 96f32bae..985da609 100644 --- a/src/parser/lexer/error.rs +++ b/quil-rs/src/parser/lexer/error.rs @@ -33,4 +33,6 @@ pub enum LexErrorKind { /// Encountered an unexpected EOF #[error("unexpected EOF while parsing")] UnexpectedEOF, + #[error("error parsing an integer: {0}")] + ParseInt(#[from] std::num::ParseIntError), } diff --git a/src/parser/lexer/mod.rs b/quil-rs/src/parser/lexer/mod.rs similarity index 85% rename from src/parser/lexer/mod.rs rename to quil-rs/src/parser/lexer/mod.rs index 078fad54..00704d58 100644 --- a/src/parser/lexer/mod.rs +++ b/quil-rs/src/parser/lexer/mod.rs @@ -22,7 +22,7 @@ use nom::{ combinator::{all_consuming, map, recognize, value}, multi::many0, number::complete::double, - sequence::{preceded, terminated, tuple}, + sequence::{pair, preceded, terminated, tuple}, Finish, IResult, }; use nom_locate::LocatedSpan; @@ -34,9 +34,7 @@ use crate::parser::token::token_with_location; pub(crate) use error::InternalLexError; pub use error::{LexError, LexErrorKind}; -// TODO: replace manual parsing with strum::EnumString (FromStr)? -// See: https://github.com/rigetti/quil-rs/issues/94 -#[derive(Debug, Copy, Clone, PartialEq, Eq, strum::Display)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, strum::Display, strum::EnumString)] #[strum(serialize_all = "SCREAMING-KEBAB-CASE")] pub enum Command { Add, @@ -86,8 +84,10 @@ pub enum Command { SetScale, ShiftFrequency, ShiftPhase, + SwapPhases, Store, Sub, + Wait, Xor, } @@ -156,7 +156,7 @@ fn lex_token(input: LexInput) -> InternalLexResult { token_with_location(lex_data_type), token_with_location(lex_modifier), token_with_location(lex_punctuation), - token_with_location(lex_label), + token_with_location(lex_target), token_with_location(lex_string), // Operator must come before number (or it may be parsed as a prefix) token_with_location(lex_operator), @@ -216,6 +216,7 @@ fn recognize_command_or_identifier(identifier: String) -> Token { "DEFCIRCUIT" => Token::Command(DefCircuit), "MEASURE" => Token::Command(Measure), "HALT" => Token::Command(Halt), + "WAIT" => Token::Command(Wait), "JUMP-WHEN" => Token::Command(JumpWhen), "JUMP-UNLESS" => Token::Command(JumpUnless), "JUMP" => Token::Command(Jump), @@ -235,6 +236,7 @@ fn recognize_command_or_identifier(identifier: String) -> Token { "SET-FREQUENCY" => Token::Command(SetFrequency), "SET-PHASE" => Token::Command(SetPhase), "SET-SCALE" => Token::Command(SetScale), + "SWAP-PHASES" => Token::Command(SwapPhases), "SHIFT-FREQUENCY" => Token::Command(ShiftFrequency), "SHIFT-PHASE" => Token::Command(ShiftPhase), "LABEL" => Token::Command(Label), @@ -250,8 +252,8 @@ fn is_valid_identifier_end_character(chr: char) -> bool { is_valid_identifier_leading_character(chr) || chr.is_ascii_digit() } -fn is_valid_identifier_middle_character(chr: char) -> bool { - is_valid_identifier_end_character(chr) || chr == '-' +fn is_dash(chr: char) -> bool { + chr == '-' } fn lex_identifier_raw(input: LexInput) -> InternalLexResult { @@ -260,21 +262,17 @@ fn lex_identifier_raw(input: LexInput) -> InternalLexResult { map( tuple::<_, _, InternalLexError, _>(( take_while1(is_valid_identifier_leading_character), - take_while(is_valid_identifier_middle_character), + take_while(is_valid_identifier_end_character), + recognize(many0(pair( + take_while1(is_dash), + take_while1(is_valid_identifier_end_character), + ))), )), - |(left, right)| format!("{}{}", left, right), + |(leading, middle, trailing_dash_vars)| { + format!("{leading}{middle}{trailing_dash_vars}") + }, ), )(input) - .and_then(|(remaining, result)| { - if !result.ends_with(is_valid_identifier_end_character) { - Err(nom::Err::Failure(InternalLexError::from_kind( - input, - LexErrorKind::ExpectedContext("valid identifier"), - ))) - } else { - Ok((remaining, result)) - } - }) } fn lex_command_or_identifier(input: LexInput) -> InternalLexResult { @@ -283,10 +281,10 @@ fn lex_command_or_identifier(input: LexInput) -> InternalLexResult { Ok((input, token)) } -fn lex_label(input: LexInput) -> InternalLexResult { +fn lex_target(input: LexInput) -> InternalLexResult { let (input, _) = tag("@")(input)?; let (input, label) = lex_identifier_raw(input)?; - Ok((input, Token::Label(label))) + Ok((input, Token::Target(label))) } fn lex_non_blocking(input: LexInput) -> InternalLexResult { @@ -299,7 +297,11 @@ fn lex_number(input: LexInput) -> InternalLexResult { Ok(( input, match integer_parse_result { - Ok(_) => Token::Integer(float_string.parse::().unwrap()), + Ok(_) => float_string + .parse::() + .map(Token::Integer) + .map_err(|e| InternalLexError::from_kind(input, e.into())) + .map_err(nom::Err::Failure)?, Err(_) => Token::Float(double(float_string)?.1), }, )) @@ -314,6 +316,8 @@ fn lex_modifier(input: LexInput) -> InternalLexResult { value(Token::Modifier(Modifier::Controlled), tag("CONTROLLED")), value(Token::Modifier(Modifier::Dagger), tag("DAGGER")), value(Token::Modifier(Modifier::Forked), tag("FORKED")), + value(Token::Offset, tag("OFFSET")), + value(Token::PauliSum, tag("PAULI-SUM")), value(Token::Permutation, tag("PERMUTATION")), value(Token::Sharing, tag("SHARING")), ), @@ -383,6 +387,8 @@ mod tests { use nom_locate::LocatedSpan; use rstest::*; + use crate::parser::common::tests::KITCHEN_SINK_QUIL; + use super::{lex, Command, Operator, Token}; #[test] @@ -481,9 +487,9 @@ mod tests { assert_eq!( tokens, vec![ - Token::Label("hello".to_owned()), + Token::Target("hello".to_owned()), Token::NewLine, - Token::Label("world".to_owned()) + Token::Target("world".to_owned()) ] ) } @@ -542,6 +548,11 @@ mod tests { case("_", vec![Token::Identifier("_".to_string())]), case("a", vec![Token::Identifier("a".to_string())]), case("_a-2_b-2_", vec![Token::Identifier("_a-2_b-2_".to_string())]), + case("a-2-%var", vec![ + Token::Identifier("a-2".to_string()), + Token::Operator(Operator::Minus), + Token::Variable("var".to_string()) + ]) )] fn it_lexes_identifier(input: &str, expected: Vec) { let input = LocatedSpan::new(input); @@ -563,66 +574,8 @@ mod tests { /// Test that an entire sample program can be lexed without failure. #[test] - fn whole_program() { - let input = "DECLARE ro BIT[1] - DEFGATE HADAMARD AS MATRIX: - \t(1/sqrt(2)),(1/sqrt(2)) - \t(1/sqrt(2)),((-1)/sqrt(2)) - - DEFGATE RX(%theta) AS MATRIX: - \tcos((%theta/2)),((-1i)*sin((%theta/2))) - \t((-1i)*sin((%theta/2))),cos((%theta/2)) - - DEFGATE Name AS PERMUTATION: - \t1,0 - \t0,1 - - DEFCIRCUIT SIMPLE: - \tX 0 - \tX 1 - - RX 0 - CZ 0 1 - MEASURE 0 ro[0] - RESET 0 - RESET - CAPTURE 0 \"out\" my_waveform() iq[0] - DEFCAL X 0: - \tPULSE 0 \"xy\" my_waveform() - - DEFCAL RX(%theta) 0: - \tPULSE 0 \"xy\" my_waveform() - - DEFCAL MEASURE 0 dest: - \tDECLARE iq REAL[2] - \tCAPTURE 0 \"out\" flat(duration: 1000000, iqs: (2+3i)) iq[0] - - DEFFRAME 0 \"xy\": - \tSAMPLE-RATE: 3000 - - DEFFRAME 0 \"xy\": - \tDIRECTION: \"rx\" - \tCENTER-FREQUENCY: 1000 - \tHARDWARE-OBJECT: \"some object\" - \tINITIAL-FREQUENCY: 2000 - \tSAMPLE-RATE: 3000 - - DELAY 0 100 - DELAY 0 \"xy\" 100000000 - FENCE 0 - FENCE 0 1 - PULSE 0 \"xy\" my_waveform() - PULSE 0 1 \"cz\" my_parametrized_waveform(a: 1) - RAW-CAPTURE 0 \"out\" 200000000 iqs[0] - SET-FREQUENCY 0 \"xy\" 5400000000 - SET-PHASE 0 \"xy\" pi - SET-SCALE 0 \"xy\" pi - SHIFT-FREQUENCY 0 \"ro\" 6100000000 - SHIFT-PHASE 0 \"xy\" (-pi) - SHIFT-PHASE 0 \"xy\" (%theta*(2/pi)) - SWAP-PHASES 2 3 \"xy\" 3 4 \"xy\""; - - let input = LocatedSpan::new(input); + fn kitchen_sink() { + let input = LocatedSpan::new(KITCHEN_SINK_QUIL); lex(input).unwrap(); } diff --git a/src/parser/lexer/quoted_strings.rs b/quil-rs/src/parser/lexer/quoted_strings.rs similarity index 85% rename from src/parser/lexer/quoted_strings.rs rename to quil-rs/src/parser/lexer/quoted_strings.rs index df3cc473..6d8e5cb0 100644 --- a/src/parser/lexer/quoted_strings.rs +++ b/quil-rs/src/parser/lexer/quoted_strings.rs @@ -95,6 +95,7 @@ fn surrounded( #[cfg(test)] mod tests { use super::*; + use crate::instruction::QuotedString; use nom::Finish; use nom_locate::LocatedSpan; use rstest::rstest; @@ -107,10 +108,22 @@ mod tests { #[case("\"foo bar (baz) 123\" after", "foo bar (baz) 123", " after")] #[case(r#""{\"name\": \"quoted json\"}""#, r#"{"name": "quoted json"}"#, "")] #[case(r#""hello"\n"world""#, "hello", "\\n\"world\"")] - fn test_string_parser(#[case] input: &str, #[case] output: &str, #[case] leftover: &str) { - let input = LocatedSpan::new(input); - let (remaining, parsed) = unescaped_quoted_string(input).finish().unwrap(); + #[case( + "\"a \\\"string\\\" \n with newlines\"", + "a \"string\" \n with newlines", + "" + )] + fn string_parser(#[case] input: &str, #[case] output: &str, #[case] leftover: &str) { + let span = LocatedSpan::new(input); + let (remaining, parsed) = unescaped_quoted_string(span).finish().unwrap(); assert_eq!(parsed, output); assert_eq!(remaining.fragment(), &leftover); + let round_tripped = QuotedString(&parsed).to_string(); + assert!( + input.starts_with(&round_tripped), + "expected `{}` to start with `{}`", + input, + round_tripped + ); } } diff --git a/src/parser/lexer/wrapped_parsers.rs b/quil-rs/src/parser/lexer/wrapped_parsers.rs similarity index 100% rename from src/parser/lexer/wrapped_parsers.rs rename to quil-rs/src/parser/lexer/wrapped_parsers.rs diff --git a/src/parser/macros.rs b/quil-rs/src/parser/macros.rs similarity index 100% rename from src/parser/macros.rs rename to quil-rs/src/parser/macros.rs diff --git a/src/parser/mod.rs b/quil-rs/src/parser/mod.rs similarity index 98% rename from src/parser/mod.rs rename to quil-rs/src/parser/mod.rs index f3f039e4..5129ded4 100644 --- a/src/parser/mod.rs +++ b/quil-rs/src/parser/mod.rs @@ -31,7 +31,7 @@ mod token; pub(crate) use error::{ErrorInput, InternalParseError}; pub use error::{ParseError, ParserErrorKind}; -pub use lexer::{LexError, LexErrorKind}; +pub use lexer::LexError; pub use token::{Token, TokenWithLocation}; type ParserInput<'a> = &'a [TokenWithLocation<'a>]; diff --git a/src/parser/token.rs b/quil-rs/src/parser/token.rs similarity index 67% rename from src/parser/token.rs rename to quil-rs/src/parser/token.rs index 3a3aad92..18210b27 100644 --- a/src/parser/token.rs +++ b/quil-rs/src/parser/token.rs @@ -1,3 +1,4 @@ +use crate::instruction::QuotedString; use crate::parser::lexer::{Command, DataType, LexInput, LexResult, Modifier, Operator}; use std::fmt; use std::fmt::Formatter; @@ -78,7 +79,7 @@ pub enum Token { Identifier(String), Indentation, Integer(u64), - Label(String), + Target(String), LBracket, LParenthesis, NonBlocking, @@ -86,6 +87,8 @@ pub enum Token { Modifier(Modifier), NewLine, Operator(Operator), + Offset, + PauliSum, Permutation, RBracket, RParenthesis, @@ -101,28 +104,30 @@ impl fmt::Display for Token { Token::As => write!(f, "AS"), Token::Colon => write!(f, ":"), Token::Comma => write!(f, ","), - Token::Command(cmd) => write!(f, "{}", cmd), - Token::Comment(comment) => write!(f, "# {}", comment), - Token::DataType(typ) => write!(f, "{}", typ), - Token::Float(float) => write!(f, "{}", float), - Token::Identifier(ident) => write!(f, "{}", ident), + Token::Command(cmd) => write!(f, "{cmd}"), + Token::Comment(comment) => write!(f, "# {comment}"), + Token::DataType(typ) => write!(f, "{typ}"), + Token::Float(float) => write!(f, "{float}"), + Token::Identifier(ident) => write!(f, "{ident}"), Token::Indentation => write!(f, " "), - Token::Integer(i) => write!(f, "{}", i), - Token::Label(label) => write!(f, "{}", label), + Token::Integer(i) => write!(f, "{i}"), + Token::Target(label) => write!(f, "{label}"), Token::LBracket => write!(f, "["), Token::LParenthesis => write!(f, "("), Token::NonBlocking => write!(f, "NONBLOCKING"), Token::Matrix => write!(f, "MATRIX"), - Token::Modifier(m) => write!(f, "{}", m), + Token::Modifier(m) => write!(f, "{m}"), Token::NewLine => write!(f, "NEWLINE"), - Token::Operator(op) => write!(f, "{}", op), + Token::Operator(op) => write!(f, "{op}"), + Token::Offset => write!(f, "OFFSET"), + Token::PauliSum => write!(f, "PAULI-SUM"), Token::Permutation => write!(f, "PERMUTATION"), Token::RBracket => write!(f, "]"), Token::RParenthesis => write!(f, ")"), Token::Semicolon => write!(f, ";"), Token::Sharing => write!(f, "SHARING"), - Token::String(s) => write!(f, "{:?}", s), - Token::Variable(v) => write!(f, "{}", v), + Token::String(s) => write!(f, "{}", QuotedString(s)), + Token::Variable(v) => write!(f, "{v}"), } } } @@ -130,31 +135,33 @@ impl fmt::Display for Token { impl fmt::Debug for Token { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - Token::As => write!(f, "{}", self), + Token::As => write!(f, "{self}"), Token::Colon => write!(f, "COLON"), Token::Comma => write!(f, "COMMA"), - Token::Command(cmd) => write!(f, "COMMAND({})", cmd), - Token::Comment(comment) => write!(f, "COMMENT({:?})", comment), - Token::DataType(typ) => write!(f, "DATATYPE({})", typ), - Token::Float(float) => write!(f, "FLOAT({})", float), - Token::Identifier(id) => write!(f, "IDENTIFIER({})", id), + Token::Command(cmd) => write!(f, "COMMAND({cmd})"), + Token::Comment(comment) => write!(f, "COMMENT({comment:?})"), + Token::DataType(typ) => write!(f, "DATATYPE({typ})"), + Token::Float(float) => write!(f, "FLOAT({float})"), + Token::Identifier(id) => write!(f, "IDENTIFIER({id})"), Token::Indentation => write!(f, "INDENT"), - Token::Integer(i) => write!(f, "INTEGER({})", i), - Token::Label(label) => write!(f, "@{}", label), + Token::Integer(i) => write!(f, "INTEGER({i})"), + Token::Target(label) => write!(f, "@{label}"), Token::LBracket => write!(f, "LBRACKET"), Token::LParenthesis => write!(f, "LPAREN"), - Token::NonBlocking => write!(f, "{}", self), - Token::Matrix => write!(f, "{}", self), - Token::Modifier(m) => write!(f, "MODIFIER({})", m), + Token::NonBlocking => write!(f, "{self}"), + Token::Matrix => write!(f, "{self}"), + Token::Modifier(m) => write!(f, "MODIFIER({m})"), Token::NewLine => write!(f, "NEWLINE"), - Token::Operator(op) => write!(f, "OPERATOR({})", op), - Token::Permutation => write!(f, "{}", self), + Token::Operator(op) => write!(f, "OPERATOR({op})"), + Token::Offset => write!(f, "{self}"), + Token::PauliSum => write!(f, "{self}"), + Token::Permutation => write!(f, "{self}"), Token::RBracket => write!(f, "RBRACKET"), Token::RParenthesis => write!(f, "RPAREN"), Token::Semicolon => write!(f, "SEMICOLON"), - Token::Sharing => write!(f, "{}", self), - Token::String(s) => write!(f, "STRING({:?})", s), - Token::Variable(v) => write!(f, "VARIABLE({})", v), + Token::Sharing => write!(f, "{self}"), + Token::String(s) => write!(f, "STRING({s:?})"), + Token::Variable(v) => write!(f, "VARIABLE({v})"), } } } diff --git a/quil-rs/src/program/analysis/control_flow_graph.rs b/quil-rs/src/program/analysis/control_flow_graph.rs new file mode 100644 index 00000000..98f60170 --- /dev/null +++ b/quil-rs/src/program/analysis/control_flow_graph.rs @@ -0,0 +1,772 @@ +//! Construction and analysis of a control flow graph (CFG) for a Quil program. + +// Copyright 2024 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. + +use std::{ + collections::{BTreeMap, HashMap}, + fmt::Debug, +}; + +use crate::{ + instruction::{ + Instruction, InstructionHandler, Jump, JumpUnless, JumpWhen, Label, MemoryReference, Target, + }, + program::{ + scheduling::{ + schedule::{ComputedScheduleError, ComputedScheduleItem, Schedule, TimeSpan, Zero}, + ScheduleError, ScheduledBasicBlock, Seconds, + }, + ProgramError, + }, + Program, +}; + +/// A control flow graph (CFG) is a representation of a program's control flow as a directed graph. +/// Each node in the graph is a basic block, a sequence of instructions with a single entry point +/// and a single exit point. The edges in the graph represent control flow between basic blocks. +#[derive(Clone, Debug, Default)] +pub struct ControlFlowGraph<'p> { + blocks: Vec>, +} + +impl<'p> ControlFlowGraph<'p> { + /// Returns `true` if the program contains dynamic control flow, i.e. `JUMP-WHEN` or `JUMP-UNLESS` + pub fn has_dynamic_control_flow(&self) -> bool { + self.blocks + .iter() + .any(|block| block.terminator().is_dynamic()) + } + + /// Returns the basic blocks in the control flow graph. + pub fn into_blocks(self) -> Vec> { + self.blocks + } +} + +#[derive(Clone, Debug)] +pub struct ControlFlowGraphOwned { + blocks: Vec, +} + +impl From> for ControlFlowGraphOwned { + fn from(value: ControlFlowGraph) -> Self { + let blocks = value + .blocks + .into_iter() + .map(BasicBlockOwned::from) + .collect(); + ControlFlowGraphOwned { blocks } + } +} + +impl<'p> From<&'p ControlFlowGraphOwned> for ControlFlowGraph<'p> { + fn from(value: &'p ControlFlowGraphOwned) -> Self { + let blocks = value.blocks.iter().map(BasicBlock::from).collect(); + ControlFlowGraph { blocks } + } +} + +#[derive(Clone, Debug, Default)] +pub struct BasicBlock<'p> { + /// The label of the basic block, if any. An unlabeled basic block cannot be a target of a jump, but can + /// be entered by a [`BasicBlockTerminator::Continue`] from the preceding block or program start. + label: Option<&'p Target>, + + /// The instructions within the basic block, not including its terminator. + instructions: Vec<&'p Instruction>, + + /// The offset of the start of this block from the containing program, in instruction count. + /// For the first block in the program, this is `0`. This counts only "body" instructions, not + /// `DEFCAL`, `DEFFRAME`, et al. + /// + /// This is intended for use in debugging and source mapping. + instruction_index_offset: usize, + + /// The terminator of the basic block, which determines the control flow to the next basic block. + terminator: BasicBlockTerminator<'p>, +} + +impl<'p> BasicBlock<'p> { + pub fn label(&self) -> Option<&'p Target> { + self.label + } + + pub fn instruction_index_offset(&self) -> usize { + self.instruction_index_offset + } + + pub fn instructions(&self) -> &[&'p Instruction] { + self.instructions.as_ref() + } + + pub fn terminator(&self) -> &BasicBlockTerminator<'p> { + &self.terminator + } + + /// Compute the flattened schedule for this [`BasicBlock`] in terms of seconds, + /// using a default built-in calculation for the duration of scheduled instructions. + /// + /// # Arguments + /// + /// * `program` - The program containing this basic block. This is used to retrieve frame + /// and calibration information. + pub fn as_schedule_seconds( + &self, + program: &Program, + ) -> Result, BasicBlockScheduleError> { + self.as_schedule( + program, + ScheduledBasicBlock::get_instruction_duration_seconds, + ) + } + + /// Compute the schedule for this [`BasicBlock`] in terms of a generic unit of time, + /// using a provided function to calculate the duration of scheduled instructions in that unit. + /// + /// # Arguments + /// + /// * `program` - The program containing this basic block. This is used to retrieve frame + /// and calibration information. + /// * `get_duration` - A function that takes a program and an instruction and returns the + /// duration of the instruction in the desired time unit, or `None` if the instruction's + /// duration is not known. + /// + /// Note: when an instruction is expanded, the "time" of that original instruction includes + /// the times of all of the resulting instructions. This may cause gate times to be + /// longer than a user might expect. + /// + /// To understand why, consider a program like this: + /// + /// ```text + /// # One-qubit frame + /// DEFFRAME 0 "a": + /// ATTRIBUTE: 1 + /// + /// # Two-qubit frame + /// DEFFRAME 0 1 "b": + /// ATTRIBUTE: 1 + /// + /// DEFCAL A 0: + /// PULSE 0 "a" flat(duration: 1.0) + /// + /// DEFCAL B 0 1: + /// FENCE 1 + /// PULSE 0 1 "b" flat(duration: 1.0) + /// + /// A 0 + /// B 0 1 + /// ``` + /// + /// `B 0` will be scheduled from time 0 to time 2, because its inner `FENCE` is scheduled for time 0. + /// This may be unexpected if the user expects to see only the timing of the inner `PULSE`. + pub fn as_schedule( + &self, + program: &'p Program, + get_duration: F, + ) -> Result, BasicBlockScheduleError> + where + F: Fn(&Program, &Instruction) -> Option