Paddle_QAOA.py 10.7 KB
Newer Older
Q
Quleaf 已提交
1
# Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.
Q
Quleaf 已提交
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Paddle_QAOA: To learn more about the functions and properties of this application,
you could check the corresponding Jupyter notebook under the Tutorial folder.
"""

Q
Quleaf 已提交
20 21 22 23 24
import os
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx

Q
Quleaf 已提交
25
import paddle
Q
Quleaf 已提交
26
from paddle_quantum.circuit import UAnsatz
Q
Quleaf 已提交
27
from paddle_quantum.utils import pauli_str_to_matrix
Q
Quleaf 已提交
28 29 30
from paddle_quantum.QAOA.QAOA_Prefunc import generate_graph, H_generator

# Random seed for optimizer
Q
Quleaf 已提交
31
SEED = 1024
Q
Quleaf 已提交
32 33 34 35 36 37 38 39 40

__all__ = [
    "circuit_QAOA",
    "circuit_extend_QAOA",
    "Net",
    "Paddle_QAOA",
]


Q
Quleaf 已提交
41
def circuit_QAOA(theta, adjacency_matrix, N, P):
Q
Quleaf 已提交
42 43
    """
    This function constructs the parameterized QAOA circuit which is composed of P layers of two blocks:
Q
Quleaf 已提交
44 45 46
    one block is based on the problem Hamiltonian H which encodes the classical problem,
    and the other is constructed from the driving Hamiltonian describing the rotation around Pauli X
    acting on each qubit. It outputs the final state of the QAOA circuit.
Q
Quleaf 已提交
47 48

    Args:
Q
Quleaf 已提交
49 50 51 52
        theta: parameters to be optimized in the QAOA circuit
        adjacency_matrix:  the adjacency matrix of the graph encoding the classical problem
        N: number of qubits, or equivalently, the number of parameters in the original classical problem
        P: number of layers of two blocks in the QAOA circuit
Q
Quleaf 已提交
53
    Returns:
Q
Quleaf 已提交
54
        the QAOA circuit
Q
Quleaf 已提交
55 56
    """

Q
Quleaf 已提交
57 58 59 60 61
    cir = UAnsatz(N)

    # prepare the input state in the uniform superposition of 2^N bit-strings in the computational basis
    cir.superposition_layer()
    # This loop defines the QAOA circuit with P layers of two blocks
Q
Quleaf 已提交
62
    for layer in range(P):
Q
Quleaf 已提交
63 64
        # The second and third loops construct the first block which involves two-qubit operation
        #  e^{-i\gamma Z_iZ_j} acting on a pair of qubits or nodes i and j in the circuit in each layer.
Q
Quleaf 已提交
65 66
        for row in range(N):
            for col in range(N):
Q
Quleaf 已提交
67 68 69 70 71 72 73
                if adjacency_matrix[row, col] and row < col:
                    cir.cnot([row, col])
                    cir.rz(theta[layer][0], col)
                    cir.cnot([row, col])
        # This loop constructs the second block only involving the single-qubit operation e^{-i\beta X}.
        for i in range(N):
            cir.rx(theta[layer][1], i)
Q
Quleaf 已提交
74

Q
Quleaf 已提交
75
    return cir
Q
Quleaf 已提交
76 77


Q
Quleaf 已提交
78
def circuit_extend_QAOA(theta, adjacency_matrix, N, P):
Q
Quleaf 已提交
79
    """
Q
Quleaf 已提交
80
    This is an extended version of the QAOA circuit, and the main difference is the block constructed
Q
Quleaf 已提交
81 82 83 84 85 86 87 88 89 90
    from the driving Hamiltonian describing the rotation around an arbitrary direction on each qubit.

    Args:
        theta: parameters to be optimized in the QAOA circuit
        input_state: input state of the QAOA circuit which usually is the uniform superposition of 2^N bit-strings
                     in the computational basis
        adjacency_matrix:  the adjacency matrix of the problem graph encoding the original problem
        N: number of qubits, or equivalently, the number of parameters in the original classical problem
        P: number of layers of two blocks in the QAOA circuit
    Returns:
Q
Quleaf 已提交
91
        the extended QAOA circuit
Q
Quleaf 已提交
92

Q
Quleaf 已提交
93 94 95 96
    Note:
        If this circuit_extend_QAOA function is used to construct QAOA circuit, then we need to change the parameter layer
        in the Net function defined below from the Net(shape=[D, 2]) for circuit_QAOA function to Net(shape=[D, 4])
        because the number of parameters doubles in each layer in this QAOA circuit.
Q
Quleaf 已提交
97
    """
Q
Quleaf 已提交
98
    cir = UAnsatz(N)
Q
Quleaf 已提交
99

Q
Quleaf 已提交
100 101
    # prepare the input state in the uniform superposition of 2^N bit-strings in the computational basis
    cir.superposition_layer()
Q
Quleaf 已提交
102 103 104
    for layer in range(P):
        for row in range(N):
            for col in range(N):
Q
Quleaf 已提交
105 106 107 108 109 110 111 112 113
                if adjacency_matrix[row, col] and row < col:
                    cir.cnot([row, col])
                    cir.rz(theta[layer][0], col)
                    cir.cnot([row, col])

        for i in range(N):
            cir.u3(*theta[layer][1:], i)

    return cir
Q
Quleaf 已提交
114 115


Q
Quleaf 已提交
116
class Net(paddle.nn.Layer):
Q
Quleaf 已提交
117 118 119 120 121 122 123 124 125
    """
    It constructs the net for QAOA which combines the  QAOA circuit with the classical optimizer which sets rules
    to update parameters described by theta introduced in the QAOA circuit.

    """

    def __init__(
            self,
            shape,
Q
Quleaf 已提交
126
            param_attr=paddle.nn.initializer.Uniform(low=0.0, high=np.pi),
Q
Quleaf 已提交
127 128
            dtype="float64",
    ):
Q
Quleaf 已提交
129 130 131
        super(Net, self).__init__()

        self.theta = self.create_parameter(
Q
Quleaf 已提交
132 133
            shape=shape, attr=param_attr, dtype=dtype, is_bias=False
        )
Q
Quleaf 已提交
134

Q
Quleaf 已提交
135
    def forward(self, adjacency_matrix, N, P, METHOD):
Q
Quleaf 已提交
136 137 138 139 140 141 142 143 144 145
        """
        This function constructs the loss function for the QAOA circuit.

        Args:
            adjacency_matrix: the adjacency matrix generated from the graph encoding the classical problem
            N: number of qubits
            P: number of layers
            METHOD: which version of QAOA is chosen to solve the problem, i.e., standard version labeled by 1 or
            extended version by 2.
        Returns:
Q
Quleaf 已提交
146
            the loss function for the parameterized QAOA circuit and the circuit itself
Q
Quleaf 已提交
147 148 149
        """

        # Generate the problem_based quantum Hamiltonian H_problem based on the classical problem in paddle
Q
Quleaf 已提交
150
        H_problem = H_generator(N, adjacency_matrix)
Q
Quleaf 已提交
151 152 153

        # The standard QAOA circuit: the function circuit_QAOA is used to construct the circuit, indexed by METHOD 1.
        if METHOD == 1:
Q
Quleaf 已提交
154
            cir = circuit_QAOA(self.theta, adjacency_matrix, N, P)
Q
Quleaf 已提交
155 156
        # The extended QAOA circuit: the function circuit_extend_QAOA is used to construct the net, indexed by METHOD 2.
        elif METHOD == 2:
Q
Quleaf 已提交
157
            cir = circuit_extend_QAOA(self.theta, adjacency_matrix, N, P)
Q
Quleaf 已提交
158 159 160
        else:
            raise ValueError("Wrong method called!")

Q
Quleaf 已提交
161 162
        cir.run_state_vector()
        loss = cir.expecval(H_problem)
Q
Quleaf 已提交
163

Q
Quleaf 已提交
164
        return loss, cir
Q
Quleaf 已提交
165 166


Q
Quleaf 已提交
167
def Paddle_QAOA(classical_graph_adjacency, N, P, METHOD, ITR, LR):
Q
Quleaf 已提交
168 169 170 171 172 173 174 175 176 177 178
    """
    This is the core function to run QAOA.

     Args:
         classical_graph_adjacency: adjacency matrix to describe the graph which encodes the classical problem
         N: number of qubits (default value N=4)
         P: number of layers of blocks in the QAOA circuit (default value P=4)
         METHOD: which version of the QAOA circuit is used: 1, standard circuit (default); 2, extended circuit
         ITR: number of iteration steps for QAOA (default value ITR=120)
         LR: learning rate for the gradient-based optimization method (default value LR=0.1)
     Returns:
Q
Quleaf 已提交
179
         the optimized QAOA circuit
Q
Quleaf 已提交
180
    """
Q
Quleaf 已提交
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
    # Construct the net or QAOA circuits based on the standard modules
    if METHOD == 1:
        net = Net(shape=[P, 2])
    # Construct the net or QAOA circuits based on the extended modules
    elif METHOD == 2:
        net = Net(shape=[P, 4])
    else:
        raise ValueError("Wrong method called!")

    # Classical optimizer
    opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())

    # Gradient descent loop
    summary_iter, summary_loss = [], []
    for itr in range(1, ITR + 1):
        loss, cir = net(
            classical_graph_adjacency, N, P, METHOD
        )
        loss.backward()
        opt.minimize(loss)
        opt.clear_grad()
Q
Quleaf 已提交
202

Q
Quleaf 已提交
203 204 205 206
        if itr % 10 == 0:
            print("iter:", itr, "  loss:", "%.4f" % loss.numpy())
        summary_loss.append(loss[0][0].numpy())
        summary_iter.append(itr)
Q
Quleaf 已提交
207 208

        theta_opt = net.parameters()[0].numpy()
Q
Quleaf 已提交
209
        # print("Optimized parameters theta:\n", theta_opt)
Q
Quleaf 已提交
210 211

        os.makedirs("output", exist_ok=True)
Q
Quleaf 已提交
212 213 214 215 216 217
        np.savez("./output/summary_data", iter=summary_iter, energy=summary_loss)

    return cir


def main(N=4):
Q
Quleaf 已提交
218
    paddle.seed(SEED)
Q
Quleaf 已提交
219 220 221 222 223 224 225
    # number of qubits or number of nodes in the graph
    N = 4
    classical_graph, classical_graph_adjacency = generate_graph(N, GRAPHMETHOD=1)
    print(classical_graph_adjacency)

    # Convert the Hamiltonian's list form to matrix form
    H_matrix = pauli_str_to_matrix(H_generator(N, classical_graph_adjacency), N)
Q
Quleaf 已提交
226

Q
Quleaf 已提交
227 228 229
    H_diag = np.diag(H_matrix).real
    H_max = np.max(H_diag)
    H_min = np.min(H_diag)
Q
Quleaf 已提交
230

Q
Quleaf 已提交
231 232
    print(H_diag)
    print('H_max:', H_max, '  H_min:', H_min)
Q
Quleaf 已提交
233

Q
Quleaf 已提交
234 235 236
    pos = nx.circular_layout(classical_graph)
    nx.draw(classical_graph, pos, width=4, with_labels=True, font_weight='bold')
    plt.show()
Q
Quleaf 已提交
237

Q
Quleaf 已提交
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
    classical_graph, classical_graph_adjacency = generate_graph(N, 1)

    opt_cir = Paddle_QAOA(classical_graph_adjacency, N=4, P=4, METHOD=1, ITR=120, LR=0.1)

    # Load the data of QAOA
    x1 = np.load('./output/summary_data.npz')

    H_min = np.ones([len(x1['iter'])]) * H_min

    # Plot loss
    loss_QAOA, = plt.plot(x1['iter'], x1['energy'], alpha=0.7, marker='', linestyle="--", linewidth=2, color='m')
    benchmark, = plt.plot(x1['iter'], H_min, alpha=0.7, marker='', linestyle=":", linewidth=2, color='b')
    plt.xlabel('Number of iteration')
    plt.ylabel('Performance of the loss function for QAOA')

    plt.legend(handles=[
        loss_QAOA,
        benchmark
    ],
        labels=[
            r'Loss function $\left\langle {\psi \left( {\bf{\theta }} \right)} '
            r'\right|H\left| {\psi \left( {\bf{\theta }} \right)} \right\rangle $',
            'The benchmark result',
        ], loc='best')

    # Show the plot
    plt.show()

Q
Quleaf 已提交
266 267
    # Measure the output state of the QAOA circuit for 1024 shots by default
    prob_measure = opt_cir.measure(plot=True)
Q
Quleaf 已提交
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293

    # Find the max value in measured probability of bitstrings
    max_prob = max(prob_measure.values())
    # Find the bitstring with max probability
    solution_list = [result[0] for result in prob_measure.items() if result[1] == max_prob]
    print("The output bitstring:", solution_list)

    # Draw the graph representing the first bitstring in the solution_list to the MaxCut-like problem
    head_bitstring = solution_list[0]

    node_cut = ["blue" if head_bitstring[node] == "1" else "red" for node in classical_graph]

    edge_cut = [
        "solid" if head_bitstring[node_row] == head_bitstring[node_col] else "dashed"
        for node_row, node_col in classical_graph.edges()
    ]
    nx.draw(
        classical_graph,
        pos,
        node_color=node_cut,
        style=edge_cut,
        width=4,
        with_labels=True,
        font_weight="bold",
    )
    plt.show()
Q
Quleaf 已提交
294 295 296 297


if __name__ == "__main__":
    main()