Qiskit: Quantum Hypergraph Statesの実装

# はじめに

量子計算を行う際に初期状態を欲しい形にしたい場合があると思います。
特に量子ニューラルネットワークなどで必要かと思われます。

仮に

```math
\frac{|000>+|001>-|010>+|011>-|100>-|101>+|110>+|111>}{2 \sqrt{2}}
```

を作るgateの組み合わせがすぐ分かれば便利じゃありませんか？
それを実現する考え方がQuantum Hypergraph Stateです。

詳しくは [Quantum Hypergraph states](https://arxiv.org/abs/1211.5554)をご覧ください。

# Quantum Hypergraph states

軽くQuantum Hypergraph statesの説明をしたいと思います。

```math
\frac{i_0|000>+i_1|001>+i_2|010>+i_3|011>+i_4|100>+i_5|101>+i_6|110>+i_7|111>}{2 \sqrt{2}}  \\
i_k = -1 \ or \ 1
```

1. 係数が$-1$である$|0...1...0>$が存在するとき、$1$であるqubitに対してZ gateを作用させる。
2. 係数を更新する。
3. 係数が$-1$である$|0...1...1...0>$が存在するとき、$1$であるqubitに対してCZ gateを作用させる。
4. 係数を更新する。
5. 係数が$-1$である$|0...1...1...1...0>$が存在するとき、$1$であるqubitに対してCCZ gateを作用させる。

3qubitの場合は、これですべての係数が1になります。

## 例

```math
\frac{|000>+|001>-|010>+|011>-|100>-|101>+|110>+|111>}{2 \sqrt{2}}
```

1. $|010>$と$|100>$の係数が$-1$なので、第二qubitと第三qubitにZ gateを作用させます。その結果、

```math
\frac{|000>+|001>+|010>-|011>+|100>+|101>+|110>+|111>}{2 \sqrt{2}}
```

2. $|011>$の係数が$-1$なので、第一qubitと第二qubitにCZ gateを作用させます。その結果、

```math
\frac{|000>+|001>+|010>+|011>+|100>+|101>+|110>-|111>}{2 \sqrt{2}}
```

3. $|111>の係数が$-1$なので、第一qubitと第二qubitと第三qubitにCCZ gateを作用させます。その結果、

```math
\frac{|000>+|001>+|010>+|011>+|100>+|101>+|110>+|111>}{2 \sqrt{2}}
```

以上で回路の構築は終わりです。

![qhgs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558839/491b555f-17ed-d070-643a-3086864b3e61.png)

# Code

Codeは以下の通りです。正直Pythonが得意なわけでは無いので、無駄な箇所があるかもしれません。効率的な書き方があったら教えてくださると有難いです。
ちなみに、今回は3qubitまでのものですが、同じようにすればいくらでも増やせると思います。

```python:
# matplotlib inline
# coding: utf-8

import numpy as np
from math import log2
from copy import deepcopy
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.quantum_info.operators import Operator
import matplotlib.pyplot as plt


def count_ones_by_bin(num):
    bin_num = bin(num)[2:]
    count = 0
    for i in bin_num:
        count += int(i)
    return count


class QuantumHypergraphState:

    def __init__(self, numqubits, states, qubit_index=None):
        ''' make Quantum HyperGraph State in circuit
        :param circuit:
        :param numqubits: maximum 3
        :param states:
        '''
        self.numqubits = numqubits
        self.states = deepcopy(states)
        if qubit_index is None:
            self.index = [i for i in range(numqubits)]
        else:
            self.index = qubit_index

    def bin_length(self, num):
        bin_num = bin(num)[2:]
        dif_len = self.numqubits - len(bin_num)
        for i in range(dif_len):
            bin_num = '0' + bin_num
        return bin_num

    def get_z_tgt(self, num):
        bin_num = self.bin_length(num)[::-1]
        z_tgt = []
        for i in range(len(bin_num)):
            if int(bin_num[i]) == 1:
                z_tgt.append(i)
        return z_tgt

    def tgt_0(self, num, tgt):
        """
        e.g. tgt = [0]
             num = 011

        """
        bin_num = self.bin_length(num)[::-1]  # 011
        count = 0
        for i in range(len(bin_num)):
            if i in tgt:
                count += int(bin_num[i])
        if count == len(tgt):
            return True
        else:
            return False

    def renew_states(self, tgt):
        for st in range(len(self.states)):
            if self.tgt_0(st, tgt):
                self.states[st] *= -1

    def get_tgt_list(self, idx_list):
        tgt_list = []
        for i in range(len(idx_list)):
            tgt_list.append(self.index[idx_list[i]])
        return tgt_list

    def construct_circuit(self, circuit, inverse=False):
        ccz = Operator([[1, 0, 0, 0, 0, 0, 0, 0],
                        [0, 1, 0, 0, 0, 0, 0, 0],
                        [0, 0, 1, 0, 0, 0, 0, 0],
                        [0, 0, 0, 1, 0, 0, 0, 0],
                        [0, 0, 0, 0, 1, 0, 0, 0],
                        [0, 0, 0, 0, 0, 1, 0, 0],
                        [0, 0, 0, 0, 0, 0, 1, 0],
                        [0, 0, 0, 0, 0, 0, 0, -1]])
        if inverse:
            gate_list = []
        else:
            circuit.h(self.index)
        for num in range(1, self.numqubits + 1):
            # statesの分だけloop
            if num == 1:
                for st in range(len(self.states)):
                    if count_ones_by_bin(st) == num:
                        if self.states[st] == -1:
                            idx = int(log2(st))
                            tgt = self.index[idx]
                            if inverse:
                                gate_list.append(['z', [tgt]])
                            else:
                                circuit.z(tgt)
                            self.renew_states([idx])
            elif num == 2:
                for st in range(len(self.states)):
                    if count_ones_by_bin(st) == num:
                        if self.states[st] == -1:
                            idx_list = self.get_z_tgt(st)
                            tgt_list = self.get_tgt_list(idx_list)
                            if inverse:
                                gate_list.append(['cz', tgt_list])
                            else:
                                circuit.cz(tgt_list[0], tgt_list[1])
                            self.renew_states(idx_list)
            else:
                for st in range(len(self.states)):
                    if count_ones_by_bin(st) == num:
                        if self.states[st] == -1:
                            idx_list = self.get_z_tgt(st)
                            tgt_list = self.get_tgt_list(idx_list)
                            if inverse:
                                gate_list.append(['ccz', tgt_list])
                            else:
                                circuit.unitary(ccz, self.index, label='ccz')
                            self.renew_states(idx_list)

        if inverse:
            gate_list = gate_list[::-1]
            for gate in gate_list:
                if gate[0] == 'ccz':
                    circuit.unitary(ccz, self.index, label='ccz')
                if gate[0] == 'cz':
                    circuit.cz(gate[1][0], gate[1][1])
                if gate[0] == 'z':
                    circuit.z(gate[1][0])
            # circuit.h(self.index)
            circuit.x(self.index)
            return circuit
        else:
            return circuit

    def draw(self):
        print(self.qc.draw(output='mpl'))

```

以上です。ありがとうございました。

# 追記

まあまあエラーを吐き出していたので更新しました：2020/04/12