The MATLAB Blog

Practical Advice for People on the Leading Edge

Quantum computing in MATLAB R2023b: On the desktop and in the cloud

Back in May of this year i attended the ISC High Performance Computing conference with a few other colleagues from MathWorks and was astonished at the number of booths related to various quantum computing intitiatives. I had more conversations about quantum computing in those few days than I'd had in years leading up to the event! As such, it seems rather timely that it's now possible to do quantum computing in MATLAB.

Getting started with quantum computing in MATLAB

The MATLAB Support Package for Quantum Computing is a free add-on developed by MathWorks. There are several ways to install it but my preferred way is via the Add-on Explorer.
Once in Add-on explorer, just search for quantum computing and you'll find it. Another way to get it is via its entry on File Exchange. Quantum computing has been available in MATLAB for a while now -- since R2023a but it recently got a few additional updates so now is a good time to dive in.
MATLAB currently supports a flavour of quantum computing called gate-based quantum computing at the root of which is a so-called quantum circuit. Let's create and visualise one
gates = [hGate(1);
cxGate(1,2);
cxGate(1,3)];
C = quantumCircuit(gates);
plot(C)
This is a three-qubit circuit with three quantum gates. Each horizonal line in the plot represents one of the qubits and the gates are arranged from left to right in the order that they are applied. The first qubit has a Hadamard gate applied to it while CNOT gates have been applied to the second and third qubits, using the first qubit as the control.
In order to actually run this quantum circuit, you'd need access to a real quantum computer and these are not the sort of thing that one has on their desk!
For now, let's simulate this circuit, specifying that each qubit has an inital state of |0 (never seen this notation before? It's an example of bra-ket notation) . We'll discuss how to run it on a real quantum computer later.
S = simulate(C,"000")
S =
QuantumState with properties: BasisStates: [8×1 string] Amplitudes: [8×1 double] NumQubits: 3
Let's express the simulated quantum state as a formula
f = formula(S)
f =
"0.70711 * |000> + 0.70711 * |111>"
There are two possible quantum states in this system but if you were were to actually observe it, you'd only see one. Which one you'd see is a matter of chance which is one of the consequences of quantum mechanics. Let's ask MATLAB for the possible states and the probabilities of seeing each one.
[states,P] = querystates(S)
states = 2×1 string
"000"
"111"
P = 2×1
0.5000 0.5000
Each state has a 50% change of being measured. We can plot this as a histogram but it's not a particularly interesting one
histogram(S)
If you actually had that quantum system, you'd not be able to directly measure probabilities. What you'd measure are the states and you'd do it a bunch of times to get an approximation of the probabilities. How do you do that in MATLAB?
% Simulate 50 quantum measurements of the circuit
M = randsample(S,100)
M =
QuantumMeasurement with properties: MeasuredStates: [2×1 string] Counts: [2×1 double] NumQubits: 3
Put the result into a table
simT = table(M.Counts,M.MeasuredStates,VariableNames=["Counts","States"])
simT = 2×2 table
 CountsStates
151"000"
249"111"
This is roughly what you'd expect if the probability of getting each state is 50%. Anyone expecting exactly 50 Counts for each state should retake probability-101!

Qubits? Gates? Quantum probabilities? What's going on?

824rqv.jpg
I've shown you how to create and simulate a quantum circuit in MATLAB but, at this point, you may be wondering what on earth is going on? How does any of this relate to implementing numerical algorithms? This talk of qubits, gates and state probabilities is rather far removed from the variables, if-statements and for-loops that I used when I first studied how to solve problems. As Alice famously said "Toto, I’ve a feeling we’re not in Kansas anymore"
Quantum computing is a totally different way of approaching computation that requires rather more explanation than I could fit into a blog post. We've got you covered, however. If you are totally new to the field of quantum computing, just start with this article in the MATLAB documentation: Introduction to Quantum Computing. You may also find this MathWorks quantum computing cheat sheet useful.
Another article that you may find interesting is this in-depth one from fellow MathWorks blogger Sofia Ma: Quantum Computing for Optimizing Investment Portfolios
An insight that really helped me make my first baby steps into quantum computing is to understand that the states of quantum systems can be represented by vectors while all of the gates have matrix representations. Here are two of them. For more, see Types of Quantum Gates
or in other words....
824r3r.jpg
If you have any other favourite sources for learning about quantum computing, let me know in the comments section.

Applications of quantum computing

Maybe you don't care so much about how to design a quantum algorithm and want to jump straight to potential applications. MathWorks has you covered here as well with a range of algorithms fully implemented in the new framework and available in the documentation. These include
We also have a set of open-source examples of quantum computing algorithms over on GitHub covering topics such as Portfolio Optimisation and Machine Learning classifiers. Pull Requests are welcome!
We've also implemented something called QUBO -- Quadratic Unconstrained Binary Optimization which can be used to solve combinatorical optimization problems such as the Traveling Salesperson Problem and the Capacitated Vehicle Routing Problem among others.
Quantum computing has the potential to revolutionise how we solve many problems that are difficult using conventional computers. That is, problems that would take a conventional supercomputer years to solve could be solved very quickly using a quantum algorithm on a suitable quantum computer. This is why there is so much hype around the subject and why there are so many quantum computing booths at supercomputing conferences.
Unfortunately, the quantum computers that currently exist are not yet powerful enough to do this and thus, revolutionise computing.
Quantum computers do exist, however, and they are good enough for us to test that our simulated algorithms do actually work on real quantum hardware...even if they are not yet quite good enough to give your local supercomputer a run for its money.

Running MATLAB quantum algorithms on real quantum computers using Amazon AWS

Real quantum computers are rather difficult to buy and it's unlikely that you have one sitting on your desk. An organisation who can give you access to some, however, is Amazon AWS and they make them available to the world via the Amazon Braket service. Details on how to connect MATLAB with the Amazon Braket service are at Run Quantum Circuit on Hardware Using AWS - MATLAB & Simulink (mathworks.com)
Before running this code, I had to create an S3 bucket with a name that started with the string "amazon-braket-". You'll need to create your own unique bucket name in your AWS account. Details on how to create S3 buckets in MATLAB can be found at Transfer Data to Amazon S3 Buckets and Access Data Using MATLAB Datastore - MATLAB & Simulink (mathworks.com)
There are two types of quantum resource available on Amazon that are of interest to us
  • QPU Devices: These devices are quantum computers used to perform probabilistic measurements of circuits. Not all QPU devices are supported. To find out if a device is supported, try connecting to the device using quantum.backend.QuantumDeviceAWS. Availability and number of qubits supported can also vary, so check the device details before sending circuits for measurement.
  • Simulator Devices: Because memory usage scales exponentially with the number of qubits, larger circuits with many qubits might stretch the limits of memory available on your local computer. To address that issue, AWS also provides cloud-scale simulators. The functionality is similar to using randsample on your local system, but the simulators can support up to 34 qubits with cloud-scale performance.
At the time of writing, the example in MathWorks documentation connects to a simulator: "SV1", a state-vector simulator used for prototyping circuits.
I want to run on the real-deal though! I can't say I've done quantum computing until I've submitted a job to a real quatum computer. I took a look at the available devices on Amazon Braket and randomly chose the Rigetti Aspen-M-3. This is a 79 Qubit device and the details section over at Amazon told me that "Rigetti quantum processors are universal, gate-model machines based on tunable superconducting qubits." Sounds cool! Superconductors and programmable quantum computers! If only 1996 physics undergraduate Mike Croucher could see me now 🙂
% This code will not work for you unless you have configured AWS correctly
reg = "us-west-1";
bucketPath = "s3://amazon-braket-walkingrandomlyblog/default"; % You'll need to change this name to your own bucket
device = quantum.backend.QuantumDeviceAWS("Aspen-M-3",Region=reg,S3Path=bucketPath)
device =
QuantumDeviceAWS with properties: Name: "Aspen-M-3" DeviceARN: "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" Region: "us-west-1" S3Path: "s3://amazon-braket-walkingrandomlyblog/default"
The fetchDetails command can be used to....um...fetch the details of the quantum device I want to submit jobs to.
fetchDetails(device)
ans = struct with fields:
deviceArn: "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" deviceCapabilities: "{"service": {"braketSchemaHeader": {"name": "braket.device_schema.device_service_properties", "version": "1"}, "executionWindows": [{"executionDay": "Everyday", "windowStartHour": "04:00:00", "windowEndHour": "06:00:00"}, {"executionDay": "Everyday", "windowStartHour": "15:00:00", "windowEndHour": "18:00:00"}], "shotsRange": [10, 100000], "deviceCost": {"price": 0.00035, "unit": "shot"}, "deviceDocumentation": {"imageUrl": "http://d1.awsstatic.com/logos/Rigetti_logo_rgb_teal.0cbe3c0653e755ab97c04ec56f3611f53e2a780c.png", "summary": "Universal gate-model QPU based on superconducting qubits", "externalDocumentationUrl": "https://qcs.rigetti.com/qpus/"}, "deviceLocation": "California, USA", "updatedAt": "2023-09-15T15:50:33+00:00", "getTaskPollIntervalMillis": 200}, "action": {"braket.ir.jaqcd.program": {"version": ["1.0", "1.1"], "actionType": "braket.ir.jaqcd.program", "supportedOperations": ["cz", "xy", "ccnot", "cnot", "cphaseshift", "cphaseshift00", "cphaseshift01", "cphaseshift10", "cswap", "h", "i", "iswap", "phaseshift", "pswap", "rx", "ry", "rz", "s", "si", "swap", "t", "ti", "x", "y", "z", "start_verbatim_box", "end_verbatim_box"], "supportedResultTypes": [{"name": "Sample", "observables": ["x", "y", "z", "h", "i"], "minShots": 10, "maxShots": 100000}, {"name": "Expectation", "observables": ["x", "y", "z", "h", "i"], "minShots": 10, "maxShots": 100000}, {"name": "Variance", "observables": ["x", "y", "z", "h", "i"], "minShots": 10, "maxShots": 100000}, {"name": "Probability", "minShots": 10, "maxShots": 100000}], "disabledQubitRewiringSupported": true}, "braket.ir.openqasm.program": {"version": ["1.0"], "actionType": "braket.ir.openqasm.program", "supportedOperations": ["cz", "xy", "ccnot", "cnot", "cphaseshift", "cphaseshift00", "cphaseshift01", "cphaseshift10", "cswap", "h", "i", "iswap", "phaseshift", "pswap", "rx", "ry", "rz", "s", "si", "swap", "t", "ti", "x", "y", "z"], "supportedModifiers": [], "supportedPragmas": ["verbatim", "braket_result_type_sample", "braket_result_type_expectation", "braket_result_type_variance", "braket_result_type_probability"], "forbiddenPragmas": ["braket_unitary_matrix", "braket_result_type_state_vector", "braket_result_type_density_matrix", "braket_result_type_amplitude", "braket_result_type_adjoint_gradient"], "maximumQubitArrays": 1, "maximumClassicalArrays": 1, "forbiddenArrayOperations": ["concatenation", "negativeIndex", "range", "rangeWithStep", "slicing", "selection"], "requiresAllQubitsMeasurement": false, "supportPhysicalQubits": true, "requiresContiguousQubitIndices": false, "supportsPartialVerbatimBox": true, "supportsUnassignedMeasurements": false, "disabledQubitRewiringSupported": false, "supportedResultTypes": [{"name": "Sample", "observables": ["x", "y", "z", "h", "i"], "minShots": 10, "maxShots": 100000}, {"name": "Expectation", "observables": ["x", "y", "z", "h", "i"], "minShots": 10, "maxShots": 100000}, {"name": "Variance", "observables": ["x", "y", "z", "h", "i"], "minShots": 10, "maxShots": 100000}, {"name": "Probability", "minShots": 10, "maxShots": 100000}]}}, "deviceParameters": {"title": "RigettiDeviceParameters", "description": "This defines the parameters common to all the Rigetti devices.\n\nAttributes:\n paradigmParameters: Parameters that are common to gatemodel paradigm\n\nExamples:\n >>> import json\n >>> input_json = {\n ... \"braketSchemaHeader\": {\n ... \"name\": \"braket.device_schema.rigetti.rigetti_device_parameters\",\n ... \"version\": \"1\",\n ... },\n ... \"paradigmParameters\": {\"braketSchemaHeader\": {\n ... \"name\": \"braket.device_schema.gate_model_parameters\",\n ... \"version\": \"1\",\n ... },\"qubitCount\": 1},\n ... }\n >>> RigettiDeviceParameters.parse_raw_schema(json.dumps(input_json))", "type": "object", "properties": {"braketSchemaHeader": {"title": "Braketschemaheader", "const": {"name": "braket.device_schema.rigetti.rigetti_device_parameters", "version": "1"}}, "paradigmParameters": {"$ref": "#/definitions/GateModelParameters"}}, "required": ["paradigmParameters"], "definitions": {"GateModelParameters": {"title": "GateModelParameters", "description": "Defines parameters common to all gate model devices.\n\nAttributes:\n qubitCount: Number of qubits used by the circuit.\n disableQubitRewiring: Whether to run the circuit with the exact qubits chosen,\n without any rewiring downstream.\n If ``True``, no qubit rewiring is allowed; if ``False``, qubit rewiring is allowed.\n\nExamples:\n >>> import json\n >>> input_json = {\n ... \"braketSchemaHeader\": {\n ... \"name\": \"braket.device_schema.gate_model_parameters\",\n ... \"version\": \"1\",\n ... },\n ... \"qubitCount\": 1,\n ... \"disableQubitRewiring\": True\n ... }\n >>> GateModelParameters.parse_raw_schema(json.dumps(input_json))", "type": "object", "properties": {"braketSchemaHeader": {"title": "Braketschemaheader", "const": {"name": "braket.device_schema.gate_model_parameters", "version": "1"}}, "qubitCount": {"title": "Qubitcount", "minimum": 0, "type": "integer"}, "disableQubitRewiring": {"title": "Disablequbitrewiring", "default": false, "type": "boolean"}}, "required": ["qubitCount"]}}}, "braketSchemaHeader": {"name": "braket.device_schema.rigetti.rigetti_device_capabilities", "version": "1"}, "paradigm": {"braketSchemaHeader": {"name": "braket.device_schema.gate_model_qpu_paradigm_properties", "version": "1"}, "connectivity": {"fullyConnected": false, "connectivityGraph": {"0": ["1", "7", "103"], "100": ["101", "107"], "10": ["11", "17", "113"], "110": ["111", "117"], "20": ["21", "27", "123"], "120": ["121", "127"], "30": ["31", "37", "133"], "130": ["131", "137"], "40": ["41", "47", "143"], "140": ["141", "147"], "1": ["0", "2", "16"], "101": ["100", "102"], "11": ["10", "12"], "111": ["110", "112", "126"], "21": ["20", "22", "36"], "121": ["120", "122"], "31": ["30", "32", "46"], "131": ["130", "132", "146"], "41": ["40", "42"], "141": ["140", "142"], "2": ["1", "3", "15"], "102": ["101", "103", "115"], "12": ["11", "13", "25"], "112": ["111", "113", "125"], "22": ["21", "23", "35"], "122": ["121", "123", "135"], "32": ["31", "33", "45"], "132": ["131", "133", "145"], "42": ["41", "43"], "142": ["141", "143"], "3": ["2", "4"], "103": ["102", "104", "0"], "13": ["12", "14"], "113": ["112", "114", "10"], "23": ["22", "24"], "123": ["122", "124", "20"], "33": ["32", "34"], "133": ["132", "134", "30"], "43": ["42", "44"], "143": ["142", "144", "40"], "4": ["3", "5"], "104": ["103", "105", "7"], "14": ["13", "15"], "114": ["113", "115", "17"], "24": ["23", "25"], "124": ["123", "125", "27"], "34": ["33", "35"], "134": ["133", "135", "37"], "44": ["43", "45"], "144": ["143", "145", "47"], "5": ["4", "6"], "105": ["104", "106"], "15": ["14", "16", "2"], "115": ["114", "102"], "25": ["24", "26", "12"], "125": ["124", "126", "112"], "35": ["34", "36", "22"], "135": ["134", "122"], "45": ["44", "46", "32"], "145": ["144", "146", "132"], "6": ["5"], "106": ["105", "107"], "16": ["15", "17", "1"], "116": ["117"], "26": ["25", "27"], "126": ["125", "127", "111"], "36": ["35", "37", "21"], "46": ["45", "47", "31"], "146": ["145", "147", "131"], "7": ["0", "104"], "107": ["106", "100"], "17": ["16", "10", "114"], "117": ["116", "110"], "27": ["26", "20", "124"], "127": ["126", "120"], "37": ["36", "30", "134"], "137": ["130"], "47": ["46", "40", "144"], "147": ["146", "140"]}}, "qubitCount": 79, "nativeGateSet": ["rx", "rz", "cz", "cphaseshift", "xy"]}, "provider": {"braketSchemaHeader": {"name": "braket.device_schema.rigetti.rigetti_provider_properties", "version": "1"}, "specs": {"1Q": {"0": {"fActiveReset": 0.966, "fRO": 0.946, "f1QRB": 0.9972872933599024, "f1QRB_std_err": 7.838351195153969e-05, "f1Q_simultaneous_RB": 0.9953169329383368, "f1Q_simultaneous_RB_std_err": 0.00024182103479684447, "T1": 1.0019627401991471e-05, "T2": 1.8156447816365015e-05}, "100": {"fActiveReset": 0.9985, "fRO": 0.972, "f1QRB": 0.998954207277828, "f1QRB_std_err": 0.00014837187531456048, "f1Q_simultaneous_RB": 0.9971558102223412, "f1Q_simultaneous_RB_std_err": 0.00015862791239059965, "T1": 1.1816795696395488e-05, "T2": 2.0095701209033546e-05}, "10": {"fActiveReset": 0.954, "fRO": 0.981, "f1QRB": 0.9992031567592476, "f1QRB_std_err": 0.00014056593724064313, "f1Q_simultaneous_RB": 0.9976156360254491, "f1Q_simultaneous_RB_std_err": 8.608369330764301e-05, "T1": 2.5578694019978698e-05, "T2": 1.9006026304791726e-05}, "110": {"fActiveReset": 0.956, "fRO": 0.982, "f1QRB": 0.9964323820015337, "f1QRB_std_err": 0.0002538166941835989, "f1Q_simultaneous_RB": 0.9944725505869456, "f1Q_simultaneous_RB_std_err": 0.00046613884655654996, "T1": 2.1541132994906675e-05, "T2": 2.4168521929465816e-05}, "20": {"fActiveReset": 0.9925, "fRO": 0.973, "f1QRB": 0.9987680979711324, "f1QRB_std_err": 0.00024454249633089407, "f1Q_simultaneous_RB": 0.9967164854629315, "f1Q_simultaneous_RB_std_err": 0.00034938876334807785, "T1": 1.916757238906719e-05, "T2": 2.0065645767958196e-05}, "120": {"fActiveReset": 0.998, "fRO": 0.977, "f1QRB": 0.9983062074153319, "f1QRB_std_err": 5.774983328102287e-05, "f1Q_simultaneous_RB": 0.9970528341245045, "f1Q_simultaneous_RB_std_err": 0.00014167831836001615, "T1": 1.2764039701629315e-05, "T2": 2.0246986797656767e-05}, "30": {"fActiveReset": 0.944, "fRO": 0.859, "f1QRB": 0.9983567784701302, "f1QRB_std_err": 0.0001484753223172316, "f1Q_simultaneous_RB": 0.9962760717321852, "f1Q_simultaneous_RB_std_err": 0.00012898655899217618, "T1": 3.72516437563419e-06, "T2": 3.60229201220574e-06}, "130": {"fActiveReset": 0.996, "fRO": 0.931, "f1QRB": 0.9953353543402388, "f1QRB_std_err": 0.0002495782404244499, "f1Q_simultaneous_RB": 0.9923808464253132, "f1Q_simultaneous_RB_std_err": 0.0003757619231654068, "T1": 2.1055966682670402e-05, "T2": 8.279538699664696e-06}, "40": {"fActiveReset": 0.9990000000000001, "fRO": 0.977, "f1QRB": 0.998754212487603, "f1QRB…" deviceName: "Aspen-M-3" deviceStatus: "ONLINE" deviceType: "QPU" providerName: "Rigetti"
I'm connected and I'm ready to go. Remember that circuit that I created at the beginning of the blog post?
plot(C)
I've simulated it on my desktop. Time to run it on the real thing in the cloud using the run command
task = run(C,device)
task =
QuantumTaskAWS with properties: Status: "queued" TaskARN: "arn:aws:braket:us-west-1:580681155480:quantum-task/c9531ea3-3de8-4ee8-ad02-cefce2f8bc99"
I'm in the quantum queue in the cloud! Let's get MATLAB to wait until the task is finished. The following command blocks execution until the task on the quantum device has finished running.
wait(task)
Eventually, the task will complete and I can get the results
R = fetchOutput(task)
R =
QuantumMeasurement with properties: MeasuredStates: [8×1 string] Counts: [8×1 double] NumQubits: 3

Results from running on a real quantum computer

When I simulated this quantum circuit on my local machine 100 times, I got the following results
simT
simT = 2×2 table
 CountsStates
151"000"
249"111"
Running the circuit on a real system gave the following
T = table(R.Counts,R.MeasuredStates,VariableNames=["Counts","States"])
T = 8×2 table
 CountsStates
132"000"
24"001"
37"010"
43"011"
53"100"
66"101"
79"110"
836"111"
The overall number of Counts sum to 100 because that's the default number of times the run command runs the circuit. Each of these runs is called a shot.
histogram(R)
What's interesting here is that along with the two states I expected to observe, I've also observed every other possible state a small number of times. I understand that this is due to noise in the system. Real quantum computers are not perfect devices and this noise is part of the reality of working with quantum algorithms on real devices.
It's difficult to convey to you how exciting this feels! I know that the computation I've done is essentially a pointless demo that I've lifted from the MATLAB documentation but there is just something thrilling about running code on a completely different type of computer to anything I've ever used before.
The run you see above is the first (and so far only) time I have ever run anything on a real quantum computer!

Costs of the AWS quantum computation

As you might expect, running jobs on a quantum computer in the cloud costs money. The way Quantum jobs are currently priced by AWS are a cost per task and an additional cost per shot.
In the context of this calculation, you can think of the task as setting up the circuit and each shot as the cost of running that circuit 1 time. At the time of writing, the device I used had a cost of 30 cents per task and 0.035 cents per shot so the 100 shot calculation cost me about $0.34. There will also be S3 storage costs but for this computation I'm guessing it will be pretty negligible. I doubt I'll be sufficiently motivated to fill in my expense claim to MathWorks for cloud costs associated with this blog post.

Version of the MATLAB Support Package for Quantum Computing used

I used version 23.2 of the MATLAB Support Package for Quantum Computing. You can see which one you have installed by executing
matlabshared.supportpkg.getInstalled
|
  • print

Comments

To leave a comment, please click here to sign in to your MathWorks Account or create a new one.