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

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

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

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

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

class VariationalQuantumGate_CZ
VariationalQuantumGate_CZ(Qubit *q1, Qubit *q2)
Function

Constructing a function through CZ gate.

Parameter

● q1: control qubit ● q2: target qubit

class VariationalQuantumGate_CNOT
VariationalQuantumGate_CNOT(Qubit *q1, Qubit *q2)
Function

Constructing a function through CNOT gate.

Parameter

● q1: control qubit ● q2: target qubit

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.