From e32984b36d9f60b379e3a9b6696584ecdce64ec4 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Tue, 23 Nov 2021 21:29:37 -0800 Subject: [PATCH] Documentation and Cleanup for Release (#1) --- .github/workflows/ci.yml | 8 +- README.md | 21 ++- docs/building.md | 125 +++++++++++++++ docs/compatibility.md | 29 ++++ docs/installing.md | 4 + eng/build.ps1 | 3 +- eng/psakefile.ps1 | 9 +- eng/utils.ps1 | 14 +- examples/generator/bell_pair.py | 41 +++++ examples/generator/bernstein_vazirani.py | 189 +++++++++++++++++++++++ examples/jit/bernstein_vazirani.bc | Bin 0 -> 7492 bytes examples/jit/bernstein_vazirani.py | 21 +++ pyqir-generator/Cargo.toml | 2 + pyqir-generator/README.md | 93 +++++++++-- pyqir-generator/src/emit.rs | 10 +- pyqir-generator/tests/test_api.py | 17 +- pyqir-generator/tox.ini | 1 - pyqir-jit/Cargo.toml | 4 +- pyqir-jit/README.md | 87 +++++++++-- pyqir-jit/pyqir_jit/__init__.py | 3 +- pyqir-jit/pyqir_jit/gatelogger.py | 82 ++++++++++ pyqir-jit/pyqir_jit/gateset.py | 10 +- pyqir-jit/pyqir_jit/jit.py | 23 +-- pyqir-jit/src/jit.rs | 67 +++----- pyqir-jit/src/python.rs | 8 +- pyqir-jit/tests/test_api.py | 77 ++------- pyqir-jit/tox.ini | 1 - pyqir-parser/Cargo.toml | 2 + pyqir-parser/README.md | 25 +-- pyqir-parser/tox.ini | 1 - qirlib/Cargo.toml | 4 + qirlib/src/context.rs | 29 ++++ 32 files changed, 804 insertions(+), 206 deletions(-) create mode 100644 docs/building.md create mode 100644 docs/compatibility.md create mode 100644 docs/installing.md create mode 100644 examples/generator/bell_pair.py create mode 100644 examples/generator/bernstein_vazirani.py create mode 100644 examples/jit/bernstein_vazirani.bc create mode 100644 examples/jit/bernstein_vazirani.py create mode 100644 pyqir-jit/pyqir_jit/gatelogger.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9fd74fc5..07b41161 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,9 +1,13 @@ name: CI on: push: - branches: [ $default-branch ] + branches: + - main pull_request: - branches: [ $default-branch ] + branches: + - main + - feature/* + - features/* env: CARGO_TERM_COLOR: always diff --git a/README.md b/README.md index 48724939..7565ae35 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,21 @@ -# PyQIR: Python APIs for QIR +# PyQIR +PyQIR is a set of APIs for generating, parsing, and evaluating [Quantum Intermediate Representation (QIR)](https://github.com/microsoft/qsharp-language/tree/main/Specifications/QIR#quantum-intermediate-representation-qir). + +- [pyqir-generator](./pyqir-generator/README.md) : Python API for generating QIR ([bitcode](https://www.llvm.org/docs/BitCodeFormat.html#id10) and [IR](https://llvm.org/docs/LangRef.html)). + - Examples + - [Bernstein-Vazirani](examples/generator/bernstein_vazirani.py) + - [Bell pair](examples/generator/bell_pair.py) +- [pyqir-jit](./pyqir-jit/README.md) : Python API for evaluating QIR using [JIT compilation](https://en.wikipedia.org/wiki/Just-in-time_compilation). + - Examples + - [Bernstein-Vazirani](examples/jit/bernstein_vazirani.py) +- [pyqir-parser](./pyqir-parser/README.md) : Python API for parsing QIR into an object model for analysis. +- [qirlib](./qirlib/README.md): Rust library wrapping [LLVM](https://llvm.org/) libraries for working with QIR. + +## Documentation + +- [Installing PyQIR](./docs/installing.md) +- [Building PyQIR from source](./docs/building.md) +- [Compatibility](./docs/compatibility.md) + +## Installation \ No newline at end of file diff --git a/docs/building.md b/docs/building.md new file mode 100644 index 00000000..1dd752e0 --- /dev/null +++ b/docs/building.md @@ -0,0 +1,125 @@ +# Building PyQIR from Source + +## Local Environment + +### Requirements + +- [Rust 1.56+](https://rustup.rs/) +- [Python 3.6+](https://www.python.org) +- [PowerShell 7+ (Core)](https://github.com/powershell/powershell#get-powershell) +- [LLVM/Clang 11.1.0](https://llvm.org/) - See [Installing LLVM](#installing-llvm) + +### Linux (Ubuntu) + +Install python and libs: + +```bash +sudo apt-get install -y --no-install-recommends python3-dev python3-pip +python3 -m pip install --user -U pip +python3 -m pip install --user maturin tox +``` + +Install Rust from [rustup](https://rustup.rs/). + +### Windows + +Install Python 3.6+ from one of the following and make sure it is added to the path. +- [Windows store](https://docs.microsoft.com/en-us/windows/python/beginners#install-python) +- [Miniconda](https://docs.conda.io/en/latest/miniconda.html#latest-miniconda-installer-links) +- [Python.org](https://www.python.org/downloads/) + +In a command prompt: + +```bash +python -m pip install --user maturin tox +``` + +Install Rust from [rustup](https://rustup.rs/). + +### MacOS + +Install Python 3.6+ from [Python.org](https://www.python.org/downloads/macos/). + +or brew: +``` +brew install 'python@3.9' +python -m pip install --user maturin tox +``` + +Install Rust from [rustup](https://rustup.rs/). + +### Installing Clang + +If you have a working installation of LLVM and [Clang](https://clang.llvm.org/), each project can be built by running `cargo build` in the project directory. If not, you can install Clang manually: + +- Linux (Ubuntu) + - ``` + apt-get update + apt-get install -y clang-11 lldb-11 lld-11 clangd-11 + ``` +- Windows + - Download and install the `LLVM-11.1.0-win64.exe` from the [11.1.0 Release](https://github.com/llvm/llvm-project/releases/tag/llvmorg-11.1.0) page. + - This package only contains the Clang components. There is no package that contains Clang and LLVM. +- MacOS + - Should be preinstalled. + +### Installing LLVM + +The build scripts will automatically download an LLVM toolchain which is detailed in the [Development](#development) section. The build installs the toolchain to `$HOME/.pyqir` (Windows: `$HOME\.pyqir`) and configures Rust to use this installation by setting the `LLVM_SYS_110_PREFIX` environment variable in the root `.cargo/config.toml` + +## Development + +Running `build.(ps1|sh|cmd)` will initialize your local environment and build the solution. The [Environment Variables](#environment-variables) section details ways to change this behavior. + +Within each project folder, the build can be run specifically for that project. + +Build commands: +- `maturin build`: Build the crate into python packages + - `maturin build --release`: Build and pass --release to cargo + - `maturin build --help`: to view more options +- `maturin develop`: Installs the crate as module in the current virtualenv +- `maturin develop && pytest`: Installs the crate as module in the current virtualenv and runs the Python tests + +If you do not wish to package and test the Python wheels, `cargo` can be used to build the project and run Rust tests. + +- `cargo build`: Build the Rust cdylib + - `cargo build --release`: Build the Rust cdylib in release mode +- `cargo test`: Build and run the Rust cdylib tests + - `cargo test --release`: Build and run the Rust cdylib tests in release mode + +[Tox](https://tox.readthedocs.io/) can be used as well: + +Two targets are available for tox: +- `python -m tox -e test` + - Runs the python tests in an isolated environment +- `python -m tox -e pack` + - Packages all wheels in an isolated environment + + +### Environment Variables + +- `PYQIR_LLVM_EXTERNAL_DIR` + - Path to where LLVM is already installed by user. Useful if you want to use your own LLVM builds for testing. +- `PYQIR_DOWNLOAD_LLVM` + - Indicator to whether the build should download LLVM cached builds. + - Build will download LLVM if needed unless this variable is defined and set to `false` +- `PYQIR_LLVM_BUILDS_URL` + - Url from where LLVM builds will be downloaded. + - Default: `https://msquantumpublic.blob.core.windows.net/llvm-builds` +- `PYQIR_CACHE_DIR` + - Root insallation path for LLVM builds + - Default if not specified: + - Linux/Mac: `$HOME/.pyqir` + - Windows: `$HOME\.pyqir` +- `LLVM_SYS_110_PREFIX` + - Required by `llvm-sys` and will be set to the version of LLVM used for configuration. + - Version dependent and will change as LLVM is updated. (`LLVM_SYS_120_PREFIX`, `LLVM_SYS_130_PREFIX`, etc) + - Not needed if you have a working LLVM installation on the path. + +### Packaging + +The `build.(ps1|sh|cmd)`, `maturin`, and `tox` builds all generate Python wheels to the `target/wheels` folder. The default Python3 installation will be used targeting Python ABI 3.6. + +The manylinux support uses a Docker image in the build scripts to run the builds in the CI environment. + +The Windows packaging will look for python installations available and build for them. More information on [supporting multiple python versions on Windows](https://tox.readthedocs.io/en/latest/developers.html?highlight=windows#multiple-python-versions-on-windows) diff --git a/docs/compatibility.md b/docs/compatibility.md new file mode 100644 index 00000000..86a7582a --- /dev/null +++ b/docs/compatibility.md @@ -0,0 +1,29 @@ +# PyQIR compatible systems + +## Operating systems + +PyQIR runs on most x86-64 operating systems that can run Python 3.6+; however, not all of these systems are equally compatible. Operating systems are grouped into tiers that represent the level of compatibility users can expect. + +* Tier 1 systems are compatible. For tier 1 systems: + * operating system is used in automated tests + * installation packages provided for them +* Tier 2 systems should be compatible with PyQIR and can be used relatively easily. For tier 2 systems: + * informal testing may have been done + * the packages for tier 1 systems will likely work in tier 2 systems + +### Tier 1 + +- Windows Server 2019 +- [Ubuntu 20.04](https://wiki.ubuntu.com/FocalFossa/ReleaseNotes) +- [Debian 9](https://www.debian.org/releases/stretch/) +- macOS 10.15 + +### Tier 2 +- Windows 10 +- Windows 11 +- [Ubuntu 18.04](https://wiki.ubuntu.com/BionicBeaver/ReleaseNotes) +- [Debian 10](https://www.debian.org/releases/buster/) +- [Debian 11](https://www.debian.org/releases/bullseye/) +- macOS 10.7-14 +- macOS 11 +- macOS 12 \ No newline at end of file diff --git a/docs/installing.md b/docs/installing.md new file mode 100644 index 00000000..a8e7b79a --- /dev/null +++ b/docs/installing.md @@ -0,0 +1,4 @@ +# Installing PyQIR + +This documentation will be updated soon. +In the meantime, instructions for how to build PyQIR from source can be found [here](building.md) \ No newline at end of file diff --git a/eng/build.ps1 b/eng/build.ps1 index 830f9ef2..fb718925 100644 --- a/eng/build.ps1 +++ b/eng/build.ps1 @@ -1,7 +1,8 @@ - # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +#Requires -PSEdition Core + <# .SYNOPSIS Build: Bootstraps psake and invokes the build. diff --git a/eng/psakefile.ps1 b/eng/psakefile.ps1 index a8df1439..3f8b5784 100644 --- a/eng/psakefile.ps1 +++ b/eng/psakefile.ps1 @@ -150,6 +150,7 @@ function Install-LlvmFromSource { $Env:PKG_NAME = Get-PackageName $Env:CMAKE_INSTALL_PREFIX = $packagePath $Env:INSTALL_LLVM_PACKAGE = $true + Assert $false -failureMessage "TODO: Migration in progress" . (Join-Path (Get-RepoRoot) "build" "llvm.ps1") Use-LlvmInstallation $packagePath } @@ -172,18 +173,18 @@ function Initialize-Environment { Use-ExternalLlvmInstallation } else { - $PYQIR_LLVM_PACKAGE_GIT_VERSION = Get-LlvmSha - Write-BuildLog "llvm-project sha: $PYQIR_LLVM_PACKAGE_GIT_VERSION" + $llvmSha = Get-LlvmSha + Write-BuildLog "llvm-project sha: $llvmSha" $packageName = Get-PackageName $packagePath = Get-InstallationDirectory $packageName if (Test-Path $packagePath) { - Write-BuildLog "LLVM target $($PYQIR_LLVM_PACKAGE_GIT_VERSION) is already installed." + Write-BuildLog "LLVM target $($llvmSha) is already installed." # LLVM is already downloaded Use-LlvmInstallation $packagePath } else { - Write-BuildLog "LLVM target $($PYQIR_LLVM_PACKAGE_GIT_VERSION) is not installed." + Write-BuildLog "LLVM target $($llvmSha) is not installed." if (Test-AllowedToDownloadLlvm) { Write-BuildLog "Downloading LLVM target $packageName " Install-LlvmFromBuildArtifacts $packagePath diff --git a/eng/utils.ps1 b/eng/utils.ps1 index 7d017b70..e70452e7 100644 --- a/eng/utils.ps1 +++ b/eng/utils.ps1 @@ -160,19 +160,9 @@ function Use-LlvmInstallation { } # Gets the LLVM version git hash -# on the CI this will come as an env var function Get-LlvmSha { - # Sometimes the CI fails to initilize PYQIR_LLVM_PACKAGE_GIT_VERSION correctly - # so we need to make sure it isn't empty. - if ((Test-Path env:\PYQIR_LLVM_PACKAGE_GIT_VERSION) -and ![string]::IsNullOrWhiteSpace($Env:PYQIR_LLVM_PACKAGE_GIT_VERSION)) { - Write-BuildLog "Use environment submodule version: $($env:PYQIR_LLVM_PACKAGE_GIT_VERSION)" - $env:PYQIR_LLVM_PACKAGE_GIT_VERSION - } - else { - $sha = exec { Get-LlvmSubmoduleSha } - Write-BuildLog "Use cached submodule version: $sha" - $sha - } + $sha = exec { Get-LlvmSubmoduleSha } + $sha } function Get-PackageName { diff --git a/examples/generator/bell_pair.py b/examples/generator/bell_pair.py new file mode 100644 index 00000000..4e2636a6 --- /dev/null +++ b/examples/generator/bell_pair.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +# Copyright(c) Microsoft Corporation. +# Licensed under the MIT License. + +from pyqir_generator import QirBuilder + + +class BellPair: + """ + This operation creates a Bell pair and returns the result + of measuring each qubit. + """ + + def __init__(self): + self.builder = QirBuilder("Bell") + self.apply() + + def apply(self): + self.builder.add_quantum_register("qubit", 2) + self.builder.add_classical_register("output", 2) + + self.builder.h("qubit0") + self.builder.cx("qubit0", "qubit1") + + self.builder.m("qubit0", "output0") + self.builder.m("qubit1", "output1") + + def write_ir_file(self, file_path: str): + self.builder.write(file_path) + + def get_ir_string(self) -> str: + return self.builder.get_ir_string() + + def generate_ir_file(file_path: str): + instance = BellPair() + instance.write(file_path) + + +if __name__ == "__main__": + print(BellPair().get_ir_string()) diff --git a/examples/generator/bernstein_vazirani.py b/examples/generator/bernstein_vazirani.py new file mode 100644 index 00000000..92abde80 --- /dev/null +++ b/examples/generator/bernstein_vazirani.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 + +# Copyright(c) Microsoft Corporation. +# Licensed under the MIT License. + +from typing import List, Callable, Tuple +from pyqir_generator import QirBuilder + + +ParityFunction: type = Callable[[Tuple[List[str], str]], None] + + +class BernsteinVazirani: + """ + Implementation of the Bernstein-Vazirani quantum algorithm + + builder: QirBuilder + """ + + def __init__(self, qubit_count: int = 8, pattern: int = 12): + """ + :param qubit_count: number of qubits to use + :type qubit_count: int + :param pattern: integer representation of the bitstring pattern + :type pattern: int + """ + self.builder = QirBuilder("Bernstein-Vazirani") + bitstring = self.int_as_bool_array(pattern, qubit_count) + Uf = self.parity_operation(bitstring) + self.parity_via_fourier_sampling(Uf, qubit_count) + + def write_ir_file(self, file_path: str): + self.builder.write(file_path) + + def get_ir_string(self) -> str: + return self.builder.get_ir_string() + + def generate_ir_file(file_path: str, qubit_count=8, pattern=12): + instance = BernsteinVazirani(qubit_count, pattern) + instance.write(file_path) + + def parity_operation_impl(self, pattern: List[bool], + queryRegister: List[str], target: str) -> None: + """ + To demonstrate the Bernstein–Vazirani algorithm, we define + a function which returns black-box operations (List[str] => None) of + the form + + U_f |π‘₯βŒͺ|𝑦βŒͺ = |π‘₯βŒͺ|𝑦 βŠ• 𝑓(π‘₯)βŒͺ, + + In particular, we define 𝑓 by providing the pattern π‘Ÿβƒ—. Thus, we can + easily assert that the pattern measured by the Bernstein–Vazirani + algorithm matches the pattern we used to define 𝑓. + + We will typically only call this function by partially applying it from + within a matching function. + + :param pattern: The bitstring π‘Ÿβƒ— used to define the function 𝑓. + :type pattern: List[bool] + :param queryRegister: qubit ids to perform operations against + :type queryRegister: List[str] + :param target: name of the target qubit + :type target: str + """ + + if len(queryRegister) != len(pattern): + raise ValueError( + 'Length of input register must be equal to the pattern length.' + ) + + for patternBit, controlQubit in zip(pattern, queryRegister): + if patternBit: + self.builder.cx(controlQubit, target) + + def parity_operation(self, pattern: List[bool]) -> ParityFunction: + """ + Given a bitstring π‘Ÿβƒ— = (rβ‚€, …, rₙ₋₁), returns an operation implementing + a unitary π‘ˆ that acts on 𝑛 + 1 qubit ids as + π‘ˆ |π‘₯βŒͺ|𝑦βŒͺ = |π‘₯βŒͺ|𝑦 βŠ• 𝑓(π‘₯)βŒͺ, + where 𝑓(π‘₯) = Ξ£α΅’ π‘₯α΅’ π‘Ÿα΅’ mod 2. + + :param pattern: The bitstring π‘Ÿβƒ— used to define the function 𝑓. + :type pattern: List[bool] + Returns: + An operation implementing π‘ˆ. + """ + return lambda register, target: self.parity_operation_impl( + pattern, + register, + target + ) + + def parity_via_fourier_sampling(self, Uf: ParityFunction, n: int) -> None: + """ + parity_via_fourier_sampling implements the Bernstein-Vazirani quantum + algorithm. This algorithm computes for a given Boolean function that + is promised to be a parity 𝑓(π‘₯β‚€, …, π‘₯ₙ₋₁) = Ξ£α΅’ π‘Ÿα΅’ π‘₯α΅’ a result in + form of a bit vector (π‘Ÿβ‚€, …, π‘Ÿβ‚™β‚‹β‚) corresponding to the parity + function. Note that it is promised that the function is actually a + parity function. + + :param Uf: A quantum operation that implements + |π‘₯βŒͺ|𝑦βŒͺ ↦ |π‘₯βŒͺ|𝑦 βŠ• 𝑓(π‘₯)βŒͺ, where 𝑓 is a Boolean function that + implements a parity Ξ£α΅’ π‘Ÿα΅’ π‘₯α΅’. + :type Uf: ParityFunction + :param n: The number of bits of the input register |π‘₯βŒͺ. + :type n: int + + + Returns: + This function returns None but the generated QIR will ruturn an array + of type `Result[]` that contains the parity π‘Ÿβƒ— = (π‘Ÿβ‚€, …, π‘Ÿβ‚™β‚‹β‚). + The result output is inferred by the declaration of classical registers + + See Also + - For details see Section 1.4.3 of Nielsen & Chuang. + + References + - [ *Ethan Bernstein and Umesh Vazirani*, + SIAM J. Comput., 26(5), 1411–1473, 1997 ] + (https:#doi.org/10.1137/S0097539796300921) + """ + + # Now, we allocate n + 1 clean qubits. + # Note that the function Uf is defined on inputs of the form (x, y), + # where x has n bits and y has 1 bit. + self.builder.add_quantum_register("qubit", n) + self.builder.add_quantum_register("target", 1) + self.builder.add_classical_register("output", n) + + target = "target0" + queryRegister: List[str] = [] + for i in range(n): + queryRegister.append(f"qubit{i}") + + # The last qubit needs to be flipped so that the function will + # actually be computed into the phase when Uf is applied. + self.builder.x(target) + + # Now, a Hadamard transform is applied to each of the qubits. + for qubit in queryRegister: + self.builder.h(qubit) + + self.builder.h(target) + # We now apply Uf to the n+1 qubits, computing |x, yβŒͺ ↦ |x, y βŠ• f(x)βŒͺ. + Uf(queryRegister, target) + + # As the last step before the measurement, a Hadamard transform is + # applied to all qubits except last one. We could apply the transform + # to the last qubit also, but this would not affect the final outcome. + for qubit in queryRegister: + self.builder.h(qubit) + + self.builder.reset(target) + + output_ids: List[str] = [] + for i in range(n): + output_ids.append(f"output{i}") + + for qubit, output in zip(queryRegister, output_ids): + self.builder.m(qubit, output) + self.builder.reset(qubit) + + def int_as_bool_array(self, number: int, bits: int) -> List[bool]: + """ + Produces a binary representation of a non-negative integer, using the + little-endian representation for the returned array. + + :param number: A non-negative integer to be converted to an array of + boolean values. + :type number: int + :param bits: The number of bits in the binary representation + of `number`. + :type bits: int + + Returns: An list of boolean values representing `number`. + """ + if(bits < 0 or bits > 63): + raise ValueError("`bits` must be between 0 and 63 {2^bits}") + if(number < 0 or number >= 2 ** bits): + raise ValueError( + f"`number` must be between 0 and 2^{bits} - 1 but was {number}" + ) + + return [bool(number & (1 << n)) for n in range(bits)] + + +if __name__ == "__main__": + print(BernsteinVazirani().get_ir_string()) diff --git a/examples/jit/bernstein_vazirani.bc b/examples/jit/bernstein_vazirani.bc new file mode 100644 index 0000000000000000000000000000000000000000..650fec61dc29217ef2cf09bd7799f557c036d074 GIT binary patch literal 7492 zcma)A3se)=x}G7)WPnLByd)74CKWAms7)fYAbOk-5LB!|twl?35+Dc)A%vhvYn?nG zv_%`mV`*#8La+7eT|GVVsy)=(btMoLmA3e((xa=w6_vKu(w6pkTP=6bBY?C$J$o%O zd(Zy<$NvBC-+K>Py3Wtiq9I5CK~OQKN?fw$f6AWw>Dcn{0xBN{@01t>EkhtEJVXhF zfblf&Cv?o%q3z)>MsQ8WPS}mj6reH%|CM4YJ|`q=XL!~Su8B@ZrAxy!w&px*Ksh5j z($j)#cZQVqpogOg-J)jV46L!arO8RjUiAyUq8-HP9-#Ln0)o=OQ}akqC{fzTZ=JE< z4SsI8_j;ov^S8HzyNB}e_O5sbNo9m23i+vdFdXx4-#)bS6bjAT1!-UU!>VNRfI5Vl zHq)pt;kTm_M0*SEG;W8Y&r;s}<}9bC5RW>(=Wt}?vHTJ~WM0y~cbQBvJ+$uZ2$?s$ z(jDr|GS?H*)MRIq_H9i$j?Viqf^W{NNZqgL3S_+#d9>$g{yDdCX1(_gw((`}Dd`NT zd9^!tTV>9{HRv- zPGc4Uo=YTX#7=Ef%Pt5`&1&28Z|}a=k+SLI&uPK>qT280#(c!5(@-Y8i%vagC}#AOLMMF)j~(OEPVpJ2Nh<=3 zpc|p0Mc>;J!H>wlbn1|@G$_WRsL;L5z6sI4eAQh$jB+f3v zSph}}#ts!aT-3xH2s7q2iWLI>#497DOKwefg`*R%I-#ajRfARJ#2W}KZLR2uVQSJV zpsFrm-3T1?08B|cy=QYf32X}N=A#>?!jhkxgHEwXlx%~iIA3|oIYrecPn78qC>R6? zryYAko<>1NV4i6Ny~a-#mO*eQ_XZ+q$JVzTpUWhjdkex9lT;Ts#bQInN*)oE15ciG z#bB$9oMQ3YLmDX$;!*>WOT)HJlXiemj7I~o<bftjmKokDVwDA0EN{f{4pk;N`@a3&9;QgGU=M$smfMdW*1 zLTBZk;_E_ok)lnL6yX!lH3>(kLbm7@qqj@B`8~Ymq}5kdMWb*S7x6s<0xU zUS{t|^hTrb`EuctohJRJ`Jzz41eP<9nIkYo_?K%J_goe%6Fs_UbZvbQx{TZ%23fE?s&vFLPJ-B zXXYhdrmq`2UE#47dTdsgy-JmeMMASHu@_zqYFktDz7 zS9F10NC|c!#kGO>p+Wftzx+j5K|8FJkC>2Ejjr_G?o6L<0SL(buK7c{%w{m`ZaEnSR_fUV{6U2cNkUtj zqv!@MzSAkbsTY5zn&H2oRSXTr_7BRz##ojo%k;Sx9CgLHyVF~A8NJ=}JNy|v-C&LL zd-IOgphDnP4G3VNM|Sm;_>NKX)c`U=NN%bmHwnoPda(r;-%yFc@Vfy7?4m<@@jaDp z@b60bt-bLhFypyt(WOtj(?PO)-H-R{=Jo34HS6YqRrl%s2ps~Pk^xSIhuM-qu7nMR z);8S&5OSX`qp#a}EB@48(Xf`L(k-Q&jls0k8%FVMJqWF2hz_viwq88yqa4D`NlfPJ zJ-};v-G$aY-RVc+%>AwfyXejJJg8`TQx*2k-|x!Y0}{$x(C(TKlI!iJi_=6v&{DAZ zNAa~Obn$~~gc4_?Z|owqEs%FVzkCJXtViG0V>ij2)4|*fgbz85eI5R07^c99R;?-~ zAp%82@hwsuLY>+uAqVLRLTZB$IR{x8g!KEH9kdpGoxWfOf&j(jr;eO$QYld6Hw)oE zX|1^!3i18M|8_<2c#u{#e3$SwiBKg$vEza|I`O7`Fw61 zn-E`!_~mK>!uhVhNETrj(AoZ_i;8=Ka8nRF9{7=$bB^d>z4tk~Sl8_B_zT^L8^=4@ zanZg$tBk)XKtUvqy;mSrt7x5;=n%DZvaV!7&xEemgSrmjyZjINm7H(%j<0cPBe%vC zO44QGWud4s&`Fzl_i0~%a1Cr{0_;B(#)hizaF!2w$hCLd4{MPw`4fb58(32qSR)kH zXbH5_I@(txxguO4AcWKs2gc1NH>#!TB(MP9!RUZ72t>z$@C2|rI6E>fYhk@RSlG-< z=i=~(Y0f8mj2+!bwh;*izeE+rleVxqy&a~T&UWFPaJvfdfKIygd=?>;VVn9Xv@e(q zBbINHa!J2eY%&IYT5S?>4y+ZVan6^=TnIY{$pTK$W$1e> zyv!$*@m~uO$yknTj}4i}g=u?i^8!E@zB8DneS!?uMVIDaV=$Os#$GZ?H0T_`bWJfs zYk4}qo@2B;9p9>|BvC%hT87=>b*f@LO@6Z%M3xn}lHTD+D8-AyQcKxf&9kBRI8x2C z5sOUy6pn^)!D)^i;evCvJ@#&pdbM7czK7_DF7M&$VHl@^bRH)6bOAc!Ak&OxJpGQp zSxXo6E^YcSSKCEj8io4-$!GU@IePaii6GPa9fgg6=>%Xp!&1UF!lfn2WgF(w-|?AN z@|4mwu%cbbG2gGFf<@rxeG>+XfCTl)SSE*YFh!?Dk9-bsDC>u=go9a#|7}vo)&9SG zsF+|^SUe4EDGsouu=UaJh~_BPC0xY?2TfRQ;#_<|-^c++OyfY$ig94xI8ZeXR03m6 zJ$89k78anw5a=tgm#!9OTpHC!{haI0e(d6_SL3>IDm#a-OO2}Dfa##fmRXso6PqSN zcy-GpX`gJ(&by>}a7-YNP!^m^T=UH6nDb)(jw1(GQTZ3fo>dI#FY1>4UqS@f)4?iQA6(taFI+TA_b)Oc3#)Qpr9~dl%15sp zm^5{wYT2Zz>&W+$q%12ZP2Db=G-YrwpYDcet z0PYSjp{S;Pi(-5mqXWL21>XbU#Vq(X0IxpDfNAGt(^xRQ{04j?JD2_zijWgTDfqff z7%>sG=bnvca{Ld*r?ILRCriG+H;r~{)!6sIssJJB!BKYUUn5r-%MY#tFPfPC%AaYE zkXa8-45Y3cN=c3(T1Ur@!AL4A_7l)iQ^nAX<-_vc@0dg^e^$o0Fv^7`hvxb6xScy9 zc6@i`z0%U`q5JP;&?PWdRX|U@`ewiE{$v}h;8iBTkRBahJGf z)7YbBy*`xN;O)gK{0Qd2xr1mZ<_O=}#G~cu?<>~sqdxShxk^#a{jF6g0uWX^++)W@ z560;K%CNS>z%z=^9Uo+2JMB0f*U5f^A=69*1U-yj`@8s9AQ{XTxbA{{Iq2&wSRZ`@ z8F9c$E`U;NfIB@Cq_~4w$fWp1KE&_KOgL#ZO#I#i&nSKY6F=I&)Y;9EQn>vxV8s$@ zZFh8KA5u;D;bke$suN`};YUf4cRyeYd*$?kJ2i8Jx&1PJY^sJmQ>{LMEdSf)N6SK; zj+f8y-ZOP5@e9V?TEZDM>-|S4?B#iEKAw@MWz%PVplBfjn)gv8@j)zXn7I83#)kE6lq@GwPhWC~M1i6ED^t^ALu2Mb^1h1Dp*sXJ!4 z?#L?Y0cYUY)m$qct9h_>3o5KpUL3o6ni-C53FnYoue&(p4Pgz9T=*g+IG-P}5k3RL z9RUOdZ0E$5P_{F+MZ|U%z8I1_fc1}ES;tsEu_cTdUI+fr%|t-Z2zW;EpJq5m*D_74 zbNbUH78#dUsk&4kbqA2hIOanxWCmT)|Ap0I*~-I?0HTvOn(``%!+w3Pi3q|yG#u!e z!pQ;0F|NK9Md#S7- zm7%YN@MRX&PF0&(+RWWC?+s+`W)surQjz=DgcP^$6T9I^jxCZ?5j#RmEz( zV-z`CI{>MW_YWq6gL>NBC#S961{5W`pQR2*7H#ZeLIdvp8lq=!Gs>Q0kJy2&B%%D%Tx`73md04U{m8`@V801{{Av*xjvNSg(IODCw6M1tql#toW7=U&6JLFxub4q0^Aiex>syBL)nq!{cF$4kV<-R;~L&hlx0C&g_2k&-NB zD^Gr~t=GTiz+jUx59}W=g>kicR-k+6lj1mj&7b18=O{W;w5e=Z66Pe;gOYSJ=ZFgM z@zEN#{vv{vAs8=2XRr|0afOJng~(Rh*cQonA-aQwnEOy6@?IH8)x{MA#;&|ZpHoL8 za_*nlGG3iR*5Zj^QI2ilzdDf0yPX4*V2X zmu@x~YS?Lm!Dgoi#T9mgsj{*fAkDuzX){-vO|?_!Og5Wov!TRhrp45yQzY5hb!NK( z2v?acc7xS!`z6d~uC1%I8)#gE*;YM82{&73Ev0SP%w=Yq*-~OQlvLLNg%7WTHON6t zl@+Gie*&ZJ)mAk8rr*D7v8JNdV5t8^K!dMK?3I(;W8Rjt;AIshw(8pIGT=U!C4*tH z#cr#x)K-)j49or{)CzMgSOAmF-_e)%>n>J-eAd}OMt_A6lTuP`nVNKN(hMk6`8%cy z|0M?j3Iyvn2@664XknccOq#P*=BjGj=5f;grZn)9t^=0nRp#1S)4Hh(s-;7}E=c2t zDaFnGE*WT-f_kbgbQxKGSxQ{w?A07pt)8Qvqnh)0vDs#+wVNv}hK;6%3Y*DNF{iSU IE_3Mr0ej1CzW@LL literal 0 HcmV?d00001 diff --git a/examples/jit/bernstein_vazirani.py b/examples/jit/bernstein_vazirani.py new file mode 100644 index 00000000..0acebd3f --- /dev/null +++ b/examples/jit/bernstein_vazirani.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +# Copyright(c) Microsoft Corporation. +# Licensed under the MIT License. + +from pyqir_jit import NonadaptiveJit, GateLogger + +from pathlib import Path +import os + +path = Path(__file__).parent +file = os.path.join(path, "bernstein_vazirani.ll") + +jit = NonadaptiveJit() +logger = GateLogger() + +print("# output from NonadaptiveJit returning the uninitialized output") +jit.eval(file, logger) + +print("# output from GateLogger") +logger.print() diff --git a/pyqir-generator/Cargo.toml b/pyqir-generator/Cargo.toml index ae656267..52b76768 100644 --- a/pyqir-generator/Cargo.toml +++ b/pyqir-generator/Cargo.toml @@ -34,6 +34,8 @@ classifier=[ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python", "Programming Language :: Rust", "Operating System :: MacOS", diff --git a/pyqir-generator/README.md b/pyqir-generator/README.md index dae6d935..d39a79cb 100644 --- a/pyqir-generator/README.md +++ b/pyqir-generator/README.md @@ -1,22 +1,89 @@ -# pyqir-generator +# pyqir_generator -## Building and Testing +The `pyqir_generator` package provides the ability to generate [QIR](https://github.com/qir-alliance/qir-spec) using a Python API. + +It is intended to be used by code automating translation processes enabling the conversion in some format to QIR via Python; i.e., this is a low level API intended to be used as a bridge to existing Python frameworks enabling the generation of QIR rather than directly consumed by an end-user. It is **not** intended to be used as a framework for algorithm and application development. + +## Examples + +There are generator examples in the repository: +- [Bernstein-Vazirani](examples/generator/bernstein_vazirani.py) +- [Bell pair](examples/generator/bell_pair.py) + +A short example is below. + +The following code creates QIR for an create Bell pair before measuring each qubit and returning the result. The unoptimized QIR is displayed in the terminal when executed: + +```python +from pyqir_generator import QirBuilder -To build this package, first install `maturin`: +builder = QirBuilder("Bell") +builder.add_quantum_register("qubit", 2) +builder.add_classical_register("output", 2) -```shell -pip install maturin +builder.h("qubit0") +builder.cx("qubit0", "qubit1") + +builder.m("qubit0", "output0") +builder.m("qubit1", "output1") + +print(builder.get_ir_string()) ``` -To build and test use `maturin develop`: +The corresponding piece in the QIR output will contain the generated function: -```shell -pip install -r requirements-dev.txt -maturin develop && pytest ``` +define internal %Array* @QuantumApplication__Run__body() { +entry: + %qubit0 = call %Qubit* @__quantum__rt__qubit_allocate() + %qubit1 = call %Qubit* @__quantum__rt__qubit_allocate() + %results = call %Array* @__quantum__rt__array_create_1d(i32 8, i64 1) + %output = call %Array* @__quantum__rt__array_create_1d(i32 8, i64 2) + %output_0_raw = call i8* @__quantum__rt__array_get_element_ptr_1d(%Array* %output, i64 0) + %output_result_0 = bitcast i8* %output_0_raw to %Result** + %zero_0 = call %Result* @__quantum__rt__result_get_zero() + call void @__quantum__rt__result_update_reference_count(%Result* %zero_0, i32 1) + store %Result* %zero_0, %Result** %output_result_0, align 8 + %output_1_raw = call i8* @__quantum__rt__array_get_element_ptr_1d(%Array* %output, i64 1) + %output_result_1 = bitcast i8* %output_1_raw to %Result** + %zero_1 = call %Result* @__quantum__rt__result_get_zero() + call void @__quantum__rt__result_update_reference_count(%Result* %zero_1, i32 1) + store %Result* %zero_1, %Result** %output_result_1, align 8 + %results_result_tmp_0_raw = call i8* @__quantum__rt__array_get_element_ptr_1d(%Array* %results, i64 0) + %results_result_tmp_result_0 = bitcast i8* %results_result_tmp_0_raw to %Array** + store %Array* %output, %Array** %results_result_tmp_result_0, align 8 + call void @Microsoft__Quantum__Intrinsic__H__body(%Qubit* %qubit0) + %__controlQubits__ = call %Array* @__quantum__rt__array_create_1d(i32 8, i64 1) + %__controlQubits__0_result_tmp_0_raw = call i8* @__quantum__rt__array_get_element_ptr_1d(%Array* %__controlQubits__, i64 0) + %__controlQubits__0_result_tmp_result_0 = bitcast i8* %__controlQubits__0_result_tmp_0_raw to %Qubit** + store %Qubit* %qubit0, %Qubit** %__controlQubits__0_result_tmp_result_0, align 8 + call void @Microsoft__Quantum__Intrinsic__X__ctl(%Array* %__controlQubits__, %Qubit* %qubit1) + call void @__quantum__rt__array_update_reference_count(%Array* %__controlQubits__, i32 -1) + %measurement = call %Result* @Microsoft__Quantum__Intrinsic__M__body(%Qubit* %qubit0) + %output0_0_raw = call i8* @__quantum__rt__array_get_element_ptr_1d(%Array* %output, i64 0) + %output0_result_0 = bitcast i8* %output0_0_raw to %Result** + %existing_value = load %Result*, %Result** %output0_result_0, align 8 + call void @__quantum__rt__result_update_reference_count(%Result* %existing_value, i32 -1) + call void @__quantum__rt__result_update_reference_count(%Result* %measurement, i32 1) + store %Result* %measurement, %Result** %output0_result_0, align 8 + %measurement1 = call %Result* @Microsoft__Quantum__Intrinsic__M__body(%Qubit* %qubit1) + %output1_1_raw = call i8* @__quantum__rt__array_get_element_ptr_1d(%Array* %output, i64 1) + %output1_result_1 = bitcast i8* %output1_1_raw to %Result** + %existing_value2 = load %Result*, %Result** %output1_result_1, align 8 + call void @__quantum__rt__result_update_reference_count(%Result* %existing_value2, i32 -1) + call void @__quantum__rt__result_update_reference_count(%Result* %measurement1, i32 1) + store %Result* %measurement1, %Result** %output1_result_1, align 8 + call void @__quantum__rt__qubit_release(%Qubit* %qubit1) + call void @__quantum__rt__qubit_release(%Qubit* %qubit0) + ret %Array* %results +} +``` + +## Building and Testing + +See [Building](../docs/building.md) -Alternatively, install tox and run the tests inside an isolated environment: +## Current Limitations -```shell -tox -e py -``` \ No newline at end of file +- Support for emitting classical computations and control flow constructs is not yet implemented, see also [this issue](https://github.com/qir-alliance/pyqir/issues/2) +- Using qubit/register names in gate calls that haven't been defined will cause an error during generation. diff --git a/pyqir-generator/src/emit.rs b/pyqir-generator/src/emit.rs index 2bd7da80..3b416a40 100644 --- a/pyqir-generator/src/emit.rs +++ b/pyqir-generator/src/emit.rs @@ -11,7 +11,8 @@ use crate::{interop::SemanticModel, qir}; pub fn write_model_to_file(model: &SemanticModel, file_name: &str) -> Result<(), String> { let ctx = inkwell::context::Context::create(); let context = populate_context(&ctx, &model)?; - + #[cfg(feature = "basic-passes")] + qirlib::passes::run_basic_passes_on(&context); context.emit_ir(file_name)?; Ok(()) @@ -20,7 +21,8 @@ pub fn write_model_to_file(model: &SemanticModel, file_name: &str) -> Result<(), pub fn get_ir_string(model: &SemanticModel) -> Result { let ctx = inkwell::context::Context::create(); let context = populate_context(&ctx, &model)?; - + #[cfg(feature = "basic-passes")] + qirlib::passes::run_basic_passes_on(&context); let ir = context.get_ir_string(); Ok(ir) @@ -29,13 +31,13 @@ pub fn get_ir_string(model: &SemanticModel) -> Result { pub fn get_bitcode_base64_string(model: &SemanticModel) -> Result { let ctx = inkwell::context::Context::create(); let context = populate_context(&ctx, &model)?; - + #[cfg(feature = "basic-passes")] + qirlib::passes::run_basic_passes_on(&context); let b64 = context.get_bitcode_base64_string(); Ok(b64) } - pub fn populate_context<'a>( ctx: &'a inkwell::context::Context, model: &'a SemanticModel, diff --git a/pyqir-generator/tests/test_api.py b/pyqir-generator/tests/test_api.py index 2273ec4d..4b566e8c 100644 --- a/pyqir-generator/tests/test_api.py +++ b/pyqir-generator/tests/test_api.py @@ -1,5 +1,8 @@ -from pyqir_generator import * -import pytest +# Copyright(c) Microsoft Corporation. +# Licensed under the MIT License. + +from pyqir_generator import QirBuilder + def test_bell(tmpdir): builder = QirBuilder("Bell circuit") @@ -14,6 +17,7 @@ def test_bell(tmpdir): print(f'Writing {file}') builder.build(str(file)) + def test_bell_no_measure(tmpdir): builder = QirBuilder("Bell circuit") builder.add_quantum_register("qr", 2) @@ -26,6 +30,7 @@ def test_bell_no_measure(tmpdir): print(f'Writing {file}') builder.build(str(file)) + def test_bernstein_vazirani(tmpdir): builder = QirBuilder("Bernstein-Vazirani") builder.add_quantum_register("input", 5) @@ -62,6 +67,7 @@ def test_bernstein_vazirani(tmpdir): print(f'Writing {file}') builder.build(str(file)) + def test_all_gates(tmpdir): builder = QirBuilder("All Gates") builder.add_quantum_register("q", 4) @@ -73,9 +79,9 @@ def test_all_gates(tmpdir): builder.cz("q1", "control0") builder.h("q0") builder.reset("q0") - builder.rx(15.0,"q1") - builder.ry(16.0,"q2") - builder.rz(17.0,"q3") + builder.rx(15.0, "q1") + builder.ry(16.0, "q2") + builder.rz(17.0, "q3") builder.s("q0") builder.s_adj("q1") builder.t("q2") @@ -93,6 +99,7 @@ def test_all_gates(tmpdir): print(f'Writing {file}') builder.build(str(file)) + def test_bernstein_vazirani_ir_string(): builder = QirBuilder("Bernstein-Vazirani") builder.add_quantum_register("input", 5) diff --git a/pyqir-generator/tox.ini b/pyqir-generator/tox.ini index 969767a2..876c125f 100644 --- a/pyqir-generator/tox.ini +++ b/pyqir-generator/tox.ini @@ -6,7 +6,6 @@ isolated_build = True # https://github.com/tox-dev/tox/issues/1550 # PYTHONIOENCODING = utf-8 needs to be set to work around windows bug setenv = - LLVM_SYS_110_PREFIX = {env:LLVM_SYS_110_PREFIX} PYTHONIOENCODING = utf-8 # needed temporarily for build to find cl.exe diff --git a/pyqir-jit/Cargo.toml b/pyqir-jit/Cargo.toml index 401151f5..e1563d2a 100644 --- a/pyqir-jit/Cargo.toml +++ b/pyqir-jit/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" [dependencies] microsoft-quantum-qir-runtime-sys = { git = "https://github.com/microsoft/qsharp-runtime", rev = "d955aec7", default-features = false, features = ["runtime", "foundation", "llvm-libloading"] } -qirlib = { path = "../qirlib" } +qirlib = { path = "../qirlib", features = ["jit"] } inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", default-features = false, features = ["llvm11-0", "target-x86"] } lazy_static = "1.4.0" mut_static = "5.0.0" @@ -40,6 +40,8 @@ classifier=[ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python", "Programming Language :: Rust", "Operating System :: MacOS", diff --git a/pyqir-jit/README.md b/pyqir-jit/README.md index 9824d2de..5d6ed601 100644 --- a/pyqir-jit/README.md +++ b/pyqir-jit/README.md @@ -1,22 +1,83 @@ -# pyqir-jit +# pyqir_jit -## Building and Testing +The 'pyqir_jit' package provides an easy way to execute generated QIR for the purpose of +1. easily testing and experimenting with QIR code +2. connecting it to low-level Python-based lab software such as e.g. [QCoDes.](https://qcodes.github.io/Qcodes/user/intro.html) + +It contains the necessary [just-in-time compilation](https://en.wikipedia.org/wiki/Just-in-time_compilation) infrastructure as well an extensibility mechanism to define what actions to perform when a gate is applied in Python. +## Examples + +There are JIT examples in the repository: +- [Bernstein-Vazirani](../examples/jit/bernstein_vazirani.py) ([Bernstein-Vazirani QIR](../examples/jit/bernstein_vazirani.ll)) + +Let's look at how to log the gate sequence for the following example: +- [Bernstein-Vazirani](../examples/jit/bernstein_vazirani.py) + We can evaluate the [generated QIR](../examples/jit/bernstein_vazirani.ll) with the `NonadaptiveJit`, and `GateLogger` to print out a simple log of the quantum application. + +```python +from pyqir_jit import NonadaptiveJit, GateLogger + +from pathlib import Path +import os -To build this package, first install `maturin`: +path = Path(__file__).parent +file = os.path.join(path, "bernstein_vazirani.ll") -```shell -pip install maturin +jit = NonadaptiveJit() +logger = GateLogger() + +print("# output from NonadaptiveJit returning the uninitialized output") +jit.eval(file, logger) + +print("# output from GateLogger") +logger.print() ``` -To build and test use `maturin develop`: +Would generate the output: -```shell -pip install -r requirements-dev.txt -maturin develop && pytest ``` +# output from NonadaptiveJit returning the uninitialized output +[[Zero, Zero, Zero, Zero, Zero, Zero, Zero, Zero]] +# output from GateLogger +qubits[9] +out[9] +x qubit[8] +h qubit[0] +h qubit[1] +h qubit[2] +h qubit[3] +h qubit[4] +h qubit[5] +h qubit[6] +h qubit[7] +h qubit[8] +cx qubit[2], qubit[8] +cx qubit[3], qubit[8] +h qubit[0] +h qubit[1] +h qubit[2] +h qubit[3] +h qubit[4] +h qubit[5] +h qubit[6] +h qubit[7] +measure qubits[0] -> out[0] +measure qubits[1] -> out[1] +measure qubits[2] -> out[2] +measure qubits[3] -> out[3] +measure qubits[4] -> out[4] +measure qubits[5] -> out[5] +measure qubits[6] -> out[6] +measure qubits[7] -> out[7] +measure qubits[8] -> out[8] +``` + +## Building and Testing + +See [Building](../docs/building.md) -Alternatively, install tox and run the tests inside an isolated environment: +## Current Limitations -```shell -tox -e py -``` \ No newline at end of file +- QIR entry point for JIT must be named `QuantumApplication__Run` +- Entry point arguments are not yet supported +- QIR must contain the defined runtime in [module.ll](../qirlib/src/module.ll); it is automatically included when using the [pyqir_generator](../pyqir-generator) package to generate QIR diff --git a/pyqir-jit/pyqir_jit/__init__.py b/pyqir-jit/pyqir_jit/__init__.py index 0fb7784c..87129ec1 100644 --- a/pyqir-jit/pyqir_jit/__init__.py +++ b/pyqir-jit/pyqir_jit/__init__.py @@ -1,5 +1,6 @@ # Copyright(c) Microsoft Corporation. # Licensed under the MIT License. -from .jit import * from .gateset import * +from .gatelogger import * +from .jit import * diff --git a/pyqir-jit/pyqir_jit/gatelogger.py b/pyqir-jit/pyqir_jit/gatelogger.py new file mode 100644 index 00000000..a8f794d7 --- /dev/null +++ b/pyqir-jit/pyqir_jit/gatelogger.py @@ -0,0 +1,82 @@ +# Copyright(c) Microsoft Corporation. +# Licensed under the MIT License. + +from pyqir_jit import GateSet + + +class GateLogger(GateSet): + """ + Records the quantum circuit operations executed during JIT execution. + + number_of_qubits: int + number_of_registers: int + instructions: List[str] + """ + + def __init__(self): + self.number_of_qubits = 0 + self.number_of_registers = 0 + self.instructions = [] + + def cx(self, control: str, target: str): + self.instructions.append(f"cx qubit[{control}], qubit[{target}]") + + def cz(self, control: str, target: str): + self.instructions.append(f"cz qubit[{control}], qubit[{target}]") + + def h(self, target: str): + self.instructions.append(f"h qubit[{target}]") + + def m(self, qubit: str, target: str): + self.instructions.append(f"m qubit[{qubit}] => out[{target}]") + + def reset(self, target: str): + self.instructions.append(f"reset {target}") + + def rx(self, theta: float, qubit: str): + self.instructions.append(f"rx theta[{theta}] qubit[{qubit}]") + + def ry(self, theta: float, qubit: str): + self.instructions.append(f"ry theta[{theta}] qubit[{qubit}]") + + def rz(self, theta: float, qubit: str): + self.instructions.append(f"rz theta[{theta}] qubit[{qubit}]") + + def s(self, qubit: str): + self.instructions.append(f"s qubit[{qubit}]") + + def s_adj(self, qubit: str): + self.instructions.append(f"s_adj qubit[{qubit}]") + + def t(self, qubit: str): + self.instructions.append(f"t qubit[{qubit}]") + + def t_adj(self, qubit: str): + self.instructions.append(f"t_adj qubit[{qubit}]") + + def x(self, qubit: str): + self.instructions.append(f"x qubit[{qubit}]") + + def y(self, qubit: str): + self.instructions.append(f"y qubit[{qubit}]") + + def z(self, qubit: str): + self.instructions.append(f"z qubit[{qubit}]") + + def dump_machine(self): + self.instructions.append("dumpmachine") + + def finish(self, metadata: dict): + super().finish(metadata) + self.number_of_qubits = metadata["number_of_qubits"] + self.number_of_registers = self.number_of_qubits + + def print(self): + print(f"qubits[{self.number_of_qubits}]") + print(f"out[{self.number_of_registers}]") + + for instruction in self.instructions: + print(instruction) + + for q in range(0, self.number_of_qubits): + print(f"measure qubits[{q}] -> out[{q}]") diff --git a/pyqir-jit/pyqir_jit/gateset.py b/pyqir-jit/pyqir_jit/gateset.py index 9957f36b..5d3349f6 100644 --- a/pyqir-jit/pyqir_jit/gateset.py +++ b/pyqir-jit/pyqir_jit/gateset.py @@ -2,8 +2,10 @@ # Licensed under the MIT License. class GateSet(object): - def __init__(self): - self.number_of_qubits = 0 + """ + Defines the quantum circuit operations which may be registered for + callbacks during JIT execution of QIR + """ def cx(self, control: str, target: str): pass @@ -54,5 +56,7 @@ def dump_machine(self): pass def finish(self, metadata: dict): - self.number_of_qubits = metadata["number_of_qubits"] + """ + Called at the end of JIT execution supplying run metadata. + """ pass diff --git a/pyqir-jit/pyqir_jit/jit.py b/pyqir-jit/pyqir_jit/jit.py index acb490e9..6a887483 100644 --- a/pyqir-jit/pyqir_jit/jit.py +++ b/pyqir-jit/pyqir_jit/jit.py @@ -1,26 +1,29 @@ # Copyright(c) Microsoft Corporation. # Licensed under the MIT License. -from typing import Any -from .pyqir_jit import * +from pyqir_jit import GateSet +from .pyqir_jit import PyNonadaptiveJit -class QirJit(object): + +class NonadaptiveJit(object): """ - The QirJit object loads bitcode/QIR for evaluation and processing + The NonadaptiveJit object loads bitcode/QIR for evaluation and processing + jit: PyNonadaptiveJit """ def __init__(self): - self.pyqirjit = PyQirJit() + self.jit = PyNonadaptiveJit() - def eval(self, file_path: str, pyobj: Any): + def eval(self, file_path: str, gateset: GateSet): """ - JIT compiles the circuit delegating quantum operations to the supplied object + JIT compiles the circuit delegating quantum operations to the supplied + GateSet :param file_path: file path of existing QIR in a ll or bc file :type file_path: str - :param pyobj: python GateSet object defining the quantum operations - :type pyobj: str + :param gateset: python GateSet based object defining the operations + :type gateset: GateSet """ - self.pyqirjit.eval(file_path, pyobj) + self.jit.eval(file_path, gateset) diff --git a/pyqir-jit/src/jit.rs b/pyqir-jit/src/jit.rs index b37f1d0f..85da76c0 100644 --- a/pyqir-jit/src/jit.rs +++ b/pyqir-jit/src/jit.rs @@ -1,21 +1,24 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use qirlib::context::{Context, ContextType}; -use crate::runtime::Simulator; +use std::path::Path; + use crate::interop::SemanticModel; +use crate::runtime::Simulator; use inkwell::targets::TargetMachine; -use inkwell::{ - passes::PassManagerBuilder, - targets::{InitializationConfig, Target}, - OptimizationLevel, -}; +use inkwell::targets::{InitializationConfig, Target}; use microsoft_quantum_qir_runtime_sys::runtime::BasicRuntimeDriver; +use qirlib::context::{Context, ContextType}; use qirlib::passes::run_basic_passes_on; -pub fn run_module(module: String) -> Result { +pub fn run_module>(path: P) -> Result { let ctx = inkwell::context::Context::create(); - let context_type = ContextType::File(&module); + let path_str = path + .as_ref() + .to_str() + .expect("Did not find a valid Unicode path string") + .to_owned(); + let context_type = ContextType::File(&path_str); let context = Context::new(&ctx, context_type)?; let model = run_ctx(context)?; Ok(model) @@ -50,49 +53,21 @@ pub fn run_ctx<'ctx>(context: Context<'ctx>) -> Result { #[cfg(test)] mod tests { - - use crate::interop::{ClassicalRegister, Measured, QuantumRegister, SemanticModel}; - use crate::interop::{Controlled, Instruction, Single}; + use std::fs::File; + use std::io::Write; use tempfile::tempdir; - use super::run_ctx; - #[ignore = "CI Requires runtime recompilation"] #[test] fn eval_test() -> Result<(), String> { - let dir = tempdir().expect(""); - let tmp_path = dir.into_path(); + let bell_qir_measure_contents = include_bytes!("../tests/bell_qir_measure.ll"); + let dir = tempdir().expect("Could not create temp dir"); + let file_path = dir.path().join("bell_qir_measure.ll"); + let mut buffer = File::create(&file_path).unwrap(); + buffer.write_all(bell_qir_measure_contents).unwrap(); - let name = String::from("Bell circuit"); - let mut model = SemanticModel::new(name); - model.add_reg(QuantumRegister::new(String::from("qr"), 0).as_register()); - model.add_reg(QuantumRegister::new(String::from("qr"), 1).as_register()); - model.add_reg(ClassicalRegister::new(String::from("qc"), 2).as_register()); + let generated_model = super::run_module(file_path)?; - model.add_inst(Instruction::H(Single::new(String::from("qr0")))); - model.add_inst(Instruction::Cx(Controlled::new( - String::from("qr0"), - String::from("qr1"), - ))); - - model.add_inst(Instruction::M(Measured::new( - String::from("qr0"), - String::from("qc0"), - ))); - model.add_inst(Instruction::M(Measured::new( - String::from("qr1"), - String::from("qc1"), - ))); - - let generated_model = run(&model)?; - - assert!(generated_model.instructions.len() == 2); + assert_eq!(generated_model.instructions.len(), 2); Ok(()) } - - pub fn run(model: &SemanticModel) -> Result { - //let ctx = inkwell::context::Context::create(); - //let context = pyqir_generator::populate_context(&ctx, &model).unwrap(); - //let model = run_ctx(context)?; - Ok(model.clone()) - } } diff --git a/pyqir-jit/src/python.rs b/pyqir-jit/src/python.rs index e5adc740..f1c2891d 100644 --- a/pyqir-jit/src/python.rs +++ b/pyqir-jit/src/python.rs @@ -13,20 +13,20 @@ use crate::interop::{ #[pymodule] fn pyqir_jit(_py: Python<'_>, m: &PyModule) -> PyResult<()> { - m.add_class::()?; + m.add_class::()?; Ok(()) } #[pyclass] -pub struct PyQirJit { +pub struct PyNonadaptiveJit { } #[pymethods] -impl PyQirJit { +impl PyNonadaptiveJit { #[new] fn new() -> Self { - PyQirJit { } + PyNonadaptiveJit { } } fn controlled( diff --git a/pyqir-jit/tests/test_api.py b/pyqir-jit/tests/test_api.py index 1677f219..e2f1a6f2 100644 --- a/pyqir-jit/tests/test_api.py +++ b/pyqir-jit/tests/test_api.py @@ -1,73 +1,18 @@ -from pyqir_jit import * -import pytest +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. -class GateLogger(GateSet): - def __init__(self): - # call parent class constructor - super().__init__() - self.number_of_registers = 0 - self.instructions = [] +from pyqir_jit import NonadaptiveJit, GateLogger - def cx(self, control: str, target: str): - self.instructions.append(f"cx control[{control}], target[{target}]") - - def cz(self, control: str, target: str): - self.instructions.append(f"cz control[{control}], target[{target}]") - - def h(self, target: str): - self.instructions.append(f"h qubit[{target}]") - - def m(self, qubit: str, target: str): - self.instructions.append(f"m qubit[{qubit}] => out[{target}]") - - def reset(self, target: str): - self.instructions.append(f"reset {target}") - - def rx(self, theta: float, qubit: str): - self.instructions.append(f"rx theta[{theta}] qubit[{qubit}]") - - def ry(self, theta: float, qubit: str): - self.instructions.append(f"ry theta[{theta}] qubit[{qubit}]") - - def rz(self, theta: float, qubit: str): - self.instructions.append(f"rz theta[{theta}] qubit[{qubit}]") - - def s(self, qubit: str): - self.instructions.append(f"s qubit[{qubit}]") - - def s_adj(self, qubit: str): - self.instructions.append(f"s_adj qubit[{qubit}]") - - def t(self, qubit: str): - self.instructions.append(f"t qubit[{qubit}]") - - def t_adj(self, qubit: str): - self.instructions.append(f"t_adj qubit[{qubit}]") - - def x(self, qubit: str): - self.instructions.append(f"x qubit[{qubit}]") - - def y(self, qubit: str): - self.instructions.append(f"y qubit[{qubit}]") - - def z(self, qubit: str): - self.instructions.append(f"z qubit[{qubit}]") - - def dump_machine(self): - self.instructions.append(f"dumpmachine") - - def finish(self, metadata: dict): - print("finished") - super().finish(metadata) - self.number_of_registers = self.number_of_qubits def test_bell_qir(): file = "tests/bell_qir_measure.ll" - qirjit = QirJit() - generator = GateLogger() - qirjit.eval(file, generator) + jit = NonadaptiveJit() + logger = GateLogger() + jit.eval(file, logger) + + logger.print() - assert len(generator.instructions) == 2 - assert str(generator.instructions[0]).startswith("h") - assert str(generator.instructions[1]).startswith("cx") + assert len(logger.instructions) == 2 + assert str(logger.instructions[0]).startswith("h") + assert str(logger.instructions[1]).startswith("cx") diff --git a/pyqir-jit/tox.ini b/pyqir-jit/tox.ini index 969767a2..876c125f 100644 --- a/pyqir-jit/tox.ini +++ b/pyqir-jit/tox.ini @@ -6,7 +6,6 @@ isolated_build = True # https://github.com/tox-dev/tox/issues/1550 # PYTHONIOENCODING = utf-8 needs to be set to work around windows bug setenv = - LLVM_SYS_110_PREFIX = {env:LLVM_SYS_110_PREFIX} PYTHONIOENCODING = utf-8 # needed temporarily for build to find cl.exe diff --git a/pyqir-parser/Cargo.toml b/pyqir-parser/Cargo.toml index ec428399..148cdf94 100644 --- a/pyqir-parser/Cargo.toml +++ b/pyqir-parser/Cargo.toml @@ -31,6 +31,8 @@ classifier=[ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python", "Programming Language :: Rust", "Operating System :: MacOS", diff --git a/pyqir-parser/README.md b/pyqir-parser/README.md index d9e77525..d70696f1 100644 --- a/pyqir-parser/README.md +++ b/pyqir-parser/README.md @@ -1,22 +1,13 @@ -# pyqir-parser +# pyqir_parser -## Building and Testing - -To build this package, first install `maturin`: +Under active development and will be updated. -```shell -pip install maturin -``` - -To build and test use `maturin develop`: +## Building and Testing -```shell -pip install -r requirements-dev.txt -maturin develop && pytest -``` +See [Building](../docs/building.md) -Alternatively, install tox and run the tests inside an isolated environment: +## Limitations -```shell -tox -e py -``` \ No newline at end of file +- Unsupported IR + - Phi nodes + - arrays diff --git a/pyqir-parser/tox.ini b/pyqir-parser/tox.ini index 969767a2..876c125f 100644 --- a/pyqir-parser/tox.ini +++ b/pyqir-parser/tox.ini @@ -6,7 +6,6 @@ isolated_build = True # https://github.com/tox-dev/tox/issues/1550 # PYTHONIOENCODING = utf-8 needs to be set to work around windows bug setenv = - LLVM_SYS_110_PREFIX = {env:LLVM_SYS_110_PREFIX} PYTHONIOENCODING = utf-8 # needed temporarily for build to find cl.exe diff --git a/qirlib/Cargo.toml b/qirlib/Cargo.toml index 0e42d845..9e176a76 100644 --- a/qirlib/Cargo.toml +++ b/qirlib/Cargo.toml @@ -21,3 +21,7 @@ tempfile = "3.2.0" [lib] name = "qirlib" + +[features] +jit = [] +default = [] \ No newline at end of file diff --git a/qirlib/src/context.rs b/qirlib/src/context.rs index 23a11107..4e15aaac 100644 --- a/qirlib/src/context.rs +++ b/qirlib/src/context.rs @@ -11,6 +11,7 @@ use crate::{constants::Constants, intrinsics::Intrinsics, runtime_library::Runti pub struct Context<'ctx> { pub context: &'ctx inkwell::context::Context, pub module: inkwell::module::Module<'ctx>, + #[cfg(feature = "jit")] pub execution_engine: inkwell::execution_engine::ExecutionEngine<'ctx>, pub builder: inkwell::builder::Builder<'ctx>, pub types: Types<'ctx>, @@ -24,6 +25,7 @@ pub enum ContextType<'ctx> { File(&'ctx String), } +#[cfg(feature = "jit")] impl<'ctx> Context<'ctx> { pub fn new( context: &'ctx inkwell::context::Context, @@ -49,6 +51,33 @@ impl<'ctx> Context<'ctx> { constants, }) } +} + +#[cfg(not(feature = "jit"))] +impl<'ctx> Context<'ctx> { + pub fn new( + context: &'ctx inkwell::context::Context, + context_type: ContextType<'ctx>, + ) -> Result { + let builder = context.create_builder(); + let module = Context::load_module(context, context_type)?; + let types = Types::new(&context, &module); + let runtime_library = RuntimeLibrary::new(&module); + let intrinsics = Intrinsics::new(&module); + let constants = Constants::new(&module, &types); + Ok(Context { + builder, + module, + types, + context, + runtime_library, + intrinsics, + constants, + }) + } +} + +impl<'ctx> Context<'ctx> { fn load_module( context: &'ctx inkwell::context::Context, context_type: ContextType<'ctx>,