7 VQC
7.1 Variable
The class of variables is the user class to implement symbolic computing and used to store the variables of the specific hybrid quantum classical network. Generally, its task is to optimize variables to minimize the cost function. A variable can be a scalar, vector or matrix.
A variable is provided with a tree structure, including child or parent nodes. The variable which has no child nodes is called a leaf node. On one hand, we can set a variable to a specific value. On the other hand, we can get the value of a variable if the values of all of its leaf nodes have been set.
7.1.1 Interface introduction
We can construct a scalar variable by inputting floating-point data or construct a vector or matrix variable by inputting a multi-dimensional array generated by the numpy library.
v1 = var(1)
a = np.array([[1.],[2.],[3.],[4.]])
v2 = var(a)
b = np.array([[1.,2.],[3.,4.]])
v3 = var(b)
Note
When defining a variable, we can define whether the type of the variable is differentiable, and by default the type is non-differentiable. A non-differentiable variable is the same as a placeholder
. When defining a differentiable variable, we should the second argument to the constructor as True such as v1 = var(1, True).
We can define and compute the corresponding expression which is composed of such operations as addition, subtraction, multiplication and division between the variables. Also, the expression is a variable.
v1 = var(10)
v2 = var(5)
add = v1 + v2
minus = v1 - v2
multiply = v1 * v2
divide = v1 / v2
We can change the value of a certain variable through the set_value
interface to get different results, without changing the structure of
the expression. We can call the eval
interface to compute the current
value of the variable.
v1 = var(1)
v2 = var(2)
add = v1 + v2
print(eval(add)) # The output is [[3.]]
v1.set_value([[3.]])
print(eval(add)) # The output is [[5.]]
Note
The get_value
interface of any variable returns the current value of the variable and does not compute the current value based on its leaf nodes. If the variable is an expression of which the value should be computed based on its leaf nodes, the eval
interface should be called for forward computing.
7.1.2 Example
We will show the applications of the interfaces related to the class of variables through more examples.
from pyqpanda import *
import numpy as np
if __name__=="__main__":
m1 = np.array([[1., 2.],[3., 4.]])
v1 = var(m1)
m2 = np.array([[5., 6.],[7., 8.]])
v2 = var(m2)
sum = v1 + v2
minus = v1 - v2
multiply = v1 * v2
print("v1: ", v1.get_value())
print("v2: ", v2.get_value())
print("sum: " , eval(sum))
print("minus: " , eval(minus))
print("multiply: " , eval(multiply))
m3 = np.array([[4., 3.],[2., 1.]])
v1.set_value(m3)
print("sum: " , eval(sum))
print("minus: " , eval(minus))
print("multiply: " , eval(multiply))
v1: [[1. 2.]
[3. 4.]]
v2: [[5. 6.]
[7. 8.]]
sum: [[ 6. 8.]
[10. 12.]]
minus: [[-4. -4.]
[-4. -4.]]
multiply: [[ 5. 12.]
[21. 32.]]
sum: [[9. 9.]
[9. 9.]]
minus: [[-1. -3.]
[-5. -7.]]
multiply: [[20. 18.]
[14. 8.]]
7.2 Operator
VQNet contains many types of operators which can manipulate variables or placeholders. Some of the operators are classical ones, such as addition, subtraction, multiplication, division, exponent, logarithm and point multiplication. In addition, VQNet contains all the common operators from classic machine learning.
And VQNet contains quantum operators which are qop and qop pmeasure. What is more, qop and qop pmeasure are related to quantum computer chips, and they can only be used in a quantum environment. For this, we need to provide additional parameters when using the two operators. Generally, we need to add the qubits referenced to and applied for by quantum machines to create the quantum environment.
VQNet defines the operators as shown below, all of which return
variables of var
type.
The operator |
Describe |
---|---|
plus |
Plus. Example: a + b, where a and b are variables of type var. |
minus |
Minus. Example: a – b. |
multiply |
Multiply. Example: a * b. |
divide |
Divide. Example: a / b. |
exponent |
Index. Example: exp(a). |
log |
Logarithmic. Example: log(a). |
polynomial |
Power. Example: poly(a, 2). |
dot |
Matrix dot. Example: dot(a.b). |
inverse |
Matrix inversion. Example: inverse(a). |
transpose |
Matrix transpose. Example: transpose(a). |
sum |
Matrix sum. Example: sum(a). |
stack |
The matrix is concatenated in either column (axis = 0) or row (axis = 1) direction. Example: stack(axis, a, b, c). |
subscript |
The subscript operation. Example: a[0]. |
qop |
Quantum operation, which takes VQC and several Hamiltonians as inputs and outputs the input Hamiltonian expectations. Example: If w represents the variable of VQC, qop(VQC(w), Hamiltonians, [quantum environment]). |
qop_pmeasure |
Quantum operation, which is similar to qop. The input of qop_pmeasure consists of the VQC, the qubit to be measured and the component. It calculates the probability of all projected states in a subspace made up of qubits to be measured, and returns some of them. Components store labels for the projected state of the target. Obviously, the probability of any projected state can be seen as the expectation of the Hamiltonian, so qop_pmeasure is a special case of qop. Example: qop_pmeasure(VQC(w), components, [quantum environment]). |
sigmoid |
The activation function. Example: sigmoid(a). |
softmax |
The activation function. Example: softmax(a). |
cross_entropy |
The cross entropy. Example: crossEntropy(a, b). |
dropout |
Dropout function. Example: dropout(a, b). |
7.3 Variational quantum logic gate
To use quantum operations qop or qop_pmeasure
in VQNet, a variational
quantum circuit (VQC
) shall be included. And variational quantum logic
gates are the basic unit that constitute the variational quantum
circuit. The variational quantum logic gate (VQG
) maintains a set of
variable parameters and a set of constant parameters internally. Only
one set of parameters can be assigned during the creation of VQG
. Where
the VQG
contains a set of constant parameters, general quantum logic
gates containing determined parameters can be generated through the VQG.
Where the VQG
contains variational parameters, the parameter values can
be modified dynamically and general quantum logic gates corresponding to
the parameters can be generated.
Currently, the following variational quantum logic gates are defined in
pyQPanda, which are all inherited from the VariationalQuantumGate
.
VQG |
Alias |
---|---|
VariationalQuantumGate_H |
VQG_H |
VariationalQuantumGate_RX |
VQG_RX |
VariationalQuantumGate_RY |
VQG_RY |
VariationalQuantumGate_RZ |
VQG_RZ |
VariationalQuantumGate_CZ |
VQG_CZ |
VariationalQuantumGate_CNOT |
VQG_CNOT |
7.3.1 Interface introduction
We can use the variational quantum logic gates by inserting them into variational quantum circuits. Also, we can input variable parameters to the variational quantum logic gates requiring input of parameters. For example, we input variable parameters x and y to variational quantum logic gates RX and RY. Alternatively, we can input constant parameters to variational quantum logic gates. For instance, we input a constant parameter 0.12 to RZ. Furthermore, we can change the parameters in the variational quantum logic gates by modifying the parameters of the variables.
x = var(1)
y = var(2)
vqc = VariationalQuantumCircuit()
vqc.insert(VariationalQuantumGate_H(q[0]))
vqc.insert(VariationalQuantumGate_RX(q[0], x))
vqc.insert(VariationalQuantumGate_RY(q[1], y))
vqc.insert(VariationalQuantumGate_RZ(q[0], 0.12))
vqc.insert(VariationalQuantumGate_CZ(q[0], q[1]))
vqc.insert(VariationalQuantumGate_CNOT(q[0], q[1]))
circuit1 = vqc.feed()
x.set_value(3)
y.set_value(4)
circuit2 = vqc.feed()
7.3.2 Example
from pyqpanda import *
if __name__=="__main__":
machine = init_quantum_machine(QMachineType.CPU)
q = machine.qAlloc_many(2)
x = var(1)
y = var(2)
vqc = VariationalQuantumCircuit()
vqc.insert(VariationalQuantumGate_H(q[0]))
vqc.insert(VariationalQuantumGate_RX(q[0], x))
vqc.insert(VariationalQuantumGate_RY(q[1], y))
vqc.insert(VariationalQuantumGate_RZ(q[0], 0.12))
vqc.insert(VariationalQuantumGate_CZ(q[0], q[1]))
vqc.insert(VariationalQuantumGate_CNOT(q[0], q[1]))
circuit1 = vqc.feed()
prog = QProg()
prog.insert(circuit1)
print(convert_qprog_to_originir(prog, machine))
x.set_value([[3.]])
y.set_value([[4.]])
circuit2 = vqc.feed()
prog2 = QProg()
prog2.insert(circuit2)
print(convert_qprog_to_originir(prog2, machine))
QINIT 2
CREG 0
H q[0]
RX q[0],(1)
RY q[1],(2)
RZ q[0],(0.12)
CZ q[0],q[1]
CNOT q[0],q[1]
QINIT 2
CREG 0
H q[0]
RX q[0],(3)
RY q[1],(4)
RZ q[0],(0.12)
CZ q[0],q[1]
CNOT q[0],q[1]
7.4 Variational quantum logic gate (VQG)
To use quantum operations qop or qop_pmeasure
in VQNet, a variational
quantum circuit (VQC
) shall be included. And variational quantum logic
gates are the basic unit that constitute the VQC. The variational
quantum logic gate (VQG
) maintains a set of variable parameters and a
set of constant parameters internally. Only one set of parameters can be
assigned during the creation of VQG. Where the VQG
contains a set of
constant parameters, general quantum logic gates containing determined
parameters can be generated through the VQG
. Where the VQG
contains
variational parameters, the parameter values can be modified dynamically
and general quantum logic gates corresponding to the parameters can be
generated.
Currently, the following variational quantum logic gates are defined in
QPanda::Variational, which are all derived from the VQG
.
VQG |
Alias |
---|---|
VariationalQuantumGate_I |
VQG_I |
VariationalQuantumGate_H |
VQG_H |
VariationalQuantumGate_T |
VQG_T |
VariationalQuantumGate_S |
VQG_S |
VariationalQuantumGate_X |
VQG_X |
VariationalQuantumGate_Y |
VQG_Y |
VariationalQuantumGate_Z |
VQG_Z |
VariationalQuantumGate_X1 |
VQG_X1 |
VariationalQuantumGate_Y1 |
VQG_Y1 |
VariationalQuantumGate_Z1 |
VQG_Z1 |
VariationalQuantumGate_U1 |
VQG_U1 |
VariationalQuantumGate_U2 |
VQG_U2 |
VariationalQuantumGate_U3 |
VQG_U3 |
VariationalQuantumGate_U4 |
VQG_U4 |
VariationalQuantumGate_RX |
VQG_RX |
VariationalQuantumGate_RY |
VQG_RY |
VariationalQuantumGate_RZ |
VQG_RZ |
VariationalQuantumGate_CRX |
VQG_CRX |
VariationalQuantumGate_CRY |
VQG_CRY |
VariationalQuantumGate_CRZ |
VQG_CRZ |
VariationalQuantumGate_CNOT |
VQG_CNOT |
VariationalQuantumGate_CZ |
VQG_CZ |
VariationalQuantumGate_SWAP |
VQG_SWAP |
VariationalQuantumGate_iSWAP |
VQG_iSWAP |
VariationalQuantumGate_SqiSWAP |
VQG_SqiSWAP |
7.4.1 Interface introduction
-
class VariationalQuantumGate
-
VariationalQuantumGate()
- Function
Constructing a function
- Parameter
N/A
-
size_t n_var()
- Function
Number of internal variables of the variational quantum logic gate.
- Parameter
N/A
- Return value
Number of variables
-
std::vector<var> &get_vars()
- Function
Obtaining the internal variables of the variational quantum logic gate.
- Parameter
N/A
- Return value
Internal variables of the variational quantum logic gate.
-
std::vector<double> &get_constants()
- Function
Obtaining the internal constants of the variational quantum logic gate.
- Parameter
N/A
- Return value
Internal constants of the variational quantum logic gate.
-
int var_pos(var_var)
- Function
Obtaining the internal constants of the variational quantum logic gate.
- Parameter
● _var: variable
- Return value
If no internal index is available, the return result will be -1.
-
virtual QGate feed() const = 0
- Function
Instantiating ``QGate ``
- Parameter
N/A
- Return value
General quantum logic gate
-
virtual QGate feed(std::map<size_t, double> offset) const
- Function
Instantiating
QGate
by specifying offsets.- Parameter
● offset: the offset mapping corresponding to the variable
- Return value
General quantum logic gate
-
VariationalQuantumGate()
A brief introduction will be given to the construction of variational quantum logic gates below:
-
class VariationalQuantumGate_H
-
VariationalQuantumGate_H(Qubit *q)
- Function
Constructing a function through H gate.
- Parameter
● q: target qubit
-
VariationalQuantumGate_H(Qubit *q)
-
class VariationalQuantumGate_RX
-
VariationalQuantumGate_RX(Qubit *q, var_var)
- Function
Constructing a function through RX gate.
- Parameter
● q: target qubit ● _var: variable
-
VariationalQuantumGate_RX(Qubit *q, double angle)
- Function
Constructing a function through RX gate.
- Parameter
● q: target qubit ● angle: parameter
-
VariationalQuantumGate_RX(Qubit *q, var_var)
-
class VariationalQuantumGate_RY
-
VariationalQuantumGate_RY(Qubit *q, var_var)
- Function
Constructing a function through RY gate.
- Parameter
● q: target qubit ● _var: variable
-
VariationalQuantumGate_RX(Qubit *q, double angle)
- Function
Constructing a function through RY gate.
- Parameter
● q: target qubit ● angle: parameter
-
VariationalQuantumGate_RY(Qubit *q, var_var)
-
class VariationalQuantumGate_RZ
-
VariationalQuantumGate_RZ(Qubit *q, var_var)
- Function
Constructing a function through RZ gate.
- Parameter
● q: target qubit ● _var: variable
-
VariationalQuantumGate_RZ(Qubit *q, double angle)
- Function
Constructing a function through RZ gate.
- Parameter
● q: target qubit ● angle: parameter
-
VariationalQuantumGate_RZ(Qubit *q, var_var)
-
class VariationalQuantumGate_CZ
-
VariationalQuantumGate_CZ(Qubit *q1, Qubit *q2)
- Function
Constructing a function through CZ gate.
- Parameter
● q1: control qubit ● q2: target qubit
-
VariationalQuantumGate_CZ(Qubit *q1, Qubit *q2)
-
class VariationalQuantumGate_CNOT
-
VariationalQuantumGate_CNOT(Qubit *q1, Qubit *q2)
- Function
Constructing a function through CNOT gate.
- Parameter
● q1: control qubit ● q2: target qubit
-
VariationalQuantumGate_CNOT(Qubit *q1, Qubit *q2)
The variational quantum logic gates are used in a way similar to quantum logic gates, which will not be repeated here. In case of any question, see the relevant contents of quantum logic gates.
7.4.2 Dynamic modification of parameters
If the constructed VQC contains variable parameters, the parameter values can be modified dynamically in the following way with the general quantum logic gates corresponding to the parameters generated.
(1): setValue(), used in the way below:
object.setValue(newValue);
(2): “=” operator re-assigned, used in the way below:
object = newValue;
7.4.2.1 Example:
import pyqpanda as pq
import numpy as np
if __name__=="__main__":
machine = pq.init_quantum_machine(pq.CPU)
q = machine.qAlloc_many(2)
x = pq.var(1)
y = pq.var(2)
temp = np.matrix([5])
ss=pq.var(temp)
vqc = pq.VariationalQuantumCircuit()
vqc.insert(pq.VariationalQuantumGate_H(q[0]))
vqc.insert(pq.VariationalQuantumGate_RX(q[0], ss))
vqc.insert(pq.VariationalQuantumGate_RY(q[1], y))
vqc.insert(pq.VariationalQuantumGate_RZ(q[0], 0.12))
vqc.insert(pq.VariationalQuantumGate_CZ(q[0], q[1]))
vqc.insert(pq.VariationalQuantumGate_CNOT(q[0], q[1]))
vqc.insert(pq.VariationalQuantumGate_U1(q[0], x))
vqc.insert(pq.VariationalQuantumGate_U2(q[0], np.pi, x))
vqc.insert(pq.VariationalQuantumGate_U3(q[0], np.pi, x, y))
vqc.insert(pq.VariationalQuantumGate_U4(q[0], np.pi, x, y,ss))
circuit1 = vqc.feed()
prog = pq.QProg()
prog.insert(circuit1)
print(pq.convert_qprog_to_originir(prog, machine))
x.set_value([[3.]])
y.set_value([[4.]])
circuit2 = vqc.feed()
prog2 = pq.QProg()
prog2.insert(circuit2)
print(pq.convert_qprog_to_originir(prog2, machine))
The above example will get the result below:
QINIT 2
CREG 0
H q[0]
RX q[0],(56)
RY q[1],(2)
RZ q[0],(0.12)
CZ q[0],q[1]
CNOT q[0],q[1]
U1 q[0],(1)
U2 q[0],(3.1415927,1)
U3 q[0],(3.1415927,1,2)
U4 q[0],(3.1415927,1,2,5)
QINIT 2
CREG 0
H q[0]
RX q[0],(56)
RY q[1],(4)
RZ q[0],(0.12)
CZ q[0],q[1]
CNOT q[0],q[1]
U1 q[0],(3)
U2 q[0],(3.1415927,3)
U3 q[0],(3.1415927,3,4)
U4 q[0],(3.1415927,3,4,5)
7.5 Optimization algorithm (gradient descent)
This chapter will describe the use of optimization algorithms in VQNet,
including the classical gradient descent algorithm and the improved
gradient descent algorithm which are two of the most commonly used
methods to solve the model parameters of machine learning algorithms,
i.e., unconstrained optimization problems. We have implemented such
algorithms as VanillaGradientDescentOptimizer
, MomentumOptimizer
,
AdaGradOptimizer
, RMSPropOptimizer
and AdamOptimizer
in pyQPanda. All
these algorithms are inherited from Optimizer
.
7.5.1 Interface introduction
We generate an optimizer by calling the minimize
interface of the
gradient descent optimizer. The gradient descent optimizer is generally
constructed as below:
VanillaGradientDescentOptimizer.minimize(
loss, # Loss function
0.01, # learning rate
1.e-6) # The end condition
MomentumOptimizer.minimize(
loss, # Loss function
0.01, # learning rate
0.9) # The momentum coefficient
AdaGradOptimizer.minimize(
loss, # Loss function
0.01, # learning rate
0.0, # Initial value of accumulation
1.e-10)# Small numbers to avoid a zero denominator
RMSOptimizer.minimize(
loss, # Loss function
0.01, # learning rate
0.9, # Historical or upcoming gradient discount factor
1.e-10)# Small numbers to avoid a zero denominator
AdamOptimizer.minimize(
loss, # Loss function
0.01, # learning rate
0.9, # First order momentum decay coefficient
0.999, # Second order momentum decay coefficient
1.e-10)# Small numbers to avoid a zero denominator
7.5.2 Example
The example codes mainly demonstrate the fitting of discrete points with straight lines. We define the training data X and Y which are variables and indicate the coordinates of the discrete points. Also, we define two differentiable variables w and b, which w represents the slope and b represents the y-intercept. Then, we define the underscore of variable Y which represents the slope w multiplied by the variable x plus the intercept.
Next, we define the loss function. and compute the mean square value between variable Y and its underscore.
We call the minimize
interface of the gradient descent optimizer and
construct a classical gradient descent optimizer by taking the loss
function, learning rate and ending condition as parameters.
We can obtain all differentiable nodes through the get_variables
interface of the optimizer.
We defined the number of iterations as 1000. Then, we conduct an
optimization operation by calling the run
interface of the optimizer.
The second parameter indicates the current number of optimizations. Now,
this parameter is only used by AdamOptimizer
, and the other optimizers
can be directly assigned with a value of 0.
We can obtain the loss value after the optimization through the
get_loss
interface of the optimizer. Through the eval interface, we can
get the current value of a differentiable variable.
from pyqpanda import *
import numpy as np
x = np.array([3.3, 4.4, 5.5, 6.71, 6.93, 4.168, 9.779, 6.182, 7.59,\
2.167, 7.042, 10.791, 5.313, 7.997, 5.654, 9.27,3.1])
y = np.array([1.7, 2.76, 2.09, 3.19, 1.694, 1.573, 3.366, 2.596, 2.53,\
1.221, 2.827, 3.465, 1.65, 2.904, 2.42, 2.94,1.3])
X = var(x.reshape(len(x), 1))
Y = var(y.reshape(len(y), 1))
W = var(0, True)
B = var(0, True)
Y_ = W*X + B
loss = sum(poly(Y_ - Y, var(2))/len(x))
optimizer = VanillaGradientDescentOptimizer.minimize(loss, 0.01, 1.e-6)
leaves = optimizer.get_variables()
for i in range(1000):
optimizer.run(leaves, 0)
loss_value = optimizer.get_loss()
print("i: ", i, " loss: ", loss_value, " W: ", eval(W,True), " b: ",\ eval(B, True))
We plot a graph with the hash points and the fitted lines.
import matplotlib.pyplot as plt
w2 = W.get_value()[0, 0]
b2 = B.get_value()[0, 0]
plt.plot(x, y, 'o', label = 'Training data')
plt.plot(x, w2*x + b2, 'r', label = 'Fitted line')
plt.legend()
plt.show()
7.6 Comprehensive example
7.6.1 QAOA
QAOA
is a well-known hybrid quantum-classical algorithm. The max-cut
problem of n objects requires n qubits to encode the result, where the
measured result (binary string) indicates the cut configuration of the
problem.
We can effectively implement QAOA
algorithm of the MAX-CUT
problem
through VQNet
. The flow chart of QAOA in VQNet is as below.
We determines a MAX-CUT problem as below:
Firstly, we input the graphic information of the MAX-CUT
problem to
construct the Hamiltonian of the problem.
problem = {'Z0 Z4':0.73,'Z0 Z5':0.33,'Z0 Z6':0.5,'Z1 Z4':0.69,'Z1 Z5':0.36,
'Z2 Z5':0.88,'Z2 Z6':0.58,'Z3 Z5':0.67,'Z3 Z6':0.43}
Then, we construct the vqc of QAOA
with the Hamiltonian and beta and
gamma, the two variable parameters to be optimized. The input parameters
of QOP
include the Hamiltonian of interest, VQC
, a set of qubits, and
the quantum operating environment. The output of QOP is the expectation
of the Hamiltonian of interest. In this problem, the loss function is
the expectation of the Hamiltonian of interest. Therefore, the output of
QOP
shall be minimized. We optimize the variables beta and gamma in the
vqc with MomentumOptimizer
.
from pyqpanda import *
import numpy as np
def oneCircuit(qlist, Hamiltonian, beta, gamma):
vqc=VariationalQuantumCircuit()
for i in range(len(Hamiltonian)):
tmp_vec=[]
item=Hamiltonian[i]
dict_p = item[0]
for iter in dict_p:
if 'Z'!= dict_p[iter]:
pass
tmp_vec.append(qlist[iter])
coef = item[1]
if 2 != len(tmp_vec):
pass
vqc.insert(VariationalQuantumGate_CNOT(tmp_vec[0], tmp_vec[1]))
vqc.insert(VariationalQuantumGate_RZ(tmp_vec[1], 2*gamma*coef))
vqc.insert(VariationalQuantumGate_CNOT(tmp_vec[0], tmp_vec[1]))
for j in qlist:
vqc.insert(VariationalQuantumGate_RX(j,2.0*beta))
return vqc
if __name__=="__main__":
problem = {'Z0 Z4':0.73,'Z0 Z5':0.33,'Z0 Z6':0.5,'Z1 Z4':0.69,'Z1 Z5':0.36,
'Z2 Z5':0.88,'Z2 Z6':0.58,'Z3 Z5':0.67,'Z3 Z6':0.43}
Hp = PauliOperator(problem)
qubit_num = Hp.getMaxIndex()
machine=init_quantum_machine(QMachineType.CPU)
qlist = machine.qAlloc_many(qubit_num)
step = 4
beta = var(np.ones((step,1),dtype = 'float64'), True)
gamma = var(np.ones((step,1),dtype = 'float64'), True)
vqc=VariationalQuantumCircuit()
for i in qlist:
vqc.insert(VariationalQuantumGate_H(i))
for i in range(step):
vqc.insert(oneCircuit(qlist,Hp.toHamiltonian(1),beta[i], gamma[i]))
loss = qop(vqc, Hp, machine, qlist)
optimizer = MomentumOptimizer.minimize(loss, 0.02, 0.9)
leaves = optimizer.get_variables()
for i in range(100):
optimizer.run(leaves, 0)
loss_value = optimizer.get_loss()
print("i: ", i, " loss:",loss_value )
# The verification results
prog = QProg()
qcir = vqc.feed()
prog.insert(qcir)
directly_run(prog)
result = quick_measure(qlist, 100)
print(result)
We draw a histogram with the measured results, from which we can see that the two qubit strings ‘0001111’ and ‘1110000’ generate the largest probability which is also the solution of this problem.