I Made a Quantum Computer Simulator That Harnesses Real Quantum Effects
Buying a quantum computer is nowhere near within reach of the average consumer, at least, if you want to own one, as IBM offers quantum computing services accessible through the cloud. The closest to a consumer-end quantum computer is SpinQ’s Triangulum; however, it still costs a small fortune and only does three qubits.
Since I cannot own my own quantum computer, that got me wondering what is the cheapest quantum technology that actually can be purchased at a more reasonable consumer price. After doing some research, I came across quantum random number generators that utilizes quantum optical effects to generate truly random numbers. These can be purchased for $2000 on a PCie card, which is definitely not cheap, but not beyond my budget.
(Warning: The drivers for this card seems to not work on all hardware setups for reasons I have been unable to diagnose why. In some machines it works great, in others it does not. If you buy the card, do not pay for ID Quantique’s support, they charge a lot for it but it is a scam. They admitted to me in writing that they do not actually have the ability to provide support for issues with their driver, and so my support ticket to figure out why the driver seems to work on some systems but not others was simply closed stating they have no ability to actually provide support for it.)
On its own, a quantum random number generator can already be used for quantum encryption, which basically just is a one-time pad algorithm using truly random numbers. However, I had an even better idea in how I could use this card to its fullest potential.
Quantum mechanics allows you to predict the outcome of an experiment in terms of an idealized probability distribution using something known as the Born rule, or less commonly referred to as Born’s law. For example, if you have a qubit with a known value, and you apply a Hadamard gate to it and then immediately measure it again, the Born rule would predict that there is a 50% chance of measuring it as a 0 and a 50% chance of measuring it as a 1. If you were to repeat the experiment many, many times over, the distribution of 0s and 1s you get in each sample — often called a shot in quantum computing lingo — would approach a distribution of half 0s and half 1s.
If I want to simulate a real quantum computer, what I would first need to do is compute the idealized probability distribution according to the Born rule for a given program. However, this would not be sufficient for a proper simulation, because in the real world, you do not get idealized probability distributions from the output of the quantum computer. You have to run the same program over and over again, with thousands, or even tens of thousands of shots, and then you form a distribution of the results which should converge to the idealized distribution predicted by the Born rule the more shots you have.
Hence, if I want to simulate a real quantum computer, what I could do is compute the idealized probability distribution and then sample from it. If the idealized distribution is 75% chance of 0 and 25% chance of 1, and you have chosen to sample it 10000 times over that many shots, then the simulator would need to basically roll a dice 10000 times that is weighted 75%:25% and your results would be in terms of the outcome of these 10000 random samples. You may run it once and find that you get a distribution of 7532:2468, then you may run it again and find you get 7498:2502, etc, every time you run it you should get a slightly different distribution, but it should all be roughly in the same ballpark as the idealized distribution.
This sampling is where the quantum random number generator comes in: the random dice rolls could be driven by the quantum random number generator such that the sampling is truly random. You could thus have a quantum computer simulator that, despite technically being a simulator and not a real quantum computer, it would not only output the same results a real quantum computer would output, but the results would also be truly random as they would be chosen based on quantum optical effects.
Computing the idealized probability distribution is a rather computationally expensive task as it grows exponentially in complexity the more qubits you add. With 8 gigabytes of memory, I could easily fit a 14 qubit simulation into it, and thus I could get the simulator to run at a decent speed up to 14 qubits by adding a GPU with at least 8 gigabytes of memory. Luckily, unlike the quantum random number generator, GPUs with this much memory come pretty cheap. The one below I bought used for only $80 and works just fine for this task.
The Software Interface
For the software side of things, I wrote the core application entirely in C using OpenCL for GPU acceleration. I divided it into two parts. The first part is the core of the simulator, which simply takes in programs as standard input from the command line and outputs the results as standard output on the command line. The second part is a web-based graphical user interface which can ping my home server with an API key to run programs and get the results, allowing me to use it from anywhere in the world.
The Command-line Interface
As already stated, the command-line interface just expects programs provided to it through standard input. The syntax of these programs I loosely based on OpenQASM 2.0. I have been a bit critical of Qiskit because, personally, I think it’s a bit silly to have someone need to learn Python to then learn quantum computing. It would be like if someone wanted to learn how to program for RISC-V, and so they got a simulator, but the simulator required them to write Python code that has functions to call each instruction.
To me, that is a bit silly, I would rather the simulator just let me write the instructions directly in the form of an assembly code. That is what OpenQASM does, and it is much more intuitive as a platform for learning, and it is also an option on IBM’s website for cloud-based quantum computing. Below is an example of a simple program using this syntax which creates two entangled qubits in what is known as a Bell state and returns the idealized Born rule probability distribution for it.
$ ./bin/QAnsel <<EOF
> qreg q[2];
> h q[0];
> cx q[0], q[1];
> born q;
> EOF
00 50.00%
01 0.00%
10 0.00%
11 50.00%
The QREG instruction just tells the simulator how many qubits of memory it needs to allocate. The simulator technically allows up to 16, but beyond 14 it no longer fits in the GPU, and it actually does not even use the GPU for calculations unless you do 5 or more qubits because the overhead of loading it into the GPU would not be worth it. The next H instruction applies the Hadamard gate to the first qubit which places it into a superposition of 0 and 1. The next CX instruction is the Controlled-NOT gate which flips the second qubit if the first qubit is 1; otherwise it does nothing. The BORN instruction displays the Born rule results, which as you can see here, the qubits are guaranteed to have the same value as they are perfectly correlated with one another.
This is an idealized result and is not what you would get on a real quantum computer. If we want more realistic results, we first need to introduce classical bits with the CREG instruction. Then, we need to use the MEASURE instruction, which will measure a qubit and store the results into a classical bit. Finally, we then need to call the SAMPLE instruction on those classical bits. This instruction can only be called once at the very end of the program and basically signals to the simulator that it needs to run the same program over and over again and return a distribution of the results.
$ ./bin/QAnsel <<EOF
qreg q[2];
creg c[2];
h q[0];
cx q[0], q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];
sample c;
EOF
00 5033 50.33%
01 0 0.00%
10 0 0.00%
11 4967 49.67%
This will actually make use of the quantum random number generator to sample these results, and thus the specific samples you get at the end are genuinely random, which is more inline with what a real quantum computer would actually output. By default, it samples using the maximum number of samples which is 10000. However, you can change the maximum number of samples using the “-s” command line option, or you can use a number of samples below the maximum by using the SHOTS instruction followed by a number.
I could even reduce the shots down to 1 and place a single qubit into a superposition of 0 and 1, and every time I run the program, I would either get 0 or 1 as the outcome. Again, recall that this would be actually computed using the quantum random number generator, so I could use this program as a truly random quantum coin.
$ ./bin/QAnsel -r <<EOF
shots 1;
qreg q[1];
creg c[1];
h q[0];
measure q[0] -> c[0];
sample c;
EOF
0 1 100.00%
1 0 0.00%
$ ./bin/QAnsel -r <<EOF
shots 1;
qreg q[1];
creg c[1];
h q[0];
measure q[0] -> c[0];
sample c;
EOF
0 0 0.00%
1 1 100.00%
Notice how I added the “-r” flag. This is because if you run this on a computer without a quantum random number generator, I would still want the program to work, so by default it actually does not use the quantum random number generator, but instead uses the rand function in C. Adding “-r” will tell it to search for a hardware random number generator, and it chooses one based on an order of precedence. Currently, the order of precedence is (1) Quantis-PCIe-40M, (2) TrueRNG V3, and (3) Intel Secure Key. The first is a quantum random number generator, the second is a hardware random number generator but not quantum, and the third uses thermal noise on the CPU, which most modern Intel CPUs support, but older ones do not.
It actually does not by default use the GPU, either. The fact it uses no special hardware by default allows it to be easily run on pretty much any platform, even those without specialized hardware. I have even managed to get this simulator to run on a MIPS router and my Sega Dreamcast. If you want to use specialized hardware, you have to use “-o” and set a level of optimization. The optimization levels are an octal, so numbers 0–7, and represent turning on different features. The least significant bit value represents enabling/disabling “smart” optimizations which do things like instruction reordering; the second least significant bit enables multi-threading; the most significant bit represents enabling GPU acceleration, and thus an optimization level of 7 represents enabling all optimization features.
If you have ran programs on a real quantum computer before, one thing you will notice is that the sample above with the Bell state does not look quite like what a real quantum computer would output, primarily because real quantum computers are noisy. Since the Born rule idealized probability distribution had 0% for outcomes 01 and 10, they never occur in the sampling results at all, yet in a real quantum computer, you would sometimes get 01 and 10 due to noise. You can add noise to the simulation as well simply by using the NOISE instruction followed by a number representing the amount of noise to add.
$ ./bin/QAnsel <<EOF
noise 0.01;
qreg q[2];
creg c[2];
h q[0];
cx q[0], q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];
sample c;
EOF
00 4964 49.64%
01 5 0.05%
10 2 0.02%
11 5029 50.29%
The Graphical User Interface
I decided to write the interface in HTML5 so I could host it anywhere and then access my simulator from any device with a network connection, as it could simply ping my server with the quantum random number generator in it to get the results. This also allows me to easily make the simulator into an Android app since all I have to do is embed the HTML5 page into a WebView.
The interface uses a drag-and-drop interface. Instructions are assumed to be executed from left-to-right. There is no “barrier” instruction because the simulator will never execute instructions out of this order. It will try to group together instructions to optimize them, and even reorder your instructions to make them more efficiently groupable, but it will not reorder an instruction if doing so would break the flow of the program, so you’re guaranteed that your instructions will always behave as if they are executing from left-to-right.
Below you can see the Bell state being set up using the drag-and-drop interface. As you drag-and-drop instructions, it will build out the OpenQASM-inspired code on the bottom-left. When you press the Play button, it pings this code to my server using your API key. If the API key is valid, it will run the code and then respond back with the results. The results are then graphed on the bottom-right.
The graph button provides the results of the program as a CSV file that can be opened in Excel/Calc. The screenshot button gives you screenshots that can be nicely copied into articles or books. Below is an example of screen-shotting the Bell state program shown above.
Examples
GHZ Experiment
In this article I go over the GHZ experiment, which is the simplest demonstration that quantum interference effects cannot be explained simply through a local hidden variable theory, and in it, I talk about measuring a particle on different axes.
The probabilities in quantum mechanics are not intrinsic to the particles, but are a relationship between the particle and the measuring device. This means that if you want to measure the particle on a different axis, you can either rotate the measuring device, or rotate the particle itself, and it has the same effect. If you have seen a Bloch sphere representation, you should not think of this is as the intrinsic state of the particle, because rotating the measuring device would rotate the orientation on the Bloch sphere even if the particle itself is not altered.
Hence, we can measure our qubits on different axes, just like as described in the GHZ experiment, by applying the appropriate instructions to rotate it. Applying no logic gate is equivalent to measuring on the z-axis, applying the Hadamard gate is equivalent to measuring on the x-axis, and applying the s-dagger gate followed by the Hadamard gate is equivalent to measuring on the y-axis.
We can thus carry out the first three sets of measurements for the GHZ experiment as shown below. The descriptions show the measurement results in terms of boolean algebra, whereby 0+0=0, 0+1=1, 1+0=1, and 1+1=0. That’s just the definition of addition in boolean algebra.
We can figure out what the X₁X₂X₃ case is ahead of time without running the experiment by just adding all three equations together. In cases where the values are the same, then the output is always zero, since 0+0=0 and 1+1=0, and thus we can cancel it out.
- (X₁+Y₂+Y₃)+(Y₁+X₂+Y₃)+(Y₁+Y₂+X₃)=1+1+1
- X₁+X₂+X₃+Y₁+Y₁+Y₂+Y₂+Y₃+Y₃=1
- X₁+X₂+X₃=1
However, if we actually run the experiment, this is the results we get.
Each experiment begins with identical initial conditions outside of differences in the measurement settings since you are measuring the qubits on different axes. The fact that you cannot simulateously pre-assign all the values in a way that is mathematically consistent with what you actually measure proves that the outcomes cannot be predetermined.
Although, I did say say that the initial conditions are not all identical because you change the measurement settings, so you could pre-assign all the values if you assume that changing the measurement settings alters the initial values. The issue with this, however, is that because you are dealing with three qubits, in principle you could spatially separate them by vast distances, and thus you would have to assume that one of the qubits “knows” what axes the other ones are being measured on instantly, no matter how far they are apart. Such a thing would not be compatible with special relativity.
Deutsch–Jozsa Algorithm
The Deutsch–Jozsa algorithm begins by defining four different functions of two difference categories: “constant” and “balanced.” Both take a single qubit as input and output a single qubit. The constant functions always output either a 0 or a 1, while the balanced functions output an equal number of 0s as 1s.
You cannot encode these functions in this way in quantum mechanics because quantum logic gates have to be unitary, and unitary logic is always reversible. While you can tell what inputs were used solely based on the output for the balanced functions, you cannot tell what inputs were used solely from the outputs in the constant functions.
To do this, you can add ancilla qubits. These are additional qubits that hold an extra bit of information that allows the function to be reversible, so we can expand our functions from one input and one output to two inputs and two outputs. If we call our input x and our ancilla qubit y, then we could create four new sets of functions, whereby the two outputs are x and y xor f(x). If we encode the functions in this way, we get these four new functions as shown below.
Implementing these in a quantum algorithm would be quite simple. The top-left function basically does nothing as the inputs are the same as the outputs, and thus requires no instructions. The bottom-left function merely flips the most-significant bit, which can be carried out with simply an X instruction on it and that’s it. The top-right function is equivalent to the CX instruction, also referred to as the Controlled-NOT gate, whereby the least-significant qubit is the control qubit. The bottom-right function can be produced by applying the X gate to the least-significant qubit, applying a CX gate with the least-significant qubit as the control, and then applying the X gate a second time to the least-significant qubit.
Consider that you have a black-box function, sometimes referred to as an oracle, whereby you do not know how the function works. All you can do is querying the function by giving it an input and observing its output. Assuming the oracle is one of the four functions above, your task is to try and figure out if the oracle is of the constant or balanced category, simply based on your queries.
If you are only allowed a single query, it at first seems impossible to actually discovery if it is a constant or balanced function. Why? Well, notice that every single input-output combination on the constant side has a corresponding equivalent on the balanced side, that means such a query is not actually enough to tell you the results.
However, it turns out it is actually possible to figure out if it is constant or balanced with a single query using interference effects. To do this, you just need to apply the X instruction to the most significant qubit, the H instruction to both qubits, pass the two qubits into the functions, then apply the H gate to the two outputs yet again. The results are held in the least significant qubit, meaning you do not even need to look at the most significant qubit.
Hence, with a single query, you can know for certain whether or not the function is of the constant or balanced variety.
Other Info
Android App
Since the interface is just HTML5 that pings an external server, it also has an Android app version which can be found by pressing the Download button.
Bloch Sphere Visualizer
The simulator also has a Bloch sphere visualizer to see how the logic gates work. This is separate from the simulation itself, as it does not need to ping the external server to show the Bloch sphere. The sphere is purely just to give you an idea of how the logic gates work and doesn’t reflect the state of the simulation. You can press the globe button to see the Bloch sphere visualizer, and then press the logic gate buttons to see how changes in the state vector are reflected upon the Bloch sphere.
I mentioned how I built this visualizer in this article.
Can I use it?
Maybe. It is currently hosted at this link here. However, it may not actually work, since I take it down for maintenance often. Below is an API key that works, although I may revoke it any time if public requests start to become a problem. If you get something like “unknown server-side error” when pressing the play button, it’s probably because the server is down.
cHVibGljOjdlNjlmMzZhMWE4OWVhNzBlYzRhNDIwZmI5YjgzMGM0MjBhYjkzZTE0ZTlhMmY2ZGE2OGJjMWQzNTVjNjExZWY6Zm9sZW9hcGkuY29tL3FhbnNlbA
T̶h̶e̶ ̶s̶o̶u̶r̶c̶e̶ ̶c̶o̶d̶e̶ ̶f̶o̶r̶ ̶t̶h̶e̶ ̶c̶o̶r̶e̶ ̶c̶o̶m̶m̶a̶n̶d̶ ̶l̶i̶n̶e̶ ̶a̶p̶p̶l̶i̶c̶a̶t̶i̶o̶n̶ ̶i̶s̶ ̶c̶u̶r̶r̶e̶n̶t̶l̶y̶ ̶n̶o̶t̶ ̶a̶v̶a̶i̶l̶a̶b̶l̶e̶ ̶a̶s̶ ̶I̶ ̶d̶o̶ ̶n̶o̶t̶ ̶t̶h̶i̶n̶k̶ ̶i̶t̶ ̶i̶s̶ ̶i̶n̶ ̶g̶o̶o̶d̶ ̶e̶n̶o̶u̶g̶h̶ ̶s̶h̶a̶p̶e̶ ̶a̶t̶ ̶t̶h̶e̶ ̶m̶o̶m̶e̶n̶t̶ ̶t̶o̶ ̶s̶h̶a̶r̶e̶,̶ ̶b̶u̶t̶ ̶p̶r̶o̶b̶a̶b̶l̶y̶ ̶w̶i̶l̶l̶ ̶l̶a̶t̶e̶r. It is now currently available. You can find the source code at this link, clicking the QAnsel.git
project, then clicking the tree
hyperlink. You can also install the simulator on a Linux PC using the commands below.
sudo wget https://www.foleosoft.com/foleosoft -O /usr/local/bin/foleosoft
sudo chmod +x /usr/local/bin/foleosoft
foleosoft update
foleosoft install QAnsel
It will then become available by running the qansel
command. Use the -?
flag to see the possible command line options, including optimization settings (such as enabling GPU acceleration), enabling supported hardware random number generators, so on and so forth. At the link, there are scripts that you can pass into it (through standard input) as examples of how to write code for it, doing various things such as the CHSH experiment, the GHZ experiment, the Elitzur — Vaidman bomb tester, so on and so forth.
If you want the graphical user interface, you first have to host QAnsel as an API. You can host any Linux CLI program using the RosadoAPI
which is installable just by running foleosoft install RosadoAPI
. When it is hosted, it can be pinged from the browser interface simply by setting up an API key that contains information about your server you are running it on (IP address, authentication key, etc) and plug that API key into the settings menu (the top-right gear icon). How to set up the API key is explained at the same link mentioned prior on the page immediately after clicking the project name and prior to clicking the tree
hyperlink.