#@title 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
#
# https://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.

"""
Explanation: Copyright 2020 The TensorFlow Authors.
End of explanation
"""


!pip install tensorflow==2.7.0

"""
Explanation: こんにちは、多くの世界
<table class="tfo-notebook-buttons" align="left">
  <td><a target="_blank" href="https://www.tensorflow.org/quantum/tutorials/hello_many_worlds"><img src="https://www.tensorflow.org/images/tf_logo_32px.png">TensorFlow.org で表示</a></td>
  <td><a target="_blank" href="https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/quantum/tutorials/hello_many_worlds.ipynb"><img src="https://www.tensorflow.org/images/colab_logo_32px.png">Google Colabで実行</a></td>
  <td><a target="_blank" href="https://github.com/tensorflow/docs-l10n/blob/master/site/ja/quantum/tutorials/hello_many_worlds.ipynb"><img src="https://www.tensorflow.org/images/GitHub-Mark-32px.png">GitHub でソースを表示</a></td>
  <td><a href="https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ja/quantum/tutorials/hello_many_worlds.ipynb"><img src="https://www.tensorflow.org/images/download_logo_32px.png">ノートブックをダウンロード</a></td>
</table>

このチュートリアルでは、古典的なニューラルネットワークが量子ビット・キャリブレーションエラーの訂正を学習する方法を紹介します。<a target="_blank" href="https://github.com/quantumlib/Cirq" class="external">Circq</a> は NISQ（ノイズの多い中間スケール量子）回路を作成、編集、呼び出すための Python フレームワークであり、ここでは Cirq が TensorFlow Quantum とどのようにやり取りするかを示します。
セットアップ
End of explanation
"""


!pip install tensorflow-quantum

# Update package resources to account for version changes.
import importlib, pkg_resources
importlib.reload(pkg_resources)

"""
Explanation: TensorFlow Quantum をインストールします。
End of explanation
"""


import tensorflow as tf
import tensorflow_quantum as tfq

import cirq
import sympy
import numpy as np

# visualization tools
%matplotlib inline
import matplotlib.pyplot as plt
from cirq.contrib.svg import SVGCircuit

"""
Explanation: 次に、TensorFlow とモジュールの依存関係をインポートします。
End of explanation
"""


a, b = sympy.symbols('a b')

"""
Explanation: 1. 基本
1.1 Cirq とパラメータ化された量子回路
TensorFlow Quantum (TFQ) について説明する前に、<a target="_blank" href="https://github.com/quantumlib/Cirq" class="external">Circq</a> の基本をいくつか見てみましょう。Cirq は、Google の量子コンピューティング用の Python ライブラリで、静的ゲートやパラメータ化されたゲートなどの回路の定義に使用します。
Cirq は、<a target="_blank" href="https://www.sympy.org" class="external">SymPy</a> シンボルを使用して自由パラメータを表します。
End of explanation
"""


# Create two qubits
q0, q1 = cirq.GridQubit.rect(1, 2)

# Create a circuit on these qubits using the parameters you created above.
circuit = cirq.Circuit(
    cirq.rx(a).on(q0),
    cirq.ry(b).on(q1), cirq.CNOT(control=q0, target=q1))

SVGCircuit(circuit)

"""
Explanation: 次のコードは、上記のパラメータを使用して 2 つの量子ビット回路を作成します。
End of explanation
"""


# Calculate a state vector with a=0.5 and b=-0.5.
resolver = cirq.ParamResolver({a: 0.5, b: -0.5})
output_state_vector = cirq.Simulator().simulate(circuit, resolver).final_state_vector
output_state_vector

"""
Explanation: 回路を評価するには、cirq.Simulatorインターフェースを使用します。回路内の自由パラメータを特定の数値に置き換えるには、cirq.ParamResolverオブジェクトを渡します。以下のコードは、パラメータ化された回路の生の状態ベクトル出力を計算します。
End of explanation
"""


z0 = cirq.Z(q0)

qubit_map={q0: 0, q1: 1}

z0.expectation_from_state_vector(output_state_vector, qubit_map).real

z0x1 = 0.5 * z0 + cirq.X(q1)

z0x1.expectation_from_state_vector(output_state_vector, qubit_map).real

"""
Explanation: 状態ベクトルは、シミュレーションの外から直接アクセスすることはできません（上記の複素数出力に注意してください）。物理的に現実的にするには、状態ベクトルを古典的コンピュータが理解できる実数に変換する測定値を指定する必要があります。Cirq は、<a target="_blank" href="https://en.wikipedia.org/wiki/Pauli_matrices" class="external">Pauli 演算子</a> $\hat{X}$, $\hat{Y}$ および $\hat{Z}$ の組み合わせを使用して測定値を指定します。例として、次のコードは、シミュレーションした状態ベクトルで $\hat{Z}_0$ と $\frac{1}{2}\hat{Z}_0 + \hat{X}_1$ を測定します。
End of explanation
"""


# Rank 1 tensor containing 1 circuit.
circuit_tensor = tfq.convert_to_tensor([circuit])

print(circuit_tensor.shape)
print(circuit_tensor.dtype)

"""
Explanation: 1.2 テンソルとしての量子回路
TensorFlow Quantum (TFQ) は、Cirq オブジェクトをテンソルに変換する関数であるtfq.convert_to_tensorを提供します。これにより、Cirq オブジェクトを<a target="_blank" href="https://www.tensorflow.org/quantum/api_docs/python/tfq/layers">量子レイヤー</a>および<a target="_blank" href="https://www.tensorflow.org/quantum/api_docs/python/tfq/get_expectation_op">量子演算</a>に送信できます。この関数は、Cirq Circuits と Cirq Paulis のリストまたは配列で呼び出すことができます。
End of explanation
"""


# Rank 1 tensor containing 2 Pauli operators.
pauli_tensor = tfq.convert_to_tensor([z0, z0x1])
pauli_tensor.shape

"""
Explanation: これは、Cirq オブジェクトをtf.stringテンソルとしてエンコードし、tfq演算は必要に応じてデコードします。
End of explanation
"""


batch_vals = np.array(np.random.uniform(0, 2 * np.pi, (5, 2)), dtype=np.float32)

"""
Explanation: 1.3 バッチ回路シミュレーション
TFQ は、期待値、サンプル、および状態ベクトルを計算するためのメソッドを提供します。まず、期待値から見ていきましょう。
期待値を計算するための最高レベルのインターフェースは、tf.keras.Layerであるtfq.layers.Expectationレイヤーです。最も単純な形式では、このレイヤーは、多くのcirq.ParamResolversでパラメータ化された回路をシミュレートすることと同等ですが、TFQ では TensorFlow セマンティクスに従ったバッチ処理が可能であり、回路は効率的な C++ コードを使用してシミュレートされます。
aとbパラメータの代わりに値のバッチを作成します。
End of explanation
"""


cirq_results = []
cirq_simulator = cirq.Simulator()

for vals in batch_vals:
    resolver = cirq.ParamResolver({a: vals[0], b: vals[1]})
    final_state_vector = cirq_simulator.simulate(circuit, resolver).final_state_vector
    cirq_results.append(
        [z0.expectation_from_state_vector(final_state_vector, {
            q0: 0,
            q1: 1
        }).real])

print('cirq batch results: \n {}'.format(np.array(cirq_results)))

"""
Explanation: Cirq のパラメータ値に対するバッチ回路の実行には、ループが必要です。
End of explanation
"""


tfq.layers.Expectation()(circuit,
                         symbol_names=[a, b],
                         symbol_values=batch_vals,
                         operators=z0)

"""
Explanation: TFQ では同じ演算が簡略化されています。
End of explanation
"""


# Parameters that the classical NN will feed values into.
control_params = sympy.symbols('theta_1 theta_2 theta_3')

# Create the parameterized circuit.
qubit = cirq.GridQubit(0, 0)
model_circuit = cirq.Circuit(
    cirq.rz(control_params[0])(qubit),
    cirq.ry(control_params[1])(qubit),
    cirq.rx(control_params[2])(qubit))

SVGCircuit(model_circuit)

"""
Explanation: 2. 量子古典ハイブリッドの最適化
以上は基本の説明でした。次に、TensorFlow Quantum を使用して量子古典ハイブリッドニューラルネットを構築しましょう。古典的なニューラルネットをトレーニングして、1 つの量子ビットを制御します。コントロールは、0または1の状態の量子ビットを正しく準備するように最適化され、シミュレートされた系統的なキャリブレーションエラーを克服します。以下の図は、アーキテクチャを示しています。
<img src="https://github.com/tensorflow/docs-l10n/blob/master/site/ja/quantum/tutorials/images/nn_control1.png?raw=true">
これはニューラルネットワークがなくても簡単に解決できる問題ですが、テーマは TFQ を使用して解決できる実際の量子制御の問題と似ています。これは、tf.keras.Model内のtfq.layers.ControlledPQC (Parametrized Quantum Circuit) レイヤーを使用した量子古典計算のエンドツーエンドの例を示しています。
このチュートリアルの実装では、アーキテクチャは 3 つの部分に分かれています。

入力回路またはデータポイント回路：最初の 3 つの $R$ ゲート。
制御回路：その他の 3 つの $R$ ゲート。
コントローラ：制御回路のパラメータを設定する古典的なニューラルネットワーク。

2.1 制御回路の定義
上の図に示すように、学習可能なシングルビットローテーションを定義します。これは、制御回路に対応します。
End of explanation
"""


# The classical neural network layers.
controller = tf.keras.Sequential([
    tf.keras.layers.Dense(10, activation='elu'),
    tf.keras.layers.Dense(3)
])

"""
Explanation: 2.2 コントローラ
次に、コントローラネットワークを定義します。
End of explanation
"""


controller(tf.constant([[0.0],[1.0]])).numpy()

"""
Explanation: コントローラにコマンドのバッチを与えると、制御された回路の制御信号のバッチが出力されます。
コントローラはランダムに初期化されるため、これらの出力はまだ有用ではありません。
End of explanation
"""


# This input is the simulated miscalibration that the model will learn to correct.
circuits_input = tf.keras.Input(shape=(),
                                # The circuit-tensor has dtype `tf.string` 
                                dtype=tf.string,
                                name='circuits_input')

# Commands will be either `0` or `1`, specifying the state to set the qubit to.
commands_input = tf.keras.Input(shape=(1,),
                                dtype=tf.dtypes.float32,
                                name='commands_input')


"""
Explanation: 2.3 コントローラを回路に接続する
tfqを使用して、コントローラを 1 つのkeras.Modelとして制御回路に接続します。
このスタイルのモデル定義の詳細については、Keras Functional API ガイドをご覧ください。
まず、モデルへの入力を定義します。
End of explanation
"""


dense_2 = controller(commands_input)

# TFQ layer for classically controlled circuits.
expectation_layer = tfq.layers.ControlledPQC(model_circuit,
                                             # Observe Z
                                             operators = cirq.Z(qubit))
expectation = expectation_layer([circuits_input, dense_2])

"""
Explanation: 次に、これらの入力に演算を適用して、計算を定義します。
End of explanation
"""


# The full Keras model is built from our layers.
model = tf.keras.Model(inputs=[circuits_input, commands_input],
                       outputs=expectation)

"""
Explanation: 次に、この計算をtf.keras.Modelとしてパッケージ化します。
End of explanation
"""


tf.keras.utils.plot_model(model, show_shapes=True, dpi=70)

"""
Explanation: ネットワークアーキテクチャは、以下のモデルのプロットで示されています。このモデルプロットをアーキテクチャ図と比較して、正確さを確認します。
注意: graphvizパッケージのシステムインストールが必要になる場合があります。
End of explanation
"""


# The command input values to the classical NN.
commands = np.array([[0], [1]], dtype=np.float32)

# The desired Z expectation value at output of quantum circuit.
expected_outputs = np.array([[1], [-1]], dtype=np.float32)

"""
Explanation: このモデルは、コントローラのコマンドと、コントローラが出力を修正しようとしている入力回路の 2 つの入力を受け取ります。 
2.4 データセット
モデルは、コマンドごとに $\hat{Z}$ の正しい測定値の出力を試行します。コマンドと正しい値の定義は以下のとおりです。
End of explanation
"""


random_rotations = np.random.uniform(0, 2 * np.pi, 3)
noisy_preparation = cirq.Circuit(
  cirq.rx(random_rotations[0])(qubit),
  cirq.ry(random_rotations[1])(qubit),
  cirq.rz(random_rotations[2])(qubit)
)
datapoint_circuits = tfq.convert_to_tensor([
  noisy_preparation
] * 2)  # Make two copied of this circuit

"""
Explanation: これは、このタスクのトレーニングデータセット全体ではありません。データセット内の各データポイントにも入力回路が必要です。
2.4 入力回路の定義
以下の入力回路は、モデルが修正することを学習するためのランダムな誤校正を定義します。
End of explanation
"""


datapoint_circuits.shape

"""
Explanation: 回路には 2 つのコピーがあります（データポイントごとに 1 つずつ）。
End of explanation
"""


model([datapoint_circuits, commands]).numpy()

"""
Explanation: 2.5 トレーニング
定義された入力を使用して、tfqモデルのテストランを実行します。
End of explanation
"""


optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)
loss = tf.keras.losses.MeanSquaredError()
model.compile(optimizer=optimizer, loss=loss)
history = model.fit(x=[datapoint_circuits, commands],
                    y=expected_outputs,
                    epochs=30,
                    verbose=0)

plt.plot(history.history['loss'])
plt.title("Learning to Control a Qubit")
plt.xlabel("Iterations")
plt.ylabel("Error in Control")
plt.show()

"""
Explanation: 次に、標準のトレーニングプロセスを実行して、これらの値をexpected_outputsに向けて調整します。
End of explanation
"""


def check_error(command_values, desired_values):
  """Based on the value in `command_value` see how well you could prepare
  the full circuit to have `desired_value` when taking expectation w.r.t. Z."""
  params_to_prepare_output = controller(command_values).numpy()
  full_circuit = noisy_preparation + model_circuit

  # Test how well you can prepare a state to get expectation the expectation
  # value in `desired_values`
  for index in [0, 1]:
    state = cirq_simulator.simulate(
        full_circuit,
        {s:v for (s,v) in zip(control_params, params_to_prepare_output[index])}
    ).final_state_vector
    expt = cirq.Z(qubit).expectation_from_state_vector(state, {qubit: 0}).real
    print(f'For a desired output (expectation) of {desired_values[index]} with'
          f' noisy preparation, the controller\nnetwork found the following '
          f'values for theta: {params_to_prepare_output[index]}\nWhich gives an'
          f' actual expectation of: {expt}\n')


check_error(commands, expected_outputs)

"""
Explanation: このプロットから、ニューラルネットワークが体系的なキャリブレーションエラーを訂正することを学習したことがわかります。
2.6 出力の確認
次に、トレーニング済みモデルを使用して、量子ビット・キャリブレーションエラーを修正します。Cirq を使用する場合は以下のとおりです。
End of explanation
"""


model([datapoint_circuits, commands])

"""
Explanation: トレーニング中の損失関数の値から、モデルの学習がどれほど進んでいるかが大まかに分かります。損失が小さいほど、上記のセルの期待値はDesired_valuesに近くなります。パラメータ値に関心がない場合は、tfqを使用して上記からの出力をいつでも確認できます。
End of explanation
"""


# Define inputs.
commands_input = tf.keras.layers.Input(shape=(1),
                                       dtype=tf.dtypes.float32,
                                       name='commands_input')
circuits_input = tf.keras.Input(shape=(),
                                # The circuit-tensor has dtype `tf.string` 
                                dtype=tf.dtypes.string,
                                name='circuits_input')
operators_input = tf.keras.Input(shape=(1,),
                                 dtype=tf.dtypes.string,
                                 name='operators_input')

"""
Explanation: 3 さまざまな演算子の固有状態の準備について学ぶ
1 と 0 に対応する $\pm \hat{Z}$ 固有状態の選択は任意でした。1 を $+ \hat{Z}$ 固有状態に対応させ、0 を $-\hat{X}$ 固有状態に対応させることも簡単にできます。そのためには、次の図に示すように、コマンドごとに異なる測定演算子を指定します。
<img src="https://github.com/tensorflow/docs-l10n/blob/master/site/ja/quantum/tutorials/images/nn_control2.png?raw=true">
これには、<code>tfq.layers.Expectation</code>を使用する必要があります。これで、入力は、回路、コマンド、および演算子の 3 つのオブジェクトを含むようになりました。出力は期待値のままです。
3.1 新しいモデルの定義
このタスクを実行するためのモデルを見てみましょう。
End of explanation
"""


# Define classical NN.
controller = tf.keras.Sequential([
    tf.keras.layers.Dense(10, activation='elu'),
    tf.keras.layers.Dense(3)
])

"""
Explanation: コントローラネットワークは次のとおりです。
End of explanation
"""


dense_2 = controller(commands_input)

# Since you aren't using a PQC or ControlledPQC you must append
# your model circuit onto the datapoint circuit tensor manually.
full_circuit = tfq.layers.AddCircuit()(circuits_input, append=model_circuit)
expectation_output = tfq.layers.Expectation()(full_circuit,
                                              symbol_names=control_params,
                                              symbol_values=dense_2,
                                              operators=operators_input)

# Contruct your Keras model.
two_axis_control_model = tf.keras.Model(
    inputs=[circuits_input, commands_input, operators_input],
    outputs=[expectation_output])

"""
Explanation: tfqを使用して、回路とコントローラを 1 つのkeras.Modelに結合します。
End of explanation
"""


# The operators to measure, for each command.
operator_data = tfq.convert_to_tensor([[cirq.X(qubit)], [cirq.Z(qubit)]])

# The command input values to the classical NN.
commands = np.array([[0], [1]], dtype=np.float32)

# The desired expectation value at output of quantum circuit.
expected_outputs = np.array([[1], [-1]], dtype=np.float32)

"""
Explanation: 3.2 データセット
model_circuitに提供する各データポイントに対して測定する演算子も含めます。
End of explanation
"""


optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)
loss = tf.keras.losses.MeanSquaredError()

two_axis_control_model.compile(optimizer=optimizer, loss=loss)

history = two_axis_control_model.fit(
    x=[datapoint_circuits, commands, operator_data],
    y=expected_outputs,
    epochs=30,
    verbose=1)

plt.plot(history.history['loss'])
plt.title("Learning to Control a Qubit")
plt.xlabel("Iterations")
plt.ylabel("Error in Control")
plt.show()

"""
Explanation: 3.3トレーニング
新しい入力と出力を使用し、keras でもう一度トレーニングします。
End of explanation
"""


controller.predict(np.array([0,1]))

"""
Explanation: 損失関数はゼロに低下しました。
controllerはスタンドアロンモデルとして利用できます。コントローラを呼び出し、各コマンド信号に対する応答を確認します。多少手間がかかりますが、これらの出力をrandom_rotationsの内容と比較します。
End of explanation
"""


