diff --git a/README.md b/README.md index b6579aa..8583eb0 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Some of the key things this approach enables: 2. Enabling hybrid algorithms to be run on machines and tools with only a gate-level API available. This includes QASM API's if you use its simulation framework. 3. Lots of optimization potential when passed large amounts of classical context that a quantum algorithm uses to accentuate its own execution. -We also have a [full feature list and quick intro to its concepts](https://github.com/oqc-community/rasqal/blob/develop/features_and_concepts.md) as well as a [draft paper](https://github.com/oqc-community/rasqal/blob/develop/docs/papers/Rasqal%20Draft%20v2.pdf) that covers its internals in excruciating detail. +We also have a [full feature list and quick intro to its concepts](https://github.com/oqc-community/rasqal/blob/develop/docs/features_and_concepts.md) as well as a [draft paper](https://github.com/oqc-community/rasqal/blob/develop/docs/papers/Rasqal%20Draft%20v2.pdf) that covers its internals in excruciating detail. If you have any features or ideas you'd like to see implemented feel free to raise a [feature request](https://github.com/oqc-community/Rasqal/issues/new?assignees=&labels=enhancement&projects=&template=feature_request.md&title=)! @@ -18,13 +18,13 @@ If you have any features or ideas you'd like to see implemented feel free to rai ### Getting Started 1. Install Rasqal in your favourite Python venv by running `pip install rasqal`. Our current testing is done with `v3.10` of Python. -2. Read the [quick start](https://github.com/oqc-community/rasqal/blob/develop/examples.md) and look at our [Python example](https://github.com/oqc-community/Rasqal/blob/develop/docs/examples.py). -3. (Optional) Read the [paper](https://github.com/oqc-community/rasqal/blob/develop/docs/papers/Rasqal%20Draft%20v2.pdf) for a deep-dive into Rasqals concepts and data structures. +2. Read the [quick start](https://github.com/oqc-community/rasqal/blob/develop/docs/quick_start.md) and look at our [examples](https://github.com/oqc-community/Rasqal/blob/develop/examples/examples.py). +3. (Optional) Read the [paper](https://github.com/oqc-community/rasqal/blob/develop/docs/papers/Rasqal%20Draft%20v3.pdf) for a deep-dive into Rasqals concepts and data structures. ### Contributing -If you'd like to contribute your first destination will be to [build the system locally](https://github.com/oqc-community/rasqal/blob/develop/building.md). -There's also a [getting started](https://github.com/oqc-community/rasqal/blob/develop/development.md) page that covers some of the most important bits you'd need to know about the project before jumping into writing code. +If you'd like to contribute your first destination will be to [build the system locally](https://github.com/oqc-community/rasqal/blob/develop/docs/building.md). +There's also a [getting started](https://github.com/oqc-community/rasqal/blob/develop/docs/development.md) page that covers some of the most important bits you'd need to know about the project before jumping into writing code. After that feel free to fork the project and put up PRs with any work you would like to add. All experimental work that isn't ready for prime time has to be disabled by default and have no impact on core execution time and stability. diff --git a/building.md b/building.md deleted file mode 100644 index eae51f6..0000000 --- a/building.md +++ /dev/null @@ -1,55 +0,0 @@ -## Building from Source - -Prerequisites: - -1. [Python 3.10](https://www.python.org/downloads/). -2. [Rust](https://www.rust-lang.org/tools/install). - -With these installed then run: - -[Linux] - -`sudo apt install -y build-essential libffi-dev xz-utils powershell curl wget gnupg apt-transport-https` - -[Windows] - -[Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/) is the windows package manager and should be installed first. - -`winget install build-essential libffi-dev xz-utils powershell curl wget gnupg apt-transport-https 7-zip` - -[Mac] - -Soon to come. - -When these tools have been downloaded you run `build.ps1` at `/src/build.ps1`. This will initialize a Python venv, build the Rust projects, install the resultant wheel into that environment and run tests. - -From this point you can build the Rust project with cargo and deal with it seperately. -But if you need to redeploy the wheel and test things from Python you need to run the build script again. - -If you have issues you can look at the [CI cross-OS build script](https://github.com/oqc-community/rasqal/blob/develop/.github/workflows/deploy-wheels.yml) and see what might be missing or out of date from the documentation. - -#### Building LLVM from source - -If your system has no LLVM binaries available you can [build it yourself](https://llvm.org/docs/GettingStarted.html#getting-the-source-code-and-building-llvm). -You should only attempt this if you're familiar with LLVM already or have no binaries available, as it is rather involved and finicky on certain operating systems. - -You can use these environmental variables to customize the LLVM build: -```bash -RSQL_LLVM_EXTERNAL_DIR=/path/to/llvm # Directory to locally-built LLVM. -RSQL_DOWNLOAD_LLVM=true # Whether to download anrd build LLVM. -RSQL_CACHE_DIR=/where/to/extract # Where to store the downloaded LLVM build. Defaults to target which gets cleared on clean. -... -``` - -#### Potential issues - -[PyCharm] - -To get PyCharm to recognize the LLVM file path you need to add `LLVM_SYS_140_PREFIX={path_to_repo}/src/target/llvm14-0` to the environment variables for any Rust command. You can also use a config.toml with the same value. - -[Windows] - -Main issue is to do with path lengths. These two changes may be needed: - -* Open the registry, go to `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem` and set `LongPathsEnabled` to 1. -* Enable long file names via git by running this: `git config --system core.longpaths true`. This will set it for every user on the system, to be only your user use `--global`. diff --git a/development.md b/development.md deleted file mode 100644 index 7dc7fb5..0000000 --- a/development.md +++ /dev/null @@ -1,25 +0,0 @@ -### R++ - -Our Rust code is actually more like C++ due to some fun pointer manipulation we do and heavily interlinked data structures. - -The main points to know are: - -1. We have a custom ref-counted smart-pointer that acts, superficially, like any other ref-counted smart pointer whose backing is a raw pointer. -2. The smart-pointer can point to anything pointer-like: actual pointers, references, mutable references, anything you can actually get a pointer too. -3. It uses macros to manipulate/call/fetch the pointers directly and avoid pointer-to-ref compiler issues. -4. You can mutate anything at any time through the smart-pointer and its macros, so mutability keywords are irrelevant. - -The smart pointer and its macros mean that two important features of Rust are disabled when they are involved: lifetime tracking/borrow checking and the one-mutable-reference constraint. -Pointer lifetimes is dealt with programatically - similar to C++ - and its internal structure allows bypassing Rusts aliasing rules when using its macros. - -Because of this the Rust you'll see will look a little different than in other projects. There are almost no lifetime constraints and those that are will be enforced to be the widest scope. -The `mut` keyword means nothing if a smart-pointer is in play, it will be able to be mutated anyway. - -But what does this actually mean in regard to writing code? Well, not much really. -You just write Rust as normal, no need to think about anything additional. -Just think of `Ptr` as a more feature-rich `Rc`. - -For anyone immediately concerned by reading this: raw pointers have special designation, specially those in `UnsafeCell`'s. -We are leaning upon some pretty niche documented constraints to keep within the bounds of Rusts expectations, if barely. - -We'd prefer to use more normal Rust, but right now its rules do not allow that without some major contortions. diff --git a/docs/FAQ.md b/docs/FAQ.md new file mode 100644 index 0000000..97503bb --- /dev/null +++ b/docs/FAQ.md @@ -0,0 +1,14 @@ +# FAQ + +> After reading this it seems like any other hybrid runtime. What makes it different? + +Rasqal shares more DNA with formal verification tools and [model checkers](https://en.wikipedia.org/wiki/Model_checking) than it does with runtimes even though superficially it does, indeed, run your code. +But it doesn't view it as running, in fact it's just generating a quantum equation from all the information you've given it, in the form it deduces will give you the best results. + +It uses all the classical information provided to provide the constraints that the algorithm is running within, as well as analysing the evolving quantum state as much as it can. +Many of the classical constraints evaluation comes from existing ideas, but it also tries to do the same to the quantum circuit it's building (within reason). + +Many of its systems are currently in their infancy so may not be immediately noticeable, but in time they will. + +Rasqal works with other runtimes and sits at the exact point where they have generated the hybrid IR they consider encapsulates, fully, the algorithm they are trying to run. +Rasqal then takes this output and crunches it down even further. \ No newline at end of file diff --git a/docs/building.md b/docs/building.md new file mode 100644 index 0000000..3facf73 --- /dev/null +++ b/docs/building.md @@ -0,0 +1,46 @@ +## Building from Source + +Prerequisites: + +1. [Python 3.10](https://www.python.org/downloads/). +2. [Rust](https://www.rust-lang.org/tools/install). + +With these installed then run: + +[Linux] + +`sudo apt-get install -y ninja-build` + +[Windows] + +[Chocolatey](https://chocolatey.org/install) is a windows package manager that we use to organize some installs. + +`choco install --accept-license -y ninja` + +Optional: +`choco uninstall -y llvm` if you are having problems with the correct LLVM install being found. + +[Mac] + +`brew install ccache ninja` + +When this is all done run `build.ps1` at `/src/build.ps1`. + +This will install and build LLVM, build all the Rust projects, build a wheel and install it into the local venv, perform style formatting and more. + +Once this has been done you can open up the Rust projects and run cargo like normal as LLVM has been built and cached. You cna also use the built venv to run the Python as well. + +You'll have to re-run the build script if you want to build a new wheel to be available from the Python, but beyond that you can develop in whatever environment most suits you. + +#### Potential issues + +[PyCharm] + +To get PyCharm to recognize the LLVM file path you need to add `LLVM_SYS_150_PREFIX={path_to_repo}/src/target/llvm15-0` to the environment variables for any Rust command. You can also use a config.toml with the same value. + +[Windows] + +Main issue is to do with path lengths. These two changes may be needed: + +* Open the registry, go to `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem` and set `LongPathsEnabled` to 1. +* Enable long file names via git by running this: `git config --system core.longpaths true`. This will set it for every user on the system, to be only your user use `--global`. diff --git a/features_and_concepts.md b/docs/features_and_concepts.md similarity index 76% rename from features_and_concepts.md rename to docs/features_and_concepts.md index f019af6..4933429 100644 --- a/features_and_concepts.md +++ b/docs/features_and_concepts.md @@ -1,27 +1,19 @@ -Whenever Rasqal is first introduced to people they usually ask "How is this different from other hybrid runtimes? It looks exactly the same!". -On the surface it operates very similarly, so this question isn't that suprising. - -Its goals and the way it views the hybrid IRs is actually quite different, but to highlight these differences requires a bit of an explainer about some of its foundational concepts and philosophies. -Giving an introduction to them will paint its features (and how it works) in a different light, so we'll quickly cover them - -If you don't need that feel free to skip to the features list. - -### Concepts +### Core Ideas Describing Rasqal as a symbolic-execution driven optimizing runtime, while accurate from a high level, also manages to tell us very little about how it works. -A more precise description is that it's a quantum algorithm synthesis engine. +A more precise description is that it's a quantum algorithm synthesis engine. It treats hybrid IRs as nothing but a blueprint for the algorithms logic, but how that algorithm is actually realized is up to it. -It may decide to recursively squash classical expressions into your circuit, alternatively expand quantum results into classical, or squash two quantum methods together. +It may decide to recursively squash classical expressions into your circuit, alternatively expand quantum results into classical, or squash two quantum methods together. As long as the codes logic ends up with the same result, how it gets there is entirely up to Rasqal to decide. -Multiple -PU machines may also be roped in to run the algorithm or power its optimization passes: QPU, GPU, CPU, HPC(U). +Multiple -PU machines may also be roped in to run the algorithm or power its optimization passes: QPU, GPU, CPU, HPC(U). Entirely depends upon what machines are nearby. -_Note: As of writing it only uses local CPU and QPU, GPU/HPC support will come later. +_Note: As of writing it only uses local CPU and QPU, GPU/HPC support will come later. The decision to do many of these transforms is also 'no' right now, since they aren't in or have a rather specific trigger condition._ -To make these decisions accurately it needs to know everything about an algorithm as it runs. -Here's where its internal structures come into play: the graphs it builds are almost entirely constant. +To make these decisions accurately it needs to know everything about an algorithm as it runs. +Here's where its internal structures come into play: the graphs it builds are almost entirely constant. No system calls, no IO, the only variables are the ones passed in from our entry-point. The only exception to this rule is when it's decided we need to call out to a QPU or other external hardware. But as all our information is towards making these executions as optimized as possible this is an acceptable area of nondeterminism. @@ -29,36 +21,34 @@ But as all our information is towards making these executions as optimized as po These nearly-constant graphs allow very strong assertions to be given around data- and control-flow, and inform us to how the circuit to be sent to the QPU should be built. The graphs are then executed, and as we step through them we dynamically build up the queries to send to other hardware, executing when we reach a point that we absolutely need the result. -Rasqal doesn't execute QIR in a traditional sense, it uses it as a schema for encoding the logic that a hybrid algorithm uses. +Rasqal doesn't execute QIR in a traditional sense, it uses it as a schema for encoding the logic that a hybrid algorithm uses. Sometimes it will run very close to the QIR as-written because it's simple enough to not need transformation. In early versions this might happen more often than not, but this is not its function. In fact the less the execution run looks like the incoming QIR the better, because it means we've been able to do a lot of optimization and prediction. - - -If you are interested in a more thorough breakdown of its internals and concepts it has [a paper](https://github.com/oqc-community/rasqal/blob/develop/docs/papers/Rasqal%20Draft%20v2.pdf) which goes into them in detail. +If you are interested in a more thorough breakdown of its internals and concepts it has [a paper](https://github.com/oqc-community/rasqal/blob/develop/docs/papers/Rasqal%20Draft%20v3.pdf) which goes into them in detail. ### Features -This is a loose list of the most important features and capabilities it has right now, as well as what we want to build in the future. +This is a loose list of the most important features and capabilities it has right now, as well as what we want to build in the future. Like most documentation this may be slightly behind its actual capabilities, so if there's something marked as 'soon' that you'd like it may already be in. Currently available: 1. Full-spec QIR support [1] including the majority of classical LLVM instructions. Any profile which inherits off the base spec is by implicitly supported. 2. Variational algorithm capabilities via entry-point arguments and return values. -3. Multi-QPU execution capabilities - the quantum parts of an algorithm will only run on machines capable of supporting it. +3. Multi-QPU execution capabilities - the quantum parts of an algorithm will only run on machines capable of supporting it. 1. This will also support parallel and distributed QPU execution in the future. 4. Ability to route hybrid circuits. Initially supported by Tket [2]. 5. Classical and quantum instructions can be fully interwoven including data- and control-flow. 6. Support for more traditional constructs such as logging and exceptions. These in the future could be lowered into the hardware. The combination of these means that even if a QPU doesn't have built-in hybrid instruction support you can use Rasqal to execute hybrid code against it. -All it needs is gate-level operation support. +All it needs is gate-level operation support. We also have a QASM builder that is used to power our simulators, but this could also be used for integrations with anything that has a QASM API. -[1] Big int is the one exception here, this isn't supported. +[1] Big int is the one exception here, this isn't supported. There are also some instructions we silently ignore because they have no impact on how Rasqal views the world, such as qubit reference counting. [2] The wording here is rather precise as we don't route the QIR, we route the circuit we generate just before its sent to the QPU. @@ -68,18 +58,16 @@ Soon to be implemented/improved: 1. Automatically lowering logic into the hardware if support is available, such as conditionals, loops and exceptions. 2. Aggressive classical operation deference and folding. Currently, if you do a measure and immediately do classical operations on the result then it'll execute the circuit built up to that point. -We want to also defer the classical operations until we find a point where we _absolutely_ need to execute. -3. More circuit metadata to be passed to the backend when working out if it can run something. Circuit size, included gates, hardware features, as much as possible. + We want to also defer the classical operations until we find a point where we _absolutely_ need to execute. +3. More circuit metadata to be passed to the backend when working out if it can run something. Circuit size, included gates, hardware features, as much as possible. 4. Predicted execution plans. Allows tools to pass in QIR and get back precise information about each quantum execution: what circuit, values, features it requires etc. -This will allow tools sitting in front of Rasqal to tailor their own optimization passes. - -5. LLVM-15+. + This will allow tools sitting in front of Rasqal to tailor their own optimization passes. -Then we also have highly experimental features that will be worked on until proven viable or not. +Then we also have highly experimental features that will be worked on until proven viable or not. These will be available only if you enable them explicitly: 1. Quantum state analysis structures for performing indepth static analysis as we go. This powers many other features. 2. Quantum fragment simulation. Finding points in a circuit that if simulated/predicted allow for better optimization or distributed processing. 3. Using our analysis tools and splice/weaving techniques to split up and run large quantum circuits across multiple smaller machines. -community/rasqal/blob/develop/examples.md) for the sorts of code you could send to Rasqal as well as what it returns. \ No newline at end of file + community/rasqal/blob/develop/examples.md) for the sorts of code you could send to Rasqal as well as what it returns. \ No newline at end of file diff --git a/docs/papers/Rasqal Draft v2.pdf b/docs/papers/Rasqal Draft v2.pdf deleted file mode 100644 index fbcd05e..0000000 Binary files a/docs/papers/Rasqal Draft v2.pdf and /dev/null differ diff --git a/docs/papers/Rasqal Draft v3.pdf b/docs/papers/Rasqal Draft v3.pdf new file mode 100644 index 0000000..1489c58 Binary files /dev/null and b/docs/papers/Rasqal Draft v3.pdf differ diff --git a/quick_start.md b/docs/quick_start.md similarity index 94% rename from quick_start.md rename to docs/quick_start.md index dfcd8c6..fdbde62 100644 --- a/quick_start.md +++ b/docs/quick_start.md @@ -1,5 +1,10 @@ ### Getting started +We have an examples folder in `/examples` that shows off much of what will be talked about here. +If you prefer to tinker with code that folder also has a Q# project you can play with and then use Rasqal to run the generated QIR. + +### Basics + To run Rasqal you'll need a QIR file, whether in its human-readable .ll form or bitcode. We have some pre-built QIR that we use for tests (`src/tests/qsharp`) that you can use and modify if needed. @@ -7,6 +12,7 @@ For our first example we're going to use the default simulator backend, so will Since we're not providing a custom backend our Python to run Rasqal is relatively simple. If your QIR has no return value or arguments, this is how you call it: + ```python from rasqal.simulators import fetch_qasm_runner diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..f923c26 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,12 @@ +## Examples + +All dependencies to run the examples are installed into the Rasqal venv if you are building normally. +If you can't run the full build, use `./bulid.ps1 -t "initialize-examples"` to install a Rasqal versio from pypi as a replacement. + +All files can be run directly. + +**Examples.py** holds examples of how to use Rasqals Python APIs to run QIR. + +**Sandbox.py** runs the sandbox Q# project in `qsharp/src`. +Modify the project as you need and then run the Python file to see how Rasqal reacts. +Currently restricted to adaptive profile QIR. \ No newline at end of file diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs/examples.py b/examples/examples.py similarity index 89% rename from docs/examples.py rename to examples/examples.py index bd32f5b..6eb4963 100644 --- a/docs/examples.py +++ b/examples/examples.py @@ -1,9 +1,10 @@ import random -from rasqal.adaptors import BuilderAdaptor, RuntimeAdaptor from rasqal.runtime import RasqalRunner from rasqal.routing import apply_routing, build_ring_architecture +from utils import RuntimeMock + def execute_base_profile_bell(): """ @@ -51,65 +52,6 @@ def execute_full_bell(): assert results == is_one -class BuilderMock(BuilderAdaptor): - def __init__(self): - self.gates = [] - - def cx(self, controls, target, radii): - self.gates.append(f"cx {controls} {target} {radii}") - - def cz(self, controls, target, radii): - self.gates.append(f"cz {controls} {target} {radii}") - - def cy(self, controls, target, radii): - self.gates.append(f"cy {controls} {target} {radii}") - - def x(self, qubit, radii): - self.gates.append(f"x {qubit} {radii}") - - def y(self, qubit, radii): - self.gates.append(f"y {qubit} {radii}") - - def z(self, qubit, radii): - self.gates.append(f"z {qubit} {radii}") - - def swap(self, qubit1, qubit2): - self.gates.append(f"swap {qubit1} {qubit2}") - - def reset(self, qubit): - self.gates.append(f"reset {qubit}") - - def measure(self, qubit): - self.gates.append(f"measure {qubit}") - - def clear(self): - if any(self.gates): - self.gates.append("clear") - - -class RuntimeMock(RuntimeAdaptor): - def __init__(self): - self.executed = [] - self.results = { - "00": 100 - } - - def execute(self, builder: BuilderMock): - # Store our gates if we need to look at them. - self.executed = builder.gates - - # Then return the results we're mocking. - return self.results - - def create_builder(self) -> BuilderAdaptor: - # Returning our builder mock every time. - return BuilderMock() - - def has_features(self, required_features): - # We just state that we can run anything - return True - - bell_base_profile = """ ; ModuleID = 'bell' source_filename = "bell" diff --git a/examples/qsharp/qsharp.json b/examples/qsharp/qsharp.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/examples/qsharp/qsharp.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/examples/qsharp/sandbox.ll b/examples/qsharp/sandbox.ll new file mode 100644 index 0000000..9b86e5b --- /dev/null +++ b/examples/qsharp/sandbox.ll @@ -0,0 +1,46 @@ +%Result = type opaque +%Qubit = type opaque + +define void @ENTRYPOINT__main() #0 { +block_0: + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) + call void @__quantum__rt__array_record_output(i64 3, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null) + ret void +} + +declare void @__quantum__qis__h__body(%Qubit*) + +declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*) + +declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + +declare void @__quantum__rt__array_record_output(i64, i8*) + +declare void @__quantum__rt__result_record_output(%Result*, i8*) + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="3" "required_num_results"="3" } +attributes #1 = { "irreversible" } + +; module flags + +!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10} + +!0 = !{i32 1, !"qir_major_version", i32 1} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} +!4 = !{i32 1, !"classical_ints", i1 true} +!5 = !{i32 1, !"qubit_resetting", i1 true} +!6 = !{i32 1, !"classical_floats", i1 false} +!7 = !{i32 1, !"backwards_branching", i1 false} +!8 = !{i32 1, !"classical_fixed_points", i1 false} +!9 = !{i32 1, !"user_functions", i1 false} +!10 = !{i32 1, !"multiple_target_branching", i1 false} diff --git a/examples/qsharp/src/main.qs b/examples/qsharp/src/main.qs new file mode 100644 index 0000000..53986bc --- /dev/null +++ b/examples/qsharp/src/main.qs @@ -0,0 +1,12 @@ +namespace Sandbox { + @EntryPoint() + operation Main() : Result[] { + use qs = Qubit[3]; + + H(qs[0]); + CNOT(qs[0], qs[1]); + CNOT(qs[0], qs[2]); + + MResetEachZ(qs) + } +} diff --git a/examples/sandbox.py b/examples/sandbox.py new file mode 100644 index 0000000..9ee04c5 --- /dev/null +++ b/examples/sandbox.py @@ -0,0 +1,29 @@ +import qsharp +import os +from qsharp import init, TargetProfile +from rasqal.runtime import RasqalRunner + +from utils import RuntimeMock + +directory_path = os.path.dirname(os.path.abspath(__file__)) + + +def run_sandbox(): + source_path = os.path.join(directory_path, "qsharp") + + # This initializes the static interpreter, so everything you want for QIR emission has to be set up here. + init(project_root=source_path, target_profile=TargetProfile.Adaptive_RI) + + # There's no auto entry-point detection and this expects an expression, including any arguments. + # So Namespace.EntryMethod(...) will then generate the QIR you want. + sandbox = qsharp.compile("Sandbox.Main()") + + runtime = RuntimeMock() + + runner = RasqalRunner(runtime) + results = runner.run_ll(str(sandbox)) + + print(f"Sandbox results: {results}") + + +run_sandbox() diff --git a/examples/utils.py b/examples/utils.py new file mode 100644 index 0000000..2ade7a7 --- /dev/null +++ b/examples/utils.py @@ -0,0 +1,67 @@ +from copy import copy + +from rasqal.adaptors import BuilderAdaptor, RuntimeAdaptor + + +class BuilderMock(BuilderAdaptor): + def __init__(self): + self.gates = [] + + def cx(self, controls, target, radii): + self.gates.append(f"cx {controls} {target} {radii}") + + def cz(self, controls, target, radii): + self.gates.append(f"cz {controls} {target} {radii}") + + def cy(self, controls, target, radii): + self.gates.append(f"cy {controls} {target} {radii}") + + def x(self, qubit, radii): + self.gates.append(f"x {qubit} {radii}") + + def y(self, qubit, radii): + self.gates.append(f"y {qubit} {radii}") + + def z(self, qubit, radii): + self.gates.append(f"z {qubit} {radii}") + + def swap(self, qubit1, qubit2): + self.gates.append(f"swap {qubit1} {qubit2}") + + def reset(self, qubit): + self.gates.append(f"reset {qubit}") + + def measure(self, qubit): + self.gates.append(f"measure {qubit}") + + def clear(self): + self.gates.clear() + + +class RuntimeMock(RuntimeAdaptor): + def __init__(self, suppress_printout=False): + self.executed = [] + self.results = { + "00": 100 + } + self.suppress = suppress_printout + + def execute(self, builder: BuilderMock): + # Store our gates if we need to look at them. + self.executed.append(copy(builder.gates)) + + if not self.suppress: + print("Running circuit:") + for gate in builder.gates: + print(gate) + + # Then return the results we're mocking. + return self.results + + def create_builder(self) -> BuilderAdaptor: + # Returning our builder mock every time. + return BuilderMock() + + def has_features(self, required_features): + # We just state that we can run anything + return True diff --git a/src/rasqal/Cargo.toml b/src/rasqal/Cargo.toml index b1632e3..0aba8c9 100644 --- a/src/rasqal/Cargo.toml +++ b/src/rasqal/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["John Dumbell"] name = "rasqal" -version = "0.1.0" +version = "0.1.6" edition = "2021" license = "BSD-3-Clause" description = "" diff --git a/src/rasqal/README.md b/src/rasqal/README.md index b6579aa..8583eb0 100644 --- a/src/rasqal/README.md +++ b/src/rasqal/README.md @@ -9,7 +9,7 @@ Some of the key things this approach enables: 2. Enabling hybrid algorithms to be run on machines and tools with only a gate-level API available. This includes QASM API's if you use its simulation framework. 3. Lots of optimization potential when passed large amounts of classical context that a quantum algorithm uses to accentuate its own execution. -We also have a [full feature list and quick intro to its concepts](https://github.com/oqc-community/rasqal/blob/develop/features_and_concepts.md) as well as a [draft paper](https://github.com/oqc-community/rasqal/blob/develop/docs/papers/Rasqal%20Draft%20v2.pdf) that covers its internals in excruciating detail. +We also have a [full feature list and quick intro to its concepts](https://github.com/oqc-community/rasqal/blob/develop/docs/features_and_concepts.md) as well as a [draft paper](https://github.com/oqc-community/rasqal/blob/develop/docs/papers/Rasqal%20Draft%20v2.pdf) that covers its internals in excruciating detail. If you have any features or ideas you'd like to see implemented feel free to raise a [feature request](https://github.com/oqc-community/Rasqal/issues/new?assignees=&labels=enhancement&projects=&template=feature_request.md&title=)! @@ -18,13 +18,13 @@ If you have any features or ideas you'd like to see implemented feel free to rai ### Getting Started 1. Install Rasqal in your favourite Python venv by running `pip install rasqal`. Our current testing is done with `v3.10` of Python. -2. Read the [quick start](https://github.com/oqc-community/rasqal/blob/develop/examples.md) and look at our [Python example](https://github.com/oqc-community/Rasqal/blob/develop/docs/examples.py). -3. (Optional) Read the [paper](https://github.com/oqc-community/rasqal/blob/develop/docs/papers/Rasqal%20Draft%20v2.pdf) for a deep-dive into Rasqals concepts and data structures. +2. Read the [quick start](https://github.com/oqc-community/rasqal/blob/develop/docs/quick_start.md) and look at our [examples](https://github.com/oqc-community/Rasqal/blob/develop/examples/examples.py). +3. (Optional) Read the [paper](https://github.com/oqc-community/rasqal/blob/develop/docs/papers/Rasqal%20Draft%20v3.pdf) for a deep-dive into Rasqals concepts and data structures. ### Contributing -If you'd like to contribute your first destination will be to [build the system locally](https://github.com/oqc-community/rasqal/blob/develop/building.md). -There's also a [getting started](https://github.com/oqc-community/rasqal/blob/develop/development.md) page that covers some of the most important bits you'd need to know about the project before jumping into writing code. +If you'd like to contribute your first destination will be to [build the system locally](https://github.com/oqc-community/rasqal/blob/develop/docs/building.md). +There's also a [getting started](https://github.com/oqc-community/rasqal/blob/develop/docs/development.md) page that covers some of the most important bits you'd need to know about the project before jumping into writing code. After that feel free to fork the project and put up PRs with any work you would like to add. All experimental work that isn't ready for prime time has to be disabled by default and have no impact on core execution time and stability. diff --git a/src/rasqal/pyproject.toml b/src/rasqal/pyproject.toml index 9a2936f..18f2d03 100644 --- a/src/rasqal/pyproject.toml +++ b/src/rasqal/pyproject.toml @@ -1,8 +1,8 @@ [project] name = "rasqal" -version = "0.1.5" +version = "0.1.6" requires-python = ">=3.10" -description = "A dynamically executed quantum-classical hybrid optimizing runtime." +description = "A hybrid quantum-classical analysis/solving runtime." license = { file = "LICENSE" } readme = "README.md" classifiers = [ diff --git a/src/rasqal/rasqal/routing.py b/src/rasqal/rasqal/routing.py index abd3fdd..bcf0c16 100644 --- a/src/rasqal/rasqal/routing.py +++ b/src/rasqal/rasqal/routing.py @@ -1,9 +1,13 @@ from typing import Dict, Tuple, List, Union +import numpy as np + from .runtime import RasqalRunner from pytket.architecture import Architecture from pytket import Circuit, OpType, Qubit, Bit -from pytket.passes import SequencePass, DefaultMappingPass +from pytket.passes import SequencePass, DefaultMappingPass, AutoRebase +from sympy import sympify +from sympy import pi as sympi from .adaptors import BuilderAdaptor, RuntimeAdaptor @@ -28,6 +32,17 @@ def apply_routing( raise ValueError(f"Cannot apply routing to {str(runtime)}") +def to_halfturn(num): + try: + return sympify(num) / sympi + except: + raise ValueError("Cannot parse angle: {}".format(num)) + + +def de_halfturn(num): + return num * np.pi + + class TketBuilder(BuilderAdaptor): def __init__(self): self.circuit = Circuit() @@ -35,29 +50,29 @@ def __init__(self): def cx(self, controls, target, radii): self.circuit.add_qubit(Qubit(target), False) self.circuit.add_qubit(Qubit(controls[0]), False) - self.circuit.CRx(radii, controls[0], target) + self.circuit.CRx(to_halfturn(radii), controls[0], target) def cz(self, controls, target, radii): self.circuit.add_qubit(Qubit(target), False) self.circuit.add_qubit(Qubit(controls[0]), False) - self.circuit.CRz(radii, controls[0], target) + self.circuit.CRz(to_halfturn(radii), controls[0], target) def cy(self, controls, target, radii): self.circuit.add_qubit(Qubit(target), False) self.circuit.add_qubit(Qubit(controls[0]), False) - self.circuit.CRy(radii, controls[0], target) + self.circuit.CRy(to_halfturn(radii), controls[0], target) def x(self, qubit, radii): self.circuit.add_qubit(Qubit(qubit), False) - self.circuit.Rx(radii, qubit) + self.circuit.Rx(to_halfturn(radii), qubit) def y(self, qubit, radii): self.circuit.add_qubit(Qubit(qubit), False) - self.circuit.Ry(radii, qubit) + self.circuit.Ry(to_halfturn(radii), qubit) def z(self, qubit, radii): self.circuit.add_qubit(Qubit(qubit), False) - self.circuit.Rz(radii, qubit) + self.circuit.Rz(to_halfturn(radii), qubit) def swap(self, qubit1, qubit2): self.circuit.add_qubit(Qubit(qubit1), False) @@ -68,7 +83,7 @@ def reset(self, qubit): # We're just using a barrier as a tag for reset for now. # Also useful so that things don't move past it. self.circuit.add_qubit(Qubit(qubit), False) - self.circuit.add_barrier([qubit]) + self.circuit.Reset(qubit) def measure(self, qubit): # We don't measure into anything, so just imply qubit index == classical bit index. @@ -102,41 +117,72 @@ def execute(self, builder) -> Dict[str, int]: builder: TketBuilder SequencePass([DefaultMappingPass(self.arch)]).apply(builder.circuit) + self._apply_rebase(builder.circuit) return self.forwarded.execute(self._forward_circuit(builder)) + def _apply_rebase(self, circuit): + """Remaps the Tket circuit to the operations we parse via the builder APIs.""" + AutoRebase( + { + OpType.X, + OpType.Z, + OpType.Y, + OpType.CX, + OpType.Rz, + OpType.Rx, + OpType.Ry, + OpType.CRx, + OpType.CRy, + OpType.CRz, + OpType.Barrier, + OpType.Measure, + OpType.Reset, + OpType.SWAP, + }, + allow_swaps=True, + ).apply(circuit) + def _forward_circuit(self, builder) -> BuilderAdaptor: """Forwards the Tket circuit on to the new builder to be run in the forwarding runtime.""" fbuilder = self.forwarded.create_builder() for gate in builder.circuit: + if gate.op.type == OpType.X: + fbuilder.x(gate.qubits[0].index[0], np.pi) + if gate.op.type == OpType.Y: + fbuilder.x(gate.qubits[0].index[0], np.pi) + if gate.op.type == OpType.Z: + fbuilder.x(gate.qubits[0].index[0], np.pi) + if gate.op.type == OpType.CX: + fbuilder.cx([gate.qubits[0].index[0]], gate.qubits[1].index[0], np.pi) if gate.op.type == OpType.Rz: - fbuilder.z(gate.qubits[0].index[0], gate.op.params[0]) + fbuilder.z(gate.qubits[0].index[0], de_halfturn(gate.op.params[0])) elif gate.op.type == OpType.Rx: - fbuilder.x(gate.qubits[0].index[0], gate.op.params[0]) + fbuilder.x(gate.qubits[0].index[0], de_halfturn(gate.op.params[0])) elif gate.op.type == OpType.Ry: - fbuilder.y(gate.qubits[0].index[0], gate.op.params[0]) + fbuilder.y(gate.qubits[0].index[0], de_halfturn(gate.op.params[0])) elif gate.op.type == OpType.CRx: fbuilder.cx( [gate.qubits[0].index[0]], gate.qubits[1].index[0], - gate.op.params[0], + de_halfturn(gate.op.params[0]), ) elif gate.op.type == OpType.CRy: fbuilder.cy( [gate.qubits[0].index[0]], gate.qubits[1].index[0], - gate.op.params[0], + de_halfturn(gate.op.params[0]), ) elif gate.op.type == OpType.CRz: fbuilder.cz( [gate.qubits[0].index[0]], gate.qubits[1].index[0], - gate.op.params[0], + de_halfturn(gate.op.params[0]), ) elif gate.op.type == OpType.SWAP: fbuilder.swap(gate.qubits[0].index[0], gate.qubits[1].index[0]) elif gate.op.type == OpType.Measure: fbuilder.measure(gate.qubits[0].index[0]) - elif gate.op.type == OpType.Barrier: + elif gate.op.type == OpType.Reset: fbuilder.reset(gate.qubits[0].index[0]) return fbuilder diff --git a/src/rasqal/rasqal/runtime.py b/src/rasqal/rasqal/runtime.py index bb030f3..85fa258 100644 --- a/src/rasqal/rasqal/runtime.py +++ b/src/rasqal/rasqal/runtime.py @@ -2,19 +2,11 @@ # Copyright (c) 2024 Oxford Quantum Circuits Ltd from os import remove -from os.path import dirname, exists, join from tempfile import NamedTemporaryFile from typing import Any, List, Union -from .utils import initialize_logger from .adaptors import RuntimeAdaptor -from ._native import DEFAULT_LOG_FILE, Executor - -dev_directory = join(dirname(__file__), "..", "..", "..", "rasqalkin") - -# Enable file logging if we're in a development environment. -if exists(dev_directory): - initialize_logger(join(f"{dev_directory}", f"{DEFAULT_LOG_FILE}")) +from ._native import Executor class RasqalRunner: diff --git a/src/rasqal/src/analysis.rs b/src/rasqal/src/analysis.rs index 89c9924..9a6b214 100644 --- a/src/rasqal/src/analysis.rs +++ b/src/rasqal/src/analysis.rs @@ -554,7 +554,7 @@ impl QuantumStatePredictor { self.state.measure(&qb.index); } } - QuantumOperations::Initialize() | QuantumOperations::I(_) => {} + QuantumOperations::Initialize() | QuantumOperations::Id(_) => {} } } } @@ -570,12 +570,12 @@ pub struct QuantumProjection { } /// A for-now list of linear gates and hardware operations that we can store and send to our -/// Python runtimes. In time these will be removed and we'll reconstruct gates from +/// Python runtimes. In time these will be removed, and we'll reconstruct gates from /// our other analysis structures. pub enum QuantumOperations { Initialize(), Reset(Vec), - I(Qubit), + Id(Qubit), U(Qubit, f64, f64, f64), X(Qubit, f64), Y(Qubit, f64), @@ -593,7 +593,7 @@ impl QuantumOperations { match self { QuantumOperations::Initialize() => vec![], QuantumOperations::Reset(qbs) => qbs.iter().collect(), - QuantumOperations::I(qb) + QuantumOperations::Id(qb) | QuantumOperations::U(qb, _, _, _) | QuantumOperations::X(qb, _) | QuantumOperations::Y(qb, _) @@ -618,7 +618,7 @@ impl Display for QuantumOperations { .collect::>() .join(", ") ), - QuantumOperations::I(qb) => format!("id[{qb}]"), + QuantumOperations::Id(qb) => format!("id[{qb}]"), QuantumOperations::U(qb, theta, phi, lambda) => { format!("U[{qb}] {theta},{phi},{lambda}") } @@ -856,7 +856,7 @@ impl QuantumProjection { builder.reset(qubit); } } - QuantumOperations::I(qb) => { + QuantumOperations::Id(qb) => { builder.i(qb); } QuantumOperations::U(qb, theta, phi, lambda) => { diff --git a/src/rasqal/src/builders.rs b/src/rasqal/src/builders.rs index a39c777..93dd721 100644 --- a/src/rasqal/src/builders.rs +++ b/src/rasqal/src/builders.rs @@ -6,7 +6,7 @@ use crate::features::QuantumFeatures; use crate::hardware::Qubit; use crate::python::RequiredFeatures; use crate::smart_pointers::Ptr; -use pyo3::{IntoPy, PyAny, PyObject, PyResult, Python}; +use pyo3::{IntoPy, PyAny, PyObject, Python}; use std::borrow::Borrow; use std::f64::consts::PI; use std::ops::{Deref, DerefMut}; diff --git a/src/rasqal/src/config.rs b/src/rasqal/src/config.rs index 246ea5b..fe85d5c 100644 --- a/src/rasqal/src/config.rs +++ b/src/rasqal/src/config.rs @@ -2,7 +2,6 @@ use crate::runtime::ActiveTracers; pub struct RasqalConfig { /// How many steps the symbolic executor is allowed to make before failing. - /// pub step_count_limit: Option, pub debug_tracers: ActiveTracers } diff --git a/src/rasqal/src/evaluator.rs b/src/rasqal/src/evaluator.rs index 647276b..3bb8508 100644 --- a/src/rasqal/src/evaluator.rs +++ b/src/rasqal/src/evaluator.rs @@ -13,10 +13,10 @@ use crate::smart_pointers::Ptr; use crate::with_mutable; use inkwell::basic_block::BasicBlock; use inkwell::module::Module; -use inkwell::types::{AnyType, AnyTypeEnum}; +use inkwell::types::AnyTypeEnum; use inkwell::values::{ - AggregateValue, AnyValue, AnyValueEnum, ArrayValue, AsValueRef, BasicValue, BasicValueEnum, - FunctionValue, InstructionOpcode, InstructionValue, StructValue + AnyValue, AnyValueEnum, AsValueRef, BasicValue, BasicValueEnum, FunctionValue, InstructionOpcode, + InstructionValue }; use inkwell::{FloatPredicate, IntPredicate}; use llvm_sys::core::{ @@ -25,13 +25,14 @@ use llvm_sys::core::{ }; use llvm_sys::prelude::LLVMValueRef; use llvm_sys::LLVMTypeKind; -use log::warn; +use log::{log, warn, Level}; use regex::Regex; use std::borrow::{Borrow, BorrowMut}; use std::collections::HashMap; use std::f64::consts::PI; use std::ffi::{c_uint, CStr}; use std::ops::Deref; +use std::time::Instant; macro_rules! operand_to_value { ($target:ident, $index:expr) => { @@ -65,10 +66,10 @@ macro_rules! operand_to_bb { }; } -// TODO: Since Inkwell dosen't expose things properly try and use the llvm-sys objects to find the -// data. We want to remove all the string fetching/matching as it's inefficent. +// TODO: Since Inkwell doesn't expose things properly try and use the llvm-sys objects to find the +// data. We want to remove all the string fetching/matching as it's inefficient. -/// Fetches the assignment variable (&{value}) from a stringified LLVM instruction. +/// Fetches the assignment variable &{value} = ... from a stringified LLVM instruction. pub fn get_ref_id_from_instruction(inst: &InstructionValue) -> String { let inst_str = inst .to_string() @@ -90,6 +91,7 @@ pub fn parse_ref_id_from_instruction(inst: &InstructionValue) -> Option parse_ref_id_from_instruction_str(&inst_str) } +/// See [`get_ref_id_from_instruction`]. pub fn parse_ref_id_from_instruction_str(inst_str: &str) -> Option { let llvm_var_finder = Regex::new("([%@][^ ]*) =").unwrap(); llvm_var_finder.captures(inst_str).map_or_else( @@ -98,13 +100,10 @@ pub fn parse_ref_id_from_instruction_str(inst_str: &str) -> Option { ) } -pub fn get_ref_id_from_value(ptr_string: &str) -> String { - parse_ref_id_from_value(ptr_string).expect("Can't parse ref-id from value.") -} - -/// TODO: Need a proper way to get the variables from a general state, while this works it's not -/// entirely bulletproof and needs tweaking as issues come up. And issues caused from it are not -/// immediately obvious. +// TODO: Need a proper way to get the variables from a general state, while this works it's not +// entirely bulletproof and needs tweaking as issues come up. And issues caused from it are not +// immediately obvious. +/// Attempts to get a variable assignment from an argument value: some_call(%Array* %register, ...). pub fn parse_ref_id_from_value(ptr_string: &str) -> Option { let ptr_string = ptr_string.trim_matches('"').trim(); let local_variable_finder: Regex = Regex::new("^.*\\s(%[\\w0-9\\-]+)$").unwrap(); @@ -141,6 +140,11 @@ pub fn parse_ref_id_from_value(ptr_string: &str) -> Option { }) } +/// See [`parse_ref_id_from_value`] +pub fn get_ref_id_from_value(ptr_string: &str) -> String { + parse_ref_id_from_value(ptr_string).expect("Can't parse ref-id from value.") +} + /// Parsing context, molds all state required by the evalautor to run. pub struct EvaluationContext<'ctx> { pub module: Ptr>, @@ -231,7 +235,10 @@ impl QIREvaluator { target_global = global.get_next_global(); } + let start = Instant::now(); let builder = self.walk_function(entry_point, context.borrow()); + let took = start.elapsed(); + log!(Level::Info, "Evaluation took {}ms.", took.as_millis()); // Create a callable graph with its arguments, but the values set as empty (validly). let mut callable = Ptr::from(CallableAnalysisGraph::new(&builder.graph)); @@ -301,7 +308,7 @@ impl QIREvaluator { let graph = Ptr::from(AnalysisGraph::new(method_name.clone())); with_mutable!(context.method_graphs.insert(method_name, graph.clone())); - // Build up anchor labels/nodes so we an associate them at the start and end. + // Build up anchor labels/nodes so we can associate them at the start and end. for bb in func.get_basic_blocks() { let bb_name = bb.get_name().to_str().unwrap().to_string(); let anchor_node = with_mutable!(graph.add_loose(Instruction::Label(bb_name.clone()))); @@ -381,8 +388,7 @@ impl QIREvaluator { unsafe { BasicValueEnum::new(LLVMGetAggregateElement(array, index as c_uint)) } } - /// `as_value` - /// almost never want to use this directly. Use [`as_value`] instead. + /// Recursive call of [`as_value`]. Call the non-recursive version for most use-cases. fn _as_value_recursive( &self, graph: &Ptr, type_enum: &AnyTypeEnum, val_enum: &AnyValueEnum, context: &Ptr @@ -499,13 +505,14 @@ impl QIREvaluator { .expect( format!( "Unable to find base profile value. Instruction: {}", - stringified_value.clone() + stringified_value ) .as_str() ) .as_str() }; + // nulls get coerced into 0 if processed correctly, but we have to do it manually here. if value == "null" { value = "0"; } @@ -513,7 +520,7 @@ impl QIREvaluator { return match name { "Qubit" => Some(Value::Qubit(Qubit::new(value.parse().unwrap()))), "Result" => Some(Value::Int(value.parse().unwrap())), - _ => panic!("Attempted specific match on non-base-profile pointer. Instruction: {}, name: {}, value: {}", stringified_value.clone(), name.clone(), value.clone()) + _ => panic!("Attempted specific match on non-base-profile pointer. Instruction: {}, name: {}, value: {}", stringified_value, name, value) }; } @@ -545,8 +552,6 @@ impl QIREvaluator { }; // TODO: Make custom results object, probably re-use projection results. - // TODO: With LLVM version upgrade this likely isn't needed, this is all pointer-based - // anyway. match struct_name { "Qubit" => Some(Value::Qubit(Qubit::new(index))), "Result" => Some(Value::Int(index)), @@ -644,7 +649,7 @@ impl QIREvaluator { self._as_value_recursive(graph, any_val.get_type().borrow(), any_val, context) } - /// Evaluates the instruction and adds it to the graph. + /// Evaluates a specific LLVM IR instruction and adds it to the graph. fn walk_instruction( &self, inst: &Ptr, graph: &Ptr, context: &Ptr @@ -660,10 +665,10 @@ impl QIREvaluator { InstructionOpcode::Br => { self.eval_branch(inst, graph, context); } - InstructionOpcode::Switch - | InstructionOpcode::IndirectBr - | InstructionOpcode::Invoke - | InstructionOpcode::FNeg => { + InstructionOpcode::Switch | InstructionOpcode::IndirectBr | InstructionOpcode::Invoke => { + todo!("{}", inst.print_to_string().to_string()) + } + InstructionOpcode::FNeg => { self.eval_fneg(inst, graph, context); } InstructionOpcode::Add => { @@ -1157,6 +1162,20 @@ impl QIREvaluator { let qb = parse_qubit(inst, 0); graph.Measure(Value::Pauli(Pauli::Z), qb, target_value); } + + // Slightly non-official QIR, measure then reset. + "__quantum__qis__mresetz__body" => { + let target_value = if let Some(val) = parse_ref_id_from_instruction(inst.borrow()) { + Value::String(val) + } else { + parse_as_value(inst, 1).expect("Can't find result register.") + }; + + let qb = parse_qubit(inst, 0); + graph.Measure(Value::Pauli(Pauli::Z), qb.clone(), target_value); + graph.Reset(qb); + } + "__quantum__qis__cx__body" => { let control = parse_qubit(inst, 0); let target = parse_qubit(inst, 1); @@ -1379,6 +1398,9 @@ impl QIREvaluator { graph.Arithmatic(ref_id, value, Operator::PowerOf, power_multiplier); } + // Output recording doesn't matter for us. + "__quantum__rt__tuple_record_output" | "__quantum__rt__array_record_output" => {} + // Bigint support that hopefully we'll just be able to ignore. "__quantum__rt__bigint_add" | "__quantum__rt__bigint_bitand" @@ -1405,8 +1427,6 @@ impl QIREvaluator { | "__quantum__rt__array_slice_1d" | "__quantum__rt__array_get_dim" | "__quantum__rt__array_concatenate" - | "__quantum__rt__tuple_record_output" - | "__quantum__rt__array_record_output" | "__quantum__rt__string_get_data" | "__quantum__rt__string_get_length" | "__quantum__rt__tuple_copy" diff --git a/src/rasqal/src/execution.rs b/src/rasqal/src/execution.rs index 5a25a4f..559f659 100644 --- a/src/rasqal/src/execution.rs +++ b/src/rasqal/src/execution.rs @@ -24,7 +24,7 @@ use inkwell::{ use crate::config::RasqalConfig; use crate::exceptions::catch_panics; -use std::ops::Deref; +use log::{log, Level}; use std::{ffi::OsStr, path::Path}; /// Executes the file. @@ -35,9 +35,15 @@ pub fn run_file( catch_panics(|| run_graph(&parse_file(path, entry_point)?, args, runtimes, config)) } +/// Parses the .ll/.bc file and builds an [`ExecutableAnalysisGraph`] for it. pub fn parse_file( path: impl AsRef, entry_point: Option<&str> ) -> Result, String> { + log!( + Level::Info, + "Parsing from {}.", + path.as_ref().to_str().unwrap() + ); let context = Context::create(); let module = file_to_module(path, &context)?; catch_panics(|| build_graph_from_module(&module, entry_point)) @@ -78,11 +84,15 @@ pub fn build_graph_from_module( Target::initialize_native(&InitializationConfig::default())?; inkwell::support::load_library_permanently(Path::new("")); + let entry_point = choose_entry_point(module_functions(module), entry_point)?; + + log!( + Level::Info, + "{} is the entry-point.", + entry_point.get_name().to_str().unwrap() + ); let evaluator = QIREvaluator::new(); - evaluator.evaluate( - &choose_entry_point(module_functions(module), entry_point)?, - &Ptr::from(module) - ) + evaluator.evaluate(&entry_point, &Ptr::from(module)) }) } @@ -212,7 +222,7 @@ mod tests { run_file( path, - &Vec::new(), + args, runtimes.borrow(), None, &Ptr::from(RasqalConfig::default()) diff --git a/src/rasqal/src/features.rs b/src/rasqal/src/features.rs index 0dcc8a0..b65e0bc 100644 --- a/src/rasqal/src/features.rs +++ b/src/rasqal/src/features.rs @@ -5,6 +5,7 @@ use std::fmt::{Display, Formatter}; /// A feature collection which a QPU needs to have in order to run a particular projection. pub struct QuantumFeatures { + /// Amount of qubits required for this feature. pub qubits: i32 } diff --git a/src/rasqal/src/graphs.rs b/src/rasqal/src/graphs.rs index 3300732..2078fe9 100644 --- a/src/rasqal/src/graphs.rs +++ b/src/rasqal/src/graphs.rs @@ -25,7 +25,7 @@ pub fn walk_logical_paths(graph: &Ptr) -> LogicalPathwayIterator LogicalPathwayIterator::new(graph) } -/// Walks the graph top-down taking all branches as it goes. Not a flat walk, as it flip=flops +/// Walks the graph top-down taking all branches as it goes. Not a flat walk, as it flip-flops /// between branches it means any pathways that are heavily weighted on one side will be completed /// later, sometimes exceptionally so. pub struct LogicalPathwayIterator { @@ -266,13 +266,8 @@ impl AnalysisGraph { self.edges.get(&node_id).unwrap() } - /// Adds this node to the graph, assigning it as the next auto-attach target. If you want - /// `add_orphan` - /// - /// While this node always gets attached as the next aa-target, you can cohose whether to add - /// `add_attached_edge` - /// You may not want to use this value in situations where you're dealing with the edge - /// attachment via another means. + /// Adds this node to the graph, assigning it as the next auto-attach target. If you don't want + /// it to auto-attach just set `add_attached_edge` as false. pub fn add_node_with_edge(&mut self, node: &Ptr, add_attached_edge: bool) { self.add_loose_node(node); @@ -567,7 +562,7 @@ impl PartialEq for CallableAnalysisGraph { impl Eq for CallableAnalysisGraph {} -/// Analysis graph that has been fully analyzed and is ready to be executed. Carries graph and +/// N analysis graph that has been fully analyzed and is ready to be executed. Carries graph and /// appropriate metadata. pub struct ExecutableAnalysisGraph { pub callable_graph: Ptr, @@ -630,8 +625,8 @@ impl Display for ExecutableAnalysisGraph { } } -/// `AnalysisGraph` -/// Note: uses auto-deref to allow it to act as an extension. +/// Wrapper for various graphs that allow you to use builder syntax on them. +/// Uses auto-deref to appropriately wrap the class. pub struct AnalysisGraphBuilder { pub graph: Ptr } @@ -707,7 +702,9 @@ impl AnalysisGraphBuilder { } pub fn I(&self, qx: Value) -> Ptr { - with_mutable_self!(self.graph.add(InstructionBuilder::Gate(GateBuilder::I(qx)))) + with_mutable_self!(self + .graph + .add(InstructionBuilder::Gate(GateBuilder::Id(qx)))) } pub fn U(&self, qx: Value, theta: f64, phi: f64, lambda: f64) -> Ptr { diff --git a/src/rasqal/src/hardware.rs b/src/rasqal/src/hardware.rs index 1eb923b..a6999ba 100644 --- a/src/rasqal/src/hardware.rs +++ b/src/rasqal/src/hardware.rs @@ -4,6 +4,7 @@ use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; +/// Virtual representation of a qubit. #[derive(Debug, Clone)] pub struct Qubit { pub index: i64 diff --git a/src/rasqal/src/instructions.rs b/src/rasqal/src/instructions.rs index cd1ca0e..a10a2c8 100644 --- a/src/rasqal/src/instructions.rs +++ b/src/rasqal/src/instructions.rs @@ -12,6 +12,7 @@ use std::fmt::{Display, Formatter}; use std::ops; use std::ops::{BitAnd, BitOr, BitXor, Deref}; +/// Common equality operators. #[derive(Copy, Clone)] pub enum Equalities { Equals, @@ -35,6 +36,7 @@ impl Display for Equalities { } } +/// Standard arithmatic and bitwise operators. pub enum Operator { Multiply, Divide, @@ -130,6 +132,7 @@ pub enum Instruction { Expression(Expression, Option) } +/// Static builder for instructions. Just makse processing them easier. pub struct InstructionBuilder {} impl InstructionBuilder { @@ -338,12 +341,13 @@ impl Display for Pauli { } } -// TODO: Remove pointers from objects who own the value - like qubit, analysis result, etc. -// A value should _be_ the representation of the value, and are always themselves wrapped in -// pointers, so the inner value also being a pointer increases complexity and adds potential for -// errors. - -/// A value that can flow around the graph. +/// Values are the single 'Value' that is embedded into the graph at creation or at runtime. They +/// are an implicitly-casting and comparing enum that have many overrides for all normal operations +/// and hook into overarching functions such as auto-folding and increasing deferral duration. +/// +/// When you have two Values who are primitives or arrays they operate as you'd expect. It's only +/// when one side s a promise or a projection do they get far more complicated and trigger further +/// functionality. pub enum Value { Empty, Byte(i8), @@ -373,7 +377,6 @@ pub enum Value { impl Clone for Value { fn clone(&self) -> Self { - // TODO: As above, strip pointers away from certain objects. match self { Value::Empty => Value::Empty, Value::Byte(val) => Value::Byte(*val), @@ -672,6 +675,11 @@ impl Value { // TODO: Improve projection results. It's a value distribution (and many other forms), come up // with rules regarding certain numbers. +/// When equality is attempted against Values they do a type match then a value match. +/// +/// For primitives they just do a cast-then-compare. +/// For arrays they do an element-value compare. +/// For more complicated objects they delegate to the custom comparetor. impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { match self { @@ -1098,7 +1106,7 @@ value_into!(bool, "bool", Bool); /// squash everything into R's with a pauli, but is there a good reason for keeping them split? pub enum Gate { /// Qubit - I(Ptr), + Id(Ptr), /// Qubit, theta, phi, lambda. U(Ptr, Ptr, Ptr, Ptr), @@ -1125,9 +1133,10 @@ pub enum Gate { pub struct GateBuilder {} +/// Static builder for gates hiding a lot of the verbosity of creation. impl GateBuilder { - /// See [`Gate::I`]. - pub fn I(qubit: Value) -> Gate { Gate::I(Ptr::from(qubit)) } + /// See [`Gate::Id`]. + pub fn Id(qubit: Value) -> Gate { Gate::Id(Ptr::from(qubit)) } /// See [`Gate::U`]. pub fn U(qubit: Value, theta: Value, phi: Value, lambda: Value) -> Gate { @@ -1144,14 +1153,17 @@ impl GateBuilder { Gate::R(Ptr::from(pauli), Ptr::from(qubit), Ptr::from(theta)) } + /// See [`Gate::X`]. pub fn X(qubit: Value, theta: Value) -> Gate { GateBuilder::R(Value::Pauli(Pauli::X), qubit, theta) } + /// See [`Gate::Y`]. pub fn Y(qubit: Value, theta: Value) -> Gate { GateBuilder::R(Value::Pauli(Pauli::Y), qubit, theta) } + /// See [`Gate::Z`]. pub fn Z(qubit: Value, theta: Value) -> Gate { GateBuilder::R(Value::Pauli(Pauli::Z), qubit, theta) } @@ -1166,14 +1178,17 @@ impl GateBuilder { ) } + /// See [`Gate::CX`]. pub fn CX(controllers: Value, target: Value, theta: Value) -> Gate { GateBuilder::CR(Value::Pauli(Pauli::X), controllers, target, theta) } + /// See [`Gate::CZ`]. pub fn CZ(controllers: Value, target: Value, theta: Value) -> Gate { GateBuilder::CR(Value::Pauli(Pauli::Z), controllers, target, theta) } + /// See [`Gate::CY`]. pub fn CY(controllers: Value, target: Value, theta: Value) -> Gate { GateBuilder::CR(Value::Pauli(Pauli::Y), controllers, target, theta) } @@ -1188,7 +1203,7 @@ impl Display for Gate { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str( match self { - Gate::I(qb) => { + Gate::Id(qb) => { format!("I {qb}") } Gate::U(qb, theta, phi, lambda) => { diff --git a/src/rasqal/src/lib.rs b/src/rasqal/src/lib.rs index 09afc6b..b4f44ad 100644 --- a/src/rasqal/src/lib.rs +++ b/src/rasqal/src/lib.rs @@ -41,7 +41,6 @@ const DEFAULT_LOG_FILE: &str = "rasqal_logs.txt"; /// Native initialization of the loggers. Defaults to executable position if deployed, if it /// detects it's in development mode it'll move log file back up the folder tree. -#[ctor::ctor] fn native_logger_initialize() { let path = if let Ok(val) = current_exe() { // If we're embedded we need to be given a different file path to log too. diff --git a/src/rasqal/src/python.rs b/src/rasqal/src/python.rs index 59e66eb..abae8ef 100644 --- a/src/rasqal/src/python.rs +++ b/src/rasqal/src/python.rs @@ -7,7 +7,6 @@ use crate::execution::{parse_file, run_file, run_graph, RuntimeCollection}; use crate::features::QuantumFeatures; use crate::graphs::ExecutableAnalysisGraph; use crate::instructions::Value; -use crate::runtime::ActiveTracers; use crate::smart_pointers::Ptr; use crate::{initialize_loggers, DEFAULT_LOG_FILE}; use log::{log, log_enabled, Level}; diff --git a/src/rasqal/src/runtime.rs b/src/rasqal/src/runtime.rs index c835488..d1efe9c 100644 --- a/src/rasqal/src/runtime.rs +++ b/src/rasqal/src/runtime.rs @@ -17,7 +17,8 @@ use log::{log, Level}; use std::borrow::{Borrow, BorrowMut}; use std::collections::{HashMap, HashSet, VecDeque}; use std::fmt::{Display, Formatter}; -use std::ops::{Add, AddAssign, Deref, DerefMut}; +use std::ops::{AddAssign, Deref, DerefMut}; +use std::time::Instant; /// Assign an order to nodes so we're able to tell trivially when one is further in the graph /// or not. @@ -410,7 +411,13 @@ impl QuantumRuntime { log!(Level::Info, "Currently executing graph:\n{}", exe_graph); } - self + log!( + Level::Info, + "Starting execution at {}.", + exe_graph.callable_graph.analysis_graph.identity + ); + let start = Instant::now(); + let results = self ._execute( exe_graph.callable_graph.analysis_graph.borrow(), &mut context @@ -419,10 +426,6 @@ impl QuantumRuntime { val.as_ref()?; let val = follow_reference(&val.unwrap(), &context); - if self.is_tracing() { - log!(Level::Info, "Total steps taken: {}", context.step_count) - } - // TODO: Centralize resolution, think we already have this elsewhere. Some(match val.deref() { Value::QuantumPromise(qbs, proj) => Ptr::from(Value::AnalysisResult(Ptr::from( @@ -442,7 +445,22 @@ impl QuantumRuntime { )), _ => val.clone() }) - }) + }); + + let took = start.elapsed(); + log!( + Level::Info, + "Run {} after {:?}ms with {} steps taken.", + if results.is_ok() { + "succeeded" + } else { + "failed" + }, + took.as_millis(), + context.step_count + ); + + results } fn _execute( @@ -472,7 +490,6 @@ impl QuantumRuntime { loop { context.step_count.add_assign(1); if let Some(limit) = &self.constraints.step_limit { - let stuff = context.step_count.deref().clone(); if context.step_count.deref() > limit { return Err(String::from(format!( "Execution step count limitation of {limit} exceeded." @@ -584,7 +601,7 @@ impl QuantumRuntime { }; // Trim since we're just logging per-line. - log!(Level::Info, "{}", message.trim()); + log!(target: &graph.identity, Level::Info, "{}", message.trim()); } Instruction::Subgraph(subgraph, var) => { let subgraph = follow_reference(subgraph, context).as_callable(); @@ -693,10 +710,10 @@ impl QuantumRuntime { } Instruction::Gate(gate) => { match gate.deref() { - Gate::I(qb) => { + Gate::Id(qb) => { let followed = follow_qubit(qb, context); let mut projection = context.activate_projection(&followed); - projection.add(&Ptr::from(QuantumOperations::I(followed.clone()))); + projection.add(&Ptr::from(QuantumOperations::Id(followed.clone()))); } Gate::U(qb, theta, phi, lambda) => { let followed = follow_qubit(qb, context); @@ -716,7 +733,7 @@ impl QuantumRuntime { match follow_reference(pauli, context).as_pauli() { Pauli::I => { - projection.add(&Ptr::from(QuantumOperations::I(followed))); + projection.add(&Ptr::from(QuantumOperations::Id(followed))); } Pauli::X => { projection.add(&Ptr::from(QuantumOperations::X(followed, radii))); diff --git a/src/rasqal/src/smart_pointers.rs b/src/rasqal/src/smart_pointers.rs index 528d162..e6c7e73 100644 --- a/src/rasqal/src/smart_pointers.rs +++ b/src/rasqal/src/smart_pointers.rs @@ -139,7 +139,7 @@ impl Display for FlexiRef { /// on pointers of differing types due to the structure of the internal data. /// /// Since its internals are raw pointers Rusts lifetime rules have no clue about them, and since -/// raw pointers are also treated specially in regards to the mutation you can take out +/// raw pointers are also treated specially regarding mutation you can take out /// infinite mutable aliases if performed through the raw pointer itself. /// /// Due to this constraint around mutation, we use macros that perform the operations on @@ -203,18 +203,7 @@ impl FlexiPtr { } } FlexiPtr::Borrow(borrow) => { - match val { - FlexiPtr::RefCounted(_) => { - panic!("Can't extract from ref-counted into borrow-driven flexi-pointer."); - } - FlexiPtr::Borrow(other_borrow) => { - // TODO: Probably doesn't work, test and fix. - // Reading a reference with a bitwise copy is probably fine as it's just a - // pointer anyway. - unsafe { (*borrow).write(other_borrow.read()) }; - } - _ => {} - } + panic!("Can't expand into borrow-driven flexi-pointers."); } _ => {} } @@ -272,7 +261,6 @@ impl FlexiPtr { unsafe { // Borrows don't require a drop, neither does None, only if we're the owner of // an object do we want to do anything to it. - if let FlexiPtr::RefCounted(ref_) = self { (**ref_).dec(); if (**ref_).ref_count() <= 0 { diff --git a/src/scripts/psakefile.ps1 b/src/scripts/psakefile.ps1 index a706aab..ba246c2 100644 --- a/src/scripts/psakefile.ps1 +++ b/src/scripts/psakefile.ps1 @@ -8,6 +8,7 @@ Properties { $ProjectRoot = Resolve-Path (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)) $Root = Join-Path $ProjectRoot src $Docs = Join-Path $ProjectRoot docs + $Examples = Join-Path $ProjectRoot examples $BuildLlvm = Join-Path $Root build-llvm $Rasqal = Join-Path $Root rasqal $Target = Join-Path $Root target @@ -18,16 +19,19 @@ Properties { } task default -depends build -task build -depends build-llvm, build-rasqal, test-rasqal, format +task build -depends build-llvm, build-rasqal, test-rasqal, run-examples, format task check -depends check-licenses task format -depends format-rust, format-python task pypi-build -depends build, audit-rasqal, check +# Task should not be invoked if you're building and installing Rasqal locally. +task initialize-examples -depends install-rasqal-from-pypi, setup-examples, run-examples + task format-python { Invoke-LoggedCommand -workingDirectory $Root { pip install ruff ruff format - ruff check --fix --exit-zero + ruff check --fix --silent --exit-zero } } @@ -83,13 +87,31 @@ task test-rasqal -depends build-rasqal { pip install pytest pytest . } +} + +# Used to install and run the examples without building Rasqal fully. +task install-rasqal-from-pypi -depends check-environment { + Invoke-LoggedCommand -workingDirectory $Root { + pip install rasqal + } +} - # Run our examples Python file. - Invoke-LoggedCommand -workingDirectory $Docs { +task setup-examples -depends check-environment { + # Install required dependencies. + Invoke-LoggedCommand -workingDirectory $Root { + pip install qsharp + } +} + +task run-examples -depends setup-examples { + # Run our Python examples. + Invoke-LoggedCommand -workingDirectory $Examples { python examples.py + python sandbox.py } } + task check-environment { $pyenv = Join-Path $Root ".env" if ((Test-Path -Path $pyenv) -eq $false) {