-
Notifications
You must be signed in to change notification settings - Fork 4
Home
Welcome to the quAPL wiki!
Because of the heavy use of custom glyphs (the special symbols APL uses, like ⍝
or ⍨
) you need to install the Dyalog IDE to properly be able to run and use quAPL. To install Dyalog download the correct version from here. Dyalog also provides specific OS installation in this page.
Once Dyalog is installed you should see a window like this:
To get quAPL running in your computer, at the moment you need to clone the github project and load it into RIDE as a Cider project. To install Cider you first need to install Tatin the package manager of APL.
In a lot of programming languages, the difference between a project and a package are not really important, often the words are interchangeable. In APL however, this is not the case. There is a conceptual difference between a project and a package, with different tools for each of them (Tatin and Cider).
The main difference between the 2, is who uses what. A user looking to just use the code will use the package 'quAPL' that they get from the Tatin package manager. A developer works with the project instead. This includes everything that the package includes with extra tests and documentation attatched to it.
Tatin is currently hosted here, with the installation instructions here, while we will walk you through the necessary steps to get everything running here, it might not be a bad idea to take a look into those pages yourself.
To get Tatin installed you need to:
- Download the latest Tatin Client version from here.
- Extract the downloaded zip on the following paths (if that specific folder stucture does not exist create it):
- Windows: C:\Users\<YOUR_USER_HERE>\Documents\Dyalog APL[-64] Unicode Files\SessionExtensions\CiderTatin
- Linux: /home/<YOUR_USER_HERE>/dyalog.U.files/SessionExtensions/CiderTatin
- MacOs: Users/<YOUR_USER_HERE>/dyalog.U.files/SessionExtensions/CiderTatin
- Once Tatin is in the correct directory, we need to tell Dyalog where it is located. I am assuming that you are running Dyalog 18.2 irregardless of your OS (if you are running a separate version check instructions here). To do that in Dyalog run:
- Windows:
]SALT.Settings cmddir ",C:\Users\<YOUR_USER_HERE>\Documents\Dyalog APL-64 18.2 Unicode Files\SessionExtensions\CiderTatin" -p
- Linux:
]SALT.Settings cmddir ",/home/<YOUR_USER_HERE>/dyalog.180U64.files/SessionExtensions/CiderTatin -p
- MacOs:
]SALT.Settings cmddir ",/Users/<YOUR_USER_HERE>/dyalog.182U64.files/SessionExtensions/CiderTatin" -p
- Windows:
Once these steps are correctly followed, Tatin should be ready to use, to verify you can run ]Tatin -?
and a list of available commands should appear.
NOTE
If you are not using Windows, and Tatin is not initialized after restarting dyalog you might need to add a setup script. Instructions for that can be found here
Given that Cider is simply a Tatin package, we can install it directly through Dyalog executing the following command:
]Tatin.InstallPackages [tatin]Cider <targetFolder>
Where target folder is the same directory in which Tatin is located in the previous step.
To check if Cider was properly installed try running ]CIDER* -?
to see if any commands show up. If it is still not working try running:
]UReset
And try again.
Once Cider is installed, loading quAPL is very straightforward. Simply run the following command and follow the prompts as they appear:
]CIDER.OpenProject "<PATH_TO_CLONED_QUAPL_REPO>" -alias=quapl
NOTE
The
-alias
flag creates an alias in Cider, allowingus to open quapl in the future without having to specify the path every time.
After following the prompts, we recommend switching your working directory to the quAPL directory but is not necessary, when running #.quapl
the project should be loaded in your workspace.
After the alias is created we can simply open quAPL running the following command:
]CIDER.OpenProject [quapl]
In this guide we will only go through the basics of quAPL. We highly recommend checking out a separate introduction to APL, here are a few of them:
- Mastering Dyalog APL, in particular its introduction chapter: Mastering Dyalog APL
- Learning APL, Goes into less detail than Mastering Dyalog APL but still very good resource: Learning APL.
quAPL is structured into separate namespaces.
These namespaces are grouped based on what the code inside of them is.
To access anything inside quAPL one needs to start the call through the general workspace ##
.
Because of this, rest of this document we will assume that there is a reference in the current workspace to all the namespaces in quAPL.
To do that simply copy the following commands:
sng ← #.quapl.sng
mlt ← #.quapl.mlt
gates ← #.quapl.gates
circuit ← #.quapl.circuit
show ← #.quapl.show
msmt ← #.quapl.measurement
We cannot achieve anything without first having qubits to play with. To create a qubit in the ground state we call the q0 function:
qa ← sng.q0
qa
1
0
To create an excited qubit:
qb ← sng.q1
qb
0
1
We can also create a qubit in an arbitrary superposition:
qc←sng.qx 0.2 0.8
qc
0.9363291776
0.3511234416
NOTE
When using
qx
, you pass the probability of the qubit being in the ground step followed by the probability of the qubit in the excited state. Do not try creating a qubit with the state prefix directly sinceqx
will only store the square root of the passed numbers.
Because the gates are just matrices, applying gates to qubits is as simple as performing a matrix multiplication operation (represented by +.×
in APL).
The gates are stored in the gates
namespace:
gates.H
0.7071067812 0.7071067812
0.7071067812 ¯0.7071067812
We can apply a Hadamard gate:
gates.H +.× qa
0.7071067812
0.7071067812
We can also apply an X gate and display the result in braket notation:
gates.X +.× qa
0
1
show.q gates.X +.× qa
|1⟩
Having individual qubits can be fun, but it is rarely useful.
When working with multiple qubits we usually want to create a register.
We can create one with the function reg
followed by the number of qubits we want to create:
reg3←circuit.reg 3
reg3
┌→┬─┐
↓0│1│
│ │0↓
├─┼→┤
│1│1│
│ │0↓
├─┼→┤
│2│1│
│ │0↓
└─┴→┘
Registers are 2d matrices, where the objects in the first column represent the index of the qubit, and the objects in the second column are column vectors representing the qubit. To access a specific qubit, we just slice the register like any other APL array:
reg3[1;2]
┌─┐
│1│
│0↓
└→┘
In the same way if we want to insert a qubit that is in a different state that is not the ground one, we simply slice the register and place the state that we want:
reg3[1;2]←⊂ sng.q1
reg3[2;2]←⊂ gates.H +.× sng.q0
reg3
┌→┬────────────┐
↓0│0 │
│ │1 ↓
├─┼~──────────→┤
│1│0.7071067812│
│ │0.7071067812↓
├─┼~──────────→┤
│2│1 │
│ │0 ↓
└─┴~──────────→┘
NOTE
Note the use of the left shoe glyph,
⊂
. This glyph “Encloses” the expression to its right, indicating that whatever the result of the expression it is a single object. This way APL knows that you are trying to insert the whole qubit vector state instead of any individual component of it.
WARNING
Because APL starting index is 1, when referencing qubits in APL, we need to add 1 to our index, even though in our registers the first item starts at 0.
There are two included tools that we can use to display the contents in a register. We can show the vector state:
show.r reg3
|0𝜑1⟩
Or we can show each qubit's vector state in the register:
show.r_in reg3
┌→┬───────────────────────────────┐
↓0│|1⟩ │
├─┼──────────────────────────────→┤
│1│0.7071067812|0⟩+0.7071067812|1⟩│
├─┼──────────────────────────────→┤
│2│|0⟩ │
└─┴──────────────────────────────→┘
Having registers is nice, but when working with multiple qubits, one usually likes to work with a vector state of all the qubits directly. To do this, we can simply thread the register:
vs← circuit.thread_reg reg3
vs
0
0
0
0
0.7071067812
0
0.7071067812
0
In the same way, sometimes we have a vector state that we wish to transform into a vector state. To do that we simply unthread the vector state:
circuit.unthread_vs vs
┌→┬────────────┐
↓0│0 │
│ │1 ↓
├─┼~──────────→┤
│1│0.7071067812│
│ │0.7071067812↓
├─┼~──────────→┤
│2│1 │
│ │0 ↓
└─┴~──────────→┘
Of course, you can have as many qubits as you want, but if you cannot measure them they are completely useless.
To measure a qubit in quAPL, you can simply use the measure
function:
msmt.measure sng.q0
┌→──────┬─┐
│┌→────┐│1│
│↓┌→┬─┐││0│
│││0│1│││ │
│││ │0↓││ │
││└─┴→┘││ │
│└────→┘↓ ↓
└──────→┴→┘
Let's unpack what measure returns.
On the left column, measure
will return all the measured qubits, where the left side of each item indicates the index of the measured qubit, and the right the state that was measured.
On the left we get the resulting vector state.
This return makes more sense when measuring a qubit in a vector state containing multiple qubits. Let's say we have a 2 qubit register, where the first qubit in a perfect superposition and the second qubit is in the ground state:
reg2←circuit.reg 2
reg2[1;2] ← ⊂gates.H +.× sng.q0
reg2
┌→┬────────────┐
↓0│0.7071067812│
│ │0.7071067812↓
├─┼~──────────→┤
│1│1 │
│ │0 ↓
└─┴~──────────→┘
We can measure specific qubits by passing their index (starting with 0) as a left side argument. When measuring qubit number 1, nothing changes:
vs ← circuit.thread_reg reg2
vs
0.7071067812
0
0.7071067812
0
1 msmt.measure vs
┌→──────┬────────────┐
│┌→────┐│0.7071067812│
│↓┌→┬─┐││0 │
│││1│1│││0.7071067812│
│││ │0↓││0 │
││└─┴→┘││ │
│└────→┘↓ ↓
└──────→┴~──────────→┘
We can see that we measure that qubit number 1 is in the ground state and our vector state remains the same. However, when measure qubit number 0:
0 msmt.measure vs
┌→──────┬─┐
│┌→────┐│0│
│↓┌→┬─┐││0│
│││0│0│││1│
│││ │1↓││0│
││└─┴→┘││ │
│└────→┘↓ ↓
└──────→┴→┘
We see that the vector state now collapsed to a specific state. Measuring rolls the dice for you, and chooses a specific state based on the probabilities of that state. If we measure qubit 0 in the same vector state again there is a %50 chance of getting that qubit in the ground state instead:
0 msmt.measure vs
┌→──────┬─┐
│┌→────┐│1│
│↓┌→┬─┐││0│
│││0│1│││0│
│││ │0↓││0│
││└─┴→┘││ │
│└────→┘↓ ↓
└──────→┴→┘
For another example of how measure works, let's say I have a register with 3 qubits, all of them in a perfect superposition:
reg3 ← circuit.reg 3
reg3[1;2] ← ⊂ gates.H +.× sng.q0
reg3[2;2] ← ⊂ gates.H +.× sng.q0
reg3[3;2] ← ⊂ gates.H +.× sng.q0
reg3
┌→┬────────────┐
↓0│0.7071067812│
│ │0.7071067812↓
├─┼~──────────→┤
│1│0.7071067812│
│ │0.7071067812↓
├─┼~──────────→┤
│2│0.7071067812│
│ │0.7071067812↓
└─┴~──────────→┘
We can measure qubit 1:
1 msmt.measure circuit.thread_reg reg3
┌→──────┬───┐
│┌→────┐│0.5│
│↓┌→┬─┐││0.5│
│││1│1│││0 │
│││ │0↓││0 │
││└─┴→┘││0.5│
│└────→┘│0.5│
│ │0 │
│ ↓0 ↓
└──────→┴~─→┘
As we can see only qubit 1 has collapsed the other 2 remain in the superposition. We can also measure only qubits 0 and 2:
0 2 msmt.measure circuit.thread_reg reg3
┌→──────┬────────────┐
│┌→────┐│0.7071067812│
│↓┌→┬─┐││0 │
│││0│1│││0.7071067812│
│││ │0↓││0 │
││└─┴→┘││0 │
│├────→┤│0 │
││┌→┬─┐││0 │
│││2│1│││0 │
│││ │0↓││ │
││└─┴→┘││ │
│└────→┘↓ ↓
└──────→┴~──────────→┘
And finally, passing no left side argument will measure all the qubits in the vector state:
msmt.measure circuit.thread_reg reg3
┌→──────┬─┐
│┌→────┐│0│
│↓┌→┬─┐││1│
│││0│1│││0│
│││ │0↓││0│
││└─┴→┘││0│
│├────→┤│0│
││┌→┬─┐││0│
│││1│1│││0│
│││ │0↓││ │
││└─┴→┘││ │
│├────→┤│ │
││┌→┬─┐││ │
│││2│0│││ │
│││ │1↓││ │
││└─┴→┘││ │
│└────→┘↓ ↓
└──────→┴→┘
Applying individual gates to individual qubits manually with quAPL might be simple but applying specific gates to a whole register or vector state, not so much.
With the stage
function we can apply any gate (including 2 qubit gates) to any specific qubit in a vector state.
Let's go back to the mixed 3 qubit register:
reg3
┌→┬────────────┐
↓0│0 │
│ │1 ↓
├─┼~──────────→┤
│1│0.7071067812│
│ │0.7071067812↓
├─┼~──────────→┤
│2│1 │
│ │0 ↓
└─┴~──────────→┘
If I wanted to apply a Hadamard gate to qubit 2, with the stage
function that is very simple.
First we convert the register to a vector state:
vs ← circuit.thread_reg reg3
vs
0
0
0
0
0.7071067812
0
0.7071067812
0
And we simply apply the circuit stage that we want:
((2) (⊂gates.H)) circuit.stage vs
0
0
0
0
0.5
0.5
0.5
0.5
As we can see a Hadamard gate has effectively been applied to qubit 2.
NOTE
It is important to pass the left side argument of stage correctly. The left side argument is composed of 2 elements, first are the index of the qubits you are applying the gates and the second what gates are being applied to the same ordered qubits. Note that both the indices and gates are in their own parenthesis and also note that when a single gate is being applied, this needs to be enclosed with the glyph
⊂
.
Let's do an example with a 2 qubit gate and single qubit gate at the same time.
If we take the original vector state and apply a CNOT gate to qubits 0 and 2 and another Hadamard gate to qubit 1, the code looks like:
res ← ((0 2 1) (gates.CNOT gates.H)) circuit.stage vs
res
0
0
0
0
0
1
0
0
circuit.unthread_vs res
┌→┬─┐
↓0│0│
│ │1↓
├─┼→┤
│1│1│
│ │0↓
├─┼→┤
│2│0│
│ │1↓
└─┴→┘
As we can see, both gates have been applied to the correct gates at the same time.
Before running the algorithm, we first store the code in new variables for ease of reading:
or ← #.quapl.lib.oracles.DJ
_DJ_ ← #.quapl.lib._DJ_
Let's run the Deutsch-Jozsa algorithm with 3 qubits.
The first step is creating our vector state:
vs ← circuit.thread_reg circuit.reg 3
vs
┌→┐
↓1│
│0│
│0│
│0│
│0│
│0│
│0│
│0│
└~┘
Currently we only have 2 different oracles in quAPL (more to come in the future!) which are located here. Because we have implemented the algorithm as an operator we can run it as one, accepting as a left argument the oracle and right argument the vector state:
or.XOR _DJ_ vs
┌→┐
↓0│
│0│
│0│
│1│
│0│
│0│
│0│
│0│
└~┘
or.zero _DJ_ vs
┌→┐
↓1│
│0│
│0│
│0│
│0│
│0│
│0│
│0│
└~┘
The return is the resulting vector state after having applied the algorithm before measuring.
WARNING
When writing the oracles namespace, Dyalog, will show as options multiple items, the only current ones that are actually oracles are
XOR
andzero
. Any other object will result in the function crashing.