diff --git a/docs/concepts/compilation.md b/docs/concepts/compilation.md index f164c377..efcbf490 100644 --- a/docs/concepts/compilation.md +++ b/docs/concepts/compilation.md @@ -12,14 +12,12 @@ Quantum programs are often deeply nested structures. A program can call multiple Here's an example to illustrate what we're talking about. Below we show a routine with two children, each of them having some example resource defined in terms of their local parameters. - ```yaml --8<-- "docs/data/compilation_example.yaml" ``` ![example routine](../images/compilation_example.png) - As we can see, both `a` and `b` have a resource `x` defined relatively to their input port size `L`. However, when looked globally, those resources would have a different value. Indeed, let's see how the port sizes propagate. @@ -35,7 +33,6 @@ This is what the compilation process is all about. Given a routine with all its and parameters, `bartiq` produces a *compiled routine* in which all port sizes and resources are defined in terms of the global input symbols of top-level routine. - ## Compilation in details Compilation can be viewed as recursive process. At every recursive call, several things need to happen in correct order. @@ -53,12 +50,11 @@ violation of some of its assumptions, and preprocesses the routine so that all t As an example, suppose some port `in_0` has size `1`. `bartiq` replaces this size with `#in_0`, and then adds a constraint saying that `#in_0 = 1`. +Currently, the following preprocessing stages take place prior to compilation: -Currently, the following preprocessing stages take place prior to compilation: - -1. Propagation of linked params. In this stage all linked parameters reaching further than a direct +1. Propagation of linked parameters. In this stage all linked parameters reaching further than a direct descendant are converted into a series of direct parameter links. This is useful because you can, for example, - link a parameter from the top-level routine to a parameter arbitrarily deep in the program structure. This is will compile correctly, despite `bartiq`'s compilation engine requirement on having only direct links. + link a parameter from the top-level routine to a parameter arbitrarily deep in the program structure. This will compile correctly, despite `bartiq`'s compilation engine requirement on having only direct links. 2. Promotion of unlinked inputs. Compilation cannot handle parameters that are not linked or passed through connections. To avoid unnecessary compilation errors, `bartiq` will promote such parameters by linking it to newly introduced input in the parent routine. 3. Introduction of port variables. As discussed above, this step converts all ports so that they have sizes @@ -71,7 +67,7 @@ ensuring that strict requirements of the compilation engine are met. ### Step 2: Recursive compilation of routines As already mentioned, the compilation process is recursive. During compilation `bartiq` -maintains a parameter map for all of the routine's children. This map gets populated whenever a new piece of +maintains a parameter map for all the routine's children. This map gets populated whenever a new piece of information is obtained, and then passed to the recursive call when each child is being compiled. What follows is a high-level, ordered overview of the main compilation process, where information is passed along edges of the routine graph. @@ -95,7 +91,6 @@ corresponding entry in parameter map is added. For instance suppose that port `i size `N`. If this port is connected to port `in_1` of child `a`, a parameter map for `a` will contain entry mapping `#in_1` to `N`. - #### Step 2.4: Child traversal The children are traversed in topological order, which ensures all required entries in the parameter maps are @@ -104,33 +99,38 @@ populated before other children are compiled. Once this step is completed, we can be sure that all resources and ports of each child are expressed in terms of global variables, which is a requirement for the next step. -#### Step 2.5 Propagating children resources +#### Step 2.5: Propagating children resources Introduces default additive and multiplicative resources. In case these resources are defined for a child or children, but not a parent, this step will add the same resource to each of the higher-level routines, by defining it as a sum (or product) of the resource over all children that define it. In the example we discussed previously, this allows us to skip the definition of `x` resource in `root` and instead have it automatically defined by `bartiq`. #### Step 2.6: Repetitions -In case a routine is repeated (i.e. has a non-empty `repetition` field), its local resource definitions get updated according +In case a routine is repeated (i.e. has a non-empty `repetition` field), its local resource definitions get updated according to the repetition rules; the repetition specification itself gets updated using the parameter map. #### Step 2.7: Resource compilation At this stage each child should have input and through ports defined in terms of global variables, and we can now compile the resources. Parents have their resources updated from the compiled resources of their children. -By default, we compile resources *transitively* such that the resources of a particular routine are defined only in terms of the relevant contributions from their immedite children, and any expressions defined locally. For instance, an additive resource `X` in a routine `Parent` might have the following value _after_ compilation: +By default, we compile resources *transitively* such that the resources of a particular routine are defined only in terms of the relevant contributions from their immediate children, and any expressions defined locally. For instance, an additive resource `X` in a routine `Parent` might have the following value _after_ compilation: + ```python Parent.resources['X'].value = Child_a.X + Child_b.X + Child_c.X ``` -This provides a performance boost when routines have multiple routines and subroutines, and expressions become unweildy. + +This provides a performance boost when routines have multiple routines and subroutines, and expressions become unwieldy. To override this, we can pass in a _compilation flag_ into the [`compile_routine`][bartiq.compile_routine] function call: + ```python from bartiq.compilation import CompilationFlags compile_routine(..., compilation_flags=CompilationFlags.EXPAND_RESOURCES) ``` + Alternatively, we can call [`evaluate`][bartiq.evaluate] on the resultant compiled routine with no variable assignments to expand the resources: + ```python expanded_resources_routine = evaluate(routine_compiled_transitively, {}) ``` @@ -139,9 +139,9 @@ expanded_resources_routine = evaluate(routine_compiled_transitively, {}) The output ports are compiled, and the new object representing compiled routine is created. -#### Step 2.9 Adding derived resources +#### Step 2.9: Adding derived resources -Finally, derived resources (provided through `derived_resources` field) are calculated and added to the routine. These resources are not provided in the initial routine (or at least not for all of the subroutines) and need to be calculated based on the existing information. +Finally, derived resources (provided through `derived_resources` field) are calculated and added to the routine. These resources are not provided in the initial routine (or at least not for all the subroutines) and need to be calculated based on the existing information. ### Step 3: Postprocessing diff --git a/docs/concepts/rewriters.md b/docs/concepts/rewriters.md index d5b895cc..142e9072 100644 --- a/docs/concepts/rewriters.md +++ b/docs/concepts/rewriters.md @@ -2,37 +2,42 @@ # Rewriters `bartiq` includes a set of utilities for manipulating and simplifying symbolic expressions, known as **rewriters**. This functionality is contained in the `analysis` submodule, and backend-specific rewriters can be imported directly. + ```python from bartiq.analysis import sympy_rewriter ``` + Here we will give an overview of the rewriting functionality currently implemented, with example usage. This document is intended for advanced users seeking to understand in-depth the logic behind rewriters, either for development or debugging purposes. We will soon have a more usage-oriented tutorial. ## Motivation -As quantum algorithms increase in complexity their symbolic resource expressions similarly become more complex. For a state of the art algorithm like double factorization the resource expressions can be almost impossible to parse, due to the sheer number of terms and symbols. For example, see Fig. 16 in [*Even more efficient quantum computations of chemistry -through tensor hypercontraction*](https://arxiv.org/pdf/2011.03494), and Eq. C39 for the associated Toffoli cost of this circuit. +As quantum algorithms increase in complexity their symbolic resource expressions similarly become more complex. For a state-of-the-art algorithm like double factorization the resource expressions can be almost impossible to parse, due to the sheer number of terms and symbols. For example, see Fig. 16 in "[Even more efficient quantum computations of chemistry +through tensor hypercontraction](https://arxiv.org/pdf/2011.03494)", and Eq. C39 for the associated Toffoli cost of this circuit. Making complex expressions more palatable is a primary motivation for rewriters; gaining insights into closed-form expressions for important resource quantities is vital for fault-tolerant quantum algorithm optimization and design. -## Overview +## Overview Rewriters are structured as dataclasses with associated methods and properties. Instantiation is done through a factory method; the only required input is an expression that we wish to modify (or rewrite). The input can be provided as a string, or as the backend-specific expression type. While much of the logic is necessarily tied to a particular backend implementation, the base class enforces some core functionality. The philosophy around rewriters is that they should be _immutable_, such that methods that change an expression actually return a new instance of the rewriter class. This allows for method chaining and easy access to previous expression forms if a change was made in error. Due to the dynamic nature of expression manipulation, rewriters are designed with interactive environments in mind. Rewriters have a `_repr_latex_` method that prints the current expression, meaning the following code in a Jupyter notebook: + ```python sympy_rewriter("a + b") ``` + would return a $\LaTeX$ (technically $KaTeX$) expression $a + b$. This, combined with method chaining, means the effect of different methods can be seen immediately. Beyond individual expression manipulation, `bartiq` also provides functionality to apply rewriter transformations to resources within compiled routines. This allows for systematic simplification of resource expressions across entire quantum algorithms. See [Applying Rewriters to Routines](#applying-rewriters-to-routines) for details. - ## Concepts + In designing the rewriter framework we implemented a number of different utility classes. A typical user should not need to interact with these objects directly, but we describe them here for completeness. -#### Instructions +### Instructions + An `Instruction` is an action that marks a change to an expression. The following `Instructions` are implemented: - `Initial` @@ -64,8 +69,8 @@ The primary purpose of `Instructions` is to track the history of an expression, !!! note "Why not just an `Enum`?" Originally the `Instructions` were implemented as an `Enum`! However, because `Assumptions` and `Substitutions` required special logic it was challenging to enforce strict typing across both an `Enum` and other dataclasses. Creating an empty class `Instruction`, with other classes inheriting from it, resulted in a cleaner implementation. +### Assumptions -#### Assumptions Assumptions about symbols can be input to the expression, and (in the case of SymPy) the backend symbolic engine attempts to simplify the expression with this new knowledge. An assumption requires three arguments: - `symbol_name: str` @@ -81,6 +86,7 @@ Assumptions about symbols can be input to the expression, and (in the case of Sy The reference value to compare the symbol to. Alternatively an assumption can be parsed directly from a string: + ```python sympy_rewriter("max(0, X)").assume("X > 0") >>> X @@ -89,12 +95,11 @@ sympy_rewriter("max(0, X)").assume("X > 0") Given an input assumption to a `sympy_rewriter`, the symbol is updated with the relevant SymPy [predicates](https://docs.sympy.org/latest/guides/assumptions.html#predicates). For some symbol `X` the predicates we support are: - positive: `X > 0`, -- nonnegative: `X >= 0` or `positive`, +- non-negative: `X >= 0` or `positive`, - negative: `X < 0`, -- nonpositive: `X<=0` or `negative`, - -From these SymPy is able to deduce other predicates. We do not implement predicates that declare if a symbol belongs to a particular number group, i.e. `integer`, `complex`, etc. We found that these kinds of assumptions did little to help simplify expressions. Similarly we do not have support for symbols being declared as infinitely large; if there is a valuable use case for these they could be easily added. +- non-positive: `X<=0` or `negative`, +From these SymPy is able to deduce other predicates. We do not implement predicates that declare if a symbol belongs to a particular number group, i.e. `integer`, `complex`, etc. We found that these kinds of assumptions did little to help simplify expressions. Similarly, we do not have support for symbols being declared as infinitely large; if there is a valuable use case for these they could be easily added. There is unfortunately [no way to input assumptions between different symbols](https://docs.sympy.org/latest/guides/assumptions.html#relations-between-different-symbols). Similarly SymPy does not implement predicates that specify a relationship between a symbol and some nonzero value, i.e. `X > 5`. However, for this latter point, we have implemented a workaround. @@ -107,14 +112,16 @@ If an assumption like `X > 5` is passed, the following logic occurs: The SymPy symbolic engine attempts to simplify the expression at each stage of this process. The drawback is that these kinds of assumptions _do not persist_. Because SymPy lacks the logic to define the relative value of a symbol beyond (non-)positivity/negativity, after this process the symbol `X` will only be defined with predicates from that restricted set. As a result, it can occasionally be useful to reapply all previously applied assumptions in order to repeat the steps lined out above. This can be achieved with the `reapply_all_assumptions()` method. -Finally, an assumption can also be applied to an expression with the same logic as above; a dummy symbol is created with the relevant predicates and (temporarily) replaces every instance of the expression. +Finally, an assumption can also be applied to an expression with the same logic as above; a dummy symbol is created with the relevant predicates and (temporarily) replaces every instance of the expression. + ```python sympy_rewriter( "max(0, log(x)) + max(1, log(x)) + max(2, log(x))" ).assume("log(x) > 2") >>> 3*log(x) ``` -However any symbols within the expression (`x` in this example) _will not_ inherit the predicates that were derived for the expression and associated dummy symbol. + +However, any symbols within the expression (`x` in this example) _will not_ inherit the predicates that were derived for the expression and associated dummy symbol.
Unexpected behaviours We rely on the SymPy engine to apply and simpify these assumptions, and we do so via the `.subs` method on SymPy expressions to substitute the aforementioned 'dummy' symbols. However, this method has some [known bugs](https://github.com/sympy/sympy/issues/19422). Consider the following SymPy code: @@ -129,6 +136,7 @@ expr = a + b - 1 expr.subs(a + b, c) >>> c - 1 ``` + The above code behaves as expected: the subexpression `a + b` is replaced by `c`. However, by making a minor change to the types in the expression: ```python @@ -136,6 +144,7 @@ expr = a + b - 1. expr.subs(a + b, c) >>> a + b - 1.0 ``` + The substitution does _not_ work. For this reason, the following rewriter code has a silent failure: ```python @@ -144,19 +153,22 @@ from bartiq.analysis import sympy_rewriter sympy_rewriter("max(0, a + b - 1.5)").assume("a + b > 1.5") >>> max(0, a + b - 1.5) ``` + Whereas this works as expected: + ```python from bartiq.analysis import sympy_rewriter sympy_rewriter("max(0, a + b - 1)").assume("a + b > 1") >>> a + b - 1 ``` + In these cases, it is advisable to pass assumptions in as the _whole_ expression, i.e. `a + b - 1.5 > 0`.
#### Substitutions -Substitutions are another powerful way of simplifying expressions. Rewriters support generic one-to-one substitutions: +Substitutions are another powerful way of simplifying expressions. Rewriters support generic one-to-one substitutions: - symbol to symbol: @@ -186,16 +198,17 @@ Substitutions are another powerful way of simplifying expressions. Rewriters sup >>> c/d ``` - There are no restrictions on the kind of replacements that can be done. Substitutions can only be passed in via strings in order to unify the API interface. -For `sympy_rewriter`, we also support _wildcard substitutions_. SymPy has [`Wild` symbols](https://docs.sympy.org/latest/modules/core.html#sympy.core.symbol.Wild) which can be used to match patterns in expressions. When using `.substitute`, a symbol prefaced with `$` will be marked as `Wild`, and will match anything that is nonzero. `Wild` symbols that are permitted to be zero can result in unusual, and often unwanted, behaviour. An example of using wildcard substitutions: +For `sympy_rewriter`, we also support _wildcard substitutions_. SymPy has [`Wild` symbols](https://docs.sympy.org/latest/modules/core.html#sympy.core.symbol.Wild) which can be used to match patterns in expressions. When using `.substitute`, a symbol prefaced with `$` will be marked as `Wild`, and will match anything that is nonzero. `Wild` symbols that are permitted to be zero can result in unusual, and often unwanted, behavior. An example of using wildcard substitutions: ```python sympy_rewriter("log(x + 2) + log(y + 4)").substitute("log($x + $y)", "f(x, y)") >>> f(2, x) + f(4, y) ``` -If symbols were marked as wild in the first argument to `.substitute` and then referenced in the second argument, the corresponding matching pattern is used. If a new, or existing, symbol is referenced, it is replaced as-is. If an existing symbol is used _as a wild symbol_, the corresponding matching pattern takes precedence. + +If symbols were marked as wild in the first argument to `.substitute` and then referenced in the second argument, the corresponding matching pattern is used. If a new, or existing, symbol is referenced, it is replaced as-is. If an existing symbol is used _as a wild symbol_, the corresponding matching pattern takes precedence. + ```python # Replace a wild pattern with a new symbol sympy_rewriter("f(x) + f(y) + z").substitute("f($x)", "t") @@ -233,6 +246,7 @@ sympy_rewriter( ``` Finally, it is possible to mix-and-match wild symbols with non-wild symbols: + ```python sympy_rewriter( "a*max(0, x) + b*max(0,y) + a*max(0, y)" @@ -240,10 +254,12 @@ sympy_rewriter( >>> a*x + a*y + b*max(0, y) ``` -##### Caveats +#### Caveats + Here we collect some caveats for wildcard substitutions. +
Matching zero arguments -As mentioned we assume that wildcard symbols are nonzero. This is to prevent perfectly valid, but perhaps unwanted, interactions. For example: +As mentioned, we assume that wildcard symbols are nonzero. This is to prevent perfectly valid, but perhaps unwanted, interactions. For example: ```python from sympy.abc import x from sympy import Wild @@ -310,7 +326,7 @@ expr = Max(a + b, f(c)) expr.match(Max(X, f(c))) >>> None ``` -The only change is now we are trying to match f(c) instead of just c. We intuitively expect this to work, as we are accessing the SymPy objects directly. Naively, we can be tempted to conclude that this change in behaviour is related to the function f. +The only change is now we are trying to match f(c) instead of just c. We intuitively expect this to work, as we are accessing the SymPy objects directly. Naively, we can be tempted to conclude that this change in behavior is related to the function f.
Case 3: Wildcard substitution works again ```python @@ -320,23 +336,23 @@ expr.match(Max(X, f(c))) ``` If we remove + b from the first argument, the matching works again! - -To avoid this unexpected behaviour, it is encouraged to be as explicit as possible and provide more Wild symbols rather than fewer: +To avoid this unexpected behavior, it is encouraged to be as explicit as possible and provide more Wild symbols rather than fewer: ```python expr = Max(a + b, f(c)) expr.match(Max(X + Y, f(c))) >>> {X: a, Y: b} ``` -As we rely on this SymPy level code when implementing substitutions through rewriters, it is important to keep these kinds of ineractions in mind. +As we rely on this SymPy level code when implementing substitutions through rewriters, it is important to keep these kinds of interactions in mind.
- ## Implementation details + Below we list some of the most important attributes, properties and methods of rewriters. In what follows, the typehint `T` is used to indicate that the type is backend-dependent expression type. For instance in the `sympy_backend`, `T = sympy.Expr`. There are broadly two kinds of methods: those that implement an `Instruction`, and thus modify the expression, and those that display information about the expression or update it temporarily. Methods that are typehinted to return `Self` return a new rewriter instance, and thus implement an `Instruction`. ### Attributes + - `expression: T` The form of the current expression. This is the attribute updated by rewriting methods and displayed by the `_repr_latex_` method in Jupyter notebooks. @@ -356,8 +372,8 @@ There are broadly two kinds of methods: those that implement an `Instruction`, a >>> {'x': ("a", "b", "c")} ``` - ### Properties + - `assumptions: tuple[Assumption, ...]` A tuple of all assumptions that have been applied to the expression, in chronological order. @@ -378,6 +394,7 @@ There are broadly two kinds of methods: those that implement an `Instruction`, a - `substitutions: tuple[Substitution, ...]` A tuple of all substitutions that have been applied to the expression, in chronological order. + ```python rewriter = (sympy_rewriter("a") .substitute("a", "x") @@ -393,7 +410,7 @@ There are broadly two kinds of methods: those that implement an `Instruction`, a - `original -> Self` - Return the rewritter instance with the original input expression. + Return the rewriter instance with the original input expression. ```python rewriter = (sympy_rewriter("a") @@ -427,6 +444,7 @@ There are broadly two kinds of methods: those that implement an `Instruction`, a ``` ### Methods + - `expand() -> Self` Expand all brackets in the expression. @@ -459,15 +477,17 @@ There are broadly two kinds of methods: those that implement an `Instruction`, a - `substitute(expr: str, replace_with: str) -> Self` - Perform a substitution. As inputs are string only, they will be parsed to the relevant backend. This permits one-to-one substitution as well as pattern matching with wildcards. + Perform a substitution. As inputs are string only, they will be parsed to the relevant backend. This permits one-to-one substitution as well as pattern matching with wildcards. One-to-one substitution: + ```python sympy_rewriter("a*b*c").substitute("b*c", "y") >>> a*y ``` Wildcard substitution: + ```python sympy_rewriter( "log(x + 1) + log(y + 4) + log(z + 6)" @@ -475,9 +495,8 @@ There are broadly two kinds of methods: those that implement an `Instruction`, a >>> f(1, x) + f(4, y) + f(6, z) ``` - - `focus(symbols: str | Iterable[str]) -> T` - + Return only terms in the expression that contain the input symbols, grouped if possible. This method only hides other terms, it does not delete them. ```python @@ -545,13 +564,14 @@ There are broadly two kinds of methods: those that implement an `Instruction`, a ``` This is equivalent to chaining the methods: + ```python sympy_rewriter("(a + 1) * max(x, 0)").expand().simplify().assume("x > 0").substitute("a", "b") >>> (b + 1)*x ``` - ### SymPy Specific Methods + While the base class enforces some functionality, SymPy allows us to extend this and implement other helpful methods. The following methods are specific to the SymPy rewriter class. - `get_symbol(symbol_name: str) -> Symbol | None` @@ -609,6 +629,7 @@ from bartiq import compile_routine ``` **Function signature:** + ```python rewrite_routine_resources( routine: CompiledRoutine, @@ -619,6 +640,7 @@ rewrite_routine_resources( ``` **Example usage:** + ```python # Assume we have a compiled routine with complex resource expressions compiled_routine = compile_routine(my_routine).routine diff --git a/docs/limitations.md b/docs/limitations.md index 933bcb1a..e3884e58 100644 --- a/docs/limitations.md +++ b/docs/limitations.md @@ -5,12 +5,12 @@ This page lists some prominent limitations and missing features. Please keep in ## Balance between exact and approximate costs For some quantum algorithms, the expression for their cost might depend on the inputs. For example the uncontrolled SWAP gate can be implemented with just 3 CNOTs (no T gates), but the controlled version requires using T gates, depending on the number of controls. This effectively introduces a conditional cost. It can be modelled using bartiq in a couple of ways: -- using a step function (Heaviside theta) allows us to model cases where the cost has different values depending if a given parameter is below or above certain threshold. -- using [piecewise sympy function](https://docs.sympy.org/latest/modules/functions/elementary.html#piecewise) -- using user-defined functions instead of sympy expressions -However, all these methods introduce additional complexities which may or may not be appropriate for a given use-case. Ultimately, bartiq does not provide any native approach for dynamic definition of routines based on the topology, so users are responsible for such decision-making prior to compilation. +- Using a step function (Heaviside theta) allows us to model cases where the cost has different values depending on whether a given parameter is below or above certain threshold. +- Using [piecewise sympy function](https://docs.sympy.org/latest/modules/functions/elementary.html#piecewise). +- Using user-defined functions instead of sympy expressions. +However, all these methods introduce additional complexities which may or may not be appropriate for a given use-case. Ultimately, bartiq does not provide any native approach for dynamic definition of routines based on the topology, so users are responsible for such decision-making prior to compilation. ## Keeping track of where given register is being used diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 484ddd45..3301159c 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -2,11 +2,12 @@ Debugging `bartiq` is not always straightforward, so please see below for a number of best practices and common issues: - - Routines can get pretty complicated very quickly, especially if nested subroutines are involved. Therefore when you get an error, try isolating the issue and work on a smaller example: - - First make sure that each child subroutine compiles correctly on its own. If not, this might suggest where the issue is. - - Try removing all the unnecessary fields, children, connections, etc. and prepare a minimal failing example. + + - First make sure that each child subroutine compiles correctly on its own. If not, this might suggest where the issue is. + - Try removing all the unnecessary fields, children, connections, etc. and prepare a minimal failing example. - Take a look at [the list of issues on GitHub](https://github.com/PsiQ/bartiq/issues) and see if other users have a similar problem! - - If not, consider creating one! - - Submitting issues is the most transparent way to give us feedback – even if something works, but is extremely unintuitive, we want to make it easier to use. The goal of this tool is to save you time, not waste it on unhelpful error messages. + + - If not, consider creating one! + - Submitting issues is the most transparent way to give us feedback – even if something works, but is extremely unintuitive, we want to make it easier to use. The goal of this tool is to save you time, not waste it on unhelpful error messages. diff --git a/docs/tutorials/01_basic_example.ipynb b/docs/tutorials/01_basic_example.ipynb index 087925ae..4024e3b7 100644 --- a/docs/tutorials/01_basic_example.ipynb +++ b/docs/tutorials/01_basic_example.ipynb @@ -46,7 +46,7 @@ "id": "7f36d3b3-e451-4c44-8a89-bb2250f54f51", "metadata": {}, "source": [ - "![title](../../images/basic_example.png)" + "![title](../images/basic_example.png)" ] }, { @@ -167,7 +167,7 @@ "\n", "Knowing T-gate costs and sizes of parameters, we can now visualize subroutines `A` and `B` like this:\n", "\n", - "![title](../../images/basic_children.png)\n", + "![title](../images/basic_children.png)\n", "\n", "This will require adding two new fields to the dictionaries defining `A` and `B` respectively:" ] @@ -440,7 +440,8 @@ "metadata": {}, "source": [ " Below you can find depiction of the uncompiled version of `my_algorithm`.\n", - "![title](../../images/basic_uncompiled.png)" + "\n", + "![title](../images/basic_uncompiled.png)" ] }, { @@ -556,7 +557,8 @@ "metadata": {}, "source": [ "So what we want to do is to get to the following picture:\n", - "![title](../../images/basic_compiled.png)\n", + "\n", + "![title](../images/basic_compiled.png)\n", "\n", "You can compare it with the previous picture and see how \"local\" variables have been replaced with \"global\" ones.\n", "\n", @@ -721,14 +723,6 @@ "\n", "In the next tutorial we'll cover how to implement a more complex algorithm from a paper." ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "39b79349-3354-4dc1-896f-d7ceb63e4bf0", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/docs/tutorials/02_alias_sampling_basic.ipynb b/docs/tutorials/02_alias_sampling_basic.ipynb index f11bee76..24223a3d 100644 --- a/docs/tutorials/02_alias_sampling_basic.ipynb +++ b/docs/tutorials/02_alias_sampling_basic.ipynb @@ -30,7 +30,7 @@ "\n", "We'll use Alias Sampling —-- an algorithm proposed by Babbush et al. in [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://journals.aps.org/prx/abstract/10.1103/PhysRevX.8.041015). This is what the circuit looks like:\n", "\n", - "![Alias Sampling](../../images/alias_sampling_paper.png)\n", + "![Alias Sampling](../images/alias_sampling_paper.png)\n", "\n", "\n", "It comes from Fig. 11 from the original paper.\n", @@ -43,7 +43,7 @@ "id": "8ec5bd3c-d8ed-4261-8f22-d7db04d65fa8", "metadata": {}, "source": [ - "In this tutorial we won't be explaining how the algorithm works in detail — partly because this is not the place, and partly because [Craig Gidney already did it in his blogpost](https://algassert.com/post/1805)\n", + "In this tutorial we won't be explaining how the algorithm works in detail — partly because this is not the place, and partly because [Craig Gidney already did it in his blogpost](https://algassert.com/post/1805).\n", "\n", "But briefly and at a high level, Alias Sampling contains the following subroutines:\n", "- $\\textrm{UNIFORM}_L$: prepares a state which is a uniform superposition over $L$ basis states\n", @@ -924,14 +924,6 @@ "- How to create a routine with multiple resources, `local_variables` and custom functions\n", "- How to use `explore_routine` and latex integration to get most out of `bartiq`" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "87ebbfa3-4698-4b8f-a9e7-304a2087594f", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/docs/tutorials/03_advanced_examples.ipynb b/docs/tutorials/03_advanced_examples.ipynb index ff7275fa..1c0c495f 100644 --- a/docs/tutorials/03_advanced_examples.ipynb +++ b/docs/tutorials/03_advanced_examples.ipynb @@ -38,7 +38,7 @@ "id": "afd9a021cad3180b", "metadata": {}, "source": [ - "In the [previous tutorial](https://psiq.github.io/bartiq/latest/tutorials/02_alias_sampling_basic/), we demonstrated how the alias sampling algorithm works, along with its subroutines - USP, $H^{\\otimes\\mu}$, QROM, comparator, and controlled SWAP. Beyond the uniform state preparation (USP) method introduced in tutorial 02, several other USP implementations are worth considering as alternatives. You can think of these implementations as interchangeable quantum circuits。\n", + "In the [previous tutorial](./02_alias_sampling_basic.html), we demonstrated how the alias sampling algorithm works, along with its subroutines - USP, $H^{\\otimes\\mu}$, QROM, comparator, and controlled SWAP. Beyond the uniform state preparation (USP) method introduced in that tutorial, several other USP implementations are worth considering as alternatives. You can think of these implementations as interchangeable quantum circuits.\n", "\n", "In this tutorial we will see if we can make alias sampling use less resources by using a different implementation of USP. " ] @@ -54,7 +54,7 @@ "\n", "We will analyze three distinct uniform state preparation routines:\n", "\n", - "1. **USP**: The basic uniform state preparation routine introduced in [Encoding Electronic Spectra...](https://arxiv.org/abs/1805.03662), also the one used in [tutorial 02](https://psiq.github.io/bartiq/latest/tutorials/02_alias_sampling_basic/).\n", + "1. **USP**: The basic uniform state preparation routine introduced in [Encoding Electronic Spectra...](https://arxiv.org/abs/1805.03662), also the one used in [the previous tutorial](./02_alias_sampling_basic.html).\n", "2. **ZeroAncillaUSP**: A more recent construction that eliminates the need for any ancilla qubits, as presented in [An efficient quantum algorithm for preparation of uniform quantum superposition states](https://arxiv.org/abs/2306.11747).\n", "3. **RUS_USP**: A variation of the method described in [Encoding Electronic Spectra...](https://arxiv.org/abs/1805.03662), using Repeat-Until-Success approach.\n", "\n", @@ -88,7 +88,7 @@ "id": "aa7a9e0ee746bff0", "metadata": {}, "source": [ - "The implementation of the `USP` method in [Encoding Electronic Spectra...](https://arxiv.org/abs/1805.03662) has been described in [tutorial 02](https://psiq.github.io/bartiq/latest/tutorials/02_alias_sampling_basic/). Let's do a quick recap.\n", + "The implementation of the `USP` method in [Encoding Electronic Spectra...](https://arxiv.org/abs/1805.03662) has been described in the [alias sampling tutorial](./02_alias_sampling_basic.html). Let's do a quick recap.\n", "\n", "### Parameters\n", "\n", @@ -107,7 +107,7 @@ "source": [ "Here, we will break down the `USP` routine into more detailed operations, including the inequality test, rotation, (uncompute) inequality test, and a (controlled) rotation. Since these operations are not run in parallel nor share qubits at the same time, representing each individual subroutine is straightforward and intuitive. Below is the circuit diagram for the USP, adapted from Figure 12 of the original paper:\n", "\n", - "![USP](../../images/usp.png)\n" + "![USP](../images/usp.png)" ] }, { @@ -448,7 +448,7 @@ "id": "df1312021f137f44", "metadata": {}, "source": [ - "and if we use this usp_dict to replace the usp in [previous tutorial](https://psiq.github.io/bartiq/latest/tutorials/02_alias_sampling_basic/)'s alias sampling example, we'll have the alias sampling with more detailed hierarchy, like this: " + "If we use this usp_dict to replace the usp in [previous tutorial](./02_alias_sampling_basic.html)'s alias sampling example, we'll have the alias sampling with more detailed hierarchy, like this: " ] }, { @@ -1017,7 +1017,7 @@ "id": "a3b180639bf1a28b", "metadata": {}, "source": [ - "An alternative approach to USP is presented in \"[_An efficient quantum algorithm for preparation of uniform quantum superposition states_](https://arxiv.org/abs/2306.11747)\". This work eliminates the need for ancilla qubits while maintaining similar asymptotic scaling to the other approach. We will refer to this as the `ZeroAncillaUSP`; the gate count for this method is detailed in Section 2.5 of the paper. Here, we will focus on the non-Clifford gate overhead and ignore the Clifford costs.\n", + "An alternative approach to USP is presented in \"[An efficient quantum algorithm for preparation of uniform quantum superposition states](https://arxiv.org/abs/2306.11747)\". This work eliminates the need for ancilla qubits while maintaining similar asymptotic scaling to the other approach. We will refer to this as the `ZeroAncillaUSP`; the gate count for this method is detailed in Section 2.5 of the paper. Here, we will focus on the non-Clifford gate overhead and ignore the Clifford costs.\n", "\n", "To create a uniform superposition of $ L $ basis states using the `ZeroAncillaUSP` method, the following non-Clifford gate overhead is required:\n", "\n", @@ -1961,9 +1961,9 @@ "\n", "In this tutorial, we explored the utility of `bartiq`'s resource estimation in more complex scenarios.\n", "\n", - "- How to explore how different implementations of a subroutine influence the resources required. TODO rephrase\n", + "- How to explore the impact of different implementations of a subroutine on the resources required.\n", "- How to effectively utilize `bartiq` to handle nested subroutines and swap them.\n", - "- How to use aggregation functions to help us analyzing the problem.\n" + "- How to use aggregation functions to help us analyze the problem.\n" ] } ], diff --git a/docs/tutorials/04_rewriters.ipynb b/docs/tutorials/04_rewriters.ipynb index 1ef2eb2d..17e2c5f6 100644 --- a/docs/tutorials/04_rewriters.ipynb +++ b/docs/tutorials/04_rewriters.ipynb @@ -186,7 +186,7 @@ "- `.all_functions_and_arguments()`: Show a set of all functions and their arguments in the expression, including nested functions (sympy only).\n", "- `.list_arguments_of_function(function_name)`: Show a list of all the unique arguments of a given function (sympy only).\n", "\n", - "A more complete overview of the functionality of rewriters can be found on the [Rewriter page](../../concepts/rewriters/) of Concepts. \n", + "A more complete overview of the functionality of rewriters can be found on the [Rewriter page](../concepts/rewriters.html) of Concepts. \n", "\n", "\n", "Looking at the $T$-gates expression above, note that the function \n", @@ -456,9 +456,9 @@ "source": [ "This is an extremely tidy representation of the number of $T$-gates in our double factorization algorithm.\n", "\n", - "Wildcard substitutions are extremely powerful, but should be used with caution! There are a number of caveats listed in the [Substitutions subsection](../../concepts/rewriters/#substitutions) on the rewriter Concepts page.\n", + "Wildcard substitutions are extremely powerful, but should be used with caution! There are a number of caveats listed in the [Substitutions subsection](../concepts/rewriters.html#substitutions) on the rewriter Concepts page.\n", "\n", - "As mentioned rewriters are designed with interactive environments in mind, so there is no need to constantly redefine the variable. All of our previous instructions can be done in a single cell via method chaining:" + "As mentioned, rewriters are designed with interactive environments in mind, so there is no need to constantly redefine the variable. All of our previous instructions can be done in a single cell via method chaining:" ] }, { @@ -697,7 +697,7 @@ "source": [ "## Summary\n", "\n", - "Rewriters are a powerful tool for simplifying symbolic expressions. This tutorial has only shown a brief preview of the utility available in rewriters, and we invite the reader to inspect the dedicated [Rewriter](../../concepts/rewriters/) summary page as well as the [API reference](../../reference/#bartiqanalysisrewriters)." + "Rewriters are a powerful tool for simplifying symbolic expressions. This tutorial has only shown a brief preview of the utility available in rewriters, and we invite the reader to inspect the dedicated [Rewriter](../concepts/rewriters.html) summary page, as well as the [API reference](../reference.html#bartiqanalysisrewriters)." ] } ], diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index c8ca9ba9..194ea33c 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -1,10 +1,10 @@ # Introduction -We currently have three tutorials for `bartiq`: +We currently have four tutorials for `bartiq`: - [Basic example](01_basic_example.ipynb) - [Alias Sampling basic](02_alias_sampling_basic.ipynb) - [Advanced examples](03_advanced_examples.ipynb) - [Rewriters](04_rewriters.ipynb) -They have been designed to gradually introduce you to the concepts we use in `bartiq`, so we recommend to go through them in order. \ No newline at end of file +They have been designed to gradually introduce you to the concepts we use in `bartiq`, so we recommend going through them in order.