Wolfram/QuantumFramework

(1.0.30) current version: 1.4.5 »

Perform analytic and numeric quantum computations

Contributed by: Wolfram Research, Quantum Computation Framework team

The Wolfram Quantum Framework brings a broad, coherent design for quantum computation, together with a host of leading-edge capabilities and full integration into Mathematica and Wolfram Language. Starting from discrete quantum mechanics, the Framework provides a high-level symbolic representation of quantum bases, states and operators. The Framework can perform measurements and is equipped with various well-known states and operators, such as Bell states and Pauli operators. Using such simulation capabilities as a foundation, one can use the Framework to model and simulate quantum circuits and algorithms.

All functions and objects in the Wolfram Quantum Framework work seamlessly with the 5,000+ built-in functions made available through Wolfram Language. The immediate availability of such functions allows one to study a full range of questions around quantum computation and can serve as a helpful resource for teaching.

Installation Instructions

To install this paclet in your Wolfram Language environment, evaluate this code:
PacletInstall["Wolfram/QuantumFramework"]


To load the code after installation, evaluate this code:
Needs["Wolfram`QuantumFramework`"]

Details

This paclet works with Version 13.1 and higher of Wolfram Language.

Paclet Guide

Examples

Quantum Basis and Quantum State (19) 

In order to use QuantumBasis, one gives dimension information as arguments, which will be interpreted as the computational basis. Alternatively, an association can be given with the basis name as the key and the corresponding basis elements as the values.


Define a 2D quantum basis (computational):

In[1]:=
QuantumBasis[2]
Out[1]=

Given a basis of dimension n, the basis elements will be indexed by the key with :

In[2]:=
Normal /@ %["ElementAssociation"]
Out[2]=

Use QuantumBasis[n,m] to define a basis for m qudits of dimension n (for which the overall dimension will be ). For example, define a 2×2×2-dimensional quantum basis (with three qubits):

In[3]:=
QuantumBasis[2, 3]
Out[3]=
In[4]:=
%["Dimensions"]
Out[4]=

Use QuantumBasis[{n1,n2,,nm}] to define an n1×n2×n3××nm-dimensional Hilbert space of qudits as a list. For example, define a 3×5-dimensional quantum basis (with two qudits):

In[5]:=
QuantumBasis[{3, 5}]
Out[5]=

A basis can also be defined as an association with the basis element names as keys and the corresponding vectors as values:

In[6]:=
QuantumBasis[<|a -> {1, I}, b -> {2, -I}|>]
Out[6]=
In[7]:=
Normal /@ %["ElementAssociation"]
Out[7]=

There are many 'named' bases built into the quantum framework, including "Computational", "PauliX", "PauliY", "PauliZ", "Fourier", "Identity", "Schwinger", "Pauli", "Dirac" and "Wigner":

In[8]:=
{QuantumBasis["Computational"], QuantumBasis["PauliX"], QuantumBasis["Schwinger"], QuantumBasis["Bell"], QuantumBasis["Dirac"]}
Out[8]=

After a basis object has been defined, it is straightforward to construct quantum states and operators. A quantum state is represented by a QuantumState object and a quantum operator is represented by QuantumOperator.

A pure quantum state is represented as a vector for which the elements are amplitudes. The corresponding basis should be given in this format: QuantumState[arg1,arg2], where arg1 specifies amplitudes or the density matrix, and arg2 specifies the basis. With no basis specified, the default basis will be the computational basis, the dimension of which depends on the amplitude vector given in arg1.

Note that the big endian convention is in use, such that qubits are labeled left-to-right, starting with 1. For example, the decimal representation of (which means 3) is . Additionally, for the eigenvalues of Pauli-Z, there is:

In[9]:=
Eigensystem[PauliMatrix[3]]
Out[9]=

Denote the eigenstate {1,0} by (which corresponds to +1 eigenvalue), and {0,1} by (which corresponds to the eigenvalue -1).


Define a pure 2-dimensional quantum state (qubit) in the Pauli-X basis:

In[10]:=
QuantumState[{1, -1 + I}, QuantumBasis["PauliX"]]
Out[10]=
In[11]:=
%["Formula"]
Out[11]=

If the basis is not specified, the default is the computational basis of dimensions ( qubits):

In[12]:=
state = QuantumState[{3, 2 I, 1, -5}]
Out[12]=
In[13]:=
state["Amplitudes"]
Out[13]=

If the vector has more than 2 elements, it is interpreted as an -qubit state, unless the dimension is specified. If fewer than 2n amplitudes are specified, right-padding is applied to reach the 2n "ceiling":

In[14]:=
state = QuantumState[{2, 0, I}]
Out[14]=
In[15]:=
state["Amplitudes"]
Out[15]=

Here is the same amplitude vector, but this time with the dimension specified:

In[16]:=
state = QuantumState[{2, 0, I}, 3];
state["Amplitudes"]
Out[17]=

Binary strings can also be used as inputs:

In[18]:=
QuantumState["001"]
Out[18]=

Many "named" states are available through the framework:

In[19]:=
{QuantumState[{"UniformSuperposition", 4}], QuantumState["PsiPlus"], QuantumState["GHZ"]}
Out[19]=

Using associations, one can create a superposition of states, where the keys are lists of corresponding indexes and the values are amplitudes.

Create a superposition of 3 qubits (i.e. QuantumBasis[2,3] as ):

In[20]:=
\[Psi] = QuantumState[<|{0, 0, 0} -> 1/Sqrt[2], {1, 1, 1} -> 1/Sqrt[2]|>, 2, 3]
Out[20]=
In[21]:=
\[Psi]["Formula"]
Out[21]=
In[22]:=
\[Psi]["Dimensions"]
Out[22]=

A superposition can also be created by simply adding two quantum state objects. For example, the previous state can also be constructed as follows:

In[23]:=
\[Psi]2 = (QuantumState["000"] + QuantumState["111"])/Sqrt[2];
\[Psi] == \[Psi]2
Out[24]=

With a built-in basis specified, amplitudes correspond to the basis elements. For example, use the Bell basis:

In[25]:=
Normal /@ QuantumBasis["Bell"]["ElementAssociation"]
Out[25]=
In[26]:=
\[Psi] = QuantumState[{2, -I, 1, 3}, QuantumBasis["Bell"]]
Out[26]=
In[27]:=
\[Psi]["Amplitudes"]
Out[27]=

A state can also be defined by inputting a density matrix:

In[28]:=
QuantumState[1/2 (IdentityMatrix[2] + PauliMatrix[2])]
Out[28]=

For pure states, one can get the corresponding normalized state vector:

In[29]:=
%["Formula"]
Out[29]=

Define a generic Bloch vector:

In[30]:=
mat[r_] /; VectorQ[r] := 1/2 (IdentityMatrix[2] + r . Table[PauliMatrix[i], {i, 3}])
In[31]:=
r = {.1, .1, 0};
state = QuantumState[mat[r]]
Out[27]=

Test to see if it is a mixed state:

In[32]:=
state["MixedStateQ"]
Out[32]=

Calculate its von Neumann entropy:

In[33]:=
state["VonNeumannEntropy"]
Out[33]=

Compute its purity:

In[34]:=
state["Purity"]
Out[34]=

Note that one can directly use a Bloch vector as an input:

In[35]:=
QuantumState[{"BlochVector", r}]
Out[35]=
In[36]:=
% == state
Out[36]=

Test to see if a matrix is positive semidefinite:

In[37]:=
mat[{1, 2, 0}] // PositiveSemidefiniteMatrixQ
Out[37]=

A matrix that is not positive semidefinite cannot be a density matrix in standard quantum mechanics (with some exceptional cases, such as ZX formalism). Here is the result when it is attempted to define a state using such a matrix:

In[38]:=
QuantumState[mat[{1, 2, 0}]]
Out[38]=

When a matrix is given as input but no basis is given, the default basis will be computational:

In[39]:=
m = RandomComplex[{-1 - I, 1 + I}, {8, 8}];
\[Rho] = ConjugateTranspose[m] . m;
state = QuantumState[\[Rho]]
Out[27]=
In[40]:=
state["Dimensions"]
Out[40]=

Using , define a quantum state in a 2×4-dimensional basis (and note the number of qudits):

In[41]:=
state = QuantumState[\[Rho], {2, 4}]
Out[41]=
In[42]:=
state["Dimensions"]
Out[42]=

Define a quantum state in 8D Hilbert space (with one 8-dimensional qudit only):

In[43]:=
state = QuantumState[\[Rho], 8]
Out[43]=
In[44]:=
state["Dimensions"]
Out[44]=

One can also define a state in a given basis and then transform it into a new basis. For example, transform , the computational basis, into the Pauli-X basis {,}:

In[45]:=
\[Psi]1 = QuantumState[{1, 0}];
\[Psi]2 = QuantumState[\[Psi]1, "PauliX"]
Out[38]=

Return the amplitudes:

In[46]:=
\[Psi]1["Formula"]
Out[46]=
In[47]:=
\[Psi]2["Formula"]
Out[47]=

Note that the states are the same, but defined in different bases:

In[48]:=
\[Psi]1 == \[Psi]2
Out[48]=

One can use QuantumTensorProduct to construct different states or operators. Create a tensor product of a + state with three qubits :

In[49]:=
\[Psi]1 = QuantumTensorProduct[QuantumState["Plus"], QuantumState["Plus"], QuantumState["Plus"]];
\[Psi]1["Formula"]
Out[38]=

Another way of defining is to first define a basis and then assign amplitudes:

In[50]:=
plusbasis = QuantumTensorProduct[QuantumBasis["PauliX"], QuantumBasis["PauliX"],
    QuantumBasis["PauliX"]];
In[51]:=
\[Psi]2 = QuantumState[{0, 0, 0, 0, 0, 0, 0, 1}, plusbasis];
\[Psi]2["Formula"]
Out[48]=
In[52]:=
\[Psi]2 == \[Psi]1
Out[52]=

Quantum Operators (10) 

Quantum operators can be defined by a matrix or by specifying eigenvalues with respect to a QuantumBasis. Additionally, there are many built-in named operators that can be used.


Define a Pauli-X operator:

In[53]:=
QuantumOperator["PauliX"]
Out[53]=

Apply a Pauli-X operator to a symbolic state :

In[54]:=
QuantumOperator["PauliX"][QuantumState[{\[Alpha], \[Beta]}]]
Out[54]=

Test to see if the application of the Pauli-X operator yields the correct state:

In[55]:=
% == QuantumState[{\[Beta], \[Alpha]}]
Out[55]=

Apply the Hadamard operator :

In[56]:=
QuantumOperator["Hadamard"]@QuantumState[{1, 0}]
Out[56]=

Test to see if the application of the Hadamard operator yields the correct state:

In[57]:=
% == QuantumState["Plus"]
Out[57]=

One can also compose operators. Here is a composition of two Hadamard operators and one Pauli-Z operator:

In[58]:=
QuantumOperator["Hadamard"]@
 QuantumOperator["PauliZ"]@QuantumOperator["Hadamard"]
Out[58]=

Check the relation σx=HσxH:

In[59]:=
% == QuantumOperator["PauliX"]
Out[59]=

Multi-qubit operators can take specific orders.

For instance, first define the state α+β:

In[60]:=
state = QuantumState[<|{1, 0} -> \[Alpha], {1, 1} -> \[Beta]|>, QuantumBasis[2, 2]];
state["Amplitudes"]
Out[59]=

Then, apply a Pauli-X operator on the second qubit only (by defining an order for the operator):

In[61]:=
QuantumOperator["PauliX", {2}][state]
Out[61]=

Test the result:

In[62]:=
% == QuantumState[<|{1, 0} -> \[Beta], {1, 1} -> \[Alpha]|>, QuantumBasis[2, 2]]
Out[62]=

For multi-qudit cases, one can define an order or construct the operator using QuantumTensorProduct. For example:

In[63]:=
QuantumOperator["Hadamard", {1}]@QuantumOperator["PauliX", {2}] == QuantumTensorProduct[QuantumOperator["Hadamard"], QuantumOperator["PauliX"]]
Out[63]=

Generalize Pauli matrices to higher dimensions:

In[64]:=
QuantumOperator[{"PauliX", 3}]
Out[64]=

Convert to matrix form:

In[65]:=
%["Matrix"] // MatrixForm
Out[65]=

Generalize the Hadamard operator to more qubits:

In[66]:=
QuantumOperator[{"Hadamard", 3}]
Out[66]=

Test that the Hadamard operator can be constructed as a tensor product:

In[67]:=
QuantumOperator[{"Hadamard", 3}] == QuantumTensorProduct[QuantumOperator["H"], QuantumOperator["H"], QuantumOperator["H"]]
Out[67]=

One can define a "Controlled" operator with specific target and control qudits:

In[68]:=
ct = QuantumOperator[{"C", "T", {1}}];

Return the control and target qudits:

In[69]:=
AssociationMap[ct, {"ControlOrder", "TargetOrder"}]
Out[69]=

Get the action of the operator (T-controlled (1, 2)) on :

In[70]:=
ct@QuantumTensorProduct[QuantumState[{0, 1}], QuantumState["Plus"]]
Out[70]=
In[71]:=
%["Formula"]
Out[71]=

Note that "CT" is also a "named" controlled operator in this framework:

In[72]:=
QuantumOperator["CT"] == ct
Out[72]=

One can create a new operator by performing some mathematical operations (e.g., exponential, fraction power, etc.) on a quantum operator:

In[73]:=
Exp[-I \[Phi]/2 QuantumOperator["X"]]
Out[73]=

Show that the result is the same as a rotation operator around x:

In[74]:=
% == QuantumOperator[{"RX", \[Phi]}]
Out[74]=

Get the fractional power of the NOT operator:

In[75]:=
Sqrt[QuantumOperator["NOT"]]
Out[75]=
In[76]:=
%["Matrix"] // MatrixForm
Out[76]=

Time Evolution Operators (4) 

Time evolution can be implemented by adding a parametric specification to any operator. Then, using the EvolutionOperator property of a quantum operator, one can generate the corresponding time evolution operator.

Unitary operator and analytic solution of time-dependent Schrödinger equation (2) 

Setting a Pauli-X operator as the Hamiltonian and evolving , we get:

In[77]:=
u = QuantumOperator["PauliX", "ParameterSpec" -> t][
   "EvolutionOperator"];
\[Psi]f = u@QuantumState["0"]
Out[74]=

Return the amplitudes at time :

In[78]:=
\[Psi]f["StateVector"] // Normal
Out[78]=

Evolution in a time-dependent field: Nuclear magnetic resonance (NMR) (2) 

Set up the Hamiltonian (as a time-dependent operator) in a magnetic field :

In[79]:=
hamiltonian = QuantumOperator[
   Subscript[\[Omega], 1]/2 Cos[\[Alpha]] QuantumOperator["Z"] + Subscript[\[Omega], 1]/
     2 Sin[\[Alpha]] (Cos[\[Omega] t] QuantumOperator["X"] + Sin[\[Omega] t] QuantumOperator["Y"]), "ParameterSpec" -> t];
In[80]:=
u = hamiltonian["EvolutionOperator"];
\[Psi]f = u@QuantumState[{Cos[\[Alpha]/2], Sin[\[Alpha]/2]}]
Out[78]=

Get the state vector for the pure state:

In[81]:=
\[Psi]f["StateVector"] // Normal // FullSimplify
Out[81]=

Quantum Measurement (12) 

In the Wolfram Quantum Framework, one can study projective measurements or, generally, any positive operator-valued measurement (POVM) using QuantumMeasurementOperator.

PVMs (projective measurements) (5) 

A measurement can be defined by specifying the corresponding measurement basis.


Measure a 3D system in its state basis:

In[82]:=
\[Psi]0 = QuantumState["RandomPure", 3];
m = QuantumMeasurementOperator[
   QuantumBasis[\[Psi]0["Dimensions"]]][\[Psi]0]
Out[80]=
In[83]:=
m["ProbabilityPlot"]
Out[83]=

Test to confirm that the measured states are the same as the basis states:

In[84]:=
Thread[m["States"] == {QuantumState[{1, 0, 0}, 3], QuantumState[{0, 1, 0}, 3], QuantumState[{0, 0, 1}, 3]}]
Out[84]=

Measure a 2-qubit system in the computational basis:

In[85]:=
\[Psi]0 = QuantumState[{"RandomPure", 2}];
m = QuantumMeasurementOperator[{1, 2}][\[Psi]0]
Out[80]=
In[86]:=
m["ProbabilityPlot"]
Out[86]=

Note the labels for the corresponding eigenvalues, from 0 to n-1 as follows:

In[87]:=
QuantumMeasurement[m, "Label" -> Automatic]["ProbabilityPlot"]
Out[87]=

For composite systems, one can measure one or more qudits. This can be done by specifying an order for QuantumMeasurementOperator.

2D×3D composite system:

In[88]:=
\[Psi]0 = QuantumState["RandomPure", {2, 3}];

Measure only the first qudit:

In[89]:=
QuantumMeasurementOperator[\[Psi]0[
    "Dimensions"], {1}][\[Psi]0]["ProbabilityPlot"]
Out[89]=

Measure only the second qudit:

In[90]:=
QuantumMeasurementOperator[\[Psi]0[
    "Dimensions"], {2}][\[Psi]0]["ProbabilityPlot"]
Out[90]=

Measure both qudits:

In[91]:=
QuantumMeasurementOperator[\[Psi]0["Dimensions"], {1, 2}][\[Psi]0]["ProbabilityPlot"]
Out[91]=

One can use the following format for "named" bases and their corresponding eigenvalues: QuantumMeasurementOperator[nameeigenvalues].

For example, define a measurement operator with a "named" QuantumBasis (as an eigenbasis) and a list of eigenvalues:

In[92]:=
QuantumMeasurementOperator["Bell" -> {-3, -1, 2, 4.}]
Out[92]=

POVMs (7) 

One can also give a list of POVM elements by which to define the measurement operator:

In[93]:=
states = {QuantumState[-(1/2) {1, Sqrt[3]}], QuantumState[-(1/2) {1, -Sqrt[3]}], QuantumState["0"]};
{e1, e2, e3} = 2/3 QuantumOperator[#[#["Dagger"]]] & /@ states;
povm = {e1, e2, e3};

Check that all POVM elements are explicitly positive semidefinite:

In[94]:=
PositiveSemidefiniteMatrixQ[#["Matrix"]] & /@ povm
Out[94]=

Check the completeness relations:

In[95]:=
Total[povm] == QuantumOperator["Identity"]
Out[95]=

Measure POVMs on a quantum state:

In[96]:=
qm = QuantumMeasurementOperator[povm][QuantumState[{1, 1}]];

Get the post-measurement states:

In[97]:=
qm["StatesAssociation"]
Out[97]=

Get the corresponding probabilities:

In[98]:=
N@qm["Probabilities"]
Out[98]=

Show that post-measurement states are the same as states initially defined as POVMs:

In[99]:=
Thread[qm["States"] == states]
Out[99]=

Quantum Partial Tracing, Distance and Entanglement (4) 

In the framework, there are some functionalities to explore the quantum distance, entanglement monotones and partial tracing, as well as other useful features.

Trace out the second subsystem in a 2-qubit state:

In[100]:=
QuantumPartialTrace[QuantumState["PsiPlus"], {2}]
Out[100]=

A partial trace can also be applied to QuantumBasis:

In[101]:=
QuantumPartialTrace[QuantumBasis[{3, 2}], {2}]
Out[101]=

There are several metrics by which one can measure entanglement between two qudits, such as concurrence, entanglement entropy, negativity, etc. The calculation of entanglement measure is represented by the QuantumEntanglementMonotone function.

Plot various entanglement measures for the state :

In[102]:=
Plot[{QuantumEntanglementMonotone[
   QuantumState[{\[Alpha], 0, 0, Sqrt[1 - \[Alpha]^2]}], {{1}, {2}}, "Concurrence"], QuantumEntanglementMonotone[
   QuantumState[{\[Alpha], 0, 0, Sqrt[1 - \[Alpha]^2]}], {{1}, {2}}, "LogNegativity"], QuantumEntanglementMonotone[
   QuantumState[{\[Alpha], 0, 0, Sqrt[1 - \[Alpha]^2]}], {{1}, {2}}, "EntanglementEntropy"]}, {\[Alpha], 0, 1}, PlotLegends -> {"Concurrence", "LogNegativity", "EntanglementEntropy"}]
Out[102]=

To know whether a state is entangled or separable without computing its measure, use QuantumEntangledQ.

Check whether subsystems 1 and 3 are entangled in the "W" state:

In[103]:=
state = QuantumState["W"];
state["Formula"]
Out[101]=
In[104]:=
QuantumEntangledQ[state, {{1}, {3}}]
Out[104]=

In quantum information, there exist notions of distance between quantum states, such as fidelity, trace distance, Bures angle, etc. One can use QuantumDistance to compute the distance between two quantum states with various metrics.

Measure the trace distance between a pure state and a mixed state:

In[105]:=
\[Psi]1 = QuantumState[{{1/4, 0}, {0, 3/4}}];
\[Psi]2 = QuantumState[{1, 0}];
In[106]:=
AssociationMap[
 QuantumDistance[\[Psi]1, \[Psi]2, #] &, {"Fidelity", "Trace", "BuresAngle", "HilbertSchmidt"}]
Out[106]=

Quantum Circuits (4) 

One may create a list of QuantumOperator and/or QuantumMeasurementOperator objects to build a quantum circuit, which can be represented as a QuantumCircuitOperator object.

Construct a quantum circuit that includes a controlled Hadamard gate:

In[107]:=
qc = QuantumCircuitOperator[{"NOT", "CH", "NOT", {1}}];
qc["Diagram"]
Out[94]=

The wire labels can be customized:

In[108]:=
qc["Diagram", "WireLabels" -> {{Placed["\!\(\*SubscriptBox[\(a\), \(i\)]\)", Left],
     Placed["\!\(\*SubscriptBox[\(a\), \(f\)]\)", Right]}, {Placed[
     "\!\(\*SubscriptBox[\(b\), \(i\)]\)", Left], Placed["\!\(\*SubscriptBox[\(b\), \(f\)]\)", Right]}}, "MeasurementWireLabel" -> "M"]
Out[108]=

Construct a Toffoli gate as a circuit:

In[109]:=
qc = QuantumCircuitOperator[{{"C", Sqrt["X"]} -> {2, 3}, "CX", {"C", Sqrt[QuantumOperator["X"]]["Dagger"]} -> {2, 3}, "CX" -> {1, 2}, {"C", Sqrt["X"]} -> {1, 3}}]; qc["Diagram"]
Out[109]=

Show that the circuit is the same as the Toffoli gate:

In[110]:=
QuantumOperator["Toffoli"] == qc["CircuitOperator"]
Out[110]=

Define a combination of control-0 and control-1 qubits:

In[111]:=
cu = QuantumOperator[{"C", "X", {1, 4}, {2}}, {3}];
QuantumCircuitOperator[{cu}]["Diagram"]
Out[112]=

Measurement operators can also be added to a quantum circuit. For a single-qubit unitary operator with eigenvalues ±1, a measurement of can be implemented in the following circuit (note that here, Pauli-Y is considered a operator):

In[113]:=
qc = QuantumCircuitOperator[{"H", "CY", "H", {1}}];
qc["Diagram"]
Out[114]=

Applying the circuit operators to a quantum state results in a quantum measurement:

In[115]:=
m = qc[]
Out[115]=

Calculate the state of the second qubit after the measurement by tracing over the first qubit:

In[116]:=
statesQ2 = QuantumPartialTrace[#, {1}] & /@ m["States"]
Out[116]=
In[117]:=
Thread[statesQ2 == Reverse[QuantumState /@ QuantumOperator["Y"]["Eigenvectors"]]]
Out[117]=

The post-measurement states of the second qubit should be the same as the Pauli-Y eigenstates.

Superdense coding (5) 

Alice wants to send Bob two classical bits: 00, 01, 10 or 11. She can do so by using a single qubit if her qubit and Bob's are initially prepared as Bell states:

In[118]:=
\[Phi] = QuantumState["PhiPlus"];
\[Phi]["Formula"]
Out[119]=

Depending on Alice's intended message, she will perform the following operations on her qubit:

1. To send 00, she does nothing.

2. To send 01, she applies the X-gate.

3. To send 10, she applies the Z-gate.

4. To send 11, she applies the X-gate and then the Z-gate.

Such operations can be represented as circuits, each with a final state resulting from application of its respective gate(s) to the initial Bell state:

In[120]:=
TableForm[{#1, QuantumCircuitOperator[#2]["Diagram", "WireLabels" -> None], QuantumCircuitOperator[{#2, QuantumOperator["Identity", {2}]}][
      QuantumState["PhiPlus"]]["Formula"]} & @@@ {{"00", QuantumOperator["Identity"]}, {"01", QuantumOperator["X"]}, {"10",
     QuantumOperator["Z"]}, {"11", {QuantumOperator["X"], QuantumOperator["Z"]}}}, TableHeadings -> {None, {"Message", "Gate", "Final state"}}]
Out[120]=

Next, Alice sends her qubit to Bob through a quantum channel. If Bob performs a Bell measurement on his qubits, he receives Alice's message.

Alice's "messaging" with Bob can be fully implemented in a quantum circuit using two ancillary qubits. In the circuit, the first qubit is Alice's, the second one is Bob's and the third and the fourth are the ancillary qubits. Note that Alice sends her qubit to Bob and Bob performs a measurement on qubits 1 and 2:

In[121]:=
superdense = QuantumCircuitOperator[{"H", "CNOT", "CX" -> {4, 1}, "CZ" -> {3, 1},
     "CNOT", "H", {1}, {2}}];
superdense["Diagram"]
Out[68]=

Define an initial state as , where is the code that Alice wants to send Bob, which is encoded in two ancillary qubits (qubits 3 and 4).

Run the state through the circuit and return the outcome probabilities:

In[122]:=
superdense[QuantumState["00" <> #1 <> #2]]["ProbabilityPlot"] & @@@ Tuples[{"0", "1"}, 2] // GraphicsRow
Out[122]=

For each case, Bob finds Alice's code with probability 1.

Quantum teleportation (4) 

Quantum teleportation is the reverse of superdense coding. Here, one wants to teleport a generic unknown quantum bit. Suppose that Alice wants to send a qubit (qubit 1) to Bob. To implement a quantum circuit for teleporting a qubit, Alice and Bob share an entangled state (qubits 2 and 3). Qubits 1 and 2 represent Alice's system, while qubit 3 is Bob's. The goal is to transfer the state of Alice's first qubit to Bob's qubit.

Set up a circuit:

In[123]:=
qc = QuantumCircuitOperator[{"H" -> 2, "CX" -> {2, 3}, "CX", "H", "CX" -> {2, 3}, "CZ" -> {1, 3}, {1}, {2}}];
In[124]:=
qc["Diagram"]
Out[124]=

The state to be teleported is =α+β, where α and β are unknown amplitudes. The input state of the circuit is as follows:

In[125]:=
\[Psi]0 = QuantumTensorProduct[QuantumState[{\[Alpha], \[Beta]}], QuantumState["00"]];

Given the result of Alice's measurement on the first and second qubits, get the post-measurement states:

In[126]:=
postMeasurementStates = qc[\[Psi]0]["States"];
#["Formula"] & /@ postMeasurementStates
Out[127]=

Regardless of measurement results, the state of the third qubit is the same state as the original first qubit =α+β (with only a normalization difference). Trace out the first and second qubits and compare the reduced state of qubit 3 only:

In[128]:=
Thread[QuantumState[{\[Alpha], \[Beta]}/2] == QuantumPartialTrace[#, {1, 2}] & /@ postMeasurementStates]
Out[128]=

Bernstein–Vazirani algorithm (3) 

The goal of the Bernstein–Vazirani algorithm is to find a secret string bit as s using the action of a Bernstein–Vazirani oracle (i.e. which should be treated as a black box), which is defined by this transformation: with the index register state of n-qubits, and the state of an ancillary qubit carrying the result.

A Bernstein–Vazirani oracle for the secret bit of 101:

In[129]:=
bv = QuantumCircuitOperator[{"BernsteinVaziraniOracle", "101"}];
bv["Diagram"]
Out[101]=

A Bernstein–Vazirani circuit for the secret bit of 101:

In[130]:=
QuantumCircuitOperator[{"BernsteinVazirani", "101"}]["Diagram"]
Out[130]=

As expected, the only measurement outcome corresponds to the secret bit string:

In[131]:=
QuantumCircuitOperator[{"BernsteinVazirani", "101"}][]["ProbabilityPlot"]
Out[131]=

Grover's search algorithm (14) 

The goal of Grover's search algorithm is to find the solutions of a Boolean function . This can be done using the named circuits or oracles in the quantum framework.

The action of a Boolean oracle is defined by this transformation: with the index register state of n-qubits, and the state of an ancillary qubit carrying the result of the Boolean function f(x); meaning if =, then it will be if x is a solution of f(x), and unchanged if it is not.

Define a Boolean function of 3-SAT with five clauses:

In[132]:=
f = And[! v1 || ! v2 || ! v3, v1 || ! v2 || v3, v1 || v2 || ! v3, v1 || ! v2 || ! v3, ! v1 || v2 || v3];

Here is the truth table for this Boolean function:

In[133]:=
ResourceFunction["TruthTable"][f, {v1, v2, v3}]
Out[133]=

The corresponding oracle's quantum circuit:

In[134]:=
oracle = QuantumCircuitOperator[{"BooleanOracle", f}];

The diagram of the oracle:

In[135]:=
oracle["Diagram"]
Out[135]=

Prepare the 4-qubit in the previous circuit (i.e. the ancillary qubit) as a 0-state, and then other qubits (1–3) in the index register states. To compare with the truth table, create the index register states |x> in the order |2n-1> down to |0:

In[136]:=
states = QuantumTensorProduct[#, QuantumState["0"]] & /@ Table[QuantumState[{"Register", 3, i}], {i, 2^3 - 1, 0, -1}];

Create a list with elements {|x>,|q⊕f(x)}:

In[137]:=
TableForm[
 Transpose[{Table[
    QuantumState[{"Register", 3, i}]["Formula"], {i, 2^3 - 1, 0, -1}],
    QuantumPartialTrace[oracle[#], {1, 2, 3}]["Formula"] & /@ states}], TableHeadings -> {None, {"|x>", "|q\[CirclePlus]f(x)>"}}]
Out[137]=

The action of a phase oracle can be defined as the following transformation: with the index register state of n-qubits:

In[138]:=
phaseOracle = QuantumCircuitOperator[{"PhaseOracle", f}];

The diagram of the oracle:

In[139]:=
phaseOracle["Diagram"]
Out[139]=

Create a list with elements {|x>,(-1)f(x)|x>}:

In[140]:=
Module[{index = Table[QuantumState[{"Register", 3, i}], {i, 2^3 - 1, 0, -1}]},
 TableForm[
  Transpose[{#["Formula"] & /@ index, phaseOracle[#]["Formula"] & /@ index}], TableHeadings -> {None, {"|x>", "(-1\!\(\*SuperscriptBox[\()\), \(f \((x)\)\)]\)|x>"}}]]
Out[140]=

Generate the corresponding Grover circuit using a Boolean oracle:

In[141]:=
QuantumCircuitOperator[{"Grover", q1 && q2 && q3}]["Diagram"]
Out[141]=

Generate a Grover circuit using the phase oracle for a Boolean function:

In[142]:=
QuantumCircuitOperator[{"GroverPhase", q1 && q2 && q3}]["Diagram"]
Out[142]=

Given a Grover phase circuit for a Boolean function, calculate the probability of success of the algorithm:

In[143]:=
g = QuantumCircuitOperator[{"GroverPhase", q1 && q2 && q3}];
In[144]:=
steps = NestList[g, QuantumState[{"UniformSuperposition", 3}], 20];

Calculate the success probability after each iteration (note 111 is the solution of the Boolean function):

In[145]:=
success = Abs[Normal[QuantumState["111"]["Dagger"][#][[1, 1]]]]^2 & /@ steps;

Plot the success probability:

In[146]:=
ListLinePlot[success, GridLines -> Automatic, AxesLabel -> {"# of Grover iteration", "Probability of success" }, PlotStyle -> {Orange, Thick}]
Out[146]=

As expected, this follows the formula for the probability of success , where M is the number of correct solutions out of a total N after k steps

Quantum phase estimation (5) 

The quantum phase estimation algorithm solves the problem of finding in the eigenvalue equation = , where is a unitary operator. The inputs of the algorithm are qubits at the initial state . The output is . Consider a phase shift as the unitary operator in which the aim is to find the phase θ:

In[147]:=
QuantumOperator[{"Phase", 2 \[Pi] \[Theta]}]["Eigenvalues"]
Out[147]=

To specify the corresponding quantum circuit, one can use the built-in circuit "PhaseEstimation" that takes two input arguments: a unitary operator U and an integer n. The integer n specifies the number of qubits and controlled-Uj operators in the circuit, with j=0,1,,n-1. The accuracy of phase estimation and the success probability depends on n:

In[148]:=
phase = 1/5; n = 3;
In[149]:=
circuit = QuantumCircuitOperator[{"PhaseEstimation", QuantumOperator[{"Phase", 2 Pi phase}], n}];
circuit["Diagram", FontSize -> 11]
Out[135]=

Return the corresponding measurement, with all qubits prepared in state:

In[150]:=
m = N[circuit][];
In[151]:=
m["ProbabilityPlot"]
Out[151]=

Given the outcome with the largest probability, estimate the phase:

In[152]:=
FromDigits[Keys[TakeLargest[m["Probabilities"], 1]][[1]]["Name"], 2]/
  2^n // N
Out[152]=

As expected, it is a rough estimate, since a small value was chosen for n. If one increases n, with a higher probability, one can get a better estimate of the phase.

Estimate the phase (the expected value is 1/5=0.2) for n=6:

In[153]:=
With[{m = QuantumCircuitOperator[{"PhaseEstimation", N@QuantumOperator[{"Phase", 2 Pi phase}], 6}][]}, FromDigits[Keys[TakeLargest[m["Probabilities"], 1]][[1]]["Name"], 2]/
   2^6 // N]
Out[153]=

Disclosures

Compatibility

Wolfram Language Version 13.1

Version History

  • 1.4.5 – 12 September 2024
  • 1.4.4 – 21 August 2024
  • 1.4.3 – 12 August 2024
  • 1.4.2 – 28 June 2024
  • 1.4.1 – 22 May 2024
  • 1.4.0 – 15 May 2024
  • 1.3.7 – 06 May 2024
  • 1.3.6 – 24 April 2024
  • 1.3.5 – 08 April 2024
  • 1.3.4 – 13 March 2024
  • 1.3.3 – 14 February 2024
  • 1.3.2 – 03 February 2024
  • 1.3.1 – 17 January 2024
  • 1.3.0 – 14 January 2024
  • 1.2.15 – 13 January 2024
  • 1.2.14 – 25 December 2023
  • 1.2.13 – 21 December 2023
  • 1.2.12 – 21 November 2023
  • 1.2.11 – 01 November 2023
  • 1.2.10 – 19 October 2023
  • 1.2.9 – 02 October 2023
  • 1.2.8 – 16 September 2023
  • 1.2.7 – 12 September 2023
  • 1.2.6 – 18 August 2023
  • 1.2.5 – 17 August 2023
  • 1.2.4 – 03 August 2023
  • 1.2.3 – 01 August 2023
  • 1.2.2 – 21 July 2023
  • 1.2.1 – 06 July 2023
  • 1.2.0 – 28 June 2023
  • 1.1.4 – 28 June 2023
  • 1.1.3 – 14 June 2023
  • 1.1.2 – 01 June 2023
  • 1.1.1 – 23 May 2023
  • 1.1.0 – 05 May 2023
  • 1.0.35 – 03 May 2023
  • 1.0.34 – 26 April 2023
  • 1.0.33 – 19 April 2023
  • 1.0.31 – 06 April 2023
  • 1.0.30 – 03 April 2023
  • 1.0.29 – 08 March 2023
  • 1.0.28 – 27 January 2023
  • 1.0.27 – 01 January 2023
  • 1.0.26 – 03 December 2022
  • 1.0.25 – 05 November 2022
  • 1.0.24 – 18 October 2022
  • 1.0.23 – 11 October 2022
  • 1.0.22 – 04 October 2022
  • 1.0.21 – 30 September 2022
  • 1.0.19 – 15 August 2022
  • 1.0.18 – 08 August 2022
  • 1.0.17 – 16 July 2022
  • 1.0.15 – 25 May 2022
  • 1.0.14 – 10 May 2022
  • 1.0.13 – 24 April 2022
  • 1.0.12 – 05 April 2022
  • 1.0.11 – 05 April 2022
  • 1.0.10 – 05 April 2022
  • 1.0.9 – 05 April 2022
  • 1.0.8 – 04 April 2022
  • 1.0.6 – 04 April 2022
  • 1.0.2 – 18 January 2022
  • 0.0.1 – 08 March 2023

License Information

MIT License

Paclet Source

Source Metadata