Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Docs] Add contents to the docs #21

Merged
merged 28 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
570c8a0
add some examples of possible IR compilations
kaosmicadei Oct 11, 2024
031ee29
fix examples
kaosmicadei Oct 11, 2024
a84e07a
fix example
kaosmicadei Oct 11, 2024
6158bbe
fix last example
kaosmicadei Oct 11, 2024
5197c31
fix last example
kaosmicadei Oct 11, 2024
7302c7b
add IR structure and purpose documentation
kaosmicadei Oct 14, 2024
0bfc4c7
Fix wrong repo mentioned in README License
pimvenderbosch Nov 29, 2024
714348b
Add titles to API pages
pimvenderbosch Nov 29, 2024
3f99b9a
Update docstrings in irbuilder.py
pimvenderbosch Nov 29, 2024
ccb3d74
Fix minor spelling mistake
pimvenderbosch Nov 29, 2024
d5c59f3
Remove warning from IR Builder page
pimvenderbosch Nov 29, 2024
6364676
Update docstrings in types.py
pimvenderbosch Nov 29, 2024
a8666ba
Update docstrings in factory.py
pimvenderbosch Nov 29, 2024
1d631d9
Fix spelling mistake in returns header
pimvenderbosch Nov 29, 2024
bdf2766
Fix syntax issue in Support docstring
pimvenderbosch Nov 29, 2024
ce428ca
Update docstrings in factory_tools.py
pimvenderbosch Nov 29, 2024
5f38164
Update docstrings in irast.py
pimvenderbosch Dec 2, 2024
b405d91
Merge branch 'km/docs' into pv/docs-contents
pimvenderbosch Dec 2, 2024
2a51d5b
Fix reference to qadence 2 instead of 1
pimvenderbosch Dec 3, 2024
34d2cb5
Fix typo
pimvenderbosch Dec 3, 2024
ba8f579
Split up in multiple files
pimvenderbosch Dec 3, 2024
6c95fbc
Merge remote-tracking branch 'refs/remotes/origin/pv/docs-contents' i…
pimvenderbosch Dec 3, 2024
6b4e88f
Add subpages to content in mkdocs config
pimvenderbosch Dec 3, 2024
bcd8e84
Make figure number bold
pimvenderbosch Dec 6, 2024
d3322ab
Add API reference index page contents
pimvenderbosch Dec 6, 2024
1dfc561
Improve challenges based on @doosmk input
pimvenderbosch Dec 6, 2024
923604d
Fix typos in challenges
pimvenderbosch Dec 6, 2024
dcae4fa
Move tutorial files around
pimvenderbosch Dec 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@ Before making a contribution, please review our [code of conduct](docs/getting_s

## License

Qadence Expressions is a free and open source software package, released under the Apache License, Version 2.0.
Qadence 2 IR is a free and open source software package, released under the Apache License, Version 2.0.
3 changes: 1 addition & 2 deletions docs/api/factory.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
!!! warning
This page is under construction.
# Factory

::: qadence2_ir.factory
4 changes: 1 addition & 3 deletions docs/api/factory_tools.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
!!! warning
This page is under construction.

# Factory Tools

::: qadence2_ir.factory_tools
16 changes: 14 additions & 2 deletions docs/api/index.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
!!! warning
This page is under construction.
# API Reference

Here you can find the API specification for Qadence 2 IR.
There is a page for each module in the Qadence 2 IR package, in which all class and function definitions are documented.
The API reference is particularly useful to check the behavior of classes and functions, and to get information on arguments, attributes and other details.

Qadence 2 IR has 5 modules that are each responsible for different aspects of the IR.
For more information, see their dedicated pages:

- [`qadence2-ir.factory`](./factory.md): Defines a factory function that creates a compile function.
- [`qadence2-ir.types`](./types.md): Defines the valid types to be used in Qadence 2 IR code.
- [`qadence2-ir.irast`](./irast.md): Defines the AST that is used in front-end to IR compilation.
- [`qadence2-ir.irbuilder`](./irbuilder.md): Defines the interface for front-ends compilation.
- [`qadence2-ir.factory_tools`](./factory_tools.md): Defines tools for processing AST objects during compilation.
3 changes: 1 addition & 2 deletions docs/api/irast.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
!!! warning
This page is under construction.
# IRAST

::: qadence2_ir.irast
3 changes: 1 addition & 2 deletions docs/api/irbuilder.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
!!! warning
This page is under construction.
# IR Builder

::: qadence2_ir.irbuilder
3 changes: 1 addition & 2 deletions docs/api/types.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
!!! warning
This page is under construction.
# Types

::: qadence2_ir.types
13 changes: 13 additions & 0 deletions docs/contents/challenges.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Challenges

As pointed out [here](./ir_structure.md), digital and analog algorithms diverge in how they handle register topology. Contrary to classical computing, where the resource allocation is typically left to the OS to control, the quantum resources are explicit in this analog quantum computating IR.

For digital devices and circuit-based algorithms, the register topology is important mostly during the compilation phase to reduce the number of SWAP gates applied. Analog algorithms and devices, on the other hand, rely on the topology to ensure the proper interaction between qubits regarding connectivity and strength. That led us to consider including the abstract representation of the register (either by unitless coordinates or connectivity graph) as part of the IR.

However, register preparation doesn’t represent an instruction in the sense of runtime since it needs to be loaded before the sequence starts and (for analog algorithms) cannot be changed during execution. Even if shuttling is available, the initial register configuration needs to be known to properly evaluate the atoms’ movement since such action will affect the connectivity of the register.

Besides the register, other elements like the SLM used to target individual qubits are part of the “booting”/resources allocation that is not directly connected to the register but cannot be addressed as regular instructions. Still, its presence may affect the behavior of specific pulses, which motivated the inclusion of a “Directives” section on the IR.

Primitive operations are another challenge in neutral atoms. The analog nature of the algorithms and device makes it difficult to clearly define “primitive operations”. Elementary structures like a pulse corresponding to the neutral atom Hamiltonian and an idle/wait instruction to let the qubits interact under free coupling (without drive).

To avoid define a fixed set of operations that may not reflect the hardware capabilities and to avoid constant changes in the IR definition to include new primitives, the instructions’ names are passed as labels like `QuInstrunct("dyn_pulse", …)` and `QuInstruct("rx", …)` instead of `Pulse(…)` and `RX(…)`. This may change in the future. However, right now, this flexibility allows us to explore the hardware's capabilities without being held by a particular set of instructions.
10 changes: 10 additions & 0 deletions docs/contents/compute_stack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Compute Stack

Pasqal’s compute stack comprises four layers, as shown in Figure 1. A user defines the quantum computation using one of the front-ends: Qadence 2 Expressions or PQL. The computation to be executed is processed from layer to layer and eventually executed on the hardware. In each layer, a quantum computation is expressed in a specific data structure. The higher up the layer is in the stack, the more hardware details are abstracted away.

The top layer is user-facing, with the highest level of abstraction. For each front-end, a compiler exists that compiles the computation in Qadence 2 IR. See the section IR structure for more details on its definition. The low-level compilation process targets a backend, either a QPU or a simulator, and compiles the computation into code that can run on the targeted backend. The backend itself takes care of executing the computation on the hardware.

It's important to note that the Qadence 2 IR layer spreads over the full width of the stack, meaning that all front-ends can compile to it and any backend can be targeted from it. The two-step compilation approach reduces the coupling between elements in the stack significantly and makes the codebase, therefore, more maintainable.

![Qadence 2 stack](qadence2_stack.png)
**Figure 1:** The Qadence 2 software stack.
2 changes: 2 additions & 0 deletions docs/contents/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Qadence2 IR
Qadence 2 IR is a Pasqal initiative, to define an intermediate representation structure for neutral atom devices. The structure captures the key elements of the platform while remaining agnostic regarding hardware specifications. The goal is to simplify instruction building of analog quantum algorithms, enabling optimized instructions and compilation processes of task-specific algorithms to different platforms. By using an agnostic instruction set, Qadence 2 IR allows digital and analog instructions to work together, extending its usability to the digital-analog paradigm. The IR uses static single-assignment to simplify differentiability when running simulations.
25 changes: 25 additions & 0 deletions docs/contents/ir_structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# IR Structure
The main idea for Qadence 2 IR is to provide an abstract neutral atom device model.

Neutral atom devices usually rely on absolute values like laser power, atomic spacing in micrometers, and nanosecond pulse duration. Since the interaction between atoms and, therefore, the execution of algorithms in such devices are heavily influenced by those parameters, having a well-tuned algorithm for a specific device is desired. With that in mind, the IR definition should be independent of device-specific parameters, leaving their implementation to the backend. At the same time, the front-end compilation pipeline must build the IR from the algorithm and bridges with the backend at the low-level compilation pipeline, accessing its runtime resources.

Analog-relevant data such as the qubit register, parametric symbols and quantum instructions are wrapped in the IR, enabling each backend to handle them case-by-case. In particular, the instructions will provide only minimal information, such as the qubit support and the instruction label, i.e., which quantum operator or instruction is being applied, and the backend must provide some implementation for it.

The IR Model is split in four sections:

- Inputs
- Instructions
- Register
- Directives

The *Inputs* section is responsible for declaring the classical data and flagging them as trainable or not. This information is desired to ensure that only the parameters used in the machine learning training steps are considered for differentiability.

The *Instruction* section holds the sequence of classical computation via static single-assignment to avoid duplicate computation and help the differentiability instructions.

Quantum operations are passed as labels instead of fixed primitives (see the Challenges section). The IR definition is independent of device-specific parameters and leaves their configuration to the compiler, which builds an IR algorithm into instructions that contain the device-specific parameters.

The *Register* section holds either an abstract description of how the atoms are placed on the register or a connectivity graph, depending on the type of algorithm. Algorithms that don’t require customized registers are allowed to pass only the number of qubits. This process is delegated to the backend compiler to decide the best strategies to organize the atoms whenever possible.

The *Directives* section holds other device critical information for resource allocation like SLM mask target for individual qubit addressability.

Resource allocation, such as Registers and Directives, is usually not expected in an intermediate representation. However, as described before, those elements can affect algorithm design and pulse execution. The Challenges section presents more details about them.
Binary file added docs/contents/qadence2_stack.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 0 additions & 2 deletions docs/contents/tbd.md

This file was deleted.

171 changes: 171 additions & 0 deletions docs/tutorials/examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Examples
The following examples were generate to present some possible algorithms and may not be
fully implementable in the hardware at the moment.

## Example digital input.
```python
Model(
register = AllocQubits(2),
directives = { # for QPU options
},
inputs = {
"x": Alloc(1, trainable=True),
},
instructions = [

# data encoding
Assign("%0", Call("mul", 0.5, Load("x"))),
QuInstruction("rx", Support(target=(0,)), Load("%0")),

# cnot
QuInstruction("x", Support(target=(1,), control=(0,))),
],
)
```

## Example digital-analog input.
```python
Model(
register = AllocQubits(4),
directives = {
"dmm": {
"targets": [0, 1, 2, 3],
"weights": "equal",
}
},
settings = {},
inputs = {
"theta": Alloc(4, trainable=False)
"duration": Alloc(1, trainable=True)
"omega": Alloc(5, trainable=True)
},
instrunctions = [
# Tower feature map
Assign("%0", Call("mul", 0.31831, Load("theta")),
QuInstruct("set_dmm", Support.target_all(), Load("%0"))
QuInstruct("rx", Support.target_all(), 1.570796),
QuInstruct("dyn_local_pulse", Support.target_all(), 2.0),
QuInstruct("rx", Support.target_all(), -1.570796),

# Entanglement
QuInstruct("dyn_interact", Support.target_all(), 2.5),

# Trainable layer
QuInstruct("dyn_pulse", Support.target_all(), Load("duration"), Load("omega"), 0.0, 0.0),
],
)
```

## Example analog input.
```python
Model(
register = AllocQubits(
num_qubits = 4,
qubits_positions = [
(-2, 1), (-1, 0), (0, 0), (1, -1)
],

# optional parameters
grid_type = "triangular",
grid_scale = 1.0,
),
directives = {
"dmm": {
"targets": [0, 3],
"weights": [0.5, 1.0],
}
},
inputs = {
"duration": Alloc(1, trainable=False, attrs={"time_parameter": True}),
"omega": Alloc(4, trainable=True),
"delta": Alloc(3, trainable=True),
},
instrunctions = [
QuInstruct(
"dyn_pulse",
Support.target_all(),
Load("duration"),
Load("omega"),
Load("delta"),
0.0, # phase
),
QuInstruction(
"dyn_local_phase",
Support(target=(0, 1)), # match with dmm targets
1.2, # duration
attrs={
"concurrent": True, # starts with the previous pulse
}
),
],
)
```

## Example analog input (alternative)
This example is intend to be used with backends that either support crossing-lattice or similar
algorithms, or gridless backends (e.g. PyQ).
```python
Model(
register = AllocQubits(
num_qubits = 4,
connectivity = {
(0, 1): 1.2,
(0, 3): 0.9,
(1, 2): 1.4,
(2, 3): 2.1,
}
),
directives = {
"dmm": {
"targets": [0, 3],
"weights": [0.5, 1.0],
}
},
inputs = {
"duration": Alloc(1, trainable=False, attrs={"time_parameter": True}),
"omega": Alloc(4, trainable=True),
"delta": Alloc(3, trainable=True),
},
instrunctions = [
QuInstruct(
"dyn_pulse",
Support.target_all(),
Load("duration"),
Load("omega"),
Load("delta"),
0.0, # phase
),
QuInstruction(
"dyn_local_phase",
Support(target=(0, 1)), # match with dmm targets
attrs={
"concurrent": True, # starts with the previous pulse
"duration": 1.2,
}
),
],
)
```

```python
Model(
register=AllocQubits(
num_qubits=3,
connectivity={(0,1): 1., (0,2): .5, (1,2): .5},
),
directives={
"dmm": {"targets": [0, 1]}
},
inputs={
't': Alloc(1, trainable=True)
},
instructions=[
# The presence of the `dmm` allows a single qubit operation by
# dynamic decoupling the others two qubits.
QuInstruct('x', Support(target=(2,))),

Assign('%0', Mul(1.57, Load('t')),
QuInstruct('dyn_pulse', target_all(), Load('%0'), 1.0),
],
)
```
2 changes: 0 additions & 2 deletions docs/tutorials/index.md

This file was deleted.

7 changes: 5 additions & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ nav:
- License: getting_started/LICENSE.md

- Contents:
- TBD: contents/tbd.md
- contents/index.md
- Compute Stack: contents/compute_stack.md
- IR Structure: contents/ir_structure.md
- Challenges: contents/challenges.md

- Tutorials:
- Tutorials: tutorials/index.md
- Tutorials: tutorials/examples.md

- API:
- api/index.md
Expand Down
17 changes: 12 additions & 5 deletions qadence2_ir/factory.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
"""This module defines a factory method that creates a compiler function based on an `IRBuilder`.

The compiler function, that can be generated using the factory, should be used to to compile a
certain type of input, based on the front-end that is being used, to IR code. This is the first
step of compilation, which is followed by a compilation from IR to the targeted backend.
"""

from __future__ import annotations

from typing import Callable
Expand All @@ -9,17 +16,17 @@


def ir_compiler_factory(builder: IRBuilder[InputType]) -> Callable[[InputType], Model]:
"""Use an IRBuilder[InputType] to create an IR compiler function that converts an input of type
`InputType` and returns a Model.
"""Constructs an IR compiler function for a specific input type by using an `IRBuilder`.

The IR compiler must be named 'compile_to_model' by convention to ensure accessibility to other
engines in the framework.
The factory function uses an `IRBuilder[InputType]` to create an IR compiler function that
converts an input of type `InputType` and returns a Model. The IR compiler must be named
'compile_to_model' by convention to ensure accessibility to other engines in the framework.

Args:
builder: A concrete implementation of the generic class `IRBuilder` for a particular
`InputType`.

Return:
Returns:
A function that compiles an `InputType` object to the Qadence-IR (`Model`).
"""

Expand Down
Loading
Loading