Contents


Quantum computing in action: IBM's Q experience and the quantum shell game

A developer's experience with the IBM Q experience

Comments

As I described in my Solve the shell game with quantum computing, qubits and IBM Q blog post, 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.

Prerequisite knowledge

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.

System requirements

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.

A workaround

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.

The quantum solution

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.

Figure 1. The ASP.NET Core solution
Screen shot of the .NET solution
Screen shot of the .NET solution

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.

  • The QProcessor class is the heart of this operation. It handles the login function and the execution of the QASM code, as well as a couple of utility and cleanup routines. It also persists the security information needed to make the various web service calls and wraps up the implementation. Note the function that’s used to delete experiments. IBM’s Q experience platform saves every execution as an experiment—over time, especially when testing out ideas, your account on the Q experience platform will literally be overrun with experiments. I found deleting them immediately after they have been run to be the best way to do the housekeeping.
  • The QResult class is a simple construct to pass the success of an operation back and forth between components.
  • QUser class is an object used to hold the results of logging into the Q experience platform. In this experiment, after logging in, you want to record the userId of the user, as this is later used as the access token granting you permission to perform operations against the account.
  • The QCode class will be used to assemble the QASM code. It also carried the number of shots (the number of times to perform the quantum execution). Remember, quantum qubits are returned as a probability of being a 1 or 0). For this application, I run the quantum code for 500 shots—which is more than enough to achieve a near 100% probability of a 1 or a 0—thus removing some of the wishy-washiness of quantum processing.
  • QExecutionOutput is the most complicated class of these five classes. However, for the purposes of this application, we get to ignore most of it. The most important part is the P class lurking inside of the Data class. There are two properties, labels and values, that hold the result. The labels property tells us whether a given qubit resulted in a classical bit of 1 or 0, and the values property tell us what the probability of the result was. The class itself was generated from the JSON result of executing sample code against the IBM platform. I used an online converter — in this instance, json2csharp.com — to build the QExecutionOutput class. Admittedly, this class is probably not the perfect way to handle the resulting JSON, but it was faster than writing the class by hand.

So, let’s walk through the steps these classes are going to perform.

Communicate with the IBM Q experience platform

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.

Listing 1. Sending our QResult object
     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.

  1. The first is an exchange of credentials.
  2. The second is the transmission of the QASM code itself and the receipt of the JSON data representing the result of the quantum code’s execution.

Phase 1. Exchange credentials

Let’s start with the credentials. For the IBM Q experience platform, we need an API key. To get this key:

  1. Go to your IBM Quantum experience account page.
  2. Click the Regenerate button to have an API key created for you.
    Figure 2. API token in IBM's Q Experience profile page
    Screen shot of the Regenerate button on the IBM Q Experience                     page
    Screen shot of the Regenerate button on the IBM Q Experience page
  3. Now that we have the API token, we pass it to the web service living at the “/users/loginWithToken” endpoint. The code listing below shows the PerformLogin routine we use to do this.
Listing 2. Passing the API token
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.

If the token is authenticated, we’ll get back a JSON packet similar to this:

Listing 3. Returned JSON packet
"{\"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.

Phase 2. Execute the code

Now it’s time to submit the QASM code for execution. We’ll create an ExecuteCode function with the following information:

  • A URL string with the following three parameters:
    • Shots: the number of times to perform the experiment
    • Seed
    • deviceRunType: we use “simulator,” though if you’d prefer you can run this on the real quantum processor
  • Other data including:
    • the QASM code itself
    • the codeType: for us, it’s QASM2
    • a name to assign to the run

Below is the ExecuteCode function in all its glory:

Listing 4. ExecuteCode function
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?

Grover’s algorithm and the amplitude amplification trick

To understand how quantum computing works, you need to know about Grover’s algorithm and the amplitude amplification trick.

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%).

The map

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:

  1. Pass the number of the shell selected by our user to the backend.
  2. Have the backend pass the data to the quantum processor in the form of QASM code that executes Grover’s algorithm.
  3. Retrieve those results and parse the results.
  4. Map the result data back into the reality (e.g. display which shell the user chose).

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.

Step 1. Pass the number of the shells selected by the user to the backend

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.

Listing 5. Set up the QASM code
//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.

Step 2. Have the backend pass the data to the quantum processor in the form of QASM code that executes Grover’s algorithm

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:

Listing 6. Assign the 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:

Listing 7. Add Grover's algorithm
//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:

Listing 8. Put 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.

Listing 9. Set the number of shots
this.shots = 500;

Step 3. Retrieve and parse the results

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:

Listing 10. JSON output
{"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:

Listing 11. Parsing the results
QExecutionOutput x= JsonConvert.DeserializeObject
<QExecutionOutput>(result.Message);
            Debug.WriteLine("The values are: " + x.result.data.p.labels[0]);

Step 4. Map the result back

Mapping the result back is simply another switch statement as in the following code listing:

Listing 12. Mapping the results
  //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!

Putting it all together

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.

Listing 13. Finding 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.

Play the game yourself

Want to see how this plays out in real life? Play the game and view the QASM code and the execution results. Have fun!

Conclusion

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.

Next steps


Downloadable resources


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=1052168
ArticleTitle=Quantum computing in action: IBM's Q experience and the quantum shell game
publish-date=11282017