ベスパリブ

プログラミングを主とした日記・備忘録です。ベスパ持ってないです。

ABC 284 F - ABCBAC 解いたので解説

問題:AtCoder Beginner Contest 284 F - ABCBAC

解説

公式の解説を見ながらZ-Algorithmで解いた。Z-Algorithmについては参考URLの章の記事を参照してください。


文字列Sを反転した文字列をS'とおき、SとS'をそれぞれ以下のように表現する。

文字列Sと、Sを反転した文字列S'


まず、答えが0文字になるようなパターンは以下である。

答えが0文字になるときの文字列T

このときの判定は簡単なので解説は割愛する。

解説の便宜上、以下では答えが1文字以上になるものとする。


答えが1文字以上になるとき、文字列Tは以下のような形をしている。

答えが1文字以上のときの文字列Tの形

図の i は文字列のインデックス番号をイメージしている。 0 \le i \le N-1 である。

ここで、文字列Tを半分で分割し、文字列half1とhalf2に分ける。Tの長さは2Nなので、half1とhalf2は長さNずつになる。

文字列Tを分割し、half1とhalf2に分ける。


次に、half2を反転(reverse)したものを新文字列aとし、さらにhalf1とhalf2を入れ替えたものを新文字列bとする。

新文字列aとbをつくる。


まず、新文字列aについて考える。

もし文字列Sが見つかるなら、新文字列aの先頭i+1文字と末尾のi+1文字は一致するはずである。この一致するかどうかは、Z-Algorithmを使えばO(1)で判定できる。

新文字列aに対してZ-Algorithmして得た配列をzaとする。すると以下のように文字列Sの存在を 0~i まで判定できる。

新文字列aをZ-Algorithmすると、0~iまで判定できる


次に、新文字列bについて考える。

もし文字列Sが見つかるなら、新文字列bの先頭N-i-1文字と末尾のN-i-1文字は一致するはずである。この一致するかどうかは、Z-Algorithmを使えばO(1)で判定できる。

新文字列bに対してZ-Algorithmして得た配列をzbとする。すると以下のように文字列Sの存在を i+1~N-1 まで判定できる。

新文字列bをZ-Algorithmすると、i+1~N-1まで判定できる


2つの判定をパスできたなら、文字列Sは存在することになる。

前処理で使うZ-Algorithmの計算量は O(N)

i を 0 ~ N-1 までループして、判定はO(1)でできるので、全体の計算量は O(N)となる。


感想:文字列Tを半分に分割して、反転させたり入れ替えたりするというのが天才解法だと感じた。

Tの長さが2Nなので、「半分に分割する」という発想はわりと典型なのかもしれない。

実装

C++で実装した。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

/**
 * @brief Z-Algorithm
 * 各iについて、S と S.substr(i)の共通接頭辞の長さを格納した配列Zを求める。
 *
 * @tparam T
 * @param S 文字列S
 * @return vector<T> 各iについて共通接頭辞の長さを格納した配列Zを返す。
 */
template <typename T>
vector<T> z_algorithm(string S) {
    T n = S.size();
    vector<T> z(n, 0);
    z[0] = n;

    T i=1, j=0;
    while(i < n) {
        while(i+j<n && S[j]==S[i+j]) j++;
        z[i] = j;

        if (j == 0) {
            i++; continue;
        }

        T k = 1;
        while(k<j && k+z[k]<j) {
            z[i+k] = z[k];
            k++;
        }
        i += k;
        j -= k;
    }

    return z;
}


void solve2() {
    ll N; cin >> N;
    string T; cin >> T;

    string half1 = T.substr(0,N);
    string half2 = T.substr(N,N);
    reverse(half2.begin(), half2.end());

    // 答えが0文字のとき
    if (half1 == half2) {
        cout << T.substr(N,N) << endl;
        cout << 0 << endl;
        return;
    }

    string a = half1 + half2;
    string b = half2 + half1;

    auto za = z_algorithm<ll>(a);
    auto zb = z_algorithm<ll>(b);

    for(ll i=0; i<N; i++) {
        if (za[2*N-(i+1)] == i+1) {
            ll j = N-i-1;
            if (zb[2*N-j] == j) {
                // 答えが見つかった
                cout << a.substr(0,i+1);
                reverse(b.begin(), b.end());
                cout << b.substr(0,j);
                cout << endl;
                cout << i+1 << endl;
                return;
            }
        }
    }
    cout << -1 << endl;
}

int main() {
    // solve();
    solve2();
    return 0;
}

参考URL

Rust の Range は char の範囲指定もできる

環境・バージョン

> rustup show
Default host: x86_64-pc-windows-msvc
rustup home:  C:\Users\****\.rustup

stable-x86_64-pc-windows-msvc (default)
rustc 1.58.1 (db9d1b20b 2022-01-20)

Rust の Range と パターンマッチング

Rustではパターンマッチングで閉区間のRangeが使える。

fn main() {
    let a: i8 = 10;

    match a {
        0..=10 => { println!("yes"); }
        _ => { println!("no"); }
    }
    // yes
}

残念ながら半開区間だとエラーが発生する。詳しくはここの議論を参照。

    match a {
        0..10 => { println!("yes"); }
        _ => { println!("no"); }
    }
error[E0658]: exclusive range pattern syntax is experimental
  --> src\main.rs:37:13
   |
37 |             0..10 => { println!("yes"); }
   |             ^^^^^
   |
   = note: see issue #37854 <https://github.com/rust-lang/rust/issues/37854> for more information

Range は char の範囲指定もできる

ascii コード の順番を守っているなら Range で指定できる。

A-z の範囲

for 文の場合、半開区間でも閉区間でも使える。

fn main() {
    for c in 'A'..'z' {
        print!("{}", c);
    }
    // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxy
    println!();
    for c in 'A'..='z' {
        print!("{}", c);
    }
    // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz
}

パターンマッチングの場合、閉区間のみ使える。

    let s = String::from("abcd[@");

    match s.chars().nth(0) {  // a
        Some('A'..='z') => { println!("yes"); }
        _ => { println!("no"); }
    }
    // yes

    match s.chars().nth(4) {  // [
        Some('A'..='z') => { println!("yes"); }
        _ => { println!("no"); }
    }
    // yes
    
    match s.chars().nth(5) {  // @
        Some('A'..='z') => { println!("yes"); }
        _ => { println!("no"); }
    }
    // no

a-Z は ascii コードの順番を守れてないのでエラー

    match s.chars().nth(0) {
        Some('a'..='Z') => { println!("yes"); }
        _ => { println!("no"); }
    }
error[E0030]: lower range bound must be less than or equal to upper
 --> src/main.rs:5:14
  |
5 |         Some('a'..='Z') => { println!("yes"); }
  |              ^^^ lower bound larger than upper bound

a.clone() == ref_a.clone() == (*ref_a).clone() なのか?(Rust)

#[derive(Debug, Clone, PartialEq)]
struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    pub fn area(self) -> f64 {
        // std::f64::consts::PI * (self.radius * self.radius)
        std::f64::consts::PI * (3.1 * 3.1)
    }
}

fn main() {
    let a = Circle { x: 0.0, y: 0.0, radius: 2.0 };
    let ref_a = &a;

    assert_eq!(a.clone(), ref_a.clone());
    assert_eq!(ref_a.clone(), (*ref_a).clone());
}

Rust Playground で実行

実行するとエラーは発生しなかった。

a.clone() == ref_a.clone() == (*ref_a).clone() なのだ。

Cloneトレイトの説明 を見るに、ref_a が与えられたら (*ref_a.clone).clone() としないと駄目でしょ、と思ってたんですけどそんなことはなかったです。

「暗黙的な参照外し」や「参照外し型強制」とか言うらしいですね。

IBM Quantum Challenge Fall 2022 参加記録

はじめに

2022/11/11 ~ 11/18 の期間に開催された IBM Quantum Challenge Fall 2022 のメモ兼参加記録です。

内容としては以下のとおり。

lab1

Exercise 1

文字列 string が与えられたときのベルンシュタイン・ヴァジラニ回路の関数を作る。

def bernstein_vazirani(string):
    
    # Save the length of string
    string_length = len(string)
    
    # Make a quantum circuit
    qc = QuantumCircuit(string_length+1, string_length)
    
    # Initialize each input qubit to apply a Hadamard gate and output qubit to |->
    for i in range(string_length):
        qc.h(i)
    qc.x(string_length)
    qc.h(string_length)
    qc.barrier()
    
    # Apply an oracle for the given string
    # Note: In Qiskit, numbers are assigned to the bits in a string from right to left
    rev_string = string[::-1]
    for i in range(string_length):
        if rev_string[i] == "1":
            qc.cx(i, string_length)
    qc.barrier()
        
    # Apply Hadamard gates after querying the oracle
    for i in range(string_length):
        qc.h(i)
    
    # Measurement
    qc.measure(range(string_length), range(string_length))
    
    return qc

実行結果は以下のような感じ。

文字列"111"のときの、ベルンシュタイン・ヴァジラニ回路

Exercise 2

回路qcは以下の形で与えられている。

回路qc

パラメータ theta に与える個々の位相 individual_phases も以下の形で与えられている。

phases = np.linspace(0, 2*np.pi, 50) # Specify the range of parameters to look within 0 to 2pi with 50 different phases

# Phases need to be expressed as list of lists in order to work
individual_phases = [[ph] for ph in phases]

Sampler を使い、パラメータ化された回路にパラメータをバインドする。以下が答え。

with Session(service=service, backend=backend):    
    sampler = Sampler(options=options)
    job = sampler.run(circuits=[qc for _ in range(50)], parameter_values=individual_phases)
    result = job.result()

上記のコードは、theta でパラメータ化された回路に、individual_phases の各パラメータをバインドしている。その50個分の回路の結果を実行し、集約された結果が得られるというわけらしい。

Exercise 3

【ステップ1】 RealAmplitudes を使ってランダムな回路を作る。

# Make three random circuits using RealAmplitudes

n_qubit = 5
reps1 = 2
reps2 = 3
reps3 = 4

psi1 = RealAmplitudes(n_qubit, reps=reps1)
psi2 = RealAmplitudes(n_qubit, reps=reps2)
psi3 = RealAmplitudes(n_qubit, reps=reps3)

【ステップ2】 SparsePauliOp を使ってハミルトニアンを作る。

# Make hamiltonians using SparsePauliOp

H1 = SparsePauliOp.from_list([("IIZXI",1), ("YIIIY",3)])
H2 = SparsePauliOp.from_list([("IXIII",2)])
H3 = SparsePauliOp.from_list([("IIYII",3), ("IXIZI",5)])

【ステップ3】 numpy.linspace を使って theta の値を等間隔に作る。

# Make a list of evenly spaced values for theta between 0 and 1

theta1 = np.linspace(0, 1, n_qubit*(reps1+1))
theta2 = np.linspace(0, 1, n_qubit*(reps2+1))
theta3 = np.linspace(0, 1, n_qubit*(reps3+1))

【ステップ4】 Estimator を使って各期待値を計算する。

# Use the Estimator to calculate each expectation value

with Session(service=service, backend=backend):
    
    options = Options(simulator={"seed_simulator": 42},resilience_level=0) # Do not change values in simulator
    
    estimator = Estimator(options=options)
        
    # calculate [ <psi1(theta1)|H1|psi1(theta1)>,
    #             <psi2(theta2)|H2|psi2(theta2)>,
    #             <psi3(theta3)|H3|psi3(theta3)> ]
    # Note: Please keep the order
    qcs = [psi1, psi2, psi3]
    pvs = [theta1, theta2, theta3]
    obs = [H1, H2, H3]
    job = estimator.run(circuits=qcs, parameter_values=pvs, observables=obs)

    result = job.result()
print(result)
# EstimatorResult(values=array([ 0.003 ,  1.749 , -1.0685]), metadata=[{'variance': 9.999775, 'shots': 4000}, {'variance': 0.9409989999999997, 'shots': 4000}, {'variance': 33.04502775, 'shots': 4000}])

Exercise 4

  • 完全な誤り訂正符号の実装はオーバーヘッドがでかい。
  • このオーバーヘッドは現状、現実的ではない。
  • 現時点では、小さなオーバーヘッドの実装で、エラー軽減することを目指している。
  • これらの技術はエラー緩和、エラー抑制と呼ばれている(関連するIBMの記事)。

Qiskit Runtimeには、よく使うエラー緩和とエラー抑制機能が組み込まれている。

  • DynamicalDecoupling
  • Sampler では、resilience_level=1 にするとM3が有効になる。
  • Estimator では、resilience_level=1 にするとT-REXresilience_level=2 にするとデジタルZNEが有効になる。ほかにもエラー緩和策を選べる。

以下は、トリビア問題の回答。ほとんどテキストに書いてある。私の理解に沿った理由を記載するが、誤っている可能性があるので参考程度に。

  1. 誤り訂正は誤り軽減と同じである。
    • 誤り。完全な誤り訂正の代替案として、エラー緩和やエラー抑制といった誤り軽減を利用している。
  2. DDのためにX-gateを適用する場合、2回だけ適用することが可能である。
    • 誤り。X-gateを2回適用するとなにもしないのと同じになるので、DDのためにこれをすることはある。が、2の倍数ならOKだろう。
  3. DDには最適なデカップリング順序が重要である。
    • 正しい。ハードウェアの都合上、パルスの長さは有限であり、完全な回転を実現できないので、適切なDDシーケンスを使用する必要がある。
  4. M3 は量子測定誤差緩和のためのものである。
    • 正しい。「M3は、スケーラブルな量子測定誤差軽減のためのパッケージで、割り当て行列やその逆行列を明示的に形成する必要がないため、行列不要の測定誤差軽減(matrix-free measurement mitigation, M3)ルーチンとなっています。(テキストの原文ママ)」
  5. M3を使用する場合,Resilience levelを2に設定する必要がある。
    • 誤り。Sampler では、resilience_level=1 にするとM3が有効になる。
  6. T-REXはコヒーレントな誤差を確率的な誤差に変化させる。
    • 正しい。「パウリ・トワリング」と呼ばれる手法でコヒーレントな誤差を確率的な誤差に変換している。
  7. ZNEは誤り訂正技術であり、耐故障性QCに使用される。
    • 誤り。誤り軽減技術である。
  8. ZNEはノイズのスケールファクターを増幅し、ノイズがないときの測定値を推定する。
    • 正しい。「ゼロノイズ外装(Digital Zero-noise Extrapolation, ZNE) を利用し、異なるノイズファクターに対する測定の期待値を計算し(増幅ステージ)、次に測定した期待値を使ってゼロノイズ限界での理想的な期待値を推測します(外挿ステージ)。この方法は期待値の誤差を減らす傾向がありますが、バイアスのない結果が得られることは保証されていません。(テキストの原文ママ)」
  9. グローバル・フォールディングは、量子パルスを物理的に引き伸ばすアナログ的な方法である。
    • 誤り。ローカル・フォールディングと同じ操作を回路全体に適用する、デジタル的な手法である。
  10. ZNEを用いると、すべての誤差を軽減することができる。
    • 誤り。期待値の誤差を減らす傾向はあるが、バイアスのない結果が得られるとは限らない。

Exercise 5

Sampler の回路は以下。

# build your code here
sampler_circuit = bernstein_vazirani("11111")

sampler_circuit.draw()

Estimator の回路は以下。

estimator_circuit = QuantumCircuit(2)

estimator_circuit.h(0)
estimator_circuit.cx(0,1)
estimator_circuit.ry(Parameter("theta"), 0)

estimator_circuit.draw()

各パラメータ、観測値は以下。

# parameters for estimator_circuit

number_of_phases = 10
phases = np.linspace(0, 2*np.pi, number_of_phases)
individual_phases = [[ph] for ph in phases]

# observables for estimator_circuit

Z0Z1 = SparsePauliOp.from_list([("ZZ",1)])
X0Z1 = SparsePauliOp.from_list([("ZX",1)])
Z0X1 = SparsePauliOp.from_list([("XZ",1)])
X0X1 = SparsePauliOp.from_list([("XX",1)])

ops = [Z0Z1, X0Z1, Z0X1, X0X1] # DO NOT CHANGE THE ORDER

Options の設定は以下。

# Import FakeBackend
fake_backend = FakeManila()
noise_model = NoiseModel.from_backend(fake_backend)

options_with_em = Options(
    simulator={ # Do not change values in simulator
        "noise_model": noise_model,
        "seed_simulator": 42,
    },
    # build your code here. Activate TREX for Estimator and M3 for sampler.
    resilience_level=1
)

以上の設定で、SamplerEstimator を実行する。

with Session(service=service, backend=backend):
    
    sampler = Sampler(options=options_with_em)
    sampler_result = sampler.run(circuits=sampler_circuit).result()
    
    estimator = Estimator(options=options_with_em)
    estimator_result = []
    for op in ops:
        job = estimator.run(circuits=[estimator_circuit]*number_of_phases, parameter_values=individual_phases, observables=[op]*number_of_phases)
        result = job.result()
        estimator_result.append(result)

何かメッセージが書かれている…

Exercise 6

message = "Captain's Log"

クリアすると限定公開の動画が見れる。今回、SFのストーリーがあり、しかも結構面白い。

Lab2

Exercise 1

from qiskit.circuit.library import RealAmplitudes

def lab2_ex1():
    qc = RealAmplitudes(num_qubits=4, reps=1, entanglement=[[0,1], [2,3], [1,2]], insert_barriers=True)
    
    return qc

qc = lab2_ex1()
qc.decompose().draw('mpl')

Exercise 2

M3について正誤問題。Lab1の4.1章を復習する。

Matrix-free Measurement Mitigation (M3) に関する以下の記述について、正しいか否かを答えてください。

  • Q1:M3は量子ゲートエラー緩和技術であり、フォールト・トレラントな量子コンピューターに使用される。

    • 誤り。量子ゲートではなく、量子測定の誤差軽減技術である。
  • Q2:M3は、指数関数的なオーバーヘッドをランダムビット列の数によるスケーリングに落とし込んで作られる部分空間において動作する。

    • 誤り。ランダムビット列ではなく、ノイズの多い入力ビット列によってスケーリングする。
  • Q3: M3は、ノイズのスケールファクターを増幅し、ノイズがない場合の測定値を推定する。

    • 誤り。ゼロノイズ外挿(ZNE)の説明。
  • Q4: M3の技術は、直接的な解決方法と比較して、緩和のためのメモリー使用量を削減することができる。

    • 正しい。行列を圧縮するので、メモリ使用量を削減できる。一般化最小残差法などを使った行列なしの方法もあるらしい。

Exercise 3

Sampler の文法がわかってきた。統計で例えると、回路のパラメータの個数が標本サイズで、sampler.run() で反復回数(標本数)だけ繰り返し実行するイメージ。

# 標本数 = n
theta_list = [[標本1], [標本2], ..., [標本n]]
sampler.run(circuits=[qc]*(標本数), parameter_values=theta_list, shots=shots)

で、以下が答え。

# create sampler using simulator and sample fidelities
with Session(service=service, backend=backend):
    sampler = Sampler(options=options)
    job = sampler.run(circuits=[fidelity_circuit]*len(theta_list), parameter_values=theta_list, shots=shots)
    fidelity_samples_sim = job.result()

    sampler = Sampler(options=options_noise)
    job = sampler.run(circuits=[fidelity_circuit]*len(theta_list), parameter_values=theta_list, shots=shots)
    fidelity_samples_noise = job.result()
    
    sampler = Sampler(options=options_with_em)
    job = sampler.run(circuits=[fidelity_circuit]*len(theta_list), parameter_values=theta_list, shots=shots)
    fidelity_samples_with_em = job.result()
    
fidelities_sim = []
fidelities_noise = []
fidelities_with_em = []

for i in range(10):
    fidelities_sim += [fidelity_samples_sim.quasi_dists[i][0]]
    fidelities_noise += [fidelity_samples_noise.quasi_dists[i][0]]
    fidelities_with_em += [fidelity_samples_with_em.quasi_dists[i][0]]

Exercise 4

ここ難しかった。

sample_train を使えと言われている。作成したいカーネル行列は対称な正方行列であるので……。

with Session(service=service, backend=backend):
    circuits = [fidelity_circuit]*circuit_num
    pvs = data_append(n, sample_train, sample_train)
    #
    sampler = Sampler(options=options_with_em)
    job =  sampler.run(circuits=circuits, parameter_values=pvs, shots=shots)
    quantum_kernel_em = job.result()
    #

Exercise 5

今回のカーネル行列は対称行列でないので、すべての要素を計算する必要がある。

たとえば行列の行をsample_train、列を未知のビット列に割り当てるとすると、行列の要素数は以下の通り。

n1 = len(unknown_data) # the number of unknown bit-string
n2 = 25 # the number of trained data
circuit_num_ex5 = n1*n2
print(circuit_num_ex5)

量子カーネル関数にデータをエンコードする関数は以下の通り。

def data_append_ex5(n1, n2, x1, x2):
    para_data_ex5 = []
    
    for i in range(n1):
        for j in range(n2):
            para_data_ex5.append(list(x1[i]) + list(x2[j]))
    
    return para_data_ex5

unknown_datasample_train で計算されたカーネル行列:kernel_ex5 を計算するための Sampler のコードは以下の通り。

with Session(service=service, backend=backend):
    circuits_ex5 = [fidelity_circuit]*circuit_num_ex5
    pvs_ex5 = data_append_ex5(n1, n2, unknown_data, sample_train)
    
    sampler = Sampler(options=options_with_em)
    job =  sampler.run(circuits=circuits_ex5, parameter_values=pvs_ex5, shots=shots)
    quantum_kernel_ex5 = job.result()  

Lab3

Exercise 1

qubit の数は、イジングモデルをイメージする。

# Let us first generate a graph with 4 nodes!

#### enter your code below ####

n = 4
num_qubits = n**2

#### enter your code above ####

tsp = Tsp.create_random_instance(n, seed=123)
adj_matrix = nx.to_numpy_matrix(tsp.graph)
print("distance\n", adj_matrix)

colors = ["r" for node in tsp.graph.nodes]
pos = [tsp.graph.nodes[node]["pos"] for node in tsp.graph.nodes]
draw_graph(tsp.graph, colors, pos)

Exercise2

与えられた二次問題をQUBOに変換し、イジングハミルトニアンを求めよという問題。

from qiskit_optimization.converters import QuadraticProgramToQubo

#### enter your code below ####

qp2qubo = QuadraticProgramToQubo()   #instatiate qp to qubo class
qubo = qp2qubo.convert(qp)      # convert quadratic program to qubo
qubitOp, offset = qubo.to_ising()      #convert qubo to ising

#### enter your code above ####

print("Offset:", offset)
print("Ising Hamiltonian:", str(qubitOp))

Exercise 3

Matsuo, A., Suzuki, Y., & Yamashita, S. (2020). Problem-specific parameterized quantum circuits of the VQE algorithm for optimization problems. arXiv preprint arXiv:2006.05643.の論文の3.2.1節を参考にして、3量子ビットのW状態を構築せよという問題。

from qiskit import QuantumRegister, ClassicalRegister
from qiskit.circuit import ParameterVector, QuantumCircuit

# Initialize circuit
qr = QuantumRegister(3)
cr = ClassicalRegister(3)
circuit = QuantumCircuit(qr,cr)

# Initialize Parameter with θ
theta = ParameterVector('θ', 2)

#### enter your code below ####

# Apply gates to circuit
circuit.x(0)
circuit.ry(theta[0], 1)
circuit.cz(0,1)
circuit.ry(-theta[0], 1)
circuit.ry(theta[1], 2)
circuit.cz(1,2)
circuit.ry(-theta[1], 2)
circuit.cx(1,0)
circuit.cx(2,1)

#### enter your code above ####

circuit.measure(qr,cr)
circuit.draw("mpl")

Exercise 4

from qiskit_ibm_runtime import Session, Sampler, Options

options = Options(simulator={"seed_simulator": 42},resilience_level=0) #DO NOT CHANGE

#### Enter your code below ####

# Define sampler object
with Session(service=service, backend=backend):
    pvs = [[(54.73/360)*(2*np.pi), (45/360)*(2*np.pi)]] 
    
    sampler = Sampler(options=options) # Define sampler with options above
    result = sampler.run(circuits=[circuit], parameter_values=pvs, shots=5000).result() # Run the sampler. Remember, the theta here is in degrees! :) 
    
# Plot result 
result_dict = result.quasi_dists[0] # Obtain the quasi distribution. Note: Expected result type: QuasiDistribution dict

#### Enter your code above ####

values = list(result_dict.keys()) # Obtain all the values in a list
probabilities = list(result_dict.values()) # Obtain all the probabilities in a list

plt.bar(values,probabilities,tick_label=values)
plt.show()
print(result_dict)

Exercise 5

N量子ビットのTSPエンタングラー・ルーチンを構築する関数を作成せよという問題。こういうの見るたび競プロやってて良かったと思う。

2量子ビットの場合(テキストから引用)

3量子ビットの場合(論文から引用)

from qiskit import QuantumRegister, ClassicalRegister
from qiskit.circuit import ParameterVector, QuantumCircuit

def tsp_entangler(N):
    
    # Define QuantumCircuit and ParameterVector size
    circuit = QuantumCircuit(N**2)
    theta =  ParameterVector('theta',length=(N-1)*N)
    
    ############# 
    # Hint: One of the approaches that you can go for is having an outer `for` loop for each node and 2 inner loops for each block. /
    # Continue on by applying the same routine as we did above for each node with inner `for` loops for /
    # the ry-cz-ry routine and cx gates. Selecting which indices to apply for the routine will be the /
    # key thing to solve here. The indices will depend on which parameter index you are checking for and the current node! /
    # Reference solution has one outer loop for nodes and one inner loop each for rz-cz-ry and cx gates respectively.
    ############# 
    
    #### enter your code below ####
    theta_i = 0
    for i in range(N):
        # X Gate
        circuit.x(i*N)
    
        # Parameter Gates
        for j in range(i*N+1, i*N+N):
            circuit.ry(theta[theta_i], j)
            circuit.cz(j-1, j)
            circuit.ry(-theta[theta_i], j)
            theta_i += 1
        
        # CX gates
        for j in range(i*N+1, i*N+N):
            circuit.cx(j, j-1)
        
    #### enter your code above ####

    return circuit

Exercise 6

引数:

  • estimator: ハミルトニアン演算子の期待値を計算するためのestimator primitive。
  • ansatz: 試行状態のためのパラメーター化された量子回路。
  • optimizer: 最小エネルギーを求めるための古典的なオプティマイザー。Qiskit.Optimizer、または.Minimizerプロトコルを実装したcallableとすることができる。
  • initial_point: オプティマイザーの初期点(つまり、パラメーターの初期値)。初期点の長さは、ansatzのパラメーターの数と一致しなければならない。Noneの場合、特定のパラメーター範囲内でランダムな点が生成されます。SamplingVQEは、この範囲をansatzから求めます。ansatzが範囲を指定していない場合、-2\pi, 2\piの範囲が使用されます。
  • callback: 各最適化ステップで中間データにアクセスできるコールバック。これらのデータは、評価回数、ansatzのオプティマイザーのパラメーター、estimatorの値、およびメタデーターの辞書です。

まず、3ノードのTSPエンタングラーを作成しておく。

# Define PQC for 3 node graph (Hint: Call the previous function you just made)
PQC = tsp_entangler(3)

RunVQE 関数は以下の通り。

def RunVQE(estimator, model, optimizer, operator, init=None):
    
    # Store intermediate results
    history = {"eval_count": [], "parameters": [], "mean": [], "metadata": []}
    
    # Define callback function
    def store_intermediate_result(eval_count, parameters, mean, metadata):
        history["eval_count"].append(eval_count)
        history["parameters"].append(parameters)
        history["mean"].append(mean.real)
            
    #### Enter your code below ####
    
    # Define VQE run
    vqe = VQE(estimator=estimator, ansatz=model, optimizer=optimizer, initial_point=init, callback=store_intermediate_result)

    # Compute minimum_eigenvalue
    result = vqe.compute_minimum_eigenvalue(operator=operator, aux_operators=None)
    
    #### Enter your code above ####

    return result, history["mean"]

VQEを実行し、結果を得るためのコードは以下の通り。Estimatoroptions を渡さないことに注意。

%%time
# Do not change the code below and use the given seeds for successful grading
from qiskit.utils import algorithm_globals
from qiskit.primitives import Estimator
algorithm_globals.random_seed = 123

# Define optimizer
optimizer = SPSA(maxiter=50)

# Define Initial point
np.random.seed(10)
init = np.random.rand((n-1)*n) * 2 * np.pi

# Define backend
backend = service.backends(simulator=True)[0]

#### Enter your code below ####

# Call RunVQE
with Session(service = service, backend = backend):
    estimator = Estimator()
    result, mean = RunVQE(estimator=estimator, model=PQC, optimizer=optimizer, operator=qubitOp, init=init) #----# Enter your code here. Do not pass in options)
        
#### Enter your code above ####

# Print result
print(result)

Exercise 7

前回計算したVQE結果から得られたパラメータを使って、最適化回路に渡す。

テキストに書いてあるが、Estimator に渡す parameter_values 引数は、result.optimal_parameters の値を渡す。

def BestBitstring(result, optimal_circuit):
    energy = result.eigenvalue.real
    
    options = Options(simulator={"seed_simulator": 42},resilience_level=0) # Do not change and please pass it in the Sampler call for successful grading
    
    #### enter your code below ####
    with Session(service = service, backend = backend):
        # Enter your code here. Please do pass in options in the Sampler construct.
        sampler = Sampler(options=options)
        job = sampler.run(circuits=[optimal_circuit], parameter_values=[list(result.optimal_parameters.values())])
        sampler_result = job.result() 

    # Obtain the nearest_probability_distribution for the sampler result from the quasi distribution obtained
    result_prob_dist = sampler_result.quasi_dists[0].nearest_probability_distribution()
   
    #### enter your code above ####
    
    max_key = format(max(result_prob_dist, key = result_prob_dist.get),"016b")

    result_bitstring = np.array(list(map(int, max_key)))
    
    return energy, sampler_result, result_prob_dist, result_bitstring

Exercise 8

N=3 の model_3(論文から引用)

一般化よくわかんなかったです👽。

n = 3
model_3 = QuantumCircuit(n**2)
theta = ParameterVector('theta',length=(n-1)*n//2)

# Build a circuit when n is 2
model_3.x(0)
model_3.x(6)

# Apply a parameterized W state gate
model_3.ry(theta[0],1)
model_3.cz(0,1)
model_3.ry(-theta[0],1)
model_3.cx(1,0)
model_3.cx(1,3)
model_3.cx(0,4)

model_3.ry(theta[1],7)
model_3.cz(6,7)
model_3.ry(-theta[1],7)

model_3.ry(theta[2],8)
model_3.cz(7,8)
model_3.ry(-theta[2],8)

model_3.cx(7,6)
model_3.cx(8,7)

# Apply 4 CSWAP gates
model_3.cswap(6,0,2)
model_3.cswap(6,5,3)
model_3.cswap(7,1,2)
model_3.cswap(7,4,5)

model_3.draw("mpl")

Exercise 9

4ノードでVQE解を求めよという問題。いままで作った関数を駆使して実装する。

QUBO は以下の通り。

#### Enter your code below ####

qp = tsp.to_quadratic_program()
qp2qubo = QuadraticProgramToQubo()
qubo = qp2qubo.convert(qp)
qubitOp_4, _ = qubo.to_ising()

#### Enter your code above ####

model は以下の通り。なんかソラから実装させようとしてきたけど、めんどくさいのでしない。

model = tsp_entangler(n)

Lab4

化学わかんね~~~🧪🤷

Exercise 1

Å :オングストロームと読む。1Å = 10^{-10} メートル。

value の値は、図の「???」の距離を計算する。正三角形なので計算できるのだが、小数点第6位まできっかりの値じゃないと grade はクリアできない。どういうこと?

Molecule モジュールのページが Not Found なのでエスパーすると、

  • hydrogen_t = [["原子記号", [x座標, y座標, z座標]], ...]
  • multiplicity は、多重度。今回は1らしい(テキストに書いてある)。
  • charge は、電荷?たぶん1。
#### Fill in the values below and complete the function to define the Molecule ####

# Coordinates are given in Angstrom
# value = 0.791513 / 3**(1/2)  # 0.4569802436170883
value = 0.456980
hydrogen_t = [["H", [value, 0.0, 0.0]], # ----------- Enter your code here
              ["H", [-value, 0.0, 0.0]], # ----------- Enter your code here
              ["H", [0.0, 0.791513, 0.0]]]
                  
h3p = Molecule( # Fill up the function below
    geometry=hydrogen_t, # ----------- Enter your code here
    multiplicity=1, # ----------- Enter your code here
    charge=1, # ----------- Enter your code here
)

driver = ElectronicStructureMoleculeDriver(h3p, basis="ccpvdz", driver_type=ElectronicStructureDriverType.PYSCF) 

properties = driver.run()

Exercise 2

次に、Estimator を使ってどのようにルーチンを設定するかを定義します。Estimatorは量子回路から推定される期待値と物理量を返します。ここで渡す回路 は私たちの ansatz で、物理量 は先ほど作成した qubit_op_parity ハミルトニアンで、パラメータ値 はここで x として渡した古典オプティマイザーで処理される値です。この例では、同時摂動確率的近似(SPSA)オプティマイザーを使用しています。(テキスト引用)

optimizer で最小化するスカラー関数は evaluate_expectationx0 引数には initial_point を入れる。

サンプルコード内の job_list には、PrimitiveJob ではなく 実際には EstimatorResult が入る。ややこしい。

%%time
from qiskit.utils import algorithm_globals
algorithm_globals.random_seed = 1024

# Define convergence list
convergence = []

# Keep track of jobs (Do-not-modify)
job_list = []

# Initialize estimator object
estimator = Estimator()# Enter your code here

# Define evaluate_expectation function
def evaluate_expectation(x):
    x = list(x)
    
    # Define estimator run parameters
    #### enter your code below ####
    # job = estimator.run(circuits=[ansatz], observables=[qubit_op_parity], parameter_values=[x]) # PrimitiveJob ではなく、EstimatorResultを入れる
    job = estimator.run(circuits=[ansatz], observables=[qubit_op_parity], parameter_values=[x]).result() # ----------- Enter your code here
    results = job.values[0]
    job_list.append(job)
    
    # Pass results back to callback function
    return np.real(results)

# Call back function
def callback(x,fx,ax,tx,nx):
    # Callback function to get a view on internal states and statistics of the optimizer for visualization
    convergence.append(evaluate_expectation(fx))

np.random.seed(10)

# Define initial point. We shall define a random point here based on the number of parameters in our ansatz
initial_point = np.random.random(ansatz.num_parameters)

#### enter your code below ####
# Define optimizer and pass callback function
optimizer = SPSA(maxiter=50, callback=callback) # ----------- Enter your code here

# Define minimize function
result = optimizer.minimize(fun=evaluate_expectation, x0=initial_point) # ----------- Enter your code here

Exercise 3

construct_problem は以下。

def construct_problem(geometry, charge, multiplicity, basis, num_electrons, num_molecular_orbitals):

    molecule = Molecule(geometry=geometry,
                            charge=charge, 
                            multiplicity=multiplicity) 
    driver = ElectronicStructureMoleculeDriver(molecule, basis=basis, driver_type=ElectronicStructureDriverType.PYSCF) 

    # Run the preliminary quantum chemistry calculation
    properties = driver.run()

    # Set the active space
    active_space_trafo = ActiveSpaceTransformer(num_electrons=num_electrons,
                                        num_molecular_orbitals=num_molecular_orbitals) 

    # Now you can get the reduced electronic structure problem
    problem_reduced = ElectronicStructureProblem(driver, transformers=[active_space_trafo]) # ----------- Enter your code here

    # The second quantized Hamiltonian of the reduce problem
    second_q_ops_reduced = problem_reduced.second_q_ops()

    # Set the mapper to qubits
    parity_mapper = ParityMapper() # This is the example of parity mapping

    # Set the qubit converter with two qubit reduction to reduce the computational cost 
    parity_converter = QubitConverter(parity_mapper) # ----------- Enter your code here    

    # Compute the Hamitonian in qubit form
    qubit_op_parity = parity_converter.convert(second_q_ops_reduced.get('ElectronicEnergy'), num_particles=problem_reduced.num_particles)
   
    # Get reference solution
    vqe_factory = VQEUCCFactory(quantum_instance=StatevectorSimulator(),optimizer=SLSQP(),ansatz=UCC(excitations='sd')) 
    solver = GroundStateEigensolver(parity_converter, vqe_factory)    
    real_solution = solver.solve(problem_reduced).total_energies[0]    
    
    ansatz=vqe_factory.ansatz
    
    return ansatz, qubit_op_parity, real_solution, problem_reduced

custom_vqe は以下。

def custom_vqe(estimator, ansatz, ops, problem_reduced, optimizer = None, initial_point=None):

    # Define convergence list
    convergence = []

    # Keep track of jobs (Do-not-modify)
    job_list = []

    # Define evaluate_expectation function
    def evaluate_expectation(x):
        x = list(x)

        # Define estimator run parameters
        job = estimator.run(circuits=[ansatz], observables=[ops], parameter_values=[x]).result() # ----------- Enter your code here
        results = job.values[0]
        job_list.append(job)

        # Pass results back to callback function
        return np.real(results)

    # Call back function
    def callback(x,fx,ax,tx,nx):
        # Callback function to get a view on internal states and statistics of the optimizer for visualization
        convergence.append(evaluate_expectation(fx))

    np.random.seed(10)

    # Define initial point. We shall define a random point here based on the number of parameters in our ansatz
    if initial_point is None:
        initial_point = np.random.random(ansatz.num_parameters)

    # Define optimizer and pass callback function
    if optimizer == None:
        optimizer = SPSA(maxiter=50, callback=callback)

    # Define minimize function
    result = optimizer.minimize(fun=evaluate_expectation, x0=initial_point) # ----------- Enter your code here

    vqe_interpret = []
    for i in range(len(convergence)):
        sol = MinimumEigensolverResult()
        sol.eigenvalue = convergence[i]
        sol = problem_reduced.interpret(sol).total_energies[0]
        vqe_interpret.append(sol)

    return vqe_interpret, job_list, result

grade_lab4_ex3b は、grade_lab4_ex に修正する。

## Grade and submit your solution
from qc_grader.challenges.fall_2022 import grade_lab4_ex3

grade_lab4_ex3(construct_problem, custom_vqe)

Exercise 4

  1. construct_problem で電子構造問題を作成する。
  2. custom_vqe基底状態エネルギーを計算する。
  3. グラフ化する。

という流れ。

反応エネルギーを単位[eV]で求めよという問題。

反応エネルギー = 反応後のエネルギー - 反応前のエネルギー

VQE実行の最終的な収束値を使って計算すれば良い。

K_H2eV = 27.2113845  # Hartree -> eV

# calculate reaction energy.
before_energy = (Energy_H_m[-1] + Energy_H_c[-1])
after_energy = (Energy_H_t[-1] + Energy_H_a[-1])
react_energy = after_energy - before_energy

react_vqe_ev = react_energy * K_H2eV # Fill this cell. Please note the reaction energy should be in eV and not in Hartree units for succesful grading.

print("Reaction energy VQE estimator eV", react_vqe_ev ,"\n")

Exercise 5

Variational Quantum Deflation (VQD) ルーチンを用いた第一励起状態遷移エネルギー(nm単位)を求める問題。

%%time
# Extracting solved initial point from one shot estimation done previously
initial_pt = result.raw_result.optimal_point

# List to store all estimated observables
uccsd_result = [] 

#### Enter your code below ####
for i in range(7):
    uccsd_result.append(Estimator().run(circuits=[vqe_factory.ansatz], observables=[qubit_op_parity[i]], parameter_values=[initial_pt]).result())
#### Enter your code above ####
        
energy_c_estimator=uccsd_result[0].values[0]+result.extracted_transformer_energy+result.nuclear_repulsion_energy

print('Energy =  ', energy_c_estimator)

シクロプロペニリデンの全双極子モーメントを求める問題。

双極子モーメントは、分極のベクトルの和であるらしい。方向(符号)に気をつけて足し合わせたあと、ベクトルのマグニチュードを求めれば良い。

双極子モーメント = 活性化双極子モーメント + トランスフォーマーによって抽出された双極子モーメント - 核双極子モーメント

# Fill your codes here, dipole moment computed by Nuclear dipole moment - Electronic dipole moment 
# and Electronic dipole moment computed by adding VQE reult and ctiveSpaceTransformer extracted energy part

dipxau = uccsd_result[4].values[0] + temp_dipoles[0] - temp_nu_dipoles[0] # ----------- Enter your code here (Use Partial information in element 4 in uccsd_result list)
dipyau = uccsd_result[5].values[0] + temp_dipoles[1] - temp_nu_dipoles[1] # ----------- Enter your code here (Use Partial information in element 5 in uccsd_result list)
dipzau = uccsd_result[6].values[0] + temp_dipoles[2] - temp_nu_dipoles[2] # ----------- Enter your code here (Use Partial information in element 6 in uccsd_result list)

au2debye =  1/0.393430307 # Convert to debye
dipxdebye = dipxau.real * au2debye
dipydebye = dipyau.real * au2debye
dipzdebye = dipzau.real * au2debye

dip_tot = (dipxdebye**2 + dipydebye**2 + dipzdebye**2)**(1/2) # Compute the total dipole moment magnitude here from the individual components of the vector computed above.
print('Dipole (debye) : ', dip_tot)

Final Ranked Exercise

ノイズのある回路でVQE実行するチャレンジ問題。

HACK TO THE FUTURE 2023 予選(AtCoder Heuristic Contest 016)と期間丸かぶりしてて時間なくてやってません><

おわりに

今回はSFのストーリーがあって、ブラックホールからスイングバイ脱出するという目的のもと、スペースデブリの効率的な排除のために巡回セールスマン問題をQUBOを使って解いたり、燃料を節約するために宇宙空間の化学物質の分子特性をVQEを構築して計算するといった流れになっていた。演出もなかなか力が入っていて、ただ単に問題を解くよりもエンターテイメントって感じで面白かったです。

去年と同様、修了証明書としてのバッジをもらえました。

バーボンハウス ジェネレータを作った

twitterしてたら久々に釣られたので作りました。

実用上(?)は短縮URLサービスを使ってください。

taketakeyyy.github.io

懐かしきバーボンハウス

バーボンハウスとか新作モンスターファームとか、懐古時代が来ているのか?

懐かしすぎるだろ

こういうの作ってて一番楽しいのは、昔の懐かしネタを漁ってるときが一番楽しい。そういうこともあったのだな。

強いジュナイパーは強いぞ

個人的には下レーンだろうが上レーンだろうがジュナイパー使うのは人の勝手なので全然使っていいと思いますが、死に猛攻しようとして1もうこうもできずに死ぬモクローを見ると可愛すぎて笑ってしまう。

estie プログラミングコンテスト2022(AtCoder Heuristic Contest 014)の参加記録

はじめに

atcoder.jp

開催期間:2022-09-17(土) 15:00 ~ 2022-10-01(土) 19:00 の2週間

  • AHCのコツ(メモ)
    • 評価関数から考察する(スコアを伸ばすにはどうすればいいか?)
    • ビジュアライザを色々試して考察する
    • まずは初期解を提出する
    • 制約から考察する

方針1:短い辺の四角形を優先的に作る

評価関数を眺めて、重心から遠い点を作るか、点をとにかくたくさん作ると良さそうだと予想した。

ビジュアライザをポチポチしてて思ったこととして、重心から遠い点を作ろうとして辺の長い四角形を先に作ってしまうと、その辺が邪魔して作れる点の数が減ってしまうことに気づいた。

なので、まずは辺の長さ1の四角形を作るようにして、徐々に辺の長さを伸ばすようにすればたくさんの点を作れそうなので、そうすることにした。

そのような方針で、とにかく小さい四角形を作りまくるナイーブな実装をした(たくさんバグらせて大変だった)。

最初は辺の長さは1で、徐々に大きくする。

提出コード:https://atcoder.jp/contests/ahc014/submissions/35003739

スコア:32,575,448

方針2:乱択 & 試行回数を増やす

山登り法を実装したいと思ったのだけど、どう実装したものか悩む。

出力は、新しく印の付いた点を作る履歴を出力している。この履歴の適当な区間 [start, end] をより良いスコアに差し替える山登り法(個人的に履歴差し替え法と呼んでいる)をしたいと思ったのだが、差し替えるために削除する印のついた点が、[start, end] の区間以外で利用されていると削除するわけにはいかないので、削除してはいけない点を保持しないといけない。なかなか面倒くさそうだし、削除してはいけない点は多そうなので、差し替えが成功するパターンはあんまりなさそうで、スコアが良くなるような気があまりしなかった。なので、別の方法がないかなあなどと考える。

手慰みの新方針として、方針1の実装で、小さい四角形を探索する方向を2パターン作ってより良いスコアのほうを採用することをすると、スコアが1000000程度伸びた。

なので、結構適当に乱択してもスコアが伸びるのではないかと予想した。どの程度伸びるか知りたかったので、そのような実装をした。

  • 実装概要
    • 作れる四角形の辺の長さは1にする。
    • 始点となる印の付いた点はランダムに決める
    • 始点となる点を決めたら、新しく点を作るためにどの方向を走査するかはランダムに決める
      • 新しく点を作れたら、新しく作った点を始点に再度探索をする。
      • この方法を3回ほど試して、スコアの上がり幅が大きいものを採用する
    • 新しい点を作れなくなくなったら、辺の長さを増やす。
    • 辺の長さがNかつ、新しい点を作れなくなったら終了。制限時間ギリギリになっても終了。
    • 制限時間に余裕があるなら、上記を制限時間いっぱい繰り返す。

5秒の制限時間いっぱい試行する。

提出コード:https://atcoder.jp/contests/ahc014/submissions/35038824

スコア:37,345,575

テストケースをいろいろ試して気づいたこと

  • 辺が長い四角形を優先的に作るよりも、辺が短い四角形を優先的に作ったほうが、スコアが良いことが多かった
  • テストケース "0002.txt" を見ると、初期状態が四角形に見える。この四角形を大きくできるかがスコアを良くするコツか?
    • 外側(または内側?)の点から優先的に処理したら良いかも

方針3:ビームサーチ

ビームサーチの結果。109秒かかった。

ビームサーチを実装したのだけど、TLEするし、ローカルで時間無制限に処理させても方針2と大してスコアが変わらなかった。

ビーム幅10でもTLEする始末。なんか実装間違えてる?

全然無理そうだったのでこの方針は却下した。

方針4:内側と外側で処理を分ける

入力の制約

上記の入力の制約により、初期状態は点は中心に集まっている。

新しい点を作るとき、外側に点を作れた方がスコアは高くなるはず。

外側に点を作れそうなら優先的に作り、作れないなら内側に点を作り、その後また外側に点を作れないか試行する。。。という風にすればできるだけ外側に点が作れてスコアが伸びるはず。

と思って実装したが、思いの外全然伸びなかった。

おわり

結局、方針2を高速化して試行回数を増やした乱択が最終提出となった。

最も良いスコアで 37,365,502 だった。40M超えたかったのだが…

最終提出コード:https://atcoder.jp/contests/ahc014/submissions/35326925

A-Final スコア:1,509,848,532

順位:215

解説(https://www.youtube.com/watch?v=eddDPITjzDc)を見ると、焼きなましで高いスコアが出るとのこと。まず初期解を求めて、そのあと適当に削除する点を決めたら、その点に依存している点もすべて削除して、スコアが上がるように作り直すと良いらしい。

方針2で山登りを諦めたのが敗因か。諦めない心が足りなかった。

1位のスコアは70Mを超えてた。うーん遠すぎる。

Qiskit Global Summer School 2022 参加記

Qiskit Global Summer School 2022

Qiskit Global Summer School 2022 に参加した記録です。

EDT時間で 7/18 ~ 7/29 の2週間、毎日(土日除く)動画がアップされるのでそれを観ながら勉強しつつ、演習のテキストブックもこなすというもの。

相談等はDiscordでできます。日本人用のチャンネルもあり。

Exercise 1

Qiskitの基本操作と、計算複雑性(complexity)の話。

量子回路の計算量はゲートの数と量子回路の深さが関係している。量子回路の深さはテトリスのように計算できる

lab1_ex1 ~ lab1_ex6

特に言うことなし。Qiskitの量子ビットは右側が下位ビットであることに注意する。

lab1_ex7

テキストに qft_rotations が与えられているので、それの回路数を数える問題。

下図の赤枠内のゲートを数えればよい。

赤枠の量子ゲートの個数を数えればよい

Pゲートの個数は等差数列の和で求めた。

def lab1_ex7(n:int) -> int:
    #Here we want you to build a function calculating the number of gates needed by the fourier rotation for n qubits.
    
    numberOfGates=0 
    #
    # FILL YOUR CODE IN HERE
    # TRY TO FIND AN EXPLIZIT (NON RECURSIVE) FUNCTION
    #
    # 等差数列の和の公式を使う
    numberOfGates = (n-1)*n//2 + n
    return numberOfGates

lab1_ex8

量子回路の深さは浅いほどよい。  |00 \dots 0⟩ + |11 \dots 1⟩を深さ5で作るには、どうすればよいかという問題。

  • H(0)
  • CX(0,1)
  • CX(0,2), CX(1,3)
  • CX(0,4), CX(1,5), CX(2,6), CX(3,7)
  • ...

という風にやれば深さ5で作れる。

def lab1_ex8():
    qc = QuantumCircuit(16) #Same as above
    
    #Step 1: Preparing the first qubit in superposition
    qc.h(0)
    #
    #
    # FILL YOUR CODE IN HERE
    #
    N = 16
    c_end = 1
    is_end = False
    while 1:
        if is_end:
            break

        for i in range(0, c_end):
            if i+c_end == N:
                is_end = True
                break
            qc.cx(i, i+c_end)
        c_end *= 2
    return qc

Exercise 2

Qiskit Opflow ライブラリと、ハミルトニアンの話。

詰まりどころがあったりするので、このあたりからDiscordのピン留めされたメッセージは読まないといけないと思った。

grade_lab2_ex1 ~ grade_lab2_ex7

あんまり言うことなし。ライブラリの使い方の問題。

grade_lab2_ex8

# Define a parameter t for the time in the time evolution operator
t = Parameter('t')

# Follow the instructions above to define a time-evolution operator
###INSERT CODE BELOW THIS LINE

# time_evolution_operator = (H).exp_i() # Wrong answar but pass.
time_evolution_operator = (H*t).exp_i()

###DO NOT EDIT BELOW THIS LINE

print(time_evolution_operator)

初期の頃は t がなくてもパスした。

grade_lab2_ex9 ~ grade_lab2_ex10

これもライブラリの使い方の問題。MatrixEvolution を使ってハミルトニアンの時間発展をしてみようという問題。

grade_lab2_ex11

# Define the number of steps needed to reach the previously set total time-evolution
steps = int(evolution_time/time_step_value)

# Compose the operator for a Trotter step several times to generate the 
# operator for the full time-evolution
###INSERT CODE BELOW THIS LINE

total_time_evolution_operator = I
for i in range(steps):
    total_time_evolution_operator @= time_evolution_operator
pt_evo = PauliTrotterEvolution()
total_time_evolution_circuit = pt_evo.convert(total_time_evolution_operator)
total_time_evolution_circuit = total_time_evolution_circuit.bind_parameters({t: time_step_value})
    
###DO NOT EDIT BELOW THIS LINE

print(total_time_evolution_circuit)

MatrixEvolution の代わりに PauliTrotterEvolution を使ってハミルトニアンの時間発展してみようという問題。時間発展する際に e^{−iHt}を作ることになるが、MatrixEvolution は正確に指数化するが、PauliTrotterEvolution はTrotter-Suzuki 分解を使って近似的に指数化するらしい。

grade_lab2_ex12

# Set number of qubits
num_qubits = 3
# Define time parameter
t = Parameter('t')
# Set total evolution time
evolution_time_t = 2
# Set size of time-step for Trotter evolution
time_step_value_t = 0.1
# Define the number of steps
steps_t = int(evolution_time_t/time_step_value_t)
# Create circuit
tight_binding_circuit = QuantumCircuit(num_qubits)
# Add initial state preparation
tight_binding_circuit.x(0)

# Define the Hamiltonian, the time-evolution operator, the Trotter step and the total evolution
###INSERT CODE BELOW THIS LINE

# ハミルトニアン作成
H = (I^X^X) + (X^X^I) + (I^Y^Y) + (Y^Y^I)
# H = (X^X^I) + (I^X^X) + (I^Y^Y) + (Y^Y^I) # トロッター化は近似的な手法なので、ハミルトニアンの項の順序が異なると結果も若干違ってくる

# 時間発展オペレーター作成
time_evo_op = (H*t).exp_i()
full_time_evo_op = I
for i in range(steps_t):
    full_time_evo_op @= time_evo_op

# PauliTrotterEvolutionを使って全体の時間発展
pt_evo = PauliTrotterEvolution()
exp_circuit = pt_evo.convert(full_time_evo_op)
bind_exp_circuit = exp_circuit.bind_parameters({t: time_step_value_t})

# 回路を合体
full_time_evolution_circuit = tight_binding_circuit.compose(bind_exp_circuit.to_circuit()) 

###DO NOT EDIT BELOW THIS LINE

print(full_time_evolution_circuit)

初期状態を |001⟩の状態で、で時間発展させるというもの。

最初、ハミルトニアンの項の順番が異なっていてパスできず詰まってしまった。Discordのピン留めされたメッセージに注意書きは確認しようと心に決めた。

Exercise 3

量子ノイズの話。

シミュレータではノイズのない完璧な回転操作をシミュレートしてくれるが、実機ではそうはいかない。シミュレータでもノイズを載せてシミュレートできるので、それをしてみようという内容。

ノイズは from qiskit.providers.aer.noise import NoiseModelNoiseModel ライブラリで作成できる。

grade_lab3_ex1 ~ grade_lab3_ex3

特に言うことなし。

ところで、回路のシミュレータの実行方法が2種類(実際はもっとある)あって、qiskit.execute を呼ぶ方法と、backend.run を呼ぶ方法があるのだけど、何が違うのかわかってないし、同じだとしたらなぜこのような実装になっているのかもわからない。

# 以下の2つは何が違う?
result1 = qiskit.execute(circuit, shots_backend, shots=num_shots).result()
result2 = shots_backend.run(circuit, shots=num_shots).result()

grade_lab3_ex4

scipycurve_fit を使って、カーブフィッティング(曲線あてはめ)をしてみようという問題。

# Define a Gaussian function for the fit
def gaussian(x, a, mean, sigma):
    return a * (1/(sigma*np.sqrt(2*np.pi)))*np.exp(-((x - mean)**2 / (2 * sigma**2)))

# Set initial estimates for the fit: the maximum height of the histogram, the theoretical
# average and the theoretical standard deviation
height_estimate = np.max(yh)
mu_estimate = 0.5
sigma_estimate = np.sqrt(mu_estimate*(1-mu_estimate)/num_shots)

# Define list to store the estimated values of the average (mu_sigma[0]) and standard deviation (mu_sigma[1])
mu_sigma = []

# Use the curve_fit function to fit the experimental data using the definition of the Gaussian function
# which will return the estimate of the parameters.
###INSERT CODE BELOW THIS LINE

popt, pcov = curve_fit(gaussian, x_01_h, yh, [height_estimate, mu_estimate, sigma_estimate])  # popt:推定値, pcov: 共分散

mu_sigma = [0]*2
mu_sigma[0] = mu_estimate
mu_sigma[1] = sigma_estimate

###DO NOT EDIT BELOW THIS LINE   

print("The mean is ", mu_sigma[0])
print("The standard deviation is ", mu_sigma[1])

# Plot experimental data and the fit
plt.scatter(x_01_h, yh, label = 'data', color = 'red')
plt.plot(x_01, gaussian(x_01, *popt), label = 'fit', linestyle = '--')
plt.title(f"Using {num_shots} shots to estimate probability")
plt.xlim((0, 1))
plt.xlabel(r'probability of $\vert 0 \rangle$')
plt.ylabel('counts')
plt.legend()
plt.show()

ガウス関数のカーブフィッティング

grade_lab3_ex5 ~ grade_lab3_ex7

特に言うことなし。

grade_lab3_ex8

測定ノイズ(Measurement Noise)の話。

ノイズのあるシミュレータにおいて、 |\psi⟩状態のものを測定して、実際に得られた状態が |\phi⟩だったときの確率を p(\psi|\phi)とする(逆じゃない?と思ったが、ノイズモデルの構築#読み出しエラー - Qiskitを参照すると合ってる。ただ、測定エラーの軽減では行列の形が転置している)と、 |0⟩状態と |1⟩状態を基底としたときの混同行列(confusion matrix)A と表せる。

測定して  |0⟩ を得られる確率を p0 |1⟩ を得られる確率を p1として、

  •  p_{ideal} = [p0_{ideal}, p1_{ideal} ] を理想的な確率ベクトル
  •  p_{noisy} = [p0_{noisy}, p1_{noisy} ]をノイズのある確率ベクトル

とすると、 と表せる。

# Define the confusion matrix from the probabilities found above
###INSERT CODE BELOW THIS LINE

confusion_matrix = np.array([np.array([0.0 for _ in range(2)]) for __ in range(2)])
confusion_matrix[0][0] = p0_0
confusion_matrix[0][1] = p0_1
confusion_matrix[1][0] = p1_0
confusion_matrix[1][1] = p1_1

###DO NOT EDIT BELOW THIS LINE


print("Confusion matrix:")
print(confusion_matrix)

grade_lab3_ex9

 p_{noisy} から  p_{ideal} を求めることもできる。

これにより、ノイズのある環境で得られた確率から、逆行列 A^{-1}を使って「緩和」して、理想的な確率を求めることができる。

# Invert the confusion matrix
inverse_confusion_matrix = np.linalg.inv(confusion_matrix)

# Mitigate the counts using the inverse of the confusion matrix
###INSERT CODE BELOW THIS LINE

p_vector_mitigated = inverse_confusion_matrix.dot(p_vector_noisy)
        
###DO NOT EDIT BELOW THIS LINE


print("Mitigated probability of |0>: ", p_vector_mitigated[0])
print("Mitigated probability of |1>: ", p_vector_mitigated[1])

grade_lab3_ex10

コヒーレントノイズ(Coherent Noise)の話。

コヒーレンス時間という言葉があって、それは「重ね合わせ状態を維持できる時間」という意味で理解しているのだけど、ここでコヒーレントノイズはゲートによる回転操作などの干渉によって発生する「干渉ノイズ」という意味っぽい。

ノイズモデルの作り方はやってくれているので、問題としてやること自体は難しくない。

# Set value of rotation
theta = np.pi
# Instantiate a quantum circuit
circuit = qiskit.QuantumCircuit(quantum_register, classical_register)

# Add a parametrized RX rotation and bind the value of the parameter. (By default, parameter binding is not an in-place operation)
# Then measure the qubit and calculate probability of seeing |0> after rx(np.pi) 
###INSERT CODE BELOW THIS LINE

circuit.rx(theta, 0)
circuit.measure(0,0)

###DO NOT EDIT BELOW THIS LINE

grade_lab3_ex11

インコヒーレントノイズ(Incoherent Noise)の話。

「非干渉ノイズ」とでも訳すのだろうか。何も操作をしない id ゲート(idle操作)でも、ノイズの影響を受けるらしい。

ちなみにデコヒーレンス(Decoherence)という言葉もあって、これは「量子重ね合わせ状態の破壊」という意味。

問題としては、「id ゲートにおける5%の脱分極エラー(depolarizing error)のモデルを作れ」というもの。

depolarizing って何?という話なのだが、量子技術序論 - 東京大学 のP.143, 11.5節を流し読むと、お気持ちは理解できる。曰く、

  • pの確率でX軸まわりの反転が起きるエラーを「ビットフリップエラー」
  • pの確率でZ軸まわりの反転が起きるエラーを「位相フリップエラー」
  • pの確率でY軸まわりの反転が起きるエラーを「ビット-位相フリップエラー」

という。脱分極エラー(depolarizing error)とは、確率pで「ビットフリップエラー」「位相フリップエラー」「ビット-位相フリップエラー」のいずれかが起きるエラーのこと。だと思う。

Qiskitのテキストブックだと量子ボリュームの測定で出てくるが、説明は詳しくない。

Qiskitライブラリとしてはqiskit.providers.aer.noise.depolarizing_errorで提供されている。

# Define number of shots
num_shots_inchoherent = 10000

# Create an empty noise model
depolarizing_noise_model = NoiseModel()

# Define a depolarizing error on the identity gate for qubit zero and add it to the noise model
###INSERT CODE BELOW THIS LINE

error = depolarizing_error(0.05, 1)
depolarizing_noise_model.add_quantum_error(error, ["id"], qubits=[0])

###DO NOT EDIT BELOW THIS LINE

Exercise 4

最後のExercise。Spin-1/2 Model という量子系をシミュレートする。

grade_lab4_ex1

Exercise 2 でやったopflowライブラリを使って XXX Heisenberg ハミルトニアンを作成する。

# Returns the XXX Heisenberg model for 3 spin-1/2 particles in a line
def ex1_compute_H_heis3():
    # FILL YOUR CODE IN HERE
    
    H = (I^X^X)+(X^X^I)+(I^Y^Y)+(Y^Y^I)+(I^Z^Z)+(Z^Z^I)
    
    # Return Hamiltonian
    return H

grade_lab4_ex2

ハミルトニアンがわかれば、その系の量子状態が時間とともにどのように変化するかはシュレディンガーの方程式を使って解くことができる。

シュレディンガー方程式

opflowを使って、問題文に言われたとおりに実装するだけ。

# Returns the time evolution operator U_heis3(t) for a given time t assuming an XXX Heisenberg Hamiltonian for 3 spins-1/2 particles in a line
def ex2_compute_U_heis3(t):
    # FILL YOUR CODE IN HERE

    H = ex1_compute_H_heis3()
    U = (H*t).exp_i()
    
    return U

grade_lab4_ex3

時間発展演算子 Uができたので、量子系の任意の状態がどのように変化するかをシミュレーションできるようになった。XXX Heisenberg spin model を古典的にシミュレートしてとりあえず終了。

fidelity(フィデリティ・忠実度) という単語が出てくるが、量子情報理論の基本:フィデリティ - Qiitaによると、2つの純粋状態の類似度のことらしい。計算は簡単で、内積の絶対値をとるだけ。 |⟨\psi|\phi⟩|

混合状態のフィデリティも求めることができるが、コツがあるっぽい。

state tomography(状態トモグラフィー)という単語が出てくるが、量子情報理論の基本:量子状態トモグラフィ - Qiitaを読んでなるほどした。

(問題文訳)状態トモグラフィーとは、量子回路を複数回実行し、異なる基底で測定することで、量子回路の終端での正確な量子状態(位相情報まで)を決定する方法。最終的な量子状態が期待される状態に近ければ近いほど、フィデリティは高くなる。

ここにきてトロッターの近似について理解が進む。1-6の説明文にと書いてあるが、これはHが行列で非可換なので一般に e^{H_1 + H_2} = e^{H_1} \cdot e^{H_2} は成り立たないから。

なので、という近似を使う。Nは大きくするほど近似の精度は良くなる。これをリー・トロッターの積公式という。ということがシュレーディンガー方程式の時間発展を量子回路で表現 - Qiitaに書いてあった。

grade_lab4_ex3の問題としては、YY ゲートを作成すること。これは1-6の解説にある論文(F. Tacchino, et al., Quantum Computers as Universal Quantum Simulators: State-of-the-Art and Perspectives, Adv. Quantum Technol. 3 3 (2020) free arXiv version)に書いてある。

YY(δ)の量子回路を作れば良い。(論文から引用)

# Build a subcircuit for YY(t) two-qubit gate
def ex3_compute_YY_gate(t):
    # FILL YOUR CODE IN HERE
    
    YY_qr = QuantumRegister(2)
    YY_qc = QuantumCircuit(YY_qr, name="YY")
    
    YY_qc.rx(np.pi/2,[0,1])
    YY_qc.cnot(0,1)
    YY_qc.rz(2 * t, 1)
    YY_qc.cnot(0,1)
    YY_qc.rx(-np.pi/2,[0,1])
    
    YY = YY_qc.to_instruction()

    return YY

grade_lab4_ex4

ZZ ゲートの作成をすればOK。これも論文に書いてある。

ZZ ゲートの式(論文から引用)

量子回路ではこう。(論文から引用)

# Build a subcircuit for ZZ(t) two-qubit gate
def ex4_compute_ZZ_gate(t):
    # FILL YOUR CODE IN HERE

    ZZ_qr = QuantumRegister(2)
    ZZ_qc = QuantumCircuit(ZZ_qr, name="ZZ")
    
    ZZ_qc.cnot(0,1)
    ZZ_qc.rz(2 * t, 1)
    ZZ_qc.cnot(0,1)
        
    ZZ = ZZ_qc.to_instruction()

    return ZZ

lab4-optimization-contest

ノイズのないシミュレータで実行したとき、フィデリティは0.0003 ± 0.0002だった。

シミュレータで実行したときのstate tomography fidelity

これを実機のibmq_manilaというproviderで実行するとフィデリティは 0.0441 ± 0.0017 だった。

実機で実行したときのstate tomography fidelity

lab4-optimization-contestでは、このフィデリティをなるべく大きくする追加の最適化コンテストである(上記の例でシミュレータよりも実機でやったほうがフィデリティが高いのはなぜ?)。

フィデリティはstate_fidelityライブラリで計算される。使い方は量子操作の概要#任意の初期化 - Qiskitに書いてある。

import math
desired_vector = [
    1 / math.sqrt(16) * complex(0, 1),
    1 / math.sqrt(8) * complex(1, 0),
    1 / math.sqrt(16) * complex(1, 1),
    0,
    0,
    1 / math.sqrt(8) * complex(1, 2),
    1 / math.sqrt(16) * complex(1, 0),
    0]

state_fidelity(desired_vector, desired_vector)
# 0.9999999999999996

フィデリティを内積だと考えると直交している場合でフィデリティ0で最小になると思うので、平行にすれば値が大きくなるイメージだろうか。正規化されているので最大値は1っぽい。

近似精度を高くするためにざっと以下の方法が考えられる。

  • トロッターのNを大きくする
  • トロッター化のためのサブシステムの分割方法を変えてみる
  • トロッター化以外の方法を試す

Discordの議論を読んで思いつく方法

  • 回路の深さを減らす

トロッターのNを大きくする(trotter_stepsを増やす)

元々は trotter_steps = 4 だったので、これを大きくしてみることを試す。ノイズがないシミュレータの場合はtrotter_stepsを増やせば増やすほどフィデリティは100%に近づく(0.9くらい行った)。ただ実機の場合は回路が長くなるとノイズもそれだけ載るはずなので、ただ増やせばいいというわけではないはず。

  • trotter_steps = 100

trotter_steps=100の結果

Discordの議論を見てみてると、8か20がいいらしい。4が最悪で、1でも高い値がでるらしい(なんで?)。

  • trotter_steps = 8

trotter_steps=8の結果

たしかに100と8は大差ないスコアが出た。

トロッター化のためのサブシステムを変えてみる

論文の画像を見ると、(a) 6-CNOT decomposition (b) 3-CNOT decomposition (c) 3-U decomposition がある。今は(a)の方法で実装しているので、(b)と(c)を試す。

(c) の U_{xy} は以下で実装する。

U_xy(δ)(論文から引用)

(b) 3-CNOT decompositionの実装は以下になった。

###
# (b) 3-CNOT decomposition を実装
###
def trot_qc_3_cnot_decomposition(t, to_gate=True):
    qr = QuantumRegister(2)
    qc = QuantumCircuit(qr, name='3-CNOT decomposition')
        
    qc.cnot(0,1)
    qc.rx(2*t - math.pi/2, 0)
    qc.h(0)
    qc.rz(2*t, 1)
    qc.cnot(0,1)
    qc.h(0)
    qc.append(RZGate(2*t).inverse(), [1]) # qc.rzdg(2*t, 1)
    qc.cnot(0,1)
    qc.rx(math.pi/2, 0)
    qc.append(RXGate(math.pi/2).inverse(), [1]) # qc.rxdg(math.pi/2, 1)

    if not to_gate: return qc
    
    return qc.to_instruction()

Trot_qc_3_cnot_decom = trot_qc_3_cnot_decomposition(t, to_gate=False)
Trot_qc_3_cnot_decom.draw()

3-CNOT decomposition 回路

(c) 3-U decompositionの実装は以下のようになった。

###
# (c) 3-U decomposition を実装
###
def compute_U_xy(t):
    H = (X^X) + (Y^Y)
    U = (H*t).exp_i()
    return U

def U_xy(t):
    target_time = np.pi
    trotter_steps = 100

    U = compute_U_xy(t)
        
    pt_evo = PauliTrotterEvolution()
    exp_circuit = pt_evo.convert(U)
    
    bind_exp_circuit = exp_circuit.bind_parameters({t: target_time/trotter_steps})
    return bind_exp_circuit
# Uxy = U_xy(t).to_circuit()

def trot_qc_3_U(t, to_gate=True):
    qr = QuantumRegister(2)
    qc = QuantumCircuit(qr, name="3-U decomposition")
    
    qc = qc.compose(U_xy(t/2).to_circuit())
    qc.rx(math.pi/2, [0])
    qc.rx(math.pi/2, [1])
    qc = qc.compose(U_xy(t/2).to_circuit())
    qc.append(RXGate(math.pi/2).inverse(), [0])
    qc.append(RXGate(math.pi/2).inverse(), [1])
    qc.ry(math.pi/2, [0])
    qc.ry(math.pi/2, [1])
    qc = qc.compose(U_xy(t/2).to_circuit())
    qc.append(RYGate(math.pi/2).inverse(), [0])
    qc.append(RYGate(math.pi/2).inverse(), [1])
    
    return qc

qc_test = trot_qc_3_U(t)
qc_test.decompose().draw()

3-U decomposition 回路

ibmq_manilaを再現するシミュレータの作成

実機はibmq_manilaを使うことになっている。毎回実機にジョブを投げると時間がかかってしょうがないので、ローカルシミュレータにibmq_manilaと同程度のエラーを再現するノイズを作りたい。

ibmq_manilaのエラー率の調べ方は、 IBM Quantumに行き、左上のメニューから「Compute Resources」をクリックすると各ibmqの資源情報が見れる。そこの検索バーに「manila」と入力し、ibmq_manilaをクリックすると詳細が出てくる。「Table view」にそれっぽい情報があるので、これを参考にノイズを作ればいいと思う。

ibmq_manilaのスペック

たとえば、「Prob meas0 prep1」の項目が0.0426とは、「状態|1⟩で準備されたものが測定したら|0⟩だったときの確率」だと思う。

色々試行錯誤した結果、以下のようなノイズモデルを作成した。

def make_noise_model():
    # Measurement Noise
    noise_model = NoiseModel()
    # Measurement miss-assignement probabilities
    p_meas_prep_list = [[0.0426, 0.0138], [0.0304, 0.0256], [0.0288, 0.0164], [0.0636, 0.0182], [0.058, 0.0102]]
    
    for i in range(5):
        p0given1 = p_meas_prep_list[i][0] # Probability of measuuring |0> given the state is |1>
        p1given0 = p_meas_prep_list[i][1] # Probability of measuring |1> given the state is |0>
        readout_error = ReadoutError([[1 - p1given0, p1given0], [p0given1, 1 - p0given1]]) # Define readout error
        noise_model.add_readout_error(readout_error, qubits=[i]) # Add error to noise model
    
    # Pauli error
    pauli_error_list = [2.012e-4, 2.142e-4, 2.298e-4, 4.420e-4, 5.142e-4]
    for i in range(5):
        error = depolarizing_error(pauli_error_list[i], 1)
        noise_model.add_quantum_error(error, ["x", "y", "z"], qubits=[i])

    # U error
    u_error_list = [2.012e-4, 2.142e-4, 2.298e-4, 4.420e-4, 5.142e-4]
    for i in range(5):
        error = depolarizing_error(pauli_error_list[i], 1)
        noise_model.add_quantum_error(error, ["u1", "u2", "u3"], qubits=[i])

    # CNOT error (mixed_unitary_error使うべき?)
    cnot_error_list = [5.333e-3, 8.854e-3, 1.023e-2, 9.058e-3, 9.058e-3]
    for i in range(5):
        error = depolarizing_error(cnot_error_list[i], 1)
        noise_model.add_quantum_error(error, ["cnot"], qubits=[i])
    
    # Coherent Noise
    # Construct a 1 qubit over-rotation of the RX gate
    # epsilon = np.pi/200 # over rotation amount
    epsilon = np.pi/50 # over rotation amount
    epsilon_rotation = RXGate(epsilon).to_matrix() # get matrix representation
    over_rotation = coherent_unitary_error(epsilon_rotation)
    for i in range(5):
        noise_model.add_quantum_error(over_rotation, ['rx'], qubits=[i])
    epsilon_rotation = RYGate(epsilon).to_matrix() # get matrix representation
    over_rotation = coherent_unitary_error(epsilon_rotation)
    for i in range(5):
        noise_model.add_quantum_error(over_rotation, ['ry'], qubits=[i])
    epsilon_rotation = RZGate(epsilon).to_matrix() # get matrix representation
    over_rotation = coherent_unitary_error(epsilon_rotation)
    for i in range(5):
        noise_model.add_quantum_error(over_rotation, ['rz'], qubits=[i])
    
    # Incoherent Noise
    id_error_list = [2.012e-4, 2.142e-4, 2.298e-4, 4.420e-4, 5.142e-4]
    for i in range(5):
        error = depolarizing_error(id_error_list[i], 1)
        noise_model.add_quantum_error(error, ["id"], qubits=[i])
    
    return noise_model
noise_model_test = make_noise_model()
print(noise_model_test)
NoiseModel:
  Basis gates: ['cx', 'id', 'rx', 'ry', 'rz', 'sx', 'u1', 'u2', 'u3', 'x', 'y', 'z']
  Instructions with noise: ['rx', 'x', 'measure', 'cnot', 'rz', 'u2', 'y', 'id', 'z', 'ry', 'u3', 'u1']
  Qubits with noise: [0, 1, 2, 3, 4]
  Specific qubit errors: [('x', (0,)), ('x', (1,)), ('x', (2,)), ('x', (3,)), ('x', (4,)), ('y', (0,)), ('y', (1,)), ('y', (2,)), ('y', (3,)), ('y', (4,)), ('z', (0,)), ('z', (1,)), ('z', (2,)), ('z', (3,)), ('z', (4,)), ('u1', (0,)), ('u1', (1,)), ('u1', (2,)), ('u1', (3,)), ('u1', (4,)), ('u2', (0,)), ('u2', (1,)), ('u2', (2,)), ('u2', (3,)), ('u2', (4,)), ('u3', (0,)), ('u3', (1,)), ('u3', (2,)), ('u3', (3,)), ('u3', (4,)), ('cnot', (0,)), ('cnot', (1,)), ('cnot', (2,)), ('cnot', (3,)), ('cnot', (4,)), ('rx', (0,)), ('rx', (1,)), ('rx', (2,)), ('rx', (3,)), ('rx', (4,)), ('ry', (0,)), ('ry', (1,)), ('ry', (2,)), ('ry', (3,)), ('ry', (4,)), ('rz', (0,)), ('rz', (1,)), ('rz', (2,)), ('rz', (3,)), ('rz', (4,)), ('id', (0,)), ('id', (1,)), ('id', (2,)), ('id', (3,)), ('id', (4,)), ('measure', (0,)), ('measure', (1,)), ('measure', (2,)), ('measure', (3,)), ('measure', (4,))]

ノイズ有りシミュレータが作成できたので、各実装の回路をシミュレータと実機で実行してみて、ノイズの再現度を比べてみる。

(a) 元々のXXYYZZ の実装を使って、trotter_steps=8 でのノイズ有りシミュレータの実行結果と、実機での実行結果は以下のようになった。

XXYYZZ回路, trotter_steps=8 での実行結果

  • ノイズ有りシミュレータの結果がstate tomography fidelity on qasm_simulator = 0.3768 ± 0.0016
  • 実機の結果がstate tomography fidelity on qasm_simulator(ibmq_manila) = 0.3695 ± 0.0025

ノイズはいい感じに再現できた。

次に、(b) 3-CNOT decomposition回路を使って、trotter_steps=8でのノイズ有りシミュレータの実行結果と、実機での実行結果は以下のようになった。

3-CNOT回路, trotter_steps=8 での実行結果

  • ノイズ有りシミュレータの結果がstate tomography fidelity on qasm_simulator = 0.5231 ± 0.0015
  • 実機の結果がstate tomography fidelity on qasm_simulator(ibmq_manila) = 0.5284 ± 0.0030

次に、(c) 3-U decomposition回路を使って、trotter_steps=8でのノイズ有りシミュレータの実行結果と、実機での実行結果は以下のようになった。

3-U回路, trotter_steps=8 での実行結果

  • ノイズ有りシミュレータの結果がstate tomography fidelity on qasm_simulator = 0.4117 ± 0.0025
  • 実機の結果がstate tomography fidelity on qasm_simulator(ibmq_manila) = 0.2214 ± 0.0020

(c) の結果が低すぎる。なんか間違えてない?

C-NOTゲートの数を減らしたほうがいいのかなと思っていたので、どうしたもんかいのーといった感じ。

(b)の実装が一番スコアが高い結果となった。

Discordの議論を見る

ここでDiscordの議論を見ると、強化学習を使った方法でやって高いスコアを出した人がいたほか、4 cnot decomposition と General Error Mitigation と呼ばれる緩和技術を使って0.89722のスコアを出してる人がいた。

私はconfusion matrixを作ったりしたが、それをResultに適用する方法がいまいちわからなかった。

無理やりResultの結果を書き換えることはできて、

# confution_matrixを使ってjobの結果を緩和する
def mitigate_job_result(job_result):
    mat = make_confusion_matrix()
    # mat = make_confusion_matrix2()
    inv_mat = np.linalg.inv(mat)

    mitigated_exp_results = []
    for result in job_result.results:
        # p_noisy作成
        p_noisy = {}
        for key, value in result.data.counts.items():
            p_noisy[key] = value/result.shots
        
        # "0x0", "0x1", "0x2",... の順にp_vector_noisyを入れていく
        p_vector_noisy = []
        key_list = list(p_noisy.keys())
        key_list.sort()
        for key in key_list:
            p_vector_noisy.append(p_noisy[key])

        # 緩和した確率を得る
        p_vector_mitigated = inv_mat.dot(p_vector_noisy)

        # 緩和した結果を格納する
        result_dict = result.to_dict()
        new_data = {}
        new_dict = {}
        for key, p in zip(key_list, p_vector_mitigated):
            new_dict[key] = int(result.shots*p)
        if sum(new_dict.values()) != result.shots:
            total = sum(new_dict.values())
            while total > result.shots:
                total -= 1
                new_dict["0x0"] -= 1
            while total < result.shots:
                total += 1
                new_dict["0x6"] += 1
        
        new_data["counts"] = new_dict
        result_dict["data"] = new_data
        mitigated_exp_results.append(result.from_dict(result_dict))

    # 緩和後の結果に書き換える
    for i in range(len(job_result.results)):
        job_result.results[i] = mitigated_exp_results[i]

    # print(job_result.results)
    return job_result

とやったが、フィデリティを計算する関数でエラーが出てしまった。

CompleteMeasFitterを使えば良いともあって試したが、このライブラリがよくわからなくて、チュートリアルどおりにやるとフィデリティが下がってしまった。

あれこれ試したが時間切れになってしまった。結局ベストスコアは3-CNOT回路でtrotter_steps=8 の 0.5284 ± 0.0030 の結果で終了した。

おわり

去年と同様に修了証明書としてバッジをもらえた。

www.credly.com