diff --git a/docs/design.md b/docs/design.md index fc1437e..60232e9 100644 --- a/docs/design.md +++ b/docs/design.md @@ -3,13 +3,13 @@ ## Using lists instead of mappings One of the most controversial choices we have made in QREF is the choice of -using lists instead of mappings (a.k.a. dictionaries) for objects that +using lists instead of mappings (Python dictionaries) for objects that should have an unique name. This choice affects: - Children of a `Routine`. - Ports of a `Routine`. -For instance, why did we choose to represent ports like this: +For instance, you may ask why we chose to represent ports like this: ```yaml ports: @@ -24,13 +24,12 @@ ports: in_0: {"direction": "input", "size": 2} out_0: {"direction": "output", "size": "N"} ``` -? There are two answers to this question. +There are two answers to this question. -The first answer is purely pragmatic. On the one hand, using mappings +The first answer is purely pragmatic. On the one hand using mappings instead of lists would guarantee uniqueness of names of ports -and children. But, on the other hand, it would give a false -sense of security. To see why, consider the following example -Python code, which loads an incorrect definition of ports: +and children. However it would also give a false +sense of security. To see why consider the following example, which loads an incorrect definition of ports: ```python import yaml @@ -45,7 +44,7 @@ print(yaml.safe_load(data)) ``` If you are new to parsing YAML (or JSON) in Python you might be -surprised that the code runs at all - after all shouldn't keys +surprised that the code runs - after all shouldn't keys in YAML mappings be unique? Well they should, but most parsers will just load the last key if the duplicates are present. The code above prints: @@ -91,11 +90,11 @@ data format. Different users can use different parsers and we want to make sure everyone gets consistent results no matter what parsing library they use. -The second reason concerns only `children` field and is much more nuanced, +The second reason concerns the `children` field and is much more nuanced, but essentially boils down to the fact that lists are naturally better suited -for storing ordered information. While QREF format itself does not enforce +for storing ordered information. While QREF itself does not enforce ordering on the input data, there might be algorithms that -utilize particular ordering of subroutines. Therefore, it is +utilize a particular ordering of subroutines. Therefore, it is essential that at least the order of children is expressed in a list. !!! note @@ -104,10 +103,10 @@ essential that at least the order of children is expressed in a list. resources are sorted alphabetically by name. However, initial order of children is always preserved. -## Why isn't routine a top level object? +## Why isn't the program a top level object? The actual program you are representing in QREF is stored -as `program` property of a top-level schema object, i.e. +as the `program` property of a top-level schema object, i.e. ```yaml version: v1 diff --git a/docs/development.md b/docs/development.md index 53090c8..6c1f9e6 100644 --- a/docs/development.md +++ b/docs/development.md @@ -23,7 +23,7 @@ poetry install ### Using editable install with pip -You can also develop Poetry using `pip`: +If you prefer to manage your own environment, you can install an editable version of QREF via `pip`: ```bash pip install -e . @@ -31,14 +31,14 @@ pip install -e . !!! Warning - If you are planning to add/modify dependencies of QREF, we + If you are planning to add or modify the dependencies of QREF, we highly recommend you use Poetry instead of pip editable install. Without Poetry, you will need to edit dependencies manually, which is very error-prone. ## Setting up docs locally -In order to set up docs locally you need to have appropriate dependencies – they get instaled when running `poetry install` automatically. When done, please run: +In order to set up docs locally you need to have the appropriate dependencies – they get installed when running `poetry install` automatically. When done, please run: ```bash mkdocs serve diff --git a/docs/format.md b/docs/format.md index 729edca..dbfb3b6 100644 --- a/docs/format.md +++ b/docs/format.md @@ -1,11 +1,7 @@ # Data format ## Introduction -QREF format is a domain-specific language (DSL) for describing quantum algorithms -built on top of JSON for the purpose of resource estimation. - -In QREF, the algorithms are described as programs comprising hierarchical, directed -acyclic graph (henceforth hierarchical DAGs) of subroutines. Let's break down +In QREF, algorithms are described as a graph of subroutines. These graphs are by design hierarchical, directed, and acyclical. Let's break down what this means: - *Hierarchical* means that routines can be nested. @@ -14,43 +10,38 @@ what this means: - *Acyclic* means that traversing the graph along its edges (respecting their direction) will never lead to visiting the same node twice. -Besides specifying the connectivity between routines in the algorithms, the QREF format -also specifies how to store information relevant to resource estimation, such as +Besides specifying the connectivity between routines in the algorithm, QREF +also specifies how to store information relevant to resource estimation. This extends to known and unknown resources, parameters that might affect them and how the parameters -propagate in the algorithm's graph. +propagate in the algorithms graph. -Before describing the format in detail, let us first exemplify its usage on a simple program. +Before describing the format in detail, let's see how QREF would handle a simple algorithm. ## Basic example -In QREF, the quantum programs are represented as graphs. If you are not used to -representing computations as graph, don't worry! Before describing QREF format, -we'll demostrate how a simple circuit can be represented as a graph. - -Consider a hypothetical quantum program as depicted in the following circuit. +Consider a hypothetical algorithm as depicted in the following circuit. ![example_circuit](images/basic_circuit.svg){width="500"} -Let's forget for a while that the depicted algorithm doesn't make much sense. We can see that the circuit comprises two subroutines: - `subroutine_1` operating on a single-qubit register. - `subroutine_2` operating on a two-qubit register. We also labelled inputs to the subroutines as `in_0` and `in_1`, and the whole -output of our program (i.e. combined outputs of both subroutines) as `out`. +output of our circuit (i.e. combined outputs of both subroutines) as `out`. -Representing such a circuit as a graph is straightforward, it might look like this: +Representing this circuit as a graph is straightforward, it might look like this: ![example_routine](images/basic_program.svg) -As we can see, the graph contains both subroutines form the original circuit, -and an artificially introduced `merge` operation used to combine outputs -from the subprograms into one final outputs. +The graph contains both subroutines from the original circuit, +as well as an artificially introduced `merge` operation used to combine outputs +from the subroutines into one final output. -Now that we have our graph, let's see how it can be represented in QREF format. -As already mentioned, QREF format is built on top of JSON, so we can write QREF -files in either JSON or YAML. For our examples, those might look as follows: +Now that we have our graph, let's see how it can be represented in QREF. +As QREF is built on top of JSON we can write our algorithms +(or programs) in either JSON or YAML. For our example, those might look as follows: === "YAML" @@ -64,32 +55,35 @@ files in either JSON or YAML. For our examples, those might look as follows: --8<-- "basic_program.json" ``` -Let's dissect our example. The top-level object has two mandatory properties: +The top-level object has **two mandatory properties**: - `version`: Set to `v1` (which is the only version so far) -- `program`: This contains the actual description of the program. +- `program`: This contains the description of our algorithm. So what do we have in a `program` object? -- `name`: Mandatory name of the program, here set to the string `my_program`. +- `name`: Each program requires a name, here set to the string `my_program`. - `ports`: A collection of ports. They roughly correspond to quantum registers. - `children`: A list of children, or subroutines, of the program. -- `connections`: A list defining edges of our graph. +- `connections`: A list defining edges of our graph. Intuitively, +the connections property defines the flow of data between subroutines. -### Ports -Let us first take a look at ports, like the first input port of our program: +Let's explore some of these properties in more detail! + +### Ports +Here we highlight the first input port of our top level program, `my_program`: ```yaml {direction: input, name: in_0, size: 1} ``` -Ports, like most other components in QREF, have names, which should be distinct -among all ports of any given program (or subroutine). Each port also has -direction, which can be either `input`, `output` or `through` (for ports serving as -both input and output). Finally, each port has size. -In our simple scenario, all sizes are positive integers. However, QREF -is not limited to them, and size of a port can be either: +Like most components in QREF ports require names, and these should be unique for +the ports of a given program (or subroutine). Each port also has direction, +which can be either `input`, `output` or `through` (for ports serving as +both input and output). Finally, each port has a _size_. +In our simple scenario, all sizes are positive integers. +However, entries to the size field can take on any of the following formats: - A positive integer. - A symbol or symbolic expression (e.g. `N` or `2L + 1`) @@ -99,7 +93,7 @@ is not limited to them, and size of a port can be either: ### Children The `children` list comprises all subroutines of the program. Each entry has the -same structure as the program itself (one could say that the schema of the `program` +same structure as the program itself (such that the schema of `program` is recursive). In particular, each child should have a name (unique in the scope of their immediate parent) and some ports. They can also have connections, and their own children. @@ -120,7 +114,7 @@ There are three types of connections: ```yaml {source: subroutine_1.out, target: merge.in_1} ``` -- Connections joining a child and its parent. e.g.: +- Connections joining a parent and its child e.g.: ```yaml {source: in_0, target: subroutine_1.in} ``` @@ -128,13 +122,13 @@ There are three types of connections: ```yaml {source: merge.out, target: out} ``` -- Connections joining input and output port of a parent, known as passthroughs. +- Connections joining the input and output ports of a parent, known as passthroughs. There are no passthroughs in our simple example, but one could look like: ```yaml {source: in_0, target: out} ``` -Writing connections in this way migh be cumbersome. However, there exists +Writing connections in this way might be cumbersome. However, there exists an alternative, more concise syntax. Instead writing: ```yaml @@ -157,9 +151,9 @@ beginning looks as follows: ### Repetitions -On top of the basic fileds listed above, one can also write a QREF routine which contains repetitions. +On top of the basic fields listed above, subroutines can also be repeated. -This can be added with `repetition` field: +These can be added with `repetition` field: ```yaml repetition: @@ -171,27 +165,27 @@ repetition: `repetition` consists of two parts: -- `count` – defines how many times the child of the this routine should be repeated. +- `count` – defines how many times this routine (and its subroutines) should be repeated. - `sequence` – defines how the costs for the repetition will be aggregated. Each `sequence` has a field `type` which defines the type of the sequence. Depending on the type there are extra fields, summarized in the table below. -There are 5 different sequences that one can currently use in QREF: +There are 5 different sequences currently implemented in QREF: |
Sequence type
|
Additional fields
| Description | Example | |-------|-------|-------|-------| -| `constant`| `multiplier` | In each iteration child is repeated `multiplier` number of times. | Trotterization | -| `arithmetic`| `difference`, `initial_term` | Iteration starts from `initial_term` repetitions of a child and then we increase the of repetitions by `difference` in every iteration. | QFT | -| `geometric` | `ratio` | In each iteration number of repetitions is multiplied by `ratio`, starts for 1 repetition in the first iteartion. | QPE | -| `closed_form` | `sum`, `prod`, `num_terms_symbol` | This can be used for cases, where we know the closed-form expression for the total cost of the routine given the number of repetitions is defined `num_terms_symbol`. `sum` is an expression for additive resources and `prod` is for multiplicative. | Any | -| `custom` | `term_expression`, `iterator_symbol` | This can be used in case where we don't know the formula for closed form, but we do know the formula for each term, which is defined using `term_expression`, and we use `iterator_symbol` to denote the iterator. | Any | +| `constant`| `multiplier` | In each iteration, each child is repeated `multiplier` number of times. | Trotterization | +| `arithmetic`| `difference`, `initial_term` | Iteration starts from `initial_term` repetitions of a child and then we increase the repetitions by `difference` in every iteration. | QFT | +| `geometric` | `ratio` | In each iteration the number of repetitions is multiplied by `ratio`; starting at `1` repetition in the first iteration. | QPE | +| `closed_form` | `sum`, `prod`, `num_terms_symbol` | This can be used when we know the closed-form expression for the total cost of the routine, given the number of repetitions defined by `num_terms_symbol`. `sum` is an expression for additive resources and `prod` is for multiplicative. | Any | +| `custom` | `term_expression`, `iterator_symbol` | This can be used when we don't know the formula for closed form, but we do know the formula for each term, defined using `term_expression`. We use `iterator_symbol` to denote the iterator. | Any | -This representation abstracts out certain implementation details. Consider implementation of QPE using geometric sequence below. The child `U` of routine `Evolution` has two ports: `result` and `psi`, the with sizes `bits_of_precision` and `N`. Even though in the executable implementation each next controlled `U^2^i` only acts on one control qubit from the `result` register, there's currently no way of expressing it in QREF. + diff --git a/docs/index.md b/docs/index.md index 75c15d3..ab09dde 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,7 @@ # QREF -Quantum Resource Estimation Format (QREF) is an open format for representing -quantum algorithms, optimized for usage in quantum resource estimation (QRE). +The Quantum Resource Estimation Format (QREF) is an open format for representing +quantum algorithms. Built on top of JSON, it has been optimized for the purpose of quantum resource estimation.
diff --git a/docs/library/userguide.md b/docs/library/userguide.md index dc63464..c51c0aa 100644 --- a/docs/library/userguide.md +++ b/docs/library/userguide.md @@ -2,7 +2,7 @@ ## Installation -To install QREF Python package, clone QREF repository and install it as usual with `pip`: +To install the QREF Python package, clone the repository and install it with `pip`: ```bash # Clone QREF repo (you can use HTTP link as well) @@ -11,17 +11,16 @@ cd qref pip install . ``` -Please note that to use rendering features you need a working [graphviz](https://graphviz.org) +Please note that to use the rendering features you need a working [graphviz](https://graphviz.org) installation. -## Usage +## Data Validation +### Using JSON schema for data validation -### Using JSON schema for validating data in QREF format - -JSON schema for QREF format can be obtained by calling -[`generate_program_schema`][qref.generate_program_schema] function. -Such schema can be then used for validating user's input, e.g. using +A JSON schema for QREF can be obtained by calling +[`generate_program_schema`][qref.generate_program_schema]. +This schema can be then used for validating user input, e.g. using [`jsonschema`](https://pypi.org/project/jsonschema/) package: ```python @@ -32,22 +31,22 @@ from qref import generate_program_schema data = load_some_program() schema = generate_program_schema() -# This will raise if there are some validation errors. +# This will raise an exception if there are some validation errors. validate(schema, data) ``` ### Validation using Pydantic models If you are familiar with [Pydantic](https://docs.pydantic.dev/latest/), you might find -it easier to work with QREF Pydantic models instead of interacting with JSON schema directly. -In the example below, we create an instance of [`SchemaV1`][qref.SchemaV1] model from validated data stored in QREF format: +it easier to work with QREF pydantic models instead of interacting with JSON schema directly. +In the example below, we create an instance of the [`SchemaV1`][qref.SchemaV1] model from data stored in QREF: ```python from qref import SchemaV1 data = load_some_program() -# This will raise if data is not valid +# This will raise an exception if there are some validation errors. program = SchemaV1.model_validate(data) ``` @@ -68,31 +67,7 @@ There can be cases where a program is correct from the perspective of Pydantic v - Ports with multiple connections - Cycles in the graph -In order to validate whether the topology of the program is correct you can use `verify_topology` method. Here's a short snippet showing how one can verify their program and print out the problems (if any). - -```python -from qref.verification import verify_topology - -program = load_some_program() - -verification_output = verify_topology(program) - -if not verification_output: - print("Program topology is incorrect, due to the following issues:") - for problem in verification_output.problems: - print(problem) - -``` - -### Topology validation - -There can be cases where a program is correct from the perspective of Pydantic validation, but has incorrect topology. This includes cases such as: - -- Disconnected ports -- Ports with multiple connections -- Cycles in the graph - -In order to validate whether the topology of the program is correct you can use `verify_topology` method. Here's a short snippet showing how one can verify their program and print out the problems (if any). +In order to validate whether the topology of the program is correct you can use `verify_topology` method. Here's a short snippet showing how you can verify your program and print out the problems (if any). ```python from qref.verification import verify_topology @@ -111,8 +86,9 @@ if not verification_output: ### Rendering QREF files using `qref-render` (experimental) !!! Warning - This feature is considered experimental and may occassionally produce - incorrect results. + This feature is considered experimental and may occassionally produce + incorrect results. + QREF comes with a CLI tool for rendering hierarchical graphs of quantum algorithms. To render an algorithm stored in a file named `my_program.yaml` into a @@ -125,7 +101,9 @@ qref-render my_program.yaml my_program_graph.svg The `qref-render` tool supports `yaml` and `json` input formats, and all output formats supported by [graphviz](https://graphviz.org/). -If you prefer to use QREF's rendering capabilities from a Python script instead of the CLI, you can use the [`qref.experimental.rendering`](qref.experimental.rendering) module, which performs the same task as `qref-render`. Here, we demonstrate how to use the rendering module to visualize quantum circuits for preparing arbitrary quantum states in alias sampling. To learn more about the algorithm, please refer to the tutorial for [Bartiq](https://psiq.github.io/bartiq/latest/tutorials/02_alias_sampling_basic/) – our library for symbolic resource estimation. +If you prefer to use QREF's rendering capabilities from a Python script instead of the CLI, you can use the [`qref.experimental.rendering`](qref.experimental.rendering) module, which performs the same task as `qref-render`. + +Below we demonstate how the rendering module visualizes the quantum circuit for arbitrary state preparation in the alias sampling algorithm. This algorithm is explored in detailed in the tutorials for [Bartiq](https://psiq.github.io/bartiq/latest/tutorials/02_alias_sampling_basic/) – our library for symbolic resource estimation. We will use the `yaml` file `alias_sampling.yaml` as input to generate a graph representing this algorithm: