simulator.py 34.9 KB
Newer Older
Q
Quleaf 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 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 266 267 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 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656
# Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.
#
# 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.

"""
此模块包含构造 MBQC 模型的常用类和配套的运算模拟工具。
"""

from numpy import random, pi
from networkx import Graph, spring_layout, draw_networkx
import matplotlib.pyplot as plt
from paddle import t, to_tensor, matmul, conj, real, reshape, multiply
from paddle_quantum.mbqc.utils import plus_state, cz_gate, pauli_gate
from paddle_quantum.mbqc.utils import basis, kron, div_str_to_float
from paddle_quantum.mbqc.utils import permute_to_front, permute_systems, print_progress, plot_results
from paddle_quantum.mbqc.qobject import State, Pattern
from paddle_quantum.mbqc.transpiler import transpile

__all__ = [
    "MBQC",
    "simulate_by_mbqc"
]


class MBQC:
    r"""定义基于测量的量子计算模型 ``MBQC`` 类。

    用户可以通过实例化该类来定义自己的 MBQC 模型。
    """

    def __init__(self):
        r"""MBQC 类的构造函数,用于实例化一个 ``MBQC`` 对象。
        """
        self.__graph = None  # Graph in a MBQC model
        self.__pattern = None  # Measurement pattern in a MBQC model

        self.__bg_state = State()  # Background state of computation
        self.__history = [self.__bg_state]  # History of background states
        self.__status = self.__history[-1] if self.__history != [] else None  # latest history item

        self.vertex = None  # Vertex class to maintain all the vertices
        self.__outcome = {}  # Dictionary to store all measurement outcomes
        self.max_active = 0  # Maximum number of active vertices so far

        self.__draw = False  # Switch to draw the dynamical running process
        self.__track = False  # Switch to track the running progress
        self.__pause_time = None  # Pause time for drawing
        self.__pos = None  # Position for drawing

    class Vertex:
        r"""定义维护点列表,用于实例化一个 ``Vertex`` 对象。

        将 MBQC 算法中图的节点分为三类,并进行动态维护。

        Note:
            这是内部类,用户不需要直接调用到该类。

        Attributes:
            total (list): MBQC 算法中图上的全部节点,不随运算而改变
            pending (list): 待激活的节点,随着运算的执行而逐渐减少
            active (list): 激活的节点,与当前测量步骤直接相关的节点
            measured (list): 已被测量过的节点,随着运算的执行而逐渐增加
        """

        def __init__(self, total=None, pending=None, active=None, measured=None):
            r"""``Vertex`` 类的构造函数,用于实例化一个 ``Vertex`` 对象。

            Args:
                total (list): MBQC 算法中图上的全部节点,不随运算而改变
                pending (list): 待激活的节点,随着运算的执行而逐渐减少
                active (list): 激活的节点,与当前测量步骤直接相关的节点
                measured (list): 已被测量过的节点,随着运算的执行而逐渐增加
            """
            self.total = [] if total is None else total
            self.pending = [] if pending is None else pending
            self.active = [] if active is None else active
            self.measured = [] if measured is None else measured

    def set_graph(self, graph):
        r"""设置 MBQC 模型中的图。

        该函数用于将用户自己构造的图传递给 ``MBQC`` 实例。
        
        Args:
            graph (list): MBQC 模型中的图,由列表 ``[V, E]`` 给出, 其中 ``V`` 为节点列表,``E`` 为边列表
        """
        vertices, edges = graph

        vertices_of_edges = set([vertex for edge in edges for vertex in list(edge)])
        assert vertices_of_edges.issubset(vertices), "edge must be between the graph vertices."

        self.__graph = Graph()
        self.__graph.add_nodes_from(vertices)
        self.__graph.add_edges_from(edges)

        self.vertex = self.Vertex(total=vertices, pending=vertices, active=[], measured=[])

    def get_graph(self):
        r"""获取图的信息。

        Returns:
            nx.Graph: 图
        """
        return self.__graph

    def set_pattern(self, pattern):
        r"""设置 MBQC 模型的测量模式。

        该函数用于将用户由电路图翻译得到或自己构造的测量模式传递给 ``MBQC`` 实例。

        Warning:
            输入的 pattern 参数是 ``Pattern`` 类型,其中命令列表为标准 ``EMC`` 命令。

        Args:
            pattern (Pattern): MBQC 算法对应的测量模式
        """
        assert isinstance(pattern, Pattern), "please input a pattern of type 'Pattern'."

        self.__pattern = pattern
        cmds = self.__pattern.commands[:]

        # Check if the pattern is a standard EMC form
        cmd_map = {"E": 1, "M": 2, "X": 3, "Z": 4, "S": 5}
        cmd_num_wild = [cmd_map[cmd.name] for cmd in cmds]
        cmd_num_standard = cmd_num_wild[:]
        cmd_num_standard.sort(reverse=False)
        assert cmd_num_wild == cmd_num_standard, "input pattern is not a standard EMC form."

        # Set graph by entanglement commands
        edges = [tuple(cmd.which_qubits) for cmd in cmds if cmd.name == "E"]
        vertices = list(set([vertex for edge in edges for vertex in list(edge)]))
        graph = [vertices, edges]
        self.set_graph(graph)

    def get_pattern(self):
        r"""获取测量模式的信息。

        Returns:
            Pattern: 测量模式
        """
        return self.__pattern

    def set_input_state(self, state=None):
        r"""设置需要替换的输入量子态。

        Warning:
            与电路模型不同,MBQC 模型通常默认初始态为加态。如果用户不调用此方法设置初始量子态,则默认为加态。
            如果用户以测量模式运行 MBQC,则此处输入量子态的系统标签会被限制为从零开始的自然数,类型为整型。

        Args:
            state (State): 需要替换的量子态,默认为加态
        """
        assert self.__graph is not None, "please set 'graph' or 'pattern' before calling 'set_input_state'."
        assert isinstance(state, State) or state is None, "please input a state of type 'State'."
        vertices = list(self.__graph.nodes)

        if state is None:
            vector = plus_state()
            system = [vertices[0]]  # Activate the first vertex, system should be a list
        else:
            vector = state.vector
            # If a pattern is set, map the input state system to the pattern's input
            if self.__pattern is not None:
                assert all(isinstance(label, int) for label in state.system), "please input system labels of type 'int'"
                assert all(label >= 0 for label in state.system), "please input system labels with non-negative values"

                system = [label for label in self.__pattern.input_ if int(div_str_to_float(label[0])) in state.system]
            else:
                system = state.system
        assert set(system).issubset(vertices), "input system labels must be a subset of graph vertices."

        self.__bg_state = State(vector, system)
        self.__history = [self.__bg_state]
        self.__status = self.__history[-1]
        self.vertex = self.Vertex(total=vertices,
                                  pending=list(set(vertices).difference(system)),
                                  active=system,
                                  measured=[])
        self.max_active = len(self.vertex.active)

    def __set_position(self, pos):
        r"""设置动态过程图绘制时节点的位置坐标。

        Note:
            这是内部方法,用户并不需要直接调用到该方法。

        Args:
            pos (dict or bool, optional): 节点坐标的字典数据或者内置的坐标选择,
                                          内置的坐标选择有:``True`` 为测量模式自带的坐标,``False`` 为 ``spring_layout`` 坐标
        """
        assert isinstance(pos, bool) or isinstance(pos, dict), "'pos' should be either bool or dict."
        if isinstance(pos, dict):
            self.__pos = pos
        elif pos:
            assert self.__pattern is not None, "'pos=True' must be chosen after a pattern is set."
            self.__pos = {v: [div_str_to_float(v[1]), - div_str_to_float(v[0])] for v in list(self.__graph.nodes)}
        else:
            self.__pos = spring_layout(self.__graph)  # Use 'spring_layout' otherwise

    def __draw_process(self, which_process, which_qubit):
        r"""根据当前节点状态绘图,用以实时展示 MBQC 模型的模拟计算过程。

        Note:
            这是内部方法,用户并不需要直接调用到该方法。

        Args:
            which_process (str): MBQC 执行的阶段,"measuring", "active" 或者 "measured"
            which_qubit (any): 当前关注的节点,可以是 ``str``, ``tuple`` 等任意数据类型,但需要和图的标签类型匹配
        """
        if self.__draw:
            assert which_process in ["measuring", "active", "measured"]
            assert which_qubit in self.vertex.total, "'which_qubit' must be in the graph."

            vertex_sets = []
            # Find where the 'which_qubit' is
            if which_qubit in self.vertex.pending:
                pending = self.vertex.pending[:]
                pending.remove(which_qubit)
                vertex_sets = [pending, self.vertex.active, [which_qubit], self.vertex.measured]
            elif which_qubit in self.vertex.active:
                active = self.vertex.active[:]
                active.remove(which_qubit)
                vertex_sets = [self.vertex.pending, active, [which_qubit], self.vertex.measured]
            elif which_qubit in self.vertex.measured:
                vertex_sets = [self.vertex.pending, self.vertex.active, [], self.vertex.measured]

            # Indentify ancilla vertices
            ancilla_qubits = []
            if self.__pattern is not None:
                for vertex in list(self.__graph.nodes):
                    row_coordinate = div_str_to_float(vertex[0])
                    col_coordinate = div_str_to_float(vertex[1])
                    # Ancilla vertices do not have integer coordinates
                    if abs(col_coordinate - int(col_coordinate)) >= 1e-15 \
                            or abs(row_coordinate - int(row_coordinate)) >= 1e-15:
                        ancilla_qubits.append(vertex)

            plt.cla()
            plt.title("MBQC Running Process", fontsize=15)
            plt.xlabel("Measuring (RED)  Active (GREEN)  Pending (BLUE)  Measured (GRAY)", fontsize=12)
            plt.grid()
            mngr = plt.get_current_fig_manager()
            mngr.window.setGeometry(500, 100, 800, 600)
            colors = ['tab:blue', 'tab:green', 'tab:red', 'tab:gray']
            for j in range(4):
                for vertex in vertex_sets[j]:
                    options = {
                        "nodelist": [vertex],
                        "node_color": colors[j],
                        "node_shape": '8' if vertex in ancilla_qubits else 'o',
                        "with_labels": False,
                        "width": 3,
                    }
                    draw_networkx(self.__graph, self.__pos, **options)
                    ax = plt.gca()
                    ax.margins(0.20)
                    plt.axis("on")
                    ax.set_axisbelow(True)
            plt.pause(self.__pause_time)

    def draw_process(self, draw=True, pos=False, pause_time=0.5):
        r"""动态过程图绘制,用以实时展示 MBQC 模型的模拟计算过程。

        Args:
            draw (bool, optional): 是否绘制动态过程图的布尔开关
            pos (bool or dict, optional): 节点坐标的字典数据或者内置的坐标选择,内置的坐标选择有:
                                            ``True`` 为测量模式自带的坐标,``False`` 为 `spring_layout` 坐标
            pause_time (float, optional): 绘制动态过程图时每次更新的停顿时间
        """
        assert self.__graph is not None, "please set 'graph' or 'pattern' before calling 'draw_process'."
        assert isinstance(draw, bool), "'draw' must be bool."
        assert isinstance(pos, bool) or isinstance(pos, dict), "'pos' should be either bool or dict."
        assert pause_time > 0, "'pause_time' must be strictly larger than 0."

        self.__draw = draw
        self.__pause_time = pause_time

        if self.__draw:
            plt.figure()
            plt.ion()
            self.__set_position(pos)

    def track_progress(self, track=True):
        r""" 显示 MBQC 模型运行进度的开关。

        Args:
            track (bool, optional): ``True`` 打开进度条显示功能, ``False`` 关闭进度条显示功能
        """
        assert isinstance(track, bool), "the parameter 'track' must be bool."
        self.__track = track

    def __apply_cz(self, which_qubits_list):
        r"""对给定的两个比特作用控制 Z 门。

        Note:
            这是内部方法,用户并不需要直接调用到该方法。

        Warning:
            作用控制 Z 门的两个比特一定是被激活的。

        Args:
            which_qubits_list (list): 作用控制 Z 门的比特对标签列表,例如 ``[(1, 2), (3, 4),...]``
        """
        for which_qubits in which_qubits_list:
            assert set(which_qubits).issubset(self.vertex.active), \
                "vertices in 'which_qubits_list' must be activated first."
            assert which_qubits[0] != which_qubits[1], \
                'the control and target qubits must not be the same.'

            # Find the control and target qubits and permute them to the front
            self.__bg_state = permute_to_front(self.__bg_state, which_qubits[0])
            self.__bg_state = permute_to_front(self.__bg_state, which_qubits[1])

            new_state = self.__bg_state
            new_state_len = new_state.length
            qua_length = int(new_state_len / 4)
            cz = cz_gate()
            # Reshape the state, apply CZ and reshape it back
            new_state.vector = reshape(matmul(cz, reshape(new_state.vector, [4, qua_length])), [new_state_len, 1])

            # Update the order of active vertices and the background state
            self.vertex.active = new_state.system
            self.__bg_state = State(new_state.vector, new_state.system)

    def __apply_pauli_gate(self, gate, which_qubit):
        r"""对给定的单比特作用 Pauli 门。

        Note:
            这是内部方法,用户并不需要直接调用到该方法。

        Args:
            gate (str): Pauli 门的索引字符,"I", "X", "Y", "Z" 分别表示对应的门,在副产品处理时用 "X" 和 "Z" 门
            which_qubit (any): 作用 Pauli 门的系统标签,
                               可以是 ``str``, ``tuple`` 等任意数据类型,但需要和 MBQC 模型中节点的标签类型匹配
        """
        new_state = permute_to_front(self.__bg_state, which_qubit)
        new_state_len = new_state.length
        half_length = int(new_state_len / 2)
        gate_mat = pauli_gate(gate)
        # Reshape the state, apply X and reshape it back
        new_state.vector = reshape(matmul(gate_mat, reshape(new_state.vector, [2, half_length])), [new_state_len, 1])
        # Update the order of active vertices and the background state
        self.vertex.active = new_state.system
        self.__bg_state = State(new_state.vector, new_state.system)

    def __create_graph_state(self, which_qubit):
        r"""以待测量的比特为输入参数,生成测量当前节点所需要的最小的量子图态。

        Note:
            这是内部方法,用户并不需要直接调用到该方法。

        Args:
            which_qubit (any): 待测量比特的系统标签。
                                可以是 ``str``, ``tuple`` 等任意数据类型,但需要和 MBQC 模型中节点的标签类型匹配
        """
        # Find the neighbors of 'which_qubit'
        which_qubit_neighbors = set(self.__graph.neighbors(which_qubit))
        # Exclude the qubits already measured
        neighbors_not_measured = which_qubit_neighbors.difference(set(self.vertex.measured))
        # Create a list of system labels that will be applied to cz gates
        cz_list = [(which_qubit, qubit) for qubit in neighbors_not_measured]
        # Get the qubits to be activated
        append_qubits = {which_qubit}.union(neighbors_not_measured).difference(set(self.vertex.active))
        # Update active and pending lists
        self.vertex.active += list(append_qubits)
        self.vertex.pending = list(set(self.vertex.pending).difference(self.vertex.active))

        # Compute the new background state vector
        new_bg_state_vector = kron([self.__bg_state.vector] + [plus_state() for _ in append_qubits])

        # Update the background state and apply cz
        self.__bg_state = State(new_bg_state_vector, self.vertex.active)
        self.__apply_cz(cz_list)
        self.__draw_process("active", which_qubit)

    def __update(self):
        r"""更新历史列表和量子态信息。
        """
        self.__history.append(self.__bg_state)
        self.__status = self.__history[-1]

    def measure(self, which_qubit, basis_list):
        r"""以待测量的比特和测量基为输入参数,对该比特进行测量。

        Note:
            这是用户在实例化 MBQC 类之后最常调用的方法之一,此处我们对单比特测量模拟进行了最大程度的优化,
            随着用户对该函数的调用,MBQC 类将自动完成激活相关节点、生成所需的图态以及对特定比特进行测量的全过程,
            并记录测量结果和对应测量后的量子态。用户每调用一次该函数,就完成一次对单比特的测量操作。

        Warning:
            当且仅当用户调用 ``measure`` 类方法时,MBQC 模型才真正进行运算。

        Args:
            which_qubit (any): 待测量量子比特的系统标签,
                                可以是 ``str``, ``tuple`` 等任意数据类型,但需要和 MBQC 模型的图上标签匹配
            basis_list (list): 测量基向量构成的列表,列表元素为 ``Tensor`` 类型的列向量

        代码示例:

        .. code-block:: python

            from paddle_quantum.mbqc.simulator import MBQC
            from paddle_quantum.mbqc.qobject import State
            from paddle_quantum.mbqc.utils import zero_state, basis

            G = [['1', '2', '3'], [('1', '2'), ('2', '3')]]
            mbqc = MBQC()
            mbqc.set_graph(G)
            state = State(zero_state(), ['1'])
            mbqc.set_input_state(state)
            mbqc.measure('1', basis('X'))
            mbqc.measure('2', basis('X'))
            print("Measurement outcomes: ", mbqc.get_classical_output())

        ::

            Measurement outcomes:  {'1': 0, '2': 1}
        """
        self.__draw_process("measuring", which_qubit)
        self.__create_graph_state(which_qubit)
        assert which_qubit in self.vertex.active, 'the qubit to be measured must be activated first.'

        new_bg_state = permute_to_front(self.__bg_state, which_qubit)
        self.vertex.active = new_bg_state.system
        half_length = int(new_bg_state.length / 2)

        eps = 10 ** (-10)
        prob = [0, 0]
        state_unnorm = [0, 0]

        # Calculate the probability and post-measurement states
        for result in [0, 1]:
            basis_dagger = t(conj(basis_list[result]))
            # Reshape the state, multiply the basis and reshape it back
            state_unnorm[result] = reshape(matmul(basis_dagger,
                                                  reshape(new_bg_state.vector, [2, half_length])), [half_length, 1])
            probability = matmul(t(conj(state_unnorm[result])), state_unnorm[result])
            is_complex128 = probability.dtype == to_tensor([], dtype='complex128').dtype
            prob[result] = real(probability) if is_complex128 else probability

        # Randomly choose a result and its corresponding post-measurement state
        if prob[0].numpy().item() < eps:
            result = 1
            post_state_vector = state_unnorm[1]
        elif prob[1].numpy().item() < eps:
            result = 0
            post_state_vector = state_unnorm[0]
        else:  # Take a random choice of outcome
            result = random.choice(2, 1, p=[prob[0].numpy().item(), prob[1].numpy().item()]).item()
            # Normalize the post-measurement state
            post_state_vector = state_unnorm[result] / prob[result].sqrt()

        # Write the measurement result into the dict
        self.__outcome.update({which_qubit: int(result)})
        # Update measured, active lists
        self.vertex.measured.append(which_qubit)
        self.max_active = max(len(self.vertex.active), self.max_active)
        self.vertex.active.remove(which_qubit)

        # Update the background state and history list
        self.__bg_state = State(post_state_vector, self.vertex.active)
        self.__update()

        self.__draw_process("measured", which_qubit)

    def sum_outcomes(self, which_qubits, start=0):
        r"""根据输入的量子系统标签,在存储测量结果的字典中找到对应的测量结果,并进行求和。

        Note:
            在进行副产品纠正操作和定义适应性测量角度时,用户可以调用该方法对特定比特的测量结果求和。

        Args:
            which_qubits (list): 需要查找测量结果并求和的比特的系统标签列表
            start (int): 对结果进行求和后需要额外相加的整数

        Returns:
            int: 指定比特的测量结果的和

        代码示例:

        .. code-block:: python

            from paddle_quantum.mbqc.simulator import MBQC
            from paddle_quantum.mbqc.qobject import State
            from paddle_quantum.mbqc.utils import zero_state, basis

            G = [['1', '2', '3'], [('1', '2'), ('2', '3')]]
            mbqc = MBQC()
            mbqc.set_graph(G)
            input_state = State(zero_state(), ['1'])
            mbqc.set_input_state(input_state)
            mbqc.measure('1', basis('X'))
            mbqc.measure('2', basis('X'))
            mbqc.measure('3', basis('X'))
            print("All measurement outcomes: ", mbqc.get_classical_output())
            print("Sum of outcomes of qubits '1' and '2': ", mbqc.sum_outcomes(['1', '2']))
            print("Sum of outcomes of qubits '1', '2' and '3' with an extra 1: ", mbqc.sum_outcomes(['1', '2', '3'], 1))

        ::

            All measurement outcomes:  {'1': 0, '2': 0, '3': 1}
            Sum of outcomes of qubits '1' and '2':  0
            Sum of outcomes of qubits '1', '2' and '3' with an extra 1:  2
        """
        assert isinstance(start, int), "'start' must be of type int."

        return sum([self.__outcome[label] for label in which_qubits], start)

    def correct_byproduct(self, gate, which_qubit, power):
        r"""对测量后的量子态进行副产品纠正。

        Note:
            这是用户在实例化 MBQC 类并完成测量后,经常需要调用的一个方法。

        Args:
            gate (str): ``'X'`` 或者 ``'Z'``,分别表示 Pauli X 或 Z 门修正
            which_qubit (any): 待操作的量子比特的系统标签,可以是 ``str``, ``tuple`` 等任意数据类型,但需要和 MBQC 中图的标签类型匹配
            power (int): 副产品纠正算符的指数

        代码示例:

            此处展示的是 MBQC 模型下实现隐形传态的一个例子。

        .. code-block:: python

            from paddle_quantum.mbqc.simulator import MBQC
            from paddle_quantum.mbqc.qobject import State
            from paddle_quantum.mbqc.utils import random_state_vector, basis, compare_by_vector

            G = [['1', '2', '3'], [('1', '2'), ('2', '3')]]
            state = State(random_state_vector(1), ['1'])
            mbqc = MBQC()
            mbqc.set_graph(G)
            mbqc.set_input_state(state)
            mbqc.measure('1', basis('X'))
            mbqc.measure('2', basis('X'))
            outcome = mbqc.get_classical_output()
            mbqc.correct_byproduct('Z', '3', outcome['1'])
            mbqc.correct_byproduct('X', '3', outcome['2'])
            state_out = mbqc.get_quantum_output()
            state_std = State(state.vector, ['3'])
            compare_by_vector(state_out, state_std)

        ::

            Norm difference of the given states is:
             0.0
            They are exactly the same states.
        """
        assert gate in ['X', 'Z'], "'gate' must be 'X' or 'Z'."
        assert isinstance(power, int), "'power' must be of type 'int'."

        if power % 2 == 1:
            self.__apply_pauli_gate(gate, which_qubit)
        self.__update()

    def __run_cmd(self, cmd):
        r"""执行测量或副产品处理命令。

        Args:
            cmd (Pattern.CommandM / Pattern.CommandX / Pattern.CommandZ): 测量或副产品处理命令
        """
        assert cmd.name in ["M", "X", "Z"], "the input 'cmd' must be CommandM, CommandX or CommandZ."
        if cmd.name == "M":  # Execute measurement commands
            signal_s = self.sum_outcomes(cmd.domain_s)
            signal_t = self.sum_outcomes(cmd.domain_t)
            # The adaptive angle is (-1)^{signal_s} * angle + {signal_t} * pi
            adaptive_angle = multiply(to_tensor([(-1) ** signal_s], dtype="float64"), cmd.angle) \
                             + to_tensor([signal_t * pi], dtype="float64")
            self.measure(cmd.which_qubit, basis(cmd.plane, adaptive_angle))
        else:  # Execute byproduct correction commands
            power = self.sum_outcomes(cmd.domain)
            self.correct_byproduct(cmd.name, cmd.which_qubit, power)

    def __run_cmd_lst(self, cmd_lst, bar_start, bar_end):
        r"""对列表执行测量或副产品处理命令。

        Args:
            cmd_lst (list): 命令列表,包含测量或副产品处理命令
            bar_start (int): 进度条的开始点
            bar_end (int): 进度条的结束点
        """
        for i in range(len(cmd_lst)):
            cmd = cmd_lst[i]
            self.__run_cmd(cmd)
            print_progress((bar_start + i + 1) / bar_end, "Pattern Running Progress", self.__track)

    def __kron_unmeasured_qubits(self):
        r"""该方法将没有被作用 CZ 纠缠的节点初始化为 |+> 态,并与当前的量子态做张量积。

        Warning:
            该方法仅在用户输入测量模式时调用,当用户输入图时,如果节点没有被激活,我们默认用户没有对该节点进行任何操作。
        """
        # Turn off the plot switch
        self.__draw = False
        # As the create_graph_state function would change the measured qubits list, we need to record it
        measured_qubits = self.vertex.measured[:]

        for qubit in list(self.__graph.nodes):
            if qubit not in self.vertex.measured:
                self.__create_graph_state(qubit)
                # Update vertices and backgrounds
                self.vertex.measured.append(qubit)
                self.max_active = max(len(self.vertex.active), self.max_active)
                self.__bg_state = State(self.__bg_state.vector, self.vertex.active)

        # Restore the measured qubits
        self.vertex.measured = measured_qubits

    def run_pattern(self):
        r"""按照设置的测量模式对 MBQC 模型进行模拟。

        Warning:
            该方法必须在 ``set_pattern`` 调用后调用。
        """
        assert self.__pattern is not None, "please use this method after calling 'set_pattern'!"

        # Execute measurement commands and correction commands
        cmd_m_lst = [cmd for cmd in self.__pattern.commands if cmd.name == "M"]
        cmd_c_lst = [cmd for cmd in self.__pattern.commands if cmd.name in ["X", "Z"]]
        bar_end = len(cmd_m_lst + cmd_c_lst)

        self.__run_cmd_lst(cmd_m_lst, 0, bar_end)
        # Activate unmeasured qubits before byproduct corrections
        self.__kron_unmeasured_qubits()
        self.__run_cmd_lst(cmd_c_lst, len(cmd_m_lst), bar_end)

        # The output state's label is messy (e.g. [(2, 0), (0, 1), (1, 3)...]),
        # so we permute the systems in order
        q_output = self.__pattern.output_[1]
        self.__bg_state = permute_systems(self.__status, q_output)
        self.__update()

    @staticmethod
    def __map_qubit_to_row(out_lst):
        r"""将输出比特的标签与行数对应起来,便于查找其对应关系。

        Returns:
            dict: 返回字典,代表行数与标签的对应关系
        """
        return {int(div_str_to_float(qubit[0])): qubit for qubit in out_lst}

    def get_classical_output(self):
        r"""获取 MBQC 模型运行后的经典输出结果。

        Returns:
Q
Quleaf 已提交
657
            str or dict: 如果用户输入是测量模式,则返回测量输出节点得到的比特串,与原电路的测量结果相一致,没有被测量的比特位填充 "?",如果用户输入是图,则返回所有节点的测量结果
Q
Quleaf 已提交
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809
        """
        # If the input is pattern, return the equivalent result as the circuit model
        if self.__pattern is not None:
            width = len(self.__pattern.input_)
            c_output = self.__pattern.output_[0]
            q_output = self.__pattern.output_[1]
            # Acquire the relationship between row number and corresponding output qubit label
            output_lst = c_output + q_output
            row_and_qubit = self.__map_qubit_to_row(output_lst)

            # Obtain the string, with classical outputs denoted as their measurement outcomes
            # and quantum outputs denoted as "?"
            bit_str = [str(self.__outcome[row_and_qubit[i]])
                       if row_and_qubit[i] in c_output else '?'
                       for i in range(width)]
            string = "".join(bit_str)
            return string

        # If the input is graph, return the outcome dictionary
        else:
            return self.__outcome

    def get_history(self):
        r"""获取 MBQC 计算模拟时的中间步骤信息。

        Returns:
            list: 生成图态、进行测量、纠正副产品后运算结果构成的列表
        """
        return self.__history

    def get_quantum_output(self):
        r"""获取 MBQC 模型运行后的量子态输出结果。

        Returns:
            State: MBQC 模型运行后的量子态
        """
        return self.__status


def simulate_by_mbqc(circuit, input_state=None):
    r"""使用等价的 MBQC 模型模拟量子电路。

    该函数通过将量子电路转化为等价的 MBQC 模型并运行,从而获得等价于原始量子电路的输出结果。

    Warning:
        与 ``UAnsatz`` 不同,此处输入的 ``circuit`` 参数包含了测量操作。
        另,MBQC 模型默认初始态为加态,因此,如果用户不输入参数 ``input_state`` 设置初始量子态,则默认为加态。

    Args:
        circuit (Circuit): 量子电路图
        input_state (State, optional): 量子电路的初始量子态,默认为 :math:`|+\rangle` 态

    Returns:
        tuple: 包含如下两个元素:

            - str: 经典输出
            - State: 量子输出
    """
    if input_state is not None:
        assert isinstance(input_state, State), "the 'input_state' must be of type 'State'."

    pattern = transpile(circuit)
    mbqc = MBQC()
    mbqc.set_pattern(pattern)
    mbqc.set_input_state(input_state)
    mbqc.run_pattern()
    c_output = mbqc.get_classical_output()
    q_output = mbqc.get_quantum_output()

    # Return the classical and quantum outputs
    return c_output, q_output


def __get_sample_dict(bit_num, mea_bits, samples):
    r"""根据比特数和测量比特索引的列表,统计采样结果。

    Args:
        bit_num (int): 比特数
        mea_bits (list): 测量的比特列表
        samples (list): 采样结果

    Returns:
        dict: 统计得到的采样结果
    """
    sample_dict = {}
    for i in range(2 ** len(mea_bits)):
        str_of_order = bin(i)[2:].zfill(len(mea_bits))
        bit_str = []
        idx = 0
        for j in range(bit_num):
            if j in mea_bits:
                bit_str.append(str_of_order[idx])
                idx += 1
            else:
                bit_str.append('?')
        string = "".join(bit_str)
        sample_dict[string] = 0

    # Count sampling results
    for string in list(set(samples)):
        sample_dict[string] += samples.count(string)
    return sample_dict


def sample_by_mbqc(circuit, input_state=None, plot=False, shots=1024, print_or_not=True):
    r"""将 MBQC 模型重复运行多次,获得经典结果的统计分布。
   
    Warning:
        与 ``UAnsatz`` 不同,此处输入的 circuit 参数包含了测量操作。
        另,MBQC 模型默认初始态为加态,因此,如果用户不输入参数 `input_state` 设置初始量子态,则默认为加态。

    Args:
        circuit (Circuit): 量子电路图
        input_state (State, optional): 量子电路的初始量子态,默认为加态
        plot (bool, optional): 绘制经典采样结果的柱状图开关,默认为关闭状态
        shots (int, optional): 采样次数,默认为 1024 次
        print_or_not (bool, optional): 是否打印采样结果和绘制采样进度,默认为开启状态

    Returns:
        dict: 经典结果构成的频率字典
        list: 经典测量结果和所有采样结果(包括经典输出和量子输出)的列表
    """
    # Initialize
    if shots == 1:
        print_or_not = False
    if print_or_not:
        print("Sampling " + str(shots) + " times." + "\nWill return the sampling results.\r\n")
    width = circuit.get_width()
    mea_bits = circuit.get_measured_qubits()

    # Sampling for "shots" times
    samples = []
    all_outputs = []
    for shot in range(shots):
        if print_or_not:
            print_progress((shot + 1) / shots, "Current Sampling Progress")
        c_output, q_output = simulate_by_mbqc(circuit, input_state)
        samples.append(c_output)
        all_outputs.append([c_output, q_output])

    sample_dict = __get_sample_dict(width, mea_bits, samples)
    if print_or_not:
        print("Sample count " + "(" + str(shots) + " shots)" + " : " + str(sample_dict))
    if plot:
        dict_lst = [sample_dict]
        bar_labels = ["MBQC sample outcomes"]
        title = 'Sampling results (MBQC)'
        xlabel = "Measurement outcomes"
        ylabel = "Distribution"
        plot_results(dict_lst, bar_labels, title, xlabel, ylabel)

    return sample_dict, all_outputs