Tutorial
By Kevin Casey | Published November 28, 2017
Quantum Computing
Play the game! Want to see how this plays out in real life? Check it out here!
You can play the classic shell game and view the QASM code and the execution results. Have fun!
A conversation with a friend at my high school class reunion got me thinking about how to use quantum computing to solve a real-world problem. As a challenge to myself, I wanted to use IBM’s quantum technology, particularly the IBM Q experience, to recreate the classic shell game.
In this shell game, the user picks the shell to hide a ball under and the quantum computer guesses which shell the ball is hiding under. In this tutorial, I will walk you through the steps I took to create my version of the shell game using the IBM Q experience.
Get the code on GitHub
If you have a little experience creating websites and some familiarity with HTML and JavaScript, you’ll find this to be a very simple (and simplified) experience. I won’t describe the details of the C#, HTML or JavaScript code, as it’s somewhat irrelevant to how to use the Q experience platform. As you’ll see, the tricky stuff is all happening in the backend.
It also helps if you have some understanding of the IBM Q experience’s Quantum Composer and the nature of the Quantum Assembler—but it’s not required to follow this simple how-to.
First, you’ll need an account on IBM’s Q experience. It’s free! How cool is that? To sign up, visit the Quantum experience page.
You’ll want an integrated development editor (IDE) to write your code. For my purposes, I used Microsoft Visual Studio 2015. If you are a die-hard, you could use notepad, though I wouldn’t recommend it.
You can run your experiments locally on your system or a cloud platform of your choice. I published my code to the IBM Cloud so I could share this with you.
When thinking about how to use quantum computing to solve my puzzle, I first envisioned this project as a fully JavaScript-browser-based task. I’d write a little HTML 5, add some JS magic, and the shells would animate across the screen. I could build the quantum assembler code (QASM) in JavaScript and pass it all over to the IBM Q experience platform via a simple web service call.
Unfortunately, IBM doesn’t permit cross-origin resource sharing (CORS) from outside domains, so the browser prevented us from making any of the service calls. Stymied!
Well, not quite. But back to the drawing board.
Another alternative is to make the request from server-side code, instead of the browser, where CORS does not apply. This complicates matters a little.
Now our con game must post the layout of the shell game to the server. The server assembles the requests into QASM code and posts it to IBM’s platform. The JSON returned from this web service request is then parsed by the server and fed back to the web page, showing which shell was chosen by the quantum computer. We basically insert a go-between to solve the issue. And presto! The game works.
There are several ways to create, edit, and deploy an ASP.NET Core application to IBM Cloud. I started by creating an ASP.NET Core 1.0 project in Visual Studio. After removing the default Contact and About pages which come in the template, I modified the Views\Shared_Layout.cshtml file and the Views\Home\Index.cshtml to suit my needs.
I then created a folder under the Controllers directory to store the class files we’ll use to communicate with the IBM Q platform.
We’ll focus our attention on the IBMQ folder first, as it’s the real heart of this program.
The classes in this folder are responsible for logging into IBM’s Q experience platform, assembling the Quantum Assembler Code (QASM), and executing the code against IBM’s quantum processor and parsing the results of the execution. Executing and parsing the code is done via the exposed web interface.
So, let’s walk through the steps these classes are going to perform.
One of the first things we’ll need to do is to send data to the IBM Q experience platform. Rather than write and rewrite the same code over and over again, I lumped it into a single function called FetchAPIData. This way, other functions like the PerformLogin function only need to pass the relative URL, an HTTP method (post, delete, get etc) and the content (if posting). FetchApiData will take care of the rest.
It begins by adding the relative portion of the URL to the base URL: https://quantumexperience.ng.bluemix.net/api. If we aren’t performing a delete and if we have a User object, it will also pass the User.id as the access_token. When deleting, we pass the User.id as a request header (X-Access-Token) instead of appending the access_token to the URL.
The bulk of the code here is simply building the request message, adding content, and setting request headers. If we get back a success status code, we sent our QResult object correctly, and we are done.
private QResult FetchAPIData(string urlRelativePath, HttpMethod httpMethod, HttpContent contentToSend) { QResult result = new QResult(); //add auth token if we have a user and we arent deleting if (User != null && httpMethod !=HttpMethod.Delete) { urlRelativePath = urlRelativePath + "&access_token=" + User.id; } string url = _baseUrl + urlRelativePath; Debug.WriteLine("Performing " + httpMethod.ToString() + " to " + url); if (contentToSend!=null) { Debug.WriteLine("Sending data " + contentToSend.ReadAsStringAsync().Result); } HttpRequestMessage request = new HttpRequestMessage(httpMethod, url); request.Content = contentToSend; if (User != null) request.Headers.Add("X‑Access‑Token", User.id); using (HttpResponseMessage response = _client.SendAsync(request).Result) if (response.IsSuccessStatusCode) { using (HttpContent content = response.Content) { // ... Read the string. result.Message = content.ReadAsStringAsync().Result; result.Success = response.IsSuccessStatusCode; } } else { result.Message = response.ReasonPhrase; result.Success = false; } return result; }
And that’s it! POW!
Now that we have figured out how to communicate with the IBM platform, we need to understand what we must say. Fortunately, it is very simple—there are two phases.
Let’s start with the credentials. For the IBM Q experience platform, we need an API key. To get this key:
private QResult PerformLogin() { if (_token == string.Empty) throw new Exception("A token is required."); QResult result = new QResult(); HttpContent content= new StringContent("apiToken=" + _token, System.Text.Encoding.UTF8, "application/x‑www‑form‑urlencoded");//CONTENT‑TYPE header result = FetchAPIData("/users/loginWithToken", HttpMethod.Post, content); if (result.Success) { User = JsonConvert.DeserializeObject<QUser>(result.Message); Debug.WriteLine("Logged in and have UserID: " + User.userid); } else { User = null; } return result; }
There is only one trick here: You do not submit JSON data to this endpoint, but rather application/x-www-form-urlencoded data. This took a bit of work to figure out—again, fortunately Fiddler and Wireshark are a great boon when reverse engineering these kinds of things.
application/x-www-form-urlencoded
If the token is authenticated, we’ll get back a JSON packet similar to this:
"{\"id\":\"XXXXXXXXXXXXXXXXXXXXXXXXXX\",\"ttl\":1209600,\"created\": \"2017‑10‑03T14:51:51.918Z\",\"userId\": \"XXXXXXXXXXXXXXXXXXXXXXXXXXX7999a6\"}"
The value of the userId field is used in subsequent calls to the IBM platform. It’s good until the time-to-live (ttl) expires—which, in this simple demonstration code, we ignore. So, in our login step, we deserialize the userId out of resulting message data, and we stash that into our global user object.
Now it’s time to submit the QASM code for execution. We’ll create an ExecuteCode function with the following information:
Below is the ExecuteCode function in all its glory:
public QResult ExecuteCode(QCode code) { if (this.User == null) throw new Exception("Not logged in."); QResult result = new QResult(); string url = string.Format("/codes/execute?shots={0}&seed={1}&deviceRunType={2}", code.shots.ToString(), code.seed.ToString(), code.deviceRunType ); string data = string.Format("qasm={0}&codeType={1}&name={2}", code.qasm, code.codeType, code.name ); var kvp = new List<KeyValuePair<string, string>>(); kvp.Add(new KeyValuePair<string, string>("qasm", code.qasm)); kvp.Add(new KeyValuePair<string, string>("codeType", code.codeType)); kvp.Add(new KeyValuePair<string, string>("name", code.name)); HttpContent content = new FormUrlEncodedContent(kvp); result = FetchAPIData(url, HttpMethod.Post, content); Debug.WriteLine("ExecuteCode received the following JSON from the API:"); Debug.WriteLine(result.Message); return result; }
We pack the data into a KeyValuePair which we then FormUrlEncoded so that it can be POSTed to the URL using our FetchAPIData call.
Good start. But now what? How does a quantum processor solve a shell game?
To understand how quantum computing works, you need to know about Grover’s algorithm and the amplitude amplification trick.
The IBM Q experience tutorial on Grover’s algorithm is an excellent deep-dive into the algorithm if you need a deeper explanation.
In physical reality, if you’re given a set of shells, you simply flip them over one at a time until you find the ball. In the world of computers, this shell game is nothing more than an unstructured search.
Unstructured search is the process in which N number of elements in a set are searched by applying a Boolean evaluation (a function specifically of the form, f(x)=1) to each element in the set.
Given N elements in a set X ={x1, x2,…xN} and given a function f: X=>{0,1}, find element x in X where f(x)=1.
In our case, we have a number of shells (let’s call that number N), and we want to find the shell with a ball under it. When we select a shell, if there is a ball under the shell, that becomes a result called “true” or “1.” If we find anything under the shell besides a ball, let the result be a value called “false” or “0”. In this way, we have a function denoted f(x) = 1 when there is a ball and f(x) = 0 when there is anything other than a ball.
In the world of classical computing and our shell game, we simply read each shell, apply the function, and stop when we find the ball. This is sometimes described as “consulting the oracle” since we treat the function as something like a black box. We don’t necessarily care what goes on inside the box, just that the result of the function is a 1 or a 0. Given N number of shells, the worst possible performance of the classical computers would be O(N) such evaluations (for example, the ball is under the last shell).
Using a quantum computer and the amplitude amplification trick, we can solve this problem in O(sqr(N)) evaluations. However, quantum computing is never straightforward. A quantum computation results in a probability less than 1—not an exact answer. That said, we can run the computation numerous times until the result is any arbitrarily selected high probability (say .99 or better). For our purposes, we will use 500 shots. This will almost always end up with a near 100 percent probability (or at least one that rounds up to 100%).
Remember, we want to map our real-world problem onto the physical state of the quantum processor, execute the logic, and reverse the mapping back to reality.
In our shell game then, we need to:
Phew! Hard to say, but not too hard to do. The QCode class does all the work during instantiation. Here we break it down step-by-step.
We pass a parameter to the QCode constructor indicating which shell the user chose. This guides the creation of the QASM code to properly reflect the mapping of reality onto the quantum processor’s execution environment. The QCode constructor code begins by setting up the first part of the QASM code, which simply establishes five qubit and five classical registers.
//5 qubits and 5 classical registers string preCode = "include \"qelib1.inc\";qreg q[5];creg c[5];";
Why five? Truth be told, we only need two, but we’ll use five to keep this demo in line with the Composer scores found in the tutorial on the Q experience web site.
We assign states to the qubits. The system builds out the QASM based on which state we are trying to represent. Remember, if the user selected shell #1, we want to set the state of our qubits to the “00” state. Shell #2 is state “01” and so forth. The switch statement handles setting up these states. The code listing below shows how to assign these states:
switch (coinUnderShellNumber) { case 1: //#State=00 initCode = @"h q[1]; h q[2]; s q[1]; s q[2]; h q[2]; cx q[1],q[2]; h q[2]; s q[1]; s q[2]; "; break; case 2: //#State=01 initCode = @"h q[1]; h q[2]; s q[2]; h q[2]; cx q[1],q[2]; h q[2]; s q[2];"; break; case 3: //#State=10 initCode = @"h q[1]; h q[2]; s q[1]; h q[2]; cx q[1],q[2]; h q[2]; s q[1];"; break; case 4: //#State=11 initCode = @"h q[1]; h q[2]; h q[2]; cx q[1],q[2]; h q[2]; "; break; default: throw new Exception("There can only be 4 shells."); }
Finally, we add Grover’s algorithm to the end of this QASM code, as you can see in the following code listing:
//everything starting at the "double Hadamard" string postControlledNot = @"h q[1]; h q[2]; x q[1]; x q[2]; h q[2]; cx q[1], q[2]; h q[2]; x q[1]; x q[2]; h q[1]; h q[2]; measure q[1] ‑> c[1]; measure q[2] ‑> c[2];";
And we put it all together into a single string:
this.qasm = preCode + initCode + postControlledNot;
And we set the number of shots to 500, so that our experiment will be run enough times to wash out the probabilities.
this.shots = 500;
The ExecuteCode function we discussed earlier takes care of passing the code to the Q experience platform. If the code execution is successful, we should get back JSON similar to the following code listing:
{"result":{"date":"2017‑10‑ 03T17:07:21.819Z","data":{"creg_labels":"c[5]","p": {"qubits":[1,2],"labels":["00110"],"values":[1]}, "additionalData":{"seed":1},"qasm ... ,"userDeleted":false,"id":"7012d29df79ba1fca3e5eda009069d3d", "userId":"a3e5c196cb90688ba9a50dd7607999a6"}}
Lurking in that JSON output is the “labels” field containing our answer—in this case, “00110” (highlighted above). The “values” field lists the probability of the answer. Because we ran our simulation 500 times, the net result is close enough to 100% that the result comes back rounded to 1. Thus, parsing the result is as simple as rehydrating the JSON into an object and reading off the labels property:
QExecutionOutput x= JsonConvert.DeserializeObject <QExecutionOutput>(result.Message); Debug.WriteLine("The values are: " + x.result.data.p.labels[0]);
Mapping the result back is simply another switch statement as in the following code listing:
//parse the result and output the location of the coin QExecutionOutput x = qp.GetOutputFromMessageData(result.Message); string labels = x.result.data.p.labels[0]; switch (labels) { case "00000": Debug.WriteLine("The coin was under Shell #1"); ComputedShell.Value = "1"; break; case "00100": Debug.WriteLine("The coin was under Shell #2"); ComputedShell.Value = "2"; break; case "00010": Debug.WriteLine("The coin was under Shell #3"); ComputedShell.Value = "3"; break; case "00110": Debug.WriteLine("The coin was under Shell #4"); ComputedShell.Value = "4"; break; default: Debug.WriteLine("Something broke!"); ComputedShell.Value = "0"; break; }
If you only run a couple of shots, you might get very different results. Perhaps we should inspect the values array and select the array index with the highest probability, then choose the label with the matching array index as our answer. But, because we run this 500 times, our result is almost always an array with a single answer. As in the JSON below: "qubits":[1,2],"labels":["00110"],"values":[1]}," with a probability rounded to 1, and a single label of “00110”, in our shell game, we just read the result at index 0. In this example, the ball was hiding under the fourth shell!
"qubits":[1,2],"labels":["00110"],"values":[1]},"
In our ASP.NET web page, when the user presses the Submit button, the number of the shell is passed back in the “ShellSelected” property of the ShellGameModel class. We read this, create our QProcessor object (instantiating it with our token). Then we login.
If the login is successful, we instantiate our QCode object passing it the value of the shell selected. We then pass the code to our ExecuteCode function. We create a QExecutionOutput object from that code and read the labels property of the result.
Our labels, remember, are the qubits state (1 or 0) expressed classically. Another simple switch statement, and we can pass back what the quantum processor was able to deduce about the location of the ball.
HttpPost public IActionResult Index(ShellGameModel model) { model.ComputedShell = "0"; model.QASM = ""; model.ExecutionResults = ""; string token = "INSERTYOURTOKENHERE"; //Build the processor QProcessor qp = new QProcessor(token); //login QResult result = qp.Login(); Debug.WriteLine(string.Format("Login result. Success={0} Message={1}", result.Success.ToString(), result.Message)); if (result.Success) { int shell = 0; Int32.TryParse(model.ShellSelected, out shell); //build the QASM code QCode code = new QCode(shell); code.name = string.Format("ExperimentID {0} with Shell at {1} ", System.Guid.NewGuid().ToString(), shell.ToString()); Debug.WriteLine("Code:" + Environment.NewLine + code.qasm); //execute the code result = qp.ExecuteCode(code); Debug.WriteLine(string.Format("Code Executed Success={0}, Data={1}", result.Success.ToString(), result.Message)); //parse the result and output the location of the coin QExecutionOutput x = qp.GetOutputFromMessageData(result.Message); string labels = x.result.data.p.labels[0]; switch (labels) { case "00000": Debug.WriteLine("The coin was under Shell #1"); model.ComputedShell = "1"; break; case "00100": Debug.WriteLine("The coin was under Shell #2"); model.ComputedShell = "2"; break; case "00010": Debug.WriteLine("The coin was under Shell #3"); model.ComputedShell = "3"; break; case "00110": Debug.WriteLine("The coin was under Shell #4"); model.ComputedShell = "4"; break; default: Debug.WriteLine("Something broke!"); model.ComputedShell = "0"; break; } model.QASM = JsonConvert.SerializeObject(x.code, Formatting.Indented); model.ExecutionResults = JsonConvert.SerializeObject(x.result, Formatting.Indented); //now cleanup and delete the results QResult deleteResult = qp.DeleteExperiment(x.code.idCode); } return View(model); }
Et viola! We have taken the real-world problem of the shell game, mapped it into code, executed it on a quantum processor, processed the result and mapped the solution back into the real world.
This may not be the most elegant work you have ever seen — I can think of 100 ways to improve it — but it’s simple and effective, and for the purposes of this demonstration, close enough.
Want to see how this plays out in real life? Play the game and view the QASM code and the execution results. Have fun!
Thanks for following along on my journey with IBM’s Q experience. It was fun to try out this experimental tech and to use it to create a fun little game. Hopefully you’ll be able to take what I did in this tutorial and create some games or puzzles yourself using this quantum technology.
Peek behind the curtain of any new innovation, and you’ll likely find a foundation built on open source contributions.
AnalyticsArtificial Intelligence+
Before open source was cool, IBM was busy donating our code, establishing open licensing, pushing for open governance in the…
AnalyticsApache Spark+
Read along as a software engineer tries to understand and build with quantum. Get tips for where to start.
Back to top