FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

Googleの量子コンピュータフレームワークCirqを使ってみる(4. Cirqの中身を追ってみる)

Google量子コンピュータフレームワークであるCirqを使っており、簡単な回路なら記述できるようになった。

次に疑問点として沸いてくるのは、Cirqはどのようにして量子状態のシミュレーションを行っているのかというところだ。 Pythonのコードを見るのは苦手だけれども、どうにかしてこれを追ってみることにした。

Pythonパッケージを変更して試行してい見る。

まずはCirqでシミュレーションを実行するにあたり以下の簡単な量子回路を用意した。 非常に簡単な回路だ。Xゲートを量子ビットに適用しているだけである。

  • cnot_test.py
import cirq

qbits = [cirq.GridQubit(0, i) for i in range(1)]

circuit = cirq.Circuit()
circuit.append(cirq.X.on(qbits[0]))

simulator = cirq.google.XmonSimulator()
circuit.append(cirq.measure(qbits[0], key='c'))

results = simulator.run(circuit, repetitions=1, qubit_order=qbits)
print (results.histogram(key='c'))

量子回路は以下のようになる。

(0, 0): ───X───M('c')───

いちいちパッケージをビルドしてインストールするのは面倒なので良い方法はないかと悩んでいたのだが、Twitterpython setup.py developでリンクを張られた状態でパッケージを使用できることが分かった。これは便利。

python setup.py develop

シミュレーションの様子を追いかけてみる

まずはcircuit.X.on()circuit.CNOT.on()がどこで実行されているのかを確認する。これはどうもXを実行するためのゲートオブジェクトを返しているだけのようだ。

  • cirq/ops/common_gates.py
CNOT = CNotGate()  # Controlled Not Gate.
  • cirq/ops/common_gates.py
class CNotGate(eigen_gate.EigenGate,
               gate_features.TextDiagrammable,
               gate_features.CompositeGate,
               gate_features.TwoQubitGate,
               gate_features.QasmConvertibleGate):
...
    def on(self, *args: raw_types.QubitId,
           **kwargs: raw_types.QubitId) -> gate_operation.GateOperation:
        if not kwargs:
            return super().on(*args)
        if not args and set(kwargs.keys()) == {'control', 'target'}:
            return super().on(kwargs['control'], kwargs['target'])
        raise ValueError(
            "Expected two positional argument or else 'target' AND 'control' "
            "keyword arguments. But got args={!r}, kwargs={!r}.".format(
                args, kwargs))
  • cirq/ops/raw_types.py
class Gate:
...
    def on(self, *qubits: QubitId) -> 'gate_operation.GateOperation':
        """Returns an application of this gate to the given qubits.

        Args:
            *qubits: The collection of qubits to potentially apply the gate to.
        """
        # Avoids circular import.
        from cirq.ops import gate_operation

        if len(qubits) == 0:
            raise ValueError(
                "Applied a gate to an empty set of qubits. Gate: {}".format(
                    repr(self)))
        self.validate_args(qubits)
        return gate_operation.GateOperation(self, list(qubits))
  • cirq/ops/gate_operation.py
class GateOperation(raw_types.Operation,
                    extension.PotentialImplementation[Union[
                        gate_features.BoundedEffect,
                        gate_features.CompositeOperation,
                        gate_features.ExtrapolatableEffect,
                        gate_features.ParameterizableEffect,
                        gate_features.PhaseableEffect,
                        gate_features.ReversibleEffect,
                        gate_features.TextDiagrammable,
                        gate_features.QasmConvertibleOperation,
                    ]]):
    """An application of a gate to a collection of qubits.

    Attributes:
        gate: The applied gate.
        qubits: A sequence of the qubits on which the gate is applied.
    """

    def __init__(self,
                 gate: raw_types.Gate,
                 qubits: Sequence[raw_types.QubitId]) -> None:
        self._gate = gate
        self._qubits = tuple(qubits)

次に、量子ビットを計測したときにどのように実行されるのかを観測した。

  • cirq/google/sim/xmon_stepper.py
class Stepper(object):
...
    def sample_measurements(
            self,
            indices: List[int],
            repetitions: int=1) -> List[List[bool]]:
...
        # Calculate probabilities and reshape to tensor of qubits.
        tensor = np.reshape(np.abs(self.current_state) ** 2,
                            self._num_qubits * [2])

        # Tensor axis order is reverse of index order, so we transpose here.
        tensor = np.transpose(tensor)

このtensorという変数が怪しい気がする。いろいろ量子回路を変えながらtensorの値を出力してみた。

まずはXゲート→CNOTゲートを当てたもの。

circuit = cirq.Circuit()
circuit.append(cirq.X.on(qbits[0]))
circuit.append(cirq.CNOT.on(qbits[0], qbits[1]))

[cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)]
(0, 0): ───X───@───
               │
(0, 1): ───────X───

current_state = [ 2.0323548e-18-8.4182900e-19j  1.1845434e-17+2.8597407e-17j
 -2.2809713e-08+5.5067517e-08j  1.0000001e+00+0.0000000e+00j]

tensor = [[4.8391427e-36 9.5812607e-34]
 [3.5527145e-15 1.0000002e+00]]
Counter({1: 1})

次にCNOTゲートのみ当てたもの。

circuit = cirq.Circuit()
circuit.append(cirq.CNOT.on(qbits[0], qbits[1]))

current_state = [-2.9802322e-08+1.0000000e+00j -5.7024274e-09-1.3766877e-08j
 -2.8597404e-17-1.1845433e-17j -8.4182890e-19+2.0323546e-18j]

[cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)]
(0, 0): ───@───
           │
(0, 1): ───X───
tensor = [[1.000000e+00 2.220446e-16]
 [9.581259e-34 4.839141e-36]]
Counter({0: 1})

やはりtensorの値が変わったので、この変数が量子ビットの状態を保持していそうな気がする。

ただし具体的にどうやって量子ゲートの計算をしているのかについてはたどり着かなかった。 もう少し掘り下げてみていきたい。

f:id:msyksphinz:20180923015903p:plain