Qiskitソースコードリーディング 〜 Terra: バックエンドの取得、呼び出し、結果の取得を読む

# 何をするの?
量子コンピューティングライブラリのBlueqatを作っている私が、量子コンピューティングライブラリのQiskitのソースコードを読んでみる、という企画です。

[前回](https://qiita.com/gyu-don/private/f4ffa9f95396ee2d43c1)の続きで、今回はバックエンドの取得、呼び出し、結果の取得を読むの処理を読んでいきます。
シミュレータ自体のソースは読まずに、バックエンドまわりのインタフェースについて読んでいきます。

# Qiskit概要
QiskitはIBMが開発しているオープンソースの量子コンピューティングライブラリです。

Qiskitは以下のようにパッケージが分かれていますが、インストールする際はバラバラにやるよりも`pip install qiskit`でまとめてインストールした方がトラブルが少ないです。

|パッケージ|役割|
|:--|:--|
|Qiskit Terra|メインとなるパッケージです。回路を作るクラスや、回路を実機向けにトランスパイルする機能、APIを叩いて実機に投げる機能などが含まれています|
|Qiskit Aer|量子回路のシミュレータが含まれており、通常はQiskit Terraから呼び出します|
|Qiskit Ignis|量子回路を実機で動かした際のノイズと戦いたい人のためのライブラリです。私は使ったことがありません|
|Qiskit Aqua|量子アルゴリズムを簡単に使えるようにしたライブラリです|

今回読むのはQiskit Terraの一部です。

https://github.com/Qiskit/qiskit-terra

具体的には、README.mdに書いてあるコード

```py
from qiskit import *
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0,1], [0,1])
backend_sim = BasicAer.get_backend('qasm_simulator')
result = execute(qc, backend_sim).result()
print(result.get_counts(qc))
```

のうち、`backend_sim = BasicAer.get_backend('qasm_simulator')`以降の流れを読んでいきます。
ただし、シミュレータの中味やトランスパイルの詳細は読みません。

GitHub上のmasterブランチを読んでいきます。
現時点のコミットIDはe7be587ですが、結構頻繁に更新されているので、記事が書き終わる頃には変わる可能性もあります。ご了承ください。

# `BasicAer.get_backend`の行を読む

```py
backend_sim = BasicAer.get_backend('qasm_simulator')
```

この行を読みます。
`BasicAer`はterraの[qiskit/providers/basicaer/__init__.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/providers/basicaer/__init__.py)で

```py
BasicAer = BasicAerProvider()
```

と定義されています。

## `BasicAerProvider`を読む

[qiskit/providers/basicaer/basicaerprovider.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/providers/basicaer/basicaerprovider.py)の`BasicAerProvider`を読んでいきます。

```py
SIMULATORS = [
    QasmSimulatorPy,
    StatevectorSimulatorPy,
    UnitarySimulatorPy
]


class BasicAerProvider(BaseProvider):
    """Provider for Basic Aer backends."""

    def __init__(self, *args, **kwargs):
        super().__init__(args, kwargs)

        # Populate the list of Basic Aer backends.
        self._backends = self._verify_backends()

    # 略

    def _verify_backends(self):
        """
        Return the Basic Aer backends in `BACKENDS` that are
        effectively available (as some of them might depend on the presence
        of an optional dependency or on the existence of a binary).
        Returns:
            dict[str:BaseBackend]: a dict of Basic Aer backend instances for
                the backends that could be instantiated, keyed by backend name.
        """
        ret = OrderedDict()
        for backend_cls in SIMULATORS:
            try:
                backend_instance = self._get_backend_instance(backend_cls)
                backend_name = backend_instance.name()
                ret[backend_name] = backend_instance
            except QiskitError as err:
                # Ignore backends that could not be initialized.
                logger.info('Basic Aer backend %s is not available: %s',
                            backend_cls, str(err))
        return ret
```

一応、親クラスの`BaseProvider`の`__init__`を読んでおきます。

```py
class BaseProvider(ABC):
    """Base class for a Backend Provider."""

    def __init__(self, *args, **kwargs):
        pass
```

何もしていません。

### `BasicAerProvider._verify_backends`を読む
続いて

```py
self._backends = self._verify_backends()
```

の`_verify_backends`を読みます。コードは上に貼りましたが、`verify`って名前で検証以外のことするの本当によくない……
それは置いといて。`BasicAer`はプロバイダーで、プロバイダーとは`Backend`を持っているものである、という構造が見えてきました。

`SIMULATORS`に定義されている`[QasmSimulatorPy, StatevectorSimulatorPy, UnitarySimulatorPy]`クラスを、それぞれ`_get_backend_instance`して、(順序付き)辞書に詰め込んで返しているようです。

`_get_backend_instance`を読んでみましょう。docstringは削って引用します。

```py
    def _get_backend_instance(self, backend_cls):
        # Verify that the backend can be instantiated.
        try:
            backend_instance = backend_cls(provider=self)
        except Exception as err:
            raise QiskitError('Backend %s could not be instantiated: %s' %
                              (backend_cls, err))

        return backend_instance
```

単にバックエンドクラスのインスタンスを作ってるだけですが、インスタンスにプロバイダー自身を渡しています。
プロバイダーとバックエンドとは相互参照の関係になっています。

### `QasmSimulatorPy.__init__`を読む
これらのバックエンドクラス自体は
[qiskit/providers/basicaer/qasm_simulator.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/providers/basicaer/qasm_simulator.py)、[qiskit/providers/basicaer/statevector_simulator.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/providers/basicaer/statevector_simulator.py)、[qiskit/providers/basicaer/unitary_simulator.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/providers/basicaer/unitary_simulator.py)に定義されています。`QasmSimulatorPy.__init__`だけ見てみましょう。

```py
class QasmSimulatorPy(BaseBackend):
    """Python implementation of a qasm simulator."""

    MAX_QUBITS_MEMORY = int(log2(local_hardware_info()['memory'] * (1024 ** 3) / 16))

    DEFAULT_CONFIGURATION = {
        'backend_name': 'qasm_simulator',
        'backend_version': '2.0.0',
        'n_qubits': min(24, MAX_QUBITS_MEMORY),
        'url': 'https://github.com/Qiskit/qiskit-terra',
        'simulator': True,
        'local': True,
        'conditional': True,
        'open_pulse': False,
        'memory': True,
        'max_shots': 65536,
        'coupling_map': None,
        'description': 'A python simulator for qasm experiments',
        'basis_gates': ['u1', 'u2', 'u3', 'cx', 'id', 'unitary'],
        'gates': [
            {
                'name': 'u1',
                'parameters': ['lambda'],
                'qasm_def': 'gate u1(lambda) q { U(0,0,lambda) q; }'
            },
            {
                'name': 'u2',
                'parameters': ['phi', 'lambda'],
                'qasm_def': 'gate u2(phi,lambda) q { U(pi/2,phi,lambda) q; }'
            },
            {
                'name': 'u3',
                'parameters': ['theta', 'phi', 'lambda'],
                'qasm_def': 'gate u3(theta,phi,lambda) q { U(theta,phi,lambda) q; }'
            },
            {
                'name': 'cx',
                'parameters': ['c', 't'],
                'qasm_def': 'gate cx c,t { CX c,t; }'
            },
            {
                'name': 'id',
                'parameters': ['a'],
                'qasm_def': 'gate id a { U(0,0,0) a; }'
            },
            {
                'name': 'unitary',
                'parameters': ['matrix'],
                'qasm_def': 'unitary(matrix) q1, q2,...'
            }
        ]
    }

    DEFAULT_OPTIONS = {
        "initial_statevector": None,
        "chop_threshold": 1e-15
    }

    # Class level variable to return the final state at the end of simulation
    # This should be set to True for the statevector simulator
    SHOW_FINAL_STATE = False

    def __init__(self, configuration=None, provider=None):
        super().__init__(configuration=(
            configuration or QasmBackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)),
                         provider=provider)

        # Define attributes in __init__.
        self._local_random = np.random.RandomState()
        self._classical_memory = 0
        self._classical_register = 0
        self._statevector = 0
        self._number_of_cmembits = 0
        self._number_of_qubits = 0
        self._shots = 0
        self._memory = False
        self._initial_statevector = self.DEFAULT_OPTIONS["initial_statevector"]
        self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"]
        self._qobj_config = None
        # TEMP
        self._sample_measure = False
```

いろいろ設定入れてるなー、という感じです。やってることは単に変数の初期化などで、大したことをやっているようには見えません。
最大で24量子ビットまでしかシミュレートできないんですね。知らなかったです。(結構少なめに設定してるな、という印象です。普通のパソコンでも、もう少し頑張れるはずです)

バックエンド自体に、ショット数や、メモリなどの途中経過と思われる状態を持たせているように見えます。(Blueqatでは、そのような実装をあえて避けています)
このあたりの思想は、これからコードを読むうちにもう少し分かってくるかもしれません。


### `BasicAer`とは何であったか
- `BasicAer`は`BasicAerProvider`のインスタンス
    - `BasicAerProvider`のインスタンスはバックエンドのリストを持つ

が分かりました。続いて、`BasicAer.get_backend`を見ていきます。

## `BasicAer.get_backend`を読む
```py

    def get_backend(self, name=None, **kwargs):
        backends = self._backends.values()

        # Special handling of the `name` parameter, to support alias resolution
        # and deprecated names.
        if name:
            try:
                resolved_name = resolve_backend_name(
                    name, backends,
                    self._deprecated_backend_names(),
                    {}
                )
                name = resolved_name
            except LookupError:
                raise QiskitBackendNotFoundError(
                    "The '{}' backend is not installed in your system.".format(name))

        return super().get_backend(name=name, **kwargs)
```

せっかく`self._backends`を辞書で持ってるのに、そのまま辞書を引かずにやっているのが気になりますね。
`resolve_backend_name`を読んでみましょう。

### `resolve_backend_name`を読む
`resolve_backend_name`は[qiskit/providers/providerutils.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/providers/providerutils.py)に定義されています。

```py
def resolve_backend_name(name, backends, deprecated, aliased):
    """Resolve backend name from a deprecated name or an alias.
    A group will be resolved in order of member priorities, depending on
    availability.
    Args:
        name (str): name of backend to resolve
        backends (list[BaseBackend]): list of available backends.
        deprecated (dict[str: str]): dict of deprecated names.
        aliased (dict[str: list[str]]): dict of aliased names.
    Returns:
        str: resolved name (name of an available backend)
    Raises:
        LookupError: if name cannot be resolved through regular available
            names, nor deprecated, nor alias names.
    """
    available = [backend.name() for backend in backends]

    resolved_name = deprecated.get(name, aliased.get(name, name))
    if isinstance(resolved_name, list):
        resolved_name = next((b for b in resolved_name if b in available), "")

    if resolved_name not in available:
        raise LookupError("backend '{}' not found.".format(name))

    if name in deprecated:
        logger.warning("Backend '%s' is deprecated. Use '%s'.", name,
                       resolved_name)

    return resolved_name
```

`available`で、バックエンド名のリストを作ります。

`deprecated`は、`BasicAerProvider._deprecated_backend_names()`を見ると、前からQiskit使っていた人には分かるように

```py
    @staticmethod
    def _deprecated_backend_names():
        """Returns deprecated backend names."""
        return {
            'qasm_simulator_py': 'qasm_simulator',
            'statevector_simulator_py': 'statevector_simulator',
            'unitary_simulator_py': 'unitary_simulator',
            'local_qasm_simulator_py': 'qasm_simulator',
            'local_statevector_simulator_py': 'statevector_simulator',
            'local_unitary_simulator_py': 'unitary_simulator',
            'local_unitary_simulator': 'unitary_simulator',
            }
```

`{'古いバックエンド名': '現役のバックエンド名'}`の辞書になっています。
探したいバックエンド名が`deprecated`に入っていたら、現役のバックエンド名に変換します。

`aliased`については、`qiskit-terra`と`qiskit-aer`を探しましたが空でないものが渡されているのが見つかりませんでしたが、探したいバックエンド名が`aliased`辞書に入っていたら、別名のリストを取り出すようです。
別名がリストで複数あるので、一番最初の`available`なバックエンド名にして、もしどれも`available`でなけえれば空文字列にします。

名前の変換が終わったら、変換された名前に対応するバックエンドを見て、あればその名前を返し、なければ例外を投げます。

### 親クラスの`get_backend()`を読む
名前の変換を行って、`available`な名前を得たら、

```py
        return super().get_backend(name=name, **kwargs)
```

としていました。なので、`BaseProvider.get_backend()`を読みましょう。docstringは省略します。

```py
    def get_backend(self, name=None, **kwargs):
        backends = self.backends(name, **kwargs)
        if len(backends) > 1:
            raise QiskitBackendNotFoundError('More than one backend matches the criteria')
        if not backends:
            raise QiskitBackendNotFoundError('No backend matches the criteria')

        return backends[0]
```

`BasicAerProvider.backends(name)`で返されるリストかなにかの最初の要素を返しています。

`BasicAerProvider.backends`を読みます。

```py
    def backends(self, name=None, filters=None, **kwargs):
        # pylint: disable=arguments-differ
        backends = self._backends.values()

        # Special handling of the `name` parameter, to support alias resolution
        # and deprecated names.
        if name:
            try:
                resolved_name = resolve_backend_name(
                    name, backends,
                    self._deprecated_backend_names(),
                    {}
                )
                backends = [backend for backend in backends if
                            backend.name() == resolved_name]
            except LookupError:
                return []

        return filter_backends(backends, filters=filters, **kwargs)
```

また`resolve_backend_name`を呼び出しています。`deprecated`が循環しているとか、そういう変なことがなければ、何度やっても同じ結果が返ってくるはずです。今度は名前ではなくバックエンド自身を取り出しています。

`filter_backends`については、さほど大したことやってないのに長いので、気になる方は[qiskit/providers/providerutils.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/providers/providerutils.py)をご参照ください。

docstringとコメントだけ引用します。

```py
def filter_backends(backends, filters=None, **kwargs):
    """略
    Args:
        backends (list[BaseBackend]): list of backends.
        filters (callable): filtering conditions as a callable.
        **kwargs: dict of criteria.
    Returns:
        list[BaseBackend]: a list of backend instances matching the
            conditions.
    """
    # Inspect the backends to decide which filters belong to
    # backend.configuration and which ones to backend.status, as it does
    # not involve querying the API.

    # 1. Apply backend.configuration filtering.
    # 2. Apply backend.status filtering (it involves one API call for
    # each backend).
    # 3. Apply acceptor filter.
```

`kwargs`で、`backend.configuration`や`backend.status`に対する条件を与えます。
これらはまぜこぜで与えると、`filter_backends`側で振り分けてくれます。
さらに、関数を`filters`引数に与えると、Python組み込みの`filter`関数でフィルタした結果を返してくれます。

ちなみに、思いっきり話が逸れますが。Python組み込みの`filter`関数って、引数にNone渡してもいいんですね。知らなかった。

> Return an iterator yielding those items of iterable for which function(item)
is true. If function is None, return the items that are true.

らしいです。

```py
list(filter(None, [1, 2, 0, 3, "", "a"]))                                                        
# => [1, 2, 3, 'a']
```

ともかく、いろいろとたらい回しにされながらも、名前にマッチしたバックエンドを一つ得ることができました。
これで

```py
backend_sim = BasicAer.get_backend('qasm_simulator')
```

の行は読めたことになります。

# `execute`を読む
続いて

```py
result = execute(qc, backend_sim).result()
```

の行のうち、`execute`を読んでいきます。
[qiskit/execute.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/execute.py)から、docstringを削って引用します。

```py
def execute(experiments, backend,
            basis_gates=None, coupling_map=None,  # circuit transpile options
            backend_properties=None, initial_layout=None,
            seed_transpiler=None, optimization_level=None, pass_manager=None,
            qobj_id=None, qobj_header=None, shots=1024,  # common run options
            memory=False, max_credits=10, seed_simulator=None,
            default_qubit_los=None, default_meas_los=None,  # schedule run options
            schedule_los=None, meas_level=2, meas_return='avg',
            memory_slots=None, memory_slot_size=100, rep_time=None, parameter_binds=None,
            **run_config):
    # transpiling the circuits using given transpile options
    experiments = transpile(experiments,
                            basis_gates=basis_gates,
                            coupling_map=coupling_map,
                            backend_properties=backend_properties,
                            initial_layout=initial_layout,
                            seed_transpiler=seed_transpiler,
                            optimization_level=optimization_level,
                            backend=backend,
                            pass_manager=pass_manager,
                            )

    # assembling the circuits into a qobj to be run on the backend
    qobj = assemble(experiments,
                    qobj_id=qobj_id,
                    qobj_header=qobj_header,
                    shots=shots,
                    memory=memory,
                    max_credits=max_credits,
                    seed_simulator=seed_simulator,
                    default_qubit_los=default_qubit_los,
                    default_meas_los=default_meas_los,
                    schedule_los=schedule_los,
                    meas_level=meas_level,
                    meas_return=meas_return,
                    memory_slots=memory_slots,
                    memory_slot_size=memory_slot_size,
                    rep_time=rep_time,
                    parameter_binds=parameter_binds,
                    backend=backend,
                    **run_config
                    )

    # executing the circuits on the backend and returning the job
    return backend.run(qobj, **run_config)
```

## `transpile`を眺める
今回、トランスパイルの詳細については立ち入りません。
本当に表面だけ眺めます。

[qiskit/compiler/transpile.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/compiler/transpile.py)にあります。
docstringがとても長いので省略しますが、興味深いので、読まれることをおすすめします。

```py
def transpile(circuits,
              backend=None,
              basis_gates=None, coupling_map=None, backend_properties=None,
              initial_layout=None, seed_transpiler=None,
              optimization_level=None,
              pass_manager=None, callback=None, output_name=None):
    # transpiling schedules is not supported yet.
    if isinstance(circuits, Schedule) or \
            (isinstance(circuits, list) and all(isinstance(c, Schedule) for c in circuits)):
        return circuits

    if optimization_level is None:
        config = user_config.get_config()
        optimization_level = config.get('transpile_optimization_level', None)

    # Get TranspileConfig(s) to configure the circuit transpilation job(s)
    circuits = circuits if isinstance(circuits, list) else [circuits]
    transpile_configs = _parse_transpile_args(circuits, backend, basis_gates, coupling_map,
                                              backend_properties, initial_layout,
                                              seed_transpiler, optimization_level,
                                              pass_manager, callback, output_name)
    # Check circuit width against number of qubits in coupling_map(s)
    coupling_maps_list = list(config.coupling_map for config in transpile_configs)
    for circuit, parsed_coupling_map in zip(circuits, coupling_maps_list):
        # If coupling_map is not None
        if isinstance(parsed_coupling_map, CouplingMap):
            n_qubits = len(circuit.qubits)
            max_qubits = parsed_coupling_map.size()
            if n_qubits > max_qubits:
                raise TranspilerError('Number of qubits ({}) '.format(n_qubits) +
                                      'in {} '.format(circuit.name) +
                                      'is greater than maximum ({}) '.format(max_qubits) +
                                      'in the coupling_map')
    # Transpile circuits in parallel
    circuits = parallel_map(_transpile_circuit, list(zip(circuits, transpile_configs)))

    if len(circuits) == 1:
        return circuits[0]
    return circuits
```

実機などでは、CNOTなどがつながっていないゲートがありますので、それらを割付ながら回路を作っていく働きをしているようです。
さらに、その計算自体がしんどいので、並列化して計算しています。
引数を省略しても、バックエンドから適宜、設定を取ってきてくれるのは、バックエンドにいっぱい設定を付け加えたおかげと言えるでしょう。

回路を変換して回路自身を返します。

## `assemble`を眺める

[qiskit/compiler/assemble.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/compiler/assemble.py)にある`assemble`も、同様に眺めていきます。(docstringは省略)

```py
def assemble(experiments,
             backend=None,
             qobj_id=None, qobj_header=None,
             shots=1024, memory=False, max_credits=None, seed_simulator=None,
             qubit_lo_freq=None, meas_lo_freq=None,
             qubit_lo_range=None, meas_lo_range=None,
             schedule_los=None, meas_level=2, meas_return='avg', meas_map=None,
             memory_slot_size=100, rep_time=None, parameter_binds=None,
             **run_config):
    experiments = experiments if isinstance(experiments, list) else [experiments]
    qobj_id, qobj_header, run_config_common_dict = _parse_common_args(backend, qobj_id, qobj_header,
                                                                      shots, memory, max_credits,
                                                                      seed_simulator, **run_config)

    # assemble either circuits or schedules
    if all(isinstance(exp, QuantumCircuit) for exp in experiments):
        run_config = _parse_circuit_args(parameter_binds, **run_config_common_dict)

        # If circuits are parameterized, bind parameters and remove from run_config
        bound_experiments, run_config = _expand_parameters(circuits=experiments,
                                                           run_config=run_config)
        return assemble_circuits(circuits=bound_experiments, qobj_id=qobj_id,
                                 qobj_header=qobj_header, run_config=run_config)

    elif all(isinstance(exp, ScheduleComponent) for exp in experiments):
        run_config = _parse_pulse_args(backend, qubit_lo_freq, meas_lo_freq,
                                       qubit_lo_range, meas_lo_range,
                                       schedule_los, meas_level, meas_return,
                                       meas_map, memory_slot_size, rep_time,
                                       **run_config_common_dict)

        return assemble_schedules(schedules=experiments, qobj_id=qobj_id,
                                  qobj_header=qobj_header, run_config=run_config)

    else:
        raise QiskitError("bad input to assemble() function; "
                          "must be either circuits or schedules")
```

回路に対するアセンブルと、パルスのスケジュールに関するアセンブルができるようです。
返す値の型は`QObj`で、回路の場合は`QasmQObj`型が返ってきます。

<!--
オブジェクトが何持ってるかだけ見ていきます。`[Type]`は`Type`型のリスト、`Optional<Type>`は、`Type`型(省略可)を表すことにします。

- `class QasmQobj(Qobj):`
    - qobj_id: `str`
    - schema_version: `str` (QOBJ_VERSIONでよい)
    - header: `QobjHeader`
        - backend_name: `Optional<str>`
        - backend_version: `Optional<str>`
    - type: str (QobjType.QASM固定)
    - config: `QasmQobjConfig`
        - n_qubits: `Optional<int>`
        - max_credits: `Optional<int>`
        - seed_simulator: `Optional<int>`
        - memory_slots: `Optional<int>`
        - shots: `Optional<int>`
    - experiments: `[QasmQobjExperiment]`
- `class QasmQobjExperiment(QobjExperiment):`
    - header: `QobjExperimentHeader`
        - 中味なし
    - config: `QasmQobjExperimentConfig`
        - memory_slots: `Optional<int>`
        - n_qubits: `Optional<int>`
    - instructions: `[QasmQobjInstruction]`
- `class QasmQobjInstruction(QobjInstruction):`
    - name: `str`
    - qubits: `Optional<[int]>`
    - params: `Optional<[InstructionParameter]>`
    - memory: `Optional<[int]>`
    - conditional: `Optional<int>`
-->

`QasmQobj`型がどういうデータ構造か分からないと、この後で困るので、

```py
from qiskit import * 
qc = QuantumCircuit(2, 2) 
qc.h(0) 
qc.cx(0, 1) 
qc.measure([0,1], [0,1]) 
backend_sim = BasicAer.get_backend('qasm_simulator') 
tran = transpile(qc, backend=backend_sim)
asm = assemble(qc, backend=backend_sim)
```

の`asm`を見ると、以下のようになっていました。

```py
QasmQobj(
    config=QasmQobjConfig(
        memory=False,
        memory_slots=2,
        n_qubits=2,
        parameter_binds=[],
        shots=1024),
    experiments=[
        QasmQobjExperiment(
            config=QasmQobjExperimentConfig(memory_slots=2, n_qubits=2),
            header=QobjExperimentHeader(
                clbit_labels=[['c', 0], ['c', 1]],
                creg_sizes=[['c', 2]],
                memory_slots=2,
                n_qubits=2,
                name='circuit0',
                qreg_sizes=[['q', 2]],
                qubit_labels=[['q', 0], ['q', 1]]),
            instructions=[
                QasmQobjInstruction(
                    name='u2', params=[0, 3.14159265358979], qubits=[0]),
                QasmQobjInstruction(name='cx', qubits=[0, 1]),
                QasmQobjInstruction(memory=[0], name='measure', qubits=[0]),
                QasmQobjInstruction(memory=[1], name='measure', qubits=[1])
            ])
    ],
    header=QobjHeader(backend_name='qasm_simulator', backend_version='2.0.0'),
    qobj_id='06682d2e-bfd8-4dba-ba8e-4e46492c1609',
    schema_version='1.1.0',
    type='QASM')
```

特に問題なく読めるかと思います。ちなみにHゲートは`u2(0, π)`に変換されています。これは等価な表現です。

## `backend.run`を見る
回路を`QasmQobj`に変換したので、次は

```py
    return backend.run(qobj, **run_config)
```

を読んでいきます。[qiskit/providers/basicaer/qasm_simulator.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/providers/basicaer/qasm_simulator.py)

```py
    def run(self, qobj, backend_options=None):
        """Run qobj asynchronously.
        Args:
            qobj (Qobj): payload of the experiment
            backend_options (dict): backend options
        Returns:
            BasicAerJob: derived from BaseJob
        Additional Information:
            backend_options: Is a dict of options for the backend. It may contain
                * "initial_statevector": vector_like
            The "initial_statevector" option specifies a custom initial
            initial statevector for the simulator to be used instead of the all
            zero state. This size of this vector must be correct for the number
            of qubits in all experiments in the qobj.
            Example::
                backend_options = {
                    "initial_statevector": np.array([1, 0, 0, 1j]) / np.sqrt(2),
                }
        """
        self._set_options(qobj_config=qobj.config,
                          backend_options=backend_options)
        job_id = str(uuid.uuid4())
        job = BasicAerJob(self, job_id, self._run_job, qobj)
        job.submit()
        return job
```

オプションに初期ベクトルを入れられるのはいいですね。完全にバックエンド依存になってしまいそうですが。
`chop_threshold`オプションは、状態ベクトルをシミュレートする際に、この値以下だと値を`0`として扱うらしいです。

### `QasmSimulatorPy._set_options`を読む
`_set_options`読んでいきます。

```py
    def _set_options(self, qobj_config=None, backend_options=None):
        """Set the backend options for all experiments in a qobj"""
        # Reset default options
        self._initial_statevector = self.DEFAULT_OPTIONS["initial_statevector"]
        self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"]
        if backend_options is None:
            backend_options = {}

        # Check for custom initial statevector in backend_options first,
        # then config second
        if 'initial_statevector' in backend_options:
            self._initial_statevector = np.array(backend_options['initial_statevector'],
                                                 dtype=complex)
        elif hasattr(qobj_config, 'initial_statevector'):
            self._initial_statevector = np.array(qobj_config.initial_statevector,
                                                 dtype=complex)
        if self._initial_statevector is not None:
            # Check the initial statevector is normalized
            norm = np.linalg.norm(self._initial_statevector)
            if round(norm, 12) != 1:
                raise BasicAerError('initial statevector is not normalized: ' +
                                    'norm {} != 1'.format(norm))
        # Check for custom chop threshold
        # Replace with custom options
        if 'chop_threshold' in backend_options:
            self._chop_threshold = backend_options['chop_threshold']
        elif hasattr(qobj_config, 'chop_threshold'):
            self._chop_threshold = qobj_config.chop_threshold
```

バックエンド自身にオプションを持たせている都合上

```py
        # Reset default options
        self._initial_statevector = self.DEFAULT_OPTIONS["initial_statevector"]
        self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"]
```

がこんなところで出てきてしまっています。ともかく、ただ2つのオプションを解釈してqobjにセットしてるだけです。

### `BasicAerJob.__init__`を読む
```py
        job_id = str(uuid.uuid4())
        job = BasicAerJob(self, job_id, self._run_job, qobj)
```

job_idとして適当なUUIDを降っています。
jobとして`BasicAerJob`を作っています。`self._run_job`はメソッドですが、メソッドをそのまま渡しています。後で呼び出されたときに見ましょう。

[qiskit/providers/basicaer/basicaerjob.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/providers/basicaer/basicaerjob.py)

```py
class BasicAerJob(BaseJob):
    """BasicAerJob class.
    Attributes:
        _executor (futures.Executor): executor to handle asynchronous jobs
    """

    if sys.platform in ['darwin', 'win32']:
        _executor = futures.ThreadPoolExecutor()
    else:
        _executor = futures.ProcessPoolExecutor()

    def __init__(self, backend, job_id, fn, qobj):
        super().__init__(backend, job_id)
        self._fn = fn
        self._qobj = qobj
        self._future = None
```

データ作っただけで、この時点では何もしていません。
Windows, Macではスレッドプールを、その他ではプロセスプールを使っていることは、若干気になりました。
PythonはマルチスレッドではCPUが複数あっても、1スレッドしか同時に動きません。
どう使われるのか気にしておきましょう。

### `BasicAerJob.submit`を読む

```py
        job.submit()
        return job
```

jobをsubmitしてから、jobを返しています。`BasicAerJob.submit`を読んでいきましょう。

```py
    def submit(self):
        """Submit the job to the backend for execution.
        Raises:
            QobjValidationError: if the JSON serialization of the Qobj passed
            during construction does not validate against the Qobj schema.
            JobError: if trying to re-submit the job.
        """
        if self._future is not None:
            raise JobError("We have already submitted the job!")

        validate_qobj_against_schema(self._qobj)
        self._future = self._executor.submit(self._fn, self._job_id, self._qobj)
```

```py
        validate_qobj_against_schema(self._qobj)
```

については、Qobjが、[別ファイルに定義されているJSONスキーマ](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/schemas/qobj_schema.json)の形式に合っているかを、外部ライブラリの`jsonschema`を使って検証しているだけでした。あまり面白くないので省略します。

`_executor`は、Python標準ライブラリ`concurrent.futures`の`ThreadPoolExecutor`または`ProcessPoolExecutor`でした。その`submit`メソッドを呼んでいます。

[公式ドキュメント](https://docs.python.org/ja/3/library/concurrent.futures.html#concurrent.futures.Executor.submit)を読んでみましょう。

> `submit(fn, *args, **kwargs)`
> 呼び出し可能オブジェクト `fn` を、 `fn(*args **kwargs)` として実行するようにスケジュールし、呼び出し可能オブジェクトの実行を表現する [Future](https://docs.python.org/ja/3/library/concurrent.futures.html#concurrent.futures.Future) オブジェクトを返します。

つまり、`self._fn(self._job_id, self._qobj)`を実行するようにスケジュールするための`Future`オブジェクトを作っていることになります。

この`Future`オブジェクトは非同期実行のためのもので、`_executor`によって裏で動かされながら、計算が終わってなくてもとりあえず処理は進みます。
(ハンバーガー屋さんでハンバーガーを注文したら、出来上がるまでカウンターの前で待っていても構いませんが、番号札をもらえば、先に席を取るとか、別のことができて時間が有意義に過ごせます。通常の関数呼び出しだと、結果が返ってくるまで待つ必要がありますが、非同期実行では、返ってくるまで待たずに別のことができます。`Future`オブジェクトは、将来、ハンバーガーを受け取るための番号札のようなものです)

## `QasmSimulatorPy._run_job`を読む

`_executor`よって動かされる`self._fn(self._job_id, self._qobj)`って何だったかを思い出すと、`job = BasicAerJob(self, job_id, self._run_job, qobj)`で作った`self._run_job(job_id, qobj)`でした。

なので`QasmSimulatorPy._run_job`を読んでみましょう。

```py
    def _run_job(self, job_id, qobj):
        """Run experiments in qobj
        Args:
            job_id (str): unique id for the job.
            qobj (Qobj): job description
        Returns:
            Result: Result object
        """
        self._validate(qobj)
        result_list = []
        self._shots = qobj.config.shots
        self._memory = getattr(qobj.config, 'memory', False)
        self._qobj_config = qobj.config
        start = time.time()
        for experiment in qobj.experiments:
            result_list.append(self.run_experiment(experiment))
        end = time.time()
        result = {'backend_name': self.name(),
                  'backend_version': self._configuration.backend_version,
                  'qobj_id': qobj.qobj_id,
                  'job_id': job_id,
                  'results': result_list,
                  'status': 'COMPLETED',
                  'success': True,
                  'time_taken': (end - start),
                  'header': qobj.header.to_dict()}

        return Result.from_dict(result)
```

- `self._validate` (後で読みます)
- バックエンド自体に必要な情報をセット
- タイマー開始
    - `qobj.experiments`から`experiment`を1個ずつ取り出して
        - `result_list`に各`experiment`を`run_experiment` (後で読みます)した結果を追加
- タイマー終了　
- 結果を辞書に詰めて、`Result`型に変換して(後で読みます)返す

### `QasmSimulatorPy._validate`を読む

```py
    def _validate(self, qobj):
        """Semantic validations of the qobj which cannot be done via schemas."""
        n_qubits = qobj.config.n_qubits
        max_qubits = self.configuration().n_qubits
        if n_qubits > max_qubits:
            raise BasicAerError('Number of qubits {} '.format(n_qubits) +
                                'is greater than maximum ({}) '.format(max_qubits) +
                                'for "{}".'.format(self.name()))
        for experiment in qobj.experiments:
            name = experiment.header.name
            if experiment.config.memory_slots == 0:
                logger.warning('No classical registers in circuit "%s", '
                               'counts will be empty.', name)
            elif 'measure' not in [op.name for op in experiment.instructions]:
                logger.warning('No measurements in circuit "%s", '
                               'classical register will remain all zeros.', name)
```

qubit数が最大値を越えていないことの確認と、古典レジスタがあるか、測定が行われているかの確認をしています。
(古典レジスタ無し、測定無しはエラーではなく警告扱い)

### `QasmSimulatorPy.run_experiment`を眺める
その前に、`experiments`が何でできていたか思い出しておきましょう。

```py
    experiments=[
        QasmQobjExperiment(
            config=QasmQobjExperimentConfig(memory_slots=2, n_qubits=2),
            header=QobjExperimentHeader(
                clbit_labels=[['c', 0], ['c', 1]],
                creg_sizes=[['c', 2]],
                memory_slots=2,
                n_qubits=2,
                name='circuit0',
                qreg_sizes=[['q', 2]],
                qubit_labels=[['q', 0], ['q', 1]]),
            instructions=[
                QasmQobjInstruction(
                    name='u2', params=[0, 3.14159265358979], qubits=[0]),
                QasmQobjInstruction(name='cx', qubits=[0, 1]),
                QasmQobjInstruction(memory=[0], name='measure', qubits=[0]),
                QasmQobjInstruction(memory=[1], name='measure', qubits=[1])
            ])
    ],
```

`QasmQobjExperiment`がひとつの量子回路に対応しています。Qiskitでは、量子回路をいくつも詰めて実行できるんですが、今回はひとつだけのようです。
`for experiment in qobj.experiments:`のループを回していたので、各回路ごとに`run_experiment`を呼び出していることになります。回路には`instructions`があり、これはゲートや測定に相当します。

`run_experiment`は本当に長いので省略します。ざっくりとは、`shots`の回数だけゲートの計算、測定の計算を行って、

```py
        return {'name': experiment.header.name,
                'seed_simulator': seed_simulator,
                'shots': self._shots,
                'data': data,
                'status': 'DONE',
                'success': True,
                'time_taken': (end - start),
                'header': experiment.header.to_dict()}
```

のような結果を返します。
なお、`data`は

```py
        # Add data
        data = {'counts': dict(Counter(memory))}
        # Optionally add memory list
        if self._memory:
            data['memory'] = memory
        # Optionally add final statevector
        if self.SHOW_FINAL_STATE:
            data['statevector'] = self._get_statevector()
            # Remove empty counts and memory for statevector simulator
            if not data['counts']:
                data.pop('counts')
            if 'memory' in data and not data['memory']
```

のように、測定データなどをもたせています。

### `qiskit.result.Result`を読む

辞書型を`return Result.from_dict(result)`によって`Result`型にしています。
[qiskit/result/result.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/result/result.py)

```py
@bind_schema(ResultSchema)
class Result(BaseModel):
    """Model for Results.
    Please note that this class only describes the required fields. For the
    full description of the model, please check ``ResultSchema``.
    Attributes:
        backend_name (str): backend name.
        backend_version (str): backend version, in the form X.Y.Z.
        qobj_id (str): user-generated Qobj id.
        job_id (str): unique execution id from the backend.
        success (bool): True if complete input qobj executed correctly. (Implies
            each experiment success)
        results (list[ExperimentResult]): corresponding results for array of
            experiments of the input qobj
    """

    def __init__(self, backend_name, backend_version, qobj_id, job_id, success,
                 results, **kwargs):
        self.backend_name = backend_name
        self.backend_version = backend_version
        self.qobj_id = qobj_id
        self.job_id = job_id
        self.success = success
        self.results = results
```

本来は`from_dict`を読みたいのですが、qiskit-terraの特徴のひとつとして、JSONスキーマ周りの処理をすごくがんばっています。`from_dict`は親クラスの`BaseModel`で定義されていて、その中味が

```py
    @classmethod
    def from_dict(cls, dict_):
        """Deserialize a dict of simple types into an instance of this class.
        Note that this method requires that the model is bound with
        ``@bind_schema``.
        """
        try:
            data = cls.schema.load(dict_)
        except ValidationError as ex:
            raise ModelValidationError(
                ex.messages, ex.field_name, ex.data, ex.valid_data, **ex.kwargs) from None

        return data
```

と、とてもシンプルになっています。`Result`クラスとは別に`ResultSchema`クラスを作って

```py
class ResultSchema(BaseSchema):
    """Schema for Result."""

    # Required fields.
    backend_name = String(required=True)
    backend_version = String(required=True,
                             validate=Regexp('[0-9]+.[0-9]+.[0-9]+$'))
    qobj_id = String(required=True)
    job_id = String(required=True)
    success = Boolean(required=True)
    results = Nested(ExperimentResultSchema, required=True, many=True)

    # Optional fields.
    date = DateTime()
    status = String()
    header = Nested(ObjSchema)
```

とする、など、かなりちゃんとやっています。([/qiskit/result/models.py](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/result/models.py)で定義)
ちゃんとは読めてないんですが、これらのデータ型になっていることを確認しながら、`ExperimentResultSchema`などは再帰的にオブジェクトにしていくのではないかと思います。

いずれにせよ、データの詰め替えなので、今回は省略させてください。

手元で動かして返ってきた`Result`は、次のようになっていました。

```py
Result(
    backend_name='qasm_simulator',
    backend_version='2.0.0',
    header=Obj(backend_name='qasm_simulator', backend_version='2.0.0'),
    job_id='65b162ae-fe6b-480a-85ba-8c890d8bbf3b',
    qobj_id='c75e9b2c-da7c-4dd6-96da-d132126e6dc0',
    results=[
        ExperimentResult(
            data=ExperimentResultData(counts=Obj(0x0=493, 0x3=531)),
            header=Obj(
                clbit_labels=[['c', 0], ['c', 1]],
                creg_sizes=[['c', 2]],
                memory_slots=2,
                n_qubits=2,
                name='circuit0',
                qreg_sizes=[['q', 2]],
                qubit_labels=[['q', 0], ['q', 1]]),
            meas_level=2,
            name='circuit0',
            seed_simulator=1480888590,
            shots=1024,
            status='DONE',
            success=True,
            time_taken=0.12355875968933105)
    ],
    status='COMPLETED',
    success=True,
    time_taken=0.12369537353515625)
```

# `BasicAerJob.result()`を読む
相当、途中を飛ばしましたが、

```py
result = execute(qc, backend_sim).result()
```

の`execute(qc, backend_sim)`が終わり、`.result()`を見ていきます。
`execute`は`BasicAerJob`を返すんでしたね。

```py
    @requires_submit
    def result(self, timeout=None):
        # pylint: disable=arguments-differ
        """Get job result. The behavior is the same as the underlying
        concurrent Future objects,
        https://docs.python.org/3/library/concurrent.futures.html#future-objects
        Args:
            timeout (float): number of seconds to wait for results.
        Returns:
            qiskit.Result: Result object
        Raises:
            concurrent.futures.TimeoutError: if timeout occurred.
            concurrent.futures.CancelledError: if job cancelled before completed.
        """
        return self._future.result(timeout=timeout)
```

最初の`@requires_submit`は、`submit`をしたかどうかを見ています。
`submit`したかどうかは、`BasicAerJob`を作ったときに`self._future = None`としていましたが、`submit`すると`self._future`に`Future`オブジェクトが入るので、`None`になっていないかチェックをしたら分かります。

これは、普通に`Future`オブジェクトの`result`メソッドを呼び出しています。
[公式ドキュメント](https://docs.python.org/ja/3/library/concurrent.futures.html#concurrent.futures.Future)を見ましょう。

> `result(timeout=None)`
> 呼び出しによって返された値を返します。呼び出しがまだ完了していない場合、このメソッドは timeout 秒の間待機します。呼び出しが timeout 秒間の間に完了しない場合、 concurrent.futures.TimeoutError が送出されます。 timeout にはintかfloatを指定できます。timeout が指定されていないか、 None である場合、待機時間に制限はありません。
> future が完了する前にキャンセルされた場合 CancelledError が送出されます。
> 呼び出しが例外を送出した場合、このメソッドは同じ例外を送出します。

終わっていたら結果を返して、終わってなかったら待ってから結果を返す、というメソッドです。
結果は、上で見たとおり`Result`型のオブジェクトです。

# `Result.get_counts`を読む

とうとう最後の行です。

```py
print(result.get_counts(qc))
```

を見ていきます。

```py
    def get_counts(self, experiment=None):
        """Get the histogram data of an experiment.
        Args:
            experiment (str or QuantumCircuit or Schedule or int or None): the index of the
                experiment, as specified by ``get_data()``.
        Returns:
            dict[str:int]: a dictionary with the counts for each qubit, with
                the keys containing a string in binary format and separated
                according to the registers in circuit (e.g. ``0100 1110``).
                The string is little-endian (cr[0] on the right hand side).
        Raises:
            QiskitError: if there are no counts for the experiment.
        """
        exp = self._get_experiment(experiment)
        try:
            header = exp.header.to_dict()
        except (AttributeError, QiskitError):  # header is not available
            header = None

        if 'counts' in self.data(experiment).keys():
            return postprocess.format_counts(self.data(experiment)['counts'],
                                             header)
        elif 'statevector' in self.data(experiment).keys():
            vec = postprocess.format_statevector(self.data(experiment)['statevector'])
            return state_to_counts(vec)
        else:
            raise QiskitError('No counts for experiment "{0}"'.format(experiment))
```

`_get_experiment`では、回路名に対応した`experiment`を取り出しています。今回のように`experiment`がひとつの場合は、`experiment`引数を省略しても構いません。

`experiment`からヘッダーが取り出せたら、古典レジスタの長さが分かるので、測定結果をいい感じにゼロ埋めできます。
`format_counts`ではいい感じに文字列に変換して辞書に詰めるのをやっています。

# まとめ
今回、Qiskitで、量子計算に直接関わるところを除いた処理の流れを読んでいきました。
度重なる変更や、将来を見越した拡張性などから、とてもしんどい仕様になっている部分もありましたが、かなりいろんなことができるように作られていて、また、トランスパイルやJSONへの変換など、Blueqatでは扱っていない部分がかなり重厚で、大変勉強になる、という印象を受けました。

全体の流れを読む、という目標は今回で終了ですが、今後もQiskitソースコードリーディングは続けていこうと思います。