PennyLane 機能色々(TIPS) 

自分用のメモです。TIPSとかチートシートとかいう。
**2021/10/25 更新**
 
# What is pennylane?

https://pennylane.ai/

https://qiita.com/notori48/items/78e4dade717d9092dcb1

# いつものimport

```python
import pennylane as qml
from pennylane import numpy as np
from matplotlib import pyplot as plt
```

# 回路関係

## 基本構文

```python
dev = qml.device("default.qubit", wires=n_qubits)
@qml.qnode(dev)
def circuit(theta):
    qml.RX(theta, wires=range(n_qubits))
    return qml.expval(qml.PauliZ(i))
```

devとして、シミュレータのタイプや実機のタイプを指定する。
wires は量子ビット数のことと思えば良い。

@qml.qnode(dev)　は　量子回路circuit にdevをくっつける記法。(Pythonのデコレーター機能)
この下に　def された関数（ここではcircuit)は、devと紐づく。

デコレートされたcircuitは `qnode` というクラスになっている。

```python
type(circuit)
>pennylane.qnode.QNode
```
circuitを実行すると、指定したデバイスで回路が実行された後の結果が返ってくる。

デコレータを使わずに、以下のように記述しても同じ。

```python
def circuit_alone(theta):
    qml.RX(theta, wires=range(n_qubits))
    return qml.expval(qml.PauliZ(i))

My_Qnode = qml.QNode(circuit_alone,dev)
```

```python
type(My_Qnode)
>pennylane.qnode.QNode
```

circuitで使えるゲートはqiskit等とほぼ同じ。wiresとして量子ビット位置を指定する。

回路の戻り値はやや特殊で、基本的には期待値 expval や　サンプル sample や　確率 prob を返す必要がある。
（明示的に戻り値を指定しないとバグる）

状態ベクトル state も使えるよう。

```python
return qml.state()
```

`dev._state`でも出来るらしい？
https://discuss.pennylane.ai/t/pennylane-statevector-results-different-from-qiskit-statevector-issue/1072/28


なおオブザーバブルとしてパウリ行列積の線形和（ハミルトニアン）を使いたい場合は、Hamiltonianを構築してから ExpValCost を使う。
https://pennylane.readthedocs.io/en/latest/code/api/pennylane.ExpvalCost.html
ExpValCostの引数の `ansatz` は（デコレータの無い)circuitの意味。

##使えるdev

default.qubit, lightning.qubit, qulacs.simulator等。
シミュレータの処理速度としてはlightning.qubitとqulacs.simulatorが現状速い。


##使える勾配計算手法
parameter-shift, backprop, adjoint等
adjointが非常に速いが、シミュレータでしか使えない。
実機で使えるのはparameter-shiftのみ。
devでqulacsを指定するとparameter-shiftになってしまうので、lightning.qubitを指定してadjointを使うのが良い。

どうしてもbackpropを使いたい場合は、interfaceをちゃんと設定する必要がある。
devは.tf を使わないといけないし、デコレーターの定義で interface="tf"が必須。

##さらに早く
変分回路等で回路構造が変わらない場合は　mutable = False に設定すると、都度回路構築をしなくなるのでちょっと早い。

## テンプレート回路
https://qiita.com/notori48/items/34f56e57f289a516bb74

Amplitude/Angle embedding, StronglyEntanglingLayers が頻出

## 期待値

テンソル積の期待値を取りたいとき(量子ビット数が多いのでfor文で）
https://discuss.pennylane.ai/t/how-to-use-a-tensor-measurements-with-variable-number-of-qubits/1287/2

```python
return qml.expval(Tensor(*[qml.PauliZ(i) for i in range(n_qubits)]))
```
or

```python
obs = reduce(operator.matmul, [qml.PauliZ(i) for i in range(n_qubits)])
return qml.expval(obs)
```

明示的に書き並べたい場合は、テンソル積記号として @ が使える。

テンソル積ではなく、各1ビットへの作用(例えば`IIZII`)が取りたい時は以下のようにする。

```python
@qml.qnode(dev)
def qnode(inputs, weights):
    qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
    qml.templates.StronglyEntanglingLayers(weights, wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]
```


## 回路描画

pennylane-qiskit使用

```python
#!pip install pennylane-qiskit pylatexenc
import pennylane as qml
from pennylane import numpy as np

dev = qml.device("qiskit.aer", wires=1)

@qml.qnode(dev)
def f(x):
    qml.RX(x, wires=0)
    return qml.expval(qml.PauliZ(0))

f(0.3)

dev._circuit.draw('mpl')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/61a00b69-d680-b63c-5b74-7b5ae1b572fd.png)

### sampleを集計

```python
def sample_to_counts(sample):
    n_qubits, n_shots = np.shape(sample)
    sample_bin = (1+sample)/2
    weights = [2**i for i in range(n_qubits)]
    sample_dec = np.average(sample_bin,axis=0,weights=weights)*np.sum(weights) # sum b_n*2^n
    sample_dec = sample_dec.astype('int8')
    u, freq = np.unique(sample_dec, return_counts=True) # count frequency
    counts=dict()
    for i,j in zip(u,freq):
        counts.setdefault(format(i, '0'+str(n_qubits)+'b'),j)
    for i in range (2**n_qubits):
        counts.setdefault(format(i, '0'+str(n_qubits)+'b'),0) # zero-padding
    return counts

n_qubits = 3
dev = qml.device("default.qubit", wires=n_qubits, shots=100)

@qml.qnode(dev)
def circuit():
    qml.Hadamard(wires=[0])
    for i in range(0,n_qubits-1):
        qml.CNOT(wires=[i,i+1])
    return [qml.sample(qml.PauliZ(i)) for i in range(n_qubits)]

counts = sample_to_counts(circuit())

import matplotlib.pyplot as plt
plt.bar(counts.keys(), counts.values());
plt.xlabel('bitstrings');
plt.ylabel('counts');
plt.xticks(rotation=90);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/77892413-23a9-8f87-0a55-f9099fbf8edc.png)

# アプリケーション別

## QCL 
https://qiita.com/notori48/items/e7f27612b5037a2a5c52
### w/o Tensorflow.keras

#### Gradient Descent

```python
import pennylane as qml
from pennylane import numpy as np
from matplotlib import pyplot as plt
num_of_data = 256
X =  np.random.uniform(high=2 * np.pi, size=(num_of_data,1))
Y = np.sin(X[:,0])

########  parameters #############
n_qubits = 2 ## num. of qubit
n_layers = 2 # num of q_layers

dev = qml.device("default.qubit", wires=n_qubits, shots=None) # define a device
#dev = qml.device("lightning.qubit", wires=n_qubits, shots=None) # define a device
# Note: lightning.qubits is faster but "pip install PennyLane-Lightning" is required.

# Initial circuit parameters
var_init = np.random.uniform(high=2 * np.pi, size=(n_layers, n_qubits, 3))

# Definition of a device
@qml.qnode(dev, diff_method='adjoint')
#@qml.qnode(dev, diff_method='adjoint', mutable=False)
#Note: If set the mutable option as False, we can get speeding up but the circuit stracture should be fixed.

# Data encoding and variational ansatz
def quantum_neural_net(var, x):
    qml.templates.AngleEmbedding(x, wires=range(n_qubits))
    qml.templates.StronglyEntanglingLayers(var, wires=range(n_qubits))
    return qml.expval(qml.PauliZ(0))

def square_loss(desired, predictions):
    loss = 0
    for l, p in zip(desired, predictions):
        loss = loss + (l - p) ** 2
    loss = loss / len(desired)
    return loss

def cost(var, features, desired):
    preds = [quantum_neural_net(var, x) for x in features]
    return square_loss(desired, preds)

opt = qml.AdamOptimizer(0.1)
import time

hist_cost = []
var = var_init
for it in range(10):
    t1 = time.time() 
    var, _cost = opt.step_and_cost(lambda v: cost(v, X, Y), var)
    t2 = time.time() 
    elapsed_time = t2-t1
    print("Iter:"+str(it)+", cost="+str(_cost.numpy()))
    print(f"Time：{elapsed_time}")
    hist_cost.append(_cost)

```

`Iter:0, cost=0.614667912125593
Time：12.225000143051147
Iter:1, cost=0.614667912125593
Time：11.945000171661377`

#### scipy.minimize.optimize

```python

# Initial circuit parameters
var_init = np.random.uniform(high=2 * np.pi, size=(n_layers*n_qubits*3)) # one-dimensional array

@qml.qnode(dev, diff_method='adjoint')
def quantum_neural_net(var, x):
    var_3d_array = np.reshape(var,(n_layers,n_qubits,3))
    qml.templates.AngleEmbedding(x, wires=range(n_qubits))
    qml.templates.StronglyEntanglingLayers(var_3d_array, wires=range(n_qubits))
    return qml.expval(qml.PauliZ(0))
        
from scipy.optimize import minimize
hist_cost = []
var = var_init

count = 0
def cbf(Xi):
    global count
    global hist_cost
    count += 1
    cost_now = cost(Xi,X,Y)
    hist_cost.append(cost_now)
    print('iter = '+str(count)+' | cost = '+str(cost_now))
    
result = minimize(fun=cost, x0=var_init, args=(X,Y) ,method='Nelder-Mead', callback=cbf, options={"maxiter":200})
t2 = time.time() 
elapsed_time = t2-t1
print(f"Time：{elapsed_time}")
hist_cost.append(_cost)
print(f"Time per iteration : {elapsed_time/50}")
```
`iter = 1 | cost = 1.004932862586646
iter = 2 | cost = 1.004932862586646
iter = 3 | cost = 1.004932862586646
iter = 4 | cost = 1.004932862586646`


### w/Tensorflow.keras

#### Hybrid NN (QNN+NN)

```python
import tensorflow as tf
import keras_metrics

n_qubits = 2
layers = 2
data_dimension = 1 # output
param = {'num_epochs': 128}

dev = qml.device("lightning.qubit", wires=n_qubits)
@qml.qnode(dev, diff_method='adjoint')

def qnode(inputs, weights):
    qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
    qml.templates.StronglyEntanglingLayers(weights, wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]


weight_shapes = {"weights": (layers, n_qubits,3)}

qlayer = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits)
clayer1 = tf.keras.layers.Dense(n_qubits, activation='linear')
clayer2 = tf.keras.layers.Dense(data_dimension, activation="linear")
model = tf.keras.models.Sequential([clayer1,qlayer,clayer2])

opt = tf.keras.optimizers.Adam(learning_rate=0.01)
model.compile(opt, loss='mse')

hist = model.fit(X, Y, epochs=param['num_epochs'], validation_split=0.1, verbose=1, shuffle='True', batch_size=1024)

loss = hist.history['loss']
val_loss = hist.history['val_loss']

# lossのグラフ
plt.plot(range(param['num_epochs']), 10*np.log10(loss), marker='.', label='loss')
plt.plot(range(param['num_epochs']), 10*np.log10(val_loss), marker='.', label='val_loss')
plt.legend(loc='best', fontsize=10)
plt.grid()
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

pred = model.predict(X_test)
```
        
##### data-reuploading

```python
dev = qml.device("lightning.qubit", wires=n_qubits)

@qml.qnode(dev, diff_method='adjoint', immutable=False)
def qnode(inputs, weights):
    weights_each_layer = tf.split(weights, num_or_size_splits=layers, axis=0)
    for i in range(layers):
        qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
        qml.templates.StronglyEntanglingLayers(weights_each_layer[i], wires=range(n_qubits))
    return qml.expval(qml.PauliZ(0))

weight_shapes = {"weights": (layers, n_qubits,3)}
    
qlayer = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits)
clayer1 = tf.keras.layers.Dense(n_qubits, activation='linear')
clayer2 = tf.keras.layers.Dense(data_dimension, activation="linear")
#model = tf.keras.models.Sequential([clayer1,qlayer,clayer2])
model = tf.keras.models.Sequential([qlayer])

opt = tf.keras.optimizers.Adam(learning_rate=0.05)
model.compile(opt, loss='mse')

hist = model.fit(X, Y, epochs=3, validation_split=0.1, verbose=1, shuffle='True', batch_size=32)
```

## Classifier
https://qiita.com/notori48/items/9cfe49c79b42389db0e1

### Hybrid NN (QNN+NN)


```python
import tensorflow as tf
import keras_metrics

n_qubits = 2
layers = 4
data_dimension = 3 # output
param = {'num_epochs': 128}


dev = qml.device("default.qubit", wires=n_qubits)


@qml.qnode(dev, diff_method='adjoint')
def qnode(inputs, weights):
    qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
    qml.templates.StronglyEntanglingLayers(weights, wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]


weight_shapes = {"weights": (layers, n_qubits,3)}

qlayer = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits)
clayer1 = tf.keras.layers.Dense(n_qubits, activation='relu')
clayer2 = tf.keras.layers.Dense(3, activation="softmax")
model = tf.keras.models.Sequential([clayer1,qlayer,clayer2])

opt = tf.keras.optimizers.Adam(learning_rate=0.05)
model.compile(opt, loss='categorical_crossentropy')

hist = model.fit(x_train, y_train, validation_split=0.1, epochs=param['num_epochs'], verbose=1, shuffle='True', batch_size=30, callbacks=[early_stopping])

loss = hist.history['loss']
val_loss = hist.history['val_loss']

# lossのグラフ
plt.plot(range(len(loss)), 10*np.log10(loss), marker='.', label='loss')
plt.plot(range(len(val_loss)), 10*np.log10(val_loss), marker='.', label='val_loss')
plt.legend(loc='best', fontsize=10)
plt.grid()
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()
```

### QSVM

https://qiita.com/notori48/items/a54a5219ffd1ed6666df

```python
dev_kernel = qml.device("lightning.qubit", wires=n_qubits)

#|00...0><0.....00|
projector = np.zeros((2**n_qubits, 2**n_qubits))
projector[0, 0] = 1

@qml.qnode(dev_kernel)
def kernel(x1, x2):
    """The quantum kernel."""
    AngleEmbedding(x1, wires=range(n_qubits)) #S(x)
    qml.adjoint(AngleEmbedding)(x2, wires=range(n_qubits)) #S^{\dagger}(x')
    return qml.expval(qml.Hermitian(projector, wires=range(n_qubits)))

def kernel_matrix(A, B):
    """Compute the matrix whose entries are the kernel
       evaluated on pairwise data from sets A and B."""
    return np.array([[kernel(a, b) for b in B] for a in A])

svm = SVC(kernel=kernel_matrix).fit(X_train, y_train)

predictions = svm.predict(X_test)
accuracy_score(predictions, y_test)
```


## QAA
https://qiita.com/notori48/items/fd3c3f87d57978f40be8

```python
from pennylane.templates import ApproxTimeEvolution

T = 2
N = 8
dt = T/N
t = np.arange(0,T,dt)

n_wires = 4
wires = range(n_wires)

dev = qml.device('default.qubit', wires=n_wires)

coeffs = [1, 1, 1, 1]
op_ref = [qml.PauliX(0), qml.PauliX(1), qml.PauliX(2), qml.PauliX(3)]
op_cost = [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliZ(1) @ qml.PauliZ(2), qml.PauliZ(2) @ qml.PauliZ(3), qml.PauliZ(3) @ qml.PauliZ(0)]

hamiltonian_ref = qml.Hamiltonian(coeffs, op_ref)
hamiltonian_cost = qml.Hamiltonian(coeffs, op_cost)

@qml.qnode(dev)
def circuit():
    for i in range(n_wires):
        qml.Hadamard(wires=i)
    for tt in t:
        ApproxTimeEvolution(hamiltonian_ref, (1-tt/T)*dt, 1)
        ApproxTimeEvolution(-1*hamiltonian_cost, tt/T*dt*3, 1)
    return [qml.probs(wires = range(n_wires))]

probs = circuit()
probs = probs.numpy()
probs = probs.flatten()

# プロットする
import matplotlib.pyplot as plt
%matplotlib inline

## z方向に射影測定した時に得られる可能性があるビット列
z_basis = [format(i,"b").zfill(n_wires) for i in range(probs.size)]

plt.figure(figsize=(10, 5))
plt.xlabel("states")
plt.ylabel("probability(%)")
plt.bar(z_basis, probs*100)
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/844bc888-0a04-9b0e-8de7-c7cac15ec8c6.png)

# PennyLane-braket周り

## braketでaws上タスク回収
https://qiita.com/notori48/items/e221194303271cb7f84d

```python
def Retrieve_task(task_id):
    from braket.aws import AwsQuantumTask
    # recover task
    task_load = AwsQuantumTask(arn=task_id)

    # print status
    status = task_load.state()
    print('Status of (reconstructed) task:', status)
    print('\n')
    # wait for job to complete
    # terminal_states = ['COMPLETED', 'FAILED', 'CANCELLED']
    if status == 'COMPLETED':
        # get results
        results = task_load.result()
        # print(rigetti_results)

        # get all metadata of submitted task
        metadata = task_load.metadata()
        # example for metadata
        shots = metadata['shots']
        machine = metadata['deviceArn']
        # print example metadata
        print("{} shots taken on machine {}.\n".format(shots, machine))

        # get measurement counts
        counts = results.measurement_counts
        print('Measurement counts:', counts)

        # plot results: see effects of noise
        plt.bar(counts.keys(), counts.values());
        plt.xlabel('bitstrings');
        plt.ylabel('counts');
        plt.tight_layout();
        plt.xticks(rotation=90);

    elif status in ['FAILED', 'CANCELLED']:
        # print terminal message 
        print('Your task is in terminal status, but has not completed.')

    else:
        # print current status
        print('Sorry, your task is still being processed and has not been finalized yet.')
```

Pennylane側設定例

```python
dev_remote_ionq = qml.device(
    "braket.aws.qubit",
    device_arn=device_arn_ionq,
    wires=n_qubits,
    s3_destination_folder=s3_folder,
    shots = 1024,
    poll_timeout_seconds = 3
)
```

Pennylaneでaws上マシンを叩いたときのタスクID確認
`ionq_task_id = dev_remote_ionq.task.id`

# PennyLane-qiskit周り

https://qiita.com/notori48/items/0793ab06b72320fb03a9

`dev = qml.device('qiskit.aer', wires=2, shots=1024)`

実機は、

```python
from qiskit import IBMQ
IBMQ.load_account()
dev = qml.device('qiskit.ibmq', wires=2, backend='ibmqx2')
```
または
`dev = qml.device('qiskit.ibmq', wires=2, backend='ibmqx2', ibmqx_token="Your token")`