Komputasi Kuantum Teori dan Praktik

Mirza Nur Hidayat & Muhammad Faruq Nuruddinsyah

DOI | Simulator | 2021-2026

Daftar Isi

Pendahuluan

Komputasi Kuantum Teori dan Praktik menyajikan dasar-dasar komputasi kuantum secara mudah dan sederhana, baik dari sisi teori maupun praktik. Dokumen terdiri atas delapan bab yakni Keadaan Kuantum, Operator, Entanglement, Pemrograman Kuantum, Algoritma Kuantum, Informasi Kuantum, Algoritma Kuantum Variasional, serta Pembelajaran Mesin Kuantum.

Bab pertama, Keadaan Kuantum, mengenalkan bahasan tentang qubit, keadaan kuantum, superposisi, probabilitas, kondisi ternormalisasi, vektor Bloch, bra-ket, inner product, ortogonal dan ortonormal, outer product, matriks densitas, trace, keadaan murni, keadaan campuran, serta sistem kuantum 1-qubit.

Bab kedua, Operator, berisi bahasan tentang operator, operator Pauli, Hermitian, normal, unitary, gerbang kuantum, serta gerbang Hadamard.

Selanjutnya, bab ketiga adalah Entanglement. Bab ini membahas sistem kuantum 2-qubit, produk tensor, product state, sifat separable, sifat entangled, e-bit, serta gerbang kuantum terkontrol.

Bab keempat, Pemrograman Kuantum, berisi sajian tentang bahasa pemrograman dan SDK Qiskit, Dinfio module quantum, simulator, visualisasi qubit, sirkuit kuantum, statevector, probabilitas, histogram, pengukuran, serta akses komputer kuantum dengan Qiskit Runtime.

Bab kelima, Algoritma Kuantum, membahas fondasi dan implementasi algoritma kuantum, diantaranya yakni transformasi Walsh-Hadamard, transformasi unitary, orakel kuantum, register kuantum, paralelisme kuantum, orakel fase, algoritma Deutsch, transformasi Fourier kuantum, estimasi fase kuantum, vektor eigen, nilai eigen, serta algoritma Grover.

Bab keenam, Informasi Kuantum. Bab keenam menyajikan bahasan tentang teleportasi, superdense coding, teorema no-cloning, operasi kuantum, representasi operator-sum, operator Kraus, completely positive, trace-preserving, kanal kuantum, kanal bit flip, kanal fase flip, kanal depolarisasi, operator densitas, peredaman amplitudo, peredaman fase, trace parsial, trace distance, fidelity, entropi, entropi von Neumann, koreksi eror kuantum, serta kode bit flip 3-qubit.

Bab ketujuh, Algoritma Kuantum Variasional. Beberapa topik yang disajikan di bab ketujuh atau bab variational quantum algorithm yaitu Hamiltonian, prinsip variasional, noisy intermediate-scale quantum, nilai ekspektasi, energi dasar, variational quantum eigensolver, ansatz, parameterised quantum circuit, optimasi, iterasi, quantum approximate optimisation algorithm, cost Hamiltonian, mixing Hamiltonian, Max-Cut problem, serta graf.

Bab kedelapan, Pembelajaran Mesin Kuantum. Bab terakhir ini membahas dasar-dasar pembelajaran mesin kuantum atau quantum machine learning diantaranya yakni model kuantum, trainable, optimisable, expressivity, trainability, peta fitur kuantum, vektor fitur, ruang fitur kuantum, enkode basis, enkode amplitudo, enkode sudut, enkode fase, enkode sudut padat, kernel kuantum, quantum support vector machine, quantum support vector classifier, quantum support vector regressor, regresi ridge, variational quantum circuit, variational quantum classifier, RealAmplitudes, variational quantum regressor, quantum neural network, perceptron kuantum, serta quantum convolutional neural network.

Penulisan urutan qubit dalam dokumen ini menggunakan konvensi little-endian, yakni \(\ket{q_{n-1}...q_1q_0}\), kecuali di beberapa persamaan yang bertanda \(^*\) berarti memakai konvensi big-endian \(\ket{q_0q_1...q_{n-1}}\).

Bab 1: Keadaan Kuantum

— Qubit, keadaan kuantum, superposisi, probabilitas, kondisi ternormalisasi, vektor Bloch, bra-ket, inner product, ortogonal dan ortonormal, outer product, matriks densitas, trace, keadaan murni, keadaan campuran, sistem kuantum 1-qubit.

Jika dalam komputasi klasikal dikenal istilah bit atau binary digit, yaitu 0 dan 1, maka komputasi kuantum dibangun dengan qubit — quantum bit. Qubit adalah representasi dari keadaan kuantum — quantum state. Keadaan kuantum juga dikenal sebagai vektor atau ket. Ia merupakan superposisi dari vektor \(\ket{0}\) dan vektor \(\ket{1}\)

\[ \ket{\psi} = \alpha\ket{0} + \beta\ket{1} \tag{1.1} \]

Vektor-vektor tersebut dapat ditulis dalam bentuk vektor kolom yaitu

\[ \ket{0} = \begin{pmatrix} 1\\ 0 \end{pmatrix} \tag{1.2} \]

\[ \ket{1} = \begin{pmatrix} 0\\ 1 \end{pmatrix} \tag{1.3} \]

dan

\[ \ket{\psi} = \alpha \begin{pmatrix} 1\\ 0 \end{pmatrix} + \beta \begin{pmatrix} 0\\ 1 \end{pmatrix} = \begin{pmatrix} \alpha\\ \beta \end{pmatrix} \tag{1.4} \]

dengan \(|\alpha|^2\) merupakan probabilitas menemukan \(\ket{\psi}\) di vektor \(\ket{0}\) dan \(|\beta|^2\) adalah probabilitas menemukan \(\ket{\psi}\) di vektor \(\ket{1}\). Hubungan dari keduanya yaitu

\[ |\alpha|^2 + |\beta|^2 = 1 \tag{1.5} \]

Kondisi \((1.5)\) disebut sebagai ternormalisasi (normalised).

Vektor \((1.1)\) juga dapat ditulis sebagai

\[ \ket{\psi} = \cos{\frac{\theta}{2}}\ket{0} + e^{i \phi} \sin{\frac{\theta}{2}}\ket{1} \tag{1.6} \]

dan dikenal sebagai vektor Bloch.

Berikutnya, bra \(\bra{0}\) merupakan transpos konjugat kompleks dari ket \(\ket{0}\) sehingga bra \(\bra{0} = \big(1 \ \ 0 \big) \) dan dengan cara yang sama diperoleh bra \(\bra{1} = \big(0 \ \ 1 \big)\).

Perhatikan perhitungan inner product bra-ket berikut.

\[ \left \langle 0 \middle | 1 \right \rangle = \big(1 \ \ 0 \big) \begin{pmatrix} 0\\ 1 \end{pmatrix} = 0 \tag{1.7} \]

\[ \left \langle 1 \middle | 0 \right \rangle = \big(0 \ \ 1 \big) \begin{pmatrix} 1\\ 0 \end{pmatrix} = 0 \tag{1.8} \]

\[ \left \langle 0 \middle | 0 \right \rangle = \big(1 \ \ 0 \big) \begin{pmatrix} 1\\ 0 \end{pmatrix} = 1 \tag{1.9} \]

\[ \left \langle 1 \middle | 1 \right \rangle = \big(0 \ \ 1 \big) \begin{pmatrix} 0\\ 1 \end{pmatrix} = 1 \tag{1.10} \]

Dari perhitungan di atas terlihat bahwa \( \left \langle 0 \middle | 1 \right \rangle = 0 \) dan \( \left \langle 1 \middle | 0 \right \rangle = 0 \), sehingga ket \(\ket{0}\) dan ket \(\ket{1}\) dikenal memiliki sifat ortogonal. Kedua ket juga memenuhi kaidah ortonormal, yaitu (a) bersifat ortogonal, (b) \( \left \langle 0 \middle | 0 \right \rangle = 1 \), dan (c) \( \left \langle 1 \middle | 1 \right \rangle = 1 \).

Lalu, bagaimanakah jika ket \(\ket{0}\) dikalikan dengan bra \(\bra{0}\). Outer product ini menghasilkan sebuah matriks dan dikenal sebagai matriks densitas \(\rho\).

\[ \ket{0} \bra{0} = \begin{pmatrix} 1\\ 0 \end{pmatrix} \big(1 \ \ 0 \big) = \begin{pmatrix} 1 & 0\\ 0 & 0 \end{pmatrix} \tag{1.11} \]

dan \(\ket{1}\bra{1}\) adalah

\[ \ket{1} \bra{1} = \begin{pmatrix} 0\\ 1 \end{pmatrix} \big(0 \ \ 1 \big) = \begin{pmatrix} 0 & 0\\ 0 & 1 \end{pmatrix} \tag{1.12} \]

Penjumlahan komponen diagonal pada matriks densitas dikenal sebagai trace \(Tr\), sehingga trace untuk \(\ket{0}\bra{0}\) sebesar

\[ Tr(\rho) = 1 + 0 = 1 \tag{1.13} \]

dan trace untuk \(\ket{1}\bra{1}\) senilai

\[ Tr(\rho) = 0 + 1 = 1 \tag{1.14} \]

Nilai trace yang bernilai 1 mencerminkan normalisasi probabilitas. Trace juga menggambarkan keadaan kuantum, apakah ia keadaan murni (pure state) atau keadaan campuran (mixed state). Keadaan murni jika

\[ \rho^2 = \rho, \ Tr(\rho^2) = 1 \tag{1.15} \]

dan keadaan campuran apabila

\[ Tr(\rho^2) < 1 \tag{1.16} \]

Diberikan contoh sebuah sistem kuantum 1-qubit

\[ \ket{\psi_q} = \frac{\sqrt{3}}{2}\ket{0} + \frac{1}{2}\ket{1} \tag{1.17} \]

Bagaimanakah analisis dari vektor di atas? Secara analitis matematis, vektor \((1.17)\) mempunyai nilai \(\alpha = \frac{\sqrt{3}}{2}\) dan \(\beta = \frac{1}{2}\), sehingga \(|\alpha|^2 + |\beta|^2 = \frac{3}{4} + \frac{1}{4} = 1\). Hal ini berarti bahwa probabilitas menemukan \(\ket{\psi_q}\) di keadaan \(\ket{0}\) sebesar \(\frac{3}{4}\) dan probabilitas bernilai \(\frac{1}{4}\) di keadaan \(\ket{1}\).

Guna mendapatkan keadaan kuantum \((1.17)\), maka keadaan \(\ket{0}\) dilakukan rotasi sebesar \(\frac{\pi}{3}\). Dari \((1.1)\) dan \((1.6)\) dapat dijelaskan bahwa nilai \(\alpha\) pada \((1.17)\) yang merupakan \(\cos{\frac{\theta}{2}} = \frac{\sqrt{3}}{2}\) sehingga \(\theta = \frac{\pi}{3}\) dan \(\beta\) representasi dari \(\mathrm{e}^{i \phi} \sin{\frac{\theta}{2}} = \frac{1}{2}\) maka \(\theta = \frac{\pi}{3}\) dengan \(\phi = 0\).

Secara umum, nilai bra \(\bra{\psi}\) dari sebuah keadaan kuantum \(\ket{\psi} = \alpha\ket{0} + \beta\ket{1}\) memiliki persamaan

\[ \bra{\psi} = \alpha^*\bra{0} + \beta^*\bra{1} \tag{1.18} \]

dan untuk matriks densitasnya

\[ \ket{\psi} \bra{\psi} = \begin{pmatrix} \alpha\\ \beta \end{pmatrix} \begin{pmatrix} \alpha^* & \beta^* \end{pmatrix} \tag{1.19} \]

dengan \(\alpha^*\) dan \(\beta^*\) berturut-turut ialah konjugat kompleks dari \(\alpha\) dan \(\beta\). Misal \(\alpha = \frac{1}{\sqrt{2}}\), \(\beta = \frac{i}{\sqrt{2}}\) maka \(\alpha^* = \frac{1}{\sqrt{2}}\), \(\beta^* = \frac{-i}{\sqrt{2}}\).

Selanjutnya, dari contoh \((1.17)\) diperoleh

\[ \bra{\psi_q} = \frac{\sqrt{3}}{2}\bra{0} + \frac{1}{2}\bra{1} \tag{1.20} \]

serta

\[ \ket{\psi_q} \bra{\psi_q} = \begin{pmatrix} \frac{\sqrt{3}}{2}\\ \frac{1}{2} \end{pmatrix} \begin{pmatrix} \frac{\sqrt{3}}{2} & \frac{1}{2} \end{pmatrix} = \begin{pmatrix} \frac{3}{4} & \frac{\sqrt{3}}{4} \\ \frac{\sqrt{3}}{4} & \frac{1}{4} \end{pmatrix} \tag{1.21} \]

Dengan mengecek nilai \(Tr(\rho)\), \(\rho^2\), dan \(Tr(\rho^2)\) pada matriks densitas \((1.21)\) maka dapat diketahui sifat keadaan kuantum \(\ket{\psi_q}\) dari contoh \((1.17)\) yakni apakah ia keadaan murni atau keadaan campuran.

Bab 2: Operator

— Operator, operator Pauli, Hermitian, normal, unitary, gerbang kuantum, gerbang Hadamard.

Operator merupakan sebuah mathematical rule dan pada saat diaplikasikan ke sebuah keadaan \(\ket{\psi}\) maka akan mentransformasikannya menjadi keadaan lain \(\ket{\psi'}\). Operator dalam mekanika kuantum dapat dituliskan sebagai \(A\) sehingga

\[ A \ket{\psi} = \ket{\psi'} \tag{2.1} \]

Dalam komputasi kuantum terdapat sebuah set operator fundamental dan dikenal sebagai operator Pauli. Operator Pauli terdiri atas \(\sigma_0\), \(\sigma_1\), \(\sigma_2\), \(\sigma_3\) atau \(\sigma_0\), \(\sigma_x\), \(\sigma_y\), \(\sigma_z\) atau \(I\), \(X\), \(Y\), \(Z\).

Adapun representasi matriks dari operator Pauli yaitu

\[ I = \begin{pmatrix} 1 & 0\\ 0 & 1 \end{pmatrix} \tag{2.2} \]

\[ X = \begin{pmatrix} 0 & 1\\ 1 & 0 \end{pmatrix} \tag{2.3} \]

\[ Y = \begin{pmatrix} 0 & -i\\ i & 0 \end{pmatrix} \tag{2.4} \]

\[ Z = \begin{pmatrix} 1 & 0\\ 0 & -1 \end{pmatrix} \tag{2.5} \]

Operator juga harus memenuhi kaidah mekanika kuantum, yaitu bersifat Hermitian, normal, dan unitary. Operator dikatakan memiliki sifat Hermitian jika

\[ A = A^\dagger \tag{2.6} \]

dengan \(A^{\dagger}\) adalah transpos konjugat kompleks \(A\). Sebagai contoh adalah operator Pauli \(Y\). Transpos konjugat kompleks \(Y\) adalah

\[ Y^{\dagger} = \begin{pmatrix} 0 & -(i)\\ -(-i) & 0 \end{pmatrix} = \begin{pmatrix} 0 & -i\\ i & 0 \end{pmatrix} \tag{2.7} \]

Dari \((2.4)\) dan \((2.7)\) terlihat bahwa \(Y = Y^{\dagger}\).

Kaidah berikutnya adalah normal dan unitary. Operator disebut normal jika

\[ A A^\dagger = A^\dagger A \tag{2.8} \]

dan bersifat unitary apabila

\[ U U^\dagger = U^\dagger U = I \tag{2.9} \]

Sebagai contoh adalah pada operator Pauli \(Y\) kembali.

\[ Y Y^{\dagger} = \begin{pmatrix} 0 & -i\\ i & 0 \end{pmatrix} \begin{pmatrix} 0 & -i\\ i & 0 \end{pmatrix} = \begin{pmatrix} -i^2 & 0\\ 0 & -i^2 \end{pmatrix} \tag{2.10} \]

\[ Y^{\dagger} Y = \begin{pmatrix} 0 & -i\\ i & 0 \end{pmatrix} \begin{pmatrix} 0 & -i\\ i & 0 \end{pmatrix} = \begin{pmatrix} -i^2 & 0\\ 0 & -i^2 \end{pmatrix} \tag{2.11} \]

\[ Y Y^{\dagger} = Y^{\dagger} Y = \begin{pmatrix} 1 & 0\\ 0 & 1 \end{pmatrix} \tag{2.12} \]

Dari \((2.10)\), \((2.11)\), dan \((2.12)\) terlihat bahwa \(Y Y^\dagger = Y^\dagger Y\) dan \(Y Y^\dagger = Y^\dagger Y = I\).

Telah disebutkan di awal bab bahwa sebuah operator saat diaplikasikan pada sebuah keadaan \(\ket{\psi}\) akan mentransformasikannya menjadi keadaan lain \(\ket{\psi'}\). Sebagai contoh yaitu

\[ X \ket{0} = \ket{1} \tag{2.13} \]

Contoh \((2.13)\) dapat diuraikan dengan perhitungan matriks \((2.3)\) dan \((1.2)\) sehingga

\[ \begin{pmatrix} 0 & 1\\ 1 & 0 \end{pmatrix} \begin{pmatrix} 1\\ 0 \end{pmatrix} = \begin{pmatrix} 0\\ 1 \end{pmatrix} \tag{2.14} \]

Matriks \((2.14)\) tidak bukan merupakan representasi keadaan \(\ket{1}\) seperti pada \((1.3)\). Dari contoh ini, operator Pauli \(X\) dapat disebut sebagai gerbang kuantum (quantum gate) — gerbang \(X\). Pun demikian dengan gerbang \(Y\) dan gerbang \(Z\).

Gerbang kuantum fundamental berikutnya adalah gerbang Hadamard. Gerbang Hadamard memiliki representasi matriks

\[ H = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1\\ 1 & -1 \end{pmatrix} \tag{2.15} \]

Saat gerbang Hadamard dioperasikan pada keadaan kuantum \(\ket{0}\) maka akan mentransformasikannya menjadi keadaan kuantum \(\ket{+}\).

\[ H\ket{0} = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1\\ 1 & -1 \end{pmatrix} \begin{pmatrix} 1\\ 0 \end{pmatrix} = \frac{1}{\sqrt{2}} \begin{pmatrix} 1\\ 1 \end{pmatrix} \tag{2.16} \]

sehingga

\[ H\ket{0} = \frac{1}{\sqrt{2}} \begin{pmatrix} 1\\ 0 \end{pmatrix} + \frac{1}{\sqrt{2}} \begin{pmatrix} 0\\ 1 \end{pmatrix} = \frac{1}{\sqrt{2}} \ket{0} + \frac{1}{\sqrt{2}} \ket{1} = \ket{+} \tag{2.17} \]

Dengan cara perhitungan yang sama, operasi \(H\) pada \(\ket{1}\) akan menghasilkan vektor \(\ket{-}\).

\[ H \ket{0} = \ket{+}, \ H \ket{1} = \ket{-} \tag{2.18} \]

Dua contoh gerbang kuantum yang terakhir yakni gerbang \(S\) dan gerbang \(T\). Gerbang \(S\) dikenal dengan gerbang fase dan gerbang \(T\) sebagai gerbang \(\frac{\pi}{8}\). Adapun bentuk matriksnya yakni

\[ S = \begin{pmatrix} 1 & 0\\ 0 & i \end{pmatrix} \tag{2.19} \]

dan

\[ T = \begin{pmatrix} 1 & 0\\ 0 & e^{i\pi/4} \end{pmatrix} \tag{2.20} \]

Bab 3: Entanglement

— Sistem kuantum 2-qubit, produk tensor, product state, separable, entangled, e-bit, gerbang kuantum terkontrol.

Jika pada dua bab sebelumnya pembahasan dasar-dasar komputasi kuantum pada sistem kuantum 1-qubit, maka pada bab ketiga ini akan disajikan pembahasan pada sistem kuantum 2-qubit. Sistem kuantum 2-qubit melibatkan dua buah qubit, yakni qubit 0 dan qubit 1, yang secara umum qubit 0 ditulis sebagai \(q_0\) dan qubit 1 sebagai \(q_1\).

Sistem kuantum 1-qubit memiliki persamaan \(\ket{\psi} = \alpha \ket{0} + \beta \ket{1}\) seperti pada \((1.1)\), maka sistem kuantum 2-qubit mempunyai persamaan

\[ \ket{\psi} = \alpha \ket{00} + \beta \ket{01} + \gamma \ket{10} + \delta \ket{11} \tag{3.1} \]

dengan \(|\alpha|^2\), \(|\beta|^2\), \(|\gamma|^2\), dan \(|\delta|^2\) berturut-turut adalah probabilitas menemukan keadaan \(\ket{00}\), \(\ket{01}\), \(\ket{10}\), dan \(\ket{11}\), serta memenuhi kaidah \(|\alpha|^2 + |\beta|^2 + |\gamma|^2 + |\delta|^2 = 1\).

Contoh sistem kuantum 2-qubit yaitu keadaan

\[ \ket{\psi_1} = \ket{01} \tag{3.2} \]

Keadaan \(\ket{\psi_1}\) memiliki representasi vektor kolom

\[ \ket{\psi_1} = \begin{pmatrix} 0 \\ 1 \\ 0 \\ 0 \\ \end{pmatrix} \tag{3.3} \]

Keadaan \(\ket{\psi_1}\) juga merupakan hasil produk tensor dari keadaan \(\ket{0}\) dan \(\ket{1}\) atau dapat ditulis sebagai

\[ \ket{\psi_1} = \ket{0} \otimes \ket{1} = \begin{pmatrix} 1\\ 0 \end{pmatrix} \otimes \begin{pmatrix} 0\\ 1 \end{pmatrix} = \begin{pmatrix} 0\\ 1\\ 0\\ 0 \end{pmatrix} \tag{3.4} \]

sehingga

\[ \ket{\psi_1} = 0\ket{00} + 1\ket{01} + 0\ket{10} + 0\ket{11} = \ket{01} \tag{3.5} \]

Contoh sistem kuantum 2-qubit yang kedua adalah

\[ \ket{\psi_2} = \frac{1}{\sqrt{2}} \ket{01} + \frac{1}{\sqrt{2}} \ket{11} \tag{3.6} \]

Keadaan \(\ket{\psi_2}\) ini merupakan produk tensor dari \(\ket{+}\) dan \(\ket{1}\)

\[ \ket{\psi_2} = \ket{+} \otimes \ket{1} = \frac{1}{\sqrt{2}} \begin{pmatrix} 1\\ 1 \end{pmatrix} \otimes \begin{pmatrix} 0\\ 1 \end{pmatrix} \tag{3.7} \]

sehingga

\[ \ket{\psi_2} = \frac{1}{\sqrt{2}} \begin{pmatrix} 0\\ 1\\ 0\\ 1 \end{pmatrix} = \frac{1}{\sqrt{2}} \ket{01} + \frac{1}{\sqrt{2}} \ket{11} \tag{3.8} \]

Kedua contoh keadaan di atas \(\ket{\psi_1}\) dan \(\ket{\psi_2}\) merupakan product state atau bersifat separable. Keadaan kuantum memiliki sifat separable jika memenuhi kaidah

\[ \alpha \delta = \beta \gamma \tag{3.9} \]

Keadaan \(\ket{\psi_1} = \ket{01}\) memiliki nilai \(\alpha = 0\), \(\beta = 1\), \(\gamma = 0\), dan \(\delta = 0\), sehingga \(\alpha \delta = \beta \gamma = 0\). Selanjutnya, \(\ket{\psi_2} = \frac{1}{\sqrt{2}} \ket{01} + \frac{1}{\sqrt{2}} \ket{11}\) memiliki nilai \(\alpha = 0\), \(\beta = \frac{1}{\sqrt{2}}\), \(\gamma = 0\), dan \(\delta = \frac{1}{\sqrt{2}}\), sehingga \(\alpha \delta = \beta \gamma = 0\).

Contoh sistem kuantum 2-qubit ketiga adalah

\[ \ket{\Phi^+} = \frac{1}{\sqrt{2}}\ket{00} + \frac{1}{\sqrt{2}}\ket{11} \tag{3.10} \]

Berbeda dengan dua keadaan kuantum sebelumnya, keadaan kuantum \(\ket{\Phi^+}\) bukan merupakan product state atau tidak bersifat separable. Keadaan kuantum \(\ket{\Phi^+}\) memiliki \(\alpha \delta = \frac{1}{2}\) dan \(\beta \gamma = 0\) sehingga \(\alpha \delta \neq \beta \gamma\). Keadaan kuantum \(\ket{\Phi^+}\) dikenal sebagai entangled state atau e-bit.

Sebelum melangkah ke pembahasan tentang entangled state lebih lanjut, akan dikenalkan terlebih dahulu gerbang kuantum yang baru, yakni gerbang kuantum terkontrol atau controlled-\(U\). Gerbang ini dapat ditulis sebagai gerbang \(CU\) dan memiliki persamaan

\[ CU = \ket{0}\bra{0} \otimes I + \ket{1}\bra{1} \otimes U \tag{3.11} \]

dengan \(U\) adalah operasi unitary. Oleh karenanya, jika akan dibuat gerbang terkontrol lain semisal \(CX\), maka

\[ CX = \ket{0}\bra{0} \otimes I + \ket{1}\bra{1} \otimes X \tag{3.12} \]

yang mana \(X\) ialah gerbang \(X\).

Keadaan kuantum \(\ket{\Phi^+}\) dapat dibentuk dengan memanfaatkan gerbang \(CX\) atau juga dikenal sebagai gerbang \(CNOT\). Gerbang \(CX\) memiliki matriks

\[ CX = \begin{pmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 1\\ 0 & 0 & 1 & 0 \end{pmatrix} \tag{3.13} \]

Keadaan \(\ket{\Phi^+}\) dapat ditulis sebagai

\[ \ket{\Phi^+} = CX(H\ket{0} \otimes \ket{0}) \tag{3.14*} \]

atau

\[ \ket{\Phi^+} = CX(\ket{+} \otimes \ket{0}) \tag{3.15*} \]

maka representasi matriks dari \(\ket{\Phi^+}\) adalah

\[ \ket{\Phi^+} = \begin{pmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 1\\ 0 & 0 & 1 & 0 \end{pmatrix} \left( \frac{1}{\sqrt{2}} \begin{pmatrix} 1\\ 1 \\ \end{pmatrix} \otimes \begin{pmatrix} 1\\ 0 \end{pmatrix} \right) = \frac{1}{\sqrt{2}} \begin{pmatrix} 1\\ 0 \\ 0 \\ 1 \end{pmatrix} \tag{3.16*} \]

atau

\[ \ket{\Phi^+} = \frac{1}{\sqrt{2}}\ket{00} + \frac{1}{\sqrt{2}}\ket{11} \tag{3.17} \]

Perhitungan dari persamaan \((3.14^*)\) dapat juga dilakukan dengan cara

\[ \ket{\Phi^+} = CX \big(\frac{1}{\sqrt{2}} (\ket{0} + \ket{1}) \otimes \ket{0} \big) \tag{3.18*} \]

atau

\[ \ket{\Phi^+} = \frac{1}{\sqrt{2}} CX (\ket{00} + \ket{10}) = \frac{1}{\sqrt{2}} (CX \ket{00} + CX \ket{10}) \tag{3.19*} \]

sehingga

\[ \ket{\Phi^+} = \frac{1}{\sqrt{2}} (\ket{00} + \ket{11}) \tag{3.20*} \]

Contoh lain dari entangled state yaitu keadaan \(\ket{\Phi^-}\), keadaan \(\ket{\Psi^+}\), dan keadaan \(\ket{\Psi^-}\).

\[ \ket{\Phi^-} = \frac{1}{\sqrt{2}} (\ket{00} - \ket{11}) \tag{3.21} \]

\[ \ket{\Psi^+} = \frac{1}{\sqrt{2}} (\ket{01} + \ket{10}) \tag{3.22} \]

\[ \ket{\Psi^-} = \frac{1}{\sqrt{2}} (\ket{01} - \ket{10}) \tag{3.23} \]

Keempat keadaan kuantum terakhir di atas dikenal sebagai Bell state atau EPR state.

Bab 4: Pemrograman Kuantum

— Bahasa pemrograman dan SDK Qiskit, Dinfio module quantum, simulator, visualisasi qubit, sirkuit kuantum, statevector, probabilitas, histogram, pengukuran, akses komputer kuantum dengan Qiskit Runtime.

Salah satu pilihan bahasa pemrograman dan SDK kuantum ialah Qiskit. Qiskit merupakan sebuah framework open-source untuk pengembangan aplikasi kuantum. Diciptakan oleh IBM, Qiskit menyediakan tools dan libraries yang diperlukan untuk membuat, menguji, dan menjalankan algoritma kuantum, baik pada simulator maupun pada komputer kuantum riil IBM.

Selain Qiskit, pilihan pemrograman kedua yang dipilih yakni dengan menggunakan Dinfio module quantum.

4.1 Visualisasi Qubit

Qubit dalam komputasi kuantum dapat divisualisasikan dengan menggunakan bola Bloch. Contoh: visualisasi keadaan kuantum \(\ket{10}\). Qubit 0, \(q_0 = \ket{0}\) dan qubit 1, \(q_1 = \ket{1}\).

Code: Qubit Visualisation
                
    # Qubit Visualisation
        
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from qiskit.visualization import plot_bloch_multivector

    qc = QuantumCircuit(2)

    qc.x(1)

    state = Statevector(qc)

    plot_bloch_multivector(state)
                
                
            

4.2 Sirkuit Kuantum

Gambar di atas merupakan sirkuit kuantum dari sistem kuantum 2-qubit. Terdapat gerbang Hadamard pada \(q_0\) serta gerbang CX dengan \(q_0\) sebagai kontrol dan \(q_1\) sebagai target. Sirkuit ini tidak bukan merupakan representasi dari entangled state seperti pada \((3.10)\).

Code: Quantum Circuit
                
    # Quantum Circuit
        
    from qiskit import QuantumCircuit

    qc = QuantumCircuit(2)

    qc.h(0)
    qc.cx(0, 1)

    qc.draw("mpl", scale = 1.0, style = {"backgroundcolor": "#fff"})
                
                
            
Code: Quantum Circuit (Dinfio)
                
    ' Quantum Circuit

    import quantum

    qc = quantum_circuit(2)

    qc.h(0)
    qc.cx(0, 1)

    qc.draw()
                
                
            

4.3 Statevector

\[ \frac{\sqrt{2}}{2}\ket{00} + \frac{\sqrt{2}}{2}\ket{11} \]

Statevector output "LaTeX" hasil pemrograman keadaan kuantum entangled state \((3.10)\).

Code: Statevector
                
    # Statevector
        
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector

    qc = QuantumCircuit(2)

    qc.h(0)
    qc.cx(0, 1)

    state = Statevector(qc)

    state.draw("latex")
                
                
            
Code: Statevector (Dinfio)
                
    ' Statevector
        
    import quantum

    qc = quantum_circuit(2)

    qc.h(0)
    qc.cx(0, 1)

    qc.print_statevector("latex")
                
                
            

4.4 Probabilitas & Histogram

Probabilitas dan histogram hasil pemrograman untuk entangled state pada \((3.10)\). Hasil simulator menunjukkan probabilitas ket \(\ket{00}\) sebesar 45% dan ket \(\ket{11}\) sebesar 55%.

Code: Probability & Histogram
                
    # Probability & Histogram
        
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from qiskit.visualization import plot_histogram

    qc = QuantumCircuit(2)

    qc.h(0)
    qc.cx(0, 1)

    state = Statevector(qc)
    counts = state.sample_counts(100)
        
    print(counts)
    plot_histogram(counts)
                
                
            
Code: Probability (Dinfio)
                
    ' Probability
        
    import quantum

    qc = quantum_circuit(2)

    qc.h(0)
    qc.cx(0, 1)

    qc.print_probability()
                
                
            

4.5 Pengukuran

Pengukuran pada qubit \(q_0\) dari keadaan kuantum \(\frac{1}{\sqrt{2}} \big( \ket{00} + \ket{01} \big)\). Hasilnya yakni {'1': 55, '0': 45}.

Code: Measurement
                
    # Measurement
        
    from qiskit import QuantumCircuit
    from qiskit.primitives import StatevectorSampler

    qc = QuantumCircuit(2, 1)

    qc.h(0)

    qc.measure(0, 0)

    sampler = StatevectorSampler()
    job = sampler.run([(qc, None, 100)])
    result = job.result()

    counts = result[0].data.c.get_counts()

    print(counts)
    qc.draw("mpl", style = {"backgroundcolor": "#f5f5f5"})
                
                
            
Code: Measurement (Dinfio)
                
    ' Measurement
        
    import quantum

    qc = quantum_circuit(2, 1)
    
    qc.h(0)
    
    qc.measure(0, 0)
    
    qc.draw()
    qc.print_classical_bits()
                
                
            

4.6 Akses Komputer Kuantum

Saat ini, komputer kuantum masih tergolong sangat langka dan umumnya hanya dimiliki serta dikembangkan oleh Big Tech, universitas, dan startup global. Meskipun demikian, terdapat cara untuk mengakses komputer kuantum, yaitu melalui layanan cloud. Salah satu opsi yang tersedia adalah akses ke komputer kuantum IBM melalui Qiskit Runtime.

Berikut adalah contoh kode program untuk menyelesaikan masalah komputasi kuantum dengan memanfaatkan perangkat komputer kuantum melalui fasilitas Qiskit Runtime. Dalam contoh ini, Qiskit Runtime digunakan untuk mengakses dan menjalankan algoritma kuantum secara langsung pada perangkat keras kuantum, memberikan pengalaman praktis dalam pemrograman dan eksekusi komputasi kuantum.

Code: Qiskit IBM Runtime
                
    # Qiskit IBM Runtime

    from qiskit import QuantumCircuit
    from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
    from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

    service = QiskitRuntimeService(channel = "ibm_quantum", token = "your_IBM_account")

    qc = QuantumCircuit(2)

    qc.h(0)
    qc.cx(0, 1)

    qc.measure_all()

    backend = service.least_busy(operational = True, simulator = False)
    pm = generate_preset_pass_manager(backend = backend, optimization_level = 1)
    isa_circuit = pm.run(qc)

    sampler = Sampler(mode = backend)
    sampler.options.default_shots = 100000
    job = sampler.run([isa_circuit])

    pub_result = job.result()[0]
    print(f"Counts for the meas output register: {pub_result.data.meas.get_counts()}")
                
                
            

Bab 5: Algoritma Kuantum

— Transformasi Walsh-Hadamard, transformasi unitary, orakel kuantum, register kuantum, paralelisme kuantum, orakel fase, algoritma Deutsch, transformasi Fourier kuantum, estimasi fase kuantum, vektor eigen, nilai eigen, algoritma Grover.

Bab ini membahas fondasi dan implementasi dari algoritma kuantum, yaitu algoritma yang dijalankan pada simulator ataupun komputer kuantum dengan memanfaatkan sifat-sifat khas sistem kuantum.

5.1 Transformasi Walsh-Hadamard

Transformasi Walsh, atau juga dikenal sebagai transformasi Walsh-Hadamard, merupakan pengembangan atau generalisasi transformasi Hadamard. Sebuah qubit dengan keadaan kuantum \(\ket{0}\) jika diberikan trasformasi Hadamard \(H\) maka keadaan kuantum tersebut akan berubah menjadi keadaan superposisi \(\frac{1}{\sqrt{2}}(\ket{0} + \ket{1})\), sebagaimana telah dibahas seperti pada \((2.15)\), \((2.16)\), dan \((2.17)\). Transformasi Walsh-Hadamard mentransformasi \(n\) qubit, yang semuanya pada keadaan \(\ket{0}\), seluruhnya berubah menjadi keadaan superposisi.

Secara matematis, transformasi Walsh-Hadamard dapat ditulis sebagai

\[ W\ket{0} = \frac{1}{\sqrt{2^n}} \sum_{x=0}^{2^n-1} \ket{x} \tag{5.1.1} \]

Contoh: Implementasi transformasi Walsh-Hadamard untuk \(n=2\), maka

\[ W\ket{0} = \frac{1}{\sqrt{2^2}} \sum_{x=0}^{2^2-1} \ket{x} = \frac{1}{2} \sum_{x=0}^{3} \ket{x} \tag{5.1.2} \]

\[ W\ket{0} = \frac{1}{2} (\ket{0} + \ket{1} + \ket{2} + \ket{3}) \tag{5.1.3} \]

atau

\[ W\ket{0} = \frac{1}{2} (\ket{00} + \ket{01} + \ket{10} + \ket{11}) \tag{5.1.4} \]

Hasil \((5.1.4)\) menunjukkan bahwa semua vektor telah berada pada keadaan superposisi. Adapun contoh kegunaan transformasi Walsh-Hadamard yaitu sebagai tahap inisialisasi dalam implementasi algoritma-algoritma kuantum, semisal algoritma Deutsch dan algoritma Grover.

Code: Walsh-Hadamard Transformation
                
    # Walsh-Hadamard Transformation
        
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector

    n = 2
    qc = QuantumCircuit(n)

    for i in range(n):
        qc.h(i)

    state = Statevector(qc)

    state.draw("latex")
    qc.draw("mpl")
                
                
            
Code: Walsh-Hadamard Transformation (Dinfio)
                
    ' Walsh-Hadamard Transformation
        
    import quantum

    n = 2
    qc = quantum_circuit(n)
    
    for i, 0, n - 1
        qc.h(i)
    endfor
    
    qc.print_statevector("latex")
    qc.draw()
                
                
            

5.2 Transformasi Unitary

Transformasi unitary ialah sebuah operasi kuantum yang mana ia memetakan atau mengkodekan sebuah fungsi \(f\) dalam komputasi kuantum. Operasi kuantum ini lazim dikenal sebagai orakel kuantum atau quantum oracle. Salah satu contoh transformasi unitary disajikan dalam bentuk matematis sebagai berikut

\[ U_f:\ket{x,y} \to \ket{x, y \oplus f(x)} \tag{5.2.1*} \]

Ada dua register kuantum dalam transformasi di atas, yakni \(\ket{x}\) dan \(\ket{y}\). Register \(\ket{x}\) berperan sebagai qubit input, yakni representasi variabel masukan \(x\), dan register \(\ket{y}\) sebagai qubit tambahan atau qubit ancilla, berfungsi untuk menyimpan informasi tentang fungsi \(f\). Adapun notasi \(\oplus\) sendiri ialah operasi XOR.

Kasus ke-1: Register \(\ket{x, y} = \ket{0, 0}\) dan \(f(0) = 0\) maka

\[ U_f \ket{0, 0} = \ket{0, 0 \oplus 0} = \ket{0, 0} \tag{5.2.2*} \]

Kasus ke-2: Register \(\ket{x, y} = \ket{1, 0}\) dan \(f(1) = 1\) maka

\[ U_f \ket{1, 0} = \ket{1, 0 \oplus 1} = \ket{1, 1} \tag{5.2.3*} \]

5.3 Paralelisme Kuantum

Paralelisme kuantum yaitu sebuah konsep dalam komputasi kuantum yang memanfaatkan sifat superposisi untuk melakukan banyak perhitungan sekaligus. Fokusnya adalah pada transformasi kuantum \(U_f\) dan bagaimana ia bekerja dengan superposisi. Berikut ialah contoh perhitungan konsep paralelisme kuantum dengan memperhatikan kembali transformasi unitary seperti pada \((5.2.1^*)\).

Contoh: Nilai keadaan \(\ket{x} = H \ket{0} = \frac{1}{\sqrt{2}} (\ket{0} + \ket{1}) \) dan \(\ket{y} = \ket{0}\) maka

\[ U_f \bigg(\frac{1}{\sqrt{2}} (\ket{0} + \ket{1})\bigg) \ket{0} = \frac{1}{\sqrt{2}} (U_f \ket{00} + U_f \ket{10}) \tag{5.3.1*} \]

atau

\[ \frac{\ket{0, 0 \oplus f(0)} + \ket{1, 0 \oplus f(1)}}{\sqrt{2}} \tag{5.3.2*} \]

Hasil \((5.3.2^*)\) menunjukkan bahwa \(U_f\) dapat menghitung \(f(x)\) untuk semua \(x\) hanya dalam satu langkah.

5.4 Orakel Fase

Orakel fase dalam komputasi kuantum ialah jenis orakel yang digunakan untuk memodifikasi fase dari suatu keadaan kuantum. Secara matematis, orakel fase dapat direpresentasikan sebagai

\[ U_f \ket{x} = (-1)^{f(x)} \ket{x} \tag{5.4.1} \]

Dengan \(f(x)\) adalah fungsi yang mengevaluasi apakah \(x\) sebagai solusinya. Misalnya yaitu \(f(x)=1\) untuk solusi dan \(f(x)=0\) bukan untuk solusi. Jika \(f(x)=1\) maka keadaan \(\ket{x}\) berubah menjadi \(-\ket{x}\) atau ada perubahan fase pada \(\ket{x}\), sedang jika \(f(x)=0\) maka keadaan \(\ket{x}\) tetap.

Contoh: Penerapan orakel fase pada sistem kuantum 2 qubit. Adapun fungsi yang diharapkan yaitu \(U_f\) mengubah fase keadaan \(\ket{01}\), sementara keadaan yang lain yakni \(\ket{00}\), \(\ket{10}\), dan \(\ket{11}\) tetap. Orakel fase ini dapat ditulis sebagai

\[ U_f \ket{01} = -\ket{01} \tag{5.4.2} \]

dan

\[ U_f \ket{00} = \ket{00}, \ U_f \ket{10} = \ket{10}, \ U_f \ket{11} = \ket{11} \tag{5.4.3} \]

Adapun gerbang-gerbang kuantum yang dibutuhkan untuk membuat orakel fase \(U_f\) ini ditunjukkan dalam sirkuit kuantum yakni pada bagian yang diapit oleh dua barrier.

Hasil akhir sistem kuantum 2 qubit yang diharapkan yaitu

\[ \frac{1}{2} \ket{00} - \frac{1}{2} \ket{01} + \frac{1}{2} \ket{10} + \frac{1}{2} \ket{11} \tag{5.4.4} \]

Code: Phase Oracle
                
    # Phase Oracle Uf∣01⟩ = -∣01⟩
        
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector

    qc = QuantumCircuit(2)

    qc.h([0, 1])

    qc.barrier(0, 1)
    qc.x(1)
    qc.cz(0, 1)
    qc.x(1)
    qc.barrier(0, 1)

    state = Statevector(qc)

    state.draw("latex")
    qc.draw("mpl")
                
                
            
Code: Phase Oracle (Dinfio)
                
    ' Phase Oracle Uf∣01⟩ = -∣01⟩
        
    import quantum

    qc = quantum_circuit(2)
    
    qc.h(0)
    qc.h(1)
    
    qc.barrier()
    qc.x(1)
    qc.cz(0, 1)
    qc.x(1)
    qc.barrier()
    
    qc.print_statevector("latex")
    qc.draw()
                
                
            

5.5 Algoritma Deutsch

Algoritma Deutsch menentukan apakah suatu fungsi biner \(f: \{0, 1\} \to \{0, 1\}\) adalah konstan atau seimbang. Disebut konstan jika \(f(0) = f(1) = 0\) atau \(f(0) = f(1) = 1\). Adapun dinamai seimbang jika \(f(0) = 0, f(1) = 1\) atau \(f(0) = 1, f(1) = 0\). Dalam komputasi klasik, penentuan fungsi biner ini membutuhkan dua langkah eksekusi.

Langkah ke-1: Input = 0 ⇒ \(f(0)\) ⇒ Output = 0.
Langkah ke-2: Input = 1 ⇒ \(f(1)\) ⇒ Output = 0 atau 1.

Jika output pada langkah ke-2 adalah 0, maka fungsi biner bersifat konstan, sebaliknya apabila output pada langkah ke-2 memberi hasil 1, fungsi binernya adalah fungsi seimbang. Jadi untuk mengetahui sifat fungsi biner pada komputasi klasik dibutuhkan dua langkah implementasi. Hal ini akan berbeda jika fungsi biner tersebut dijalankan dengan komputasi kuantum, yaitu dengan hanya membutuhkan satu langkah eksekusi.

Persamaan implementasi algoritma Deutsch yakni

\[ \ket{\psi_{\text{out}}} = (H \otimes I) U_f (H \otimes H) \ket{0} \ket{1} \tag{5.5*} \]

Orakel kuantum \(U_f\) dapat diisi dengan gerbang-gerbang kuantum seperti pada tabel sesuai dengan jenis orakel \(U_f\) yang diinginkan, yakni apakah konstan atau seimbang.

Sifat \(f\) \(U_f\)
Konstan \(f(0) = f(1) = 0\) -
Konstan \(f(0) = f(1) = 1\) \(X(q_1)\)
Seimbang \(f(0) = 0\), \(f(1) = 1\) \(CX\)
Seimbang \(f(0) = 1\), \(f(1) = 0\) \(X(q_0)\), \(CX\)

Code: Deutsch’s Algorithm
                
    # Deutsch’s Algorithm
    
    from qiskit import QuantumCircuit
    from qiskit.primitives import StatevectorSampler

    qc = QuantumCircuit(2, 1)

    qc.h(0)
    qc.x(1)
    qc.h(1)

    # Uf goes here

    qc.h(0)

    qc.measure(0, 0)

    sampler = StatevectorSampler()
    job = sampler.run([(qc, None, 1024)])
    result = job.result()

    counts = result[0].data.c.get_counts()
    print(counts)

    qc.draw("mpl")
                
                
            
Code: Deutsch’s Algorithm (Dinfio)
                
    ' Deutsch’s Algorithm
    
    import quantum

    qc = quantum_circuit(2, 1)
    
    qc.h(0)
    qc.x(1)
    qc.h(1)
    
    ' Uf goes here
    
    qc.h(0)
    
    qc.measure(0, 0)
    
    qc.draw()
    qc.print_classical_bits()
                
                
            

5.6 Transformasi Fourier Kuantum

Transformasi Fourier kuantum atau Quantum Fourier Transform (QFT) merupakan operasi linear pada register kuantum \(n\) qubit yang mengubah state dalam basis komputasi \(|0\rangle, |1\rangle, \dots, |2^n-1\rangle\) menjadi basis Fourier. Adapun register kuantum sendiri ialah sekumpulan qubit yang digunakan untuk menyimpan dan memproses informasi dalam komputasi kuantum. Setiap qubit dalam register dapat berada dalam state \(|0\rangle\), \(|1\rangle\), atau kombinasi liniernya \((\alpha|0\rangle + \beta|1\rangle)\). Untuk \(n\) qubit, register kuantum dapat mewakili superposisi dari \(2^n\) state sekaligus.

\[ \ket{\psi_n} = \sum_{i=0}^{2^n-1} a_i \ket{i} \tag{5.6.1} \]

Selanjutnya, secara matematis QFT dapat ditulis sebagai

\[ QFT\ket{\psi} \mapsto \frac{1}{\sqrt{2^n}} \sum_{x=0}^{2^n-1} e^{2\pi i kx / 2^n} \ket{x} \tag{5.6.2} \]

Contoh ke-1: Implementasi QFT pada state \(\ket{0}\). Pada contoh ini, QFT diterapkan pada sistem 1-qubit \(\ket{0}\), maka \(n=1\) dan \(k=0\).

\[ QFT\ket{0} \mapsto \frac{1}{\sqrt{2^1}} \sum_{x=0}^{2^1-1} e^{2\pi i ⋅ 0 ⋅ x / 2^1} \ket{x} \tag{5.6.3} \]

atau

\[ QFT\ket{0} \mapsto \frac{1}{\sqrt{2}} \sum_{x=0}^{1} \ket{x} \tag{5.6.4} \]

Didapat

\[ QFT\ket{0} = \frac{1}{\sqrt{2}} (\ket{0} + \ket{1}) \tag{5.6.5} \]

Hasil \((5.6.5)\) menunjukkan bahwa \(QFT\ket{0}\) adalah sama dengan \(H\ket{0}\) seperti pada \((2.17)\).

Contoh ke-2: Implementasi QFT pada state \(\ket{01}\). Pada contoh kedua ini, state merupakan sistem 2-qubit, sehingga \(n=2\) dan \(k=1\), maka

\[ QFT\ket{01} \mapsto \frac{1}{\sqrt{2^2}} \sum_{x=0}^{2^2-1} e^{2\pi i ⋅ 1 ⋅ x / 2^2} \ket{x} \tag{5.6.6} \]

atau

\[ QFT\ket{01} \mapsto \frac{1}{2} \sum_{x=0}^{3} e^{\pi i ⋅ x / 2} \ket{x} \tag{5.6.7} \]

Diperoleh

\[ QFT\ket{01} = \frac{1}{2} \Big( e^{\pi i ⋅ 0 / 2} \ket{0} + e^{\pi i ⋅ 1 / 2} \ket{1} + e^{\pi i ⋅ 2 / 2} \ket{2} + e^{\pi i ⋅ 3 / 2} \ket{3} \Big) \tag{5.6.8} \]

Ingat bahwa dalam persamaan Euler \(e^{i\theta} = \cos \theta + i \sin \theta\); dan \(\ket{0} = \ket{00}, \ket{1} = \ket{01}, \ket{2} = \ket{10}, \ket{3} = \ket{11}\); maka didapatkan hasil

\[ QFT\ket{01} = \frac{1}{2} \Big( \ket{00} + i\ket{01} - \ket{10} - i\ket{11} \Big) \tag{5.6.9} \]

Code: QFT∣01⟩
                
    # QFT∣01⟩
        
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from math import pi

    qc = QuantumCircuit(2)

    qc.x(0)
    qc.barrier(0, 1)

    qc.h(1)
    qc.cp(pi/2, 0, 1)
    qc.h(0)
    qc.swap(0, 1)

    state = Statevector(qc)

    state.draw("latex")
    qc.draw("mpl")
                
                
            
Code: QFT∣01⟩ Using QFTGate
                
    # QFT∣01⟩ Using QFTGate
        
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from qiskit.circuit.library import QFTGate
    
    n = 2
    qc = QuantumCircuit(n)

    qc.x(0)

    qft = QFTGate(n)
    qc.append(qft, list(range(n)))

    state = Statevector(qc)

    state.draw("latex")
    qc.draw('mpl')
                
                
            
Code: QFT∣01⟩ Matrix Analysis
                
    # QFT∣01⟩ Matrix Analysis
    
    import numpy as np

    i = complex(0, 1)

    # Qubit |0>
    Psi = np.array([[1], 
                    [0]])

    # I Gate
    I = np.array([[1, 0], 
                  [0, 1]])

    # X Gate
    X = np.array([[0, 1], 
                  [1, 0]])

    # Hadamard Gate
    H = (1 / np.sqrt(2)) * np.array([[1, 1], 
                                     [1, -1]])

    # Control Phase PI/2
    CP = np.array([[1, 0, 0, 0], 
                   [0, 1, 0, 0], 
                   [0, 0, 1, 0], 
                   [0, 0, 0, i]], 
                   dtype=complex)

    # SWAP Gate
    SWAP = np.array([[1, 0, 0, 0], 
                     [0, 0, 1, 0], 
                     [0, 1, 0, 0], 
                     [0, 0, 0, 1]])

    Psi_1 = np.kron((np.dot(H, Psi)), (np.dot(X, Psi)))
    Psi_2 = np.dot(CP, Psi_1)
    Psi_3 = np.dot((np.kron(I, H)), Psi_2)
    Psi_4 = np.dot(SWAP, Psi_3)

    print(Psi_4)
                
                
            
Code: QFT∣01⟩ (Dinfio)
                
    ' QFT∣01⟩
    
    import quantum

    qc = quantum_circuit(2)
    
    qc.x(0)
    qc.barrier(0, 1)
    
    qc.h(1)
    qc.cp(pi / 2, 0, 1)
    qc.h(0)
    qc.swap(0, 1)
    
    ' qc.qft([0, 1])
    
    qc.print_statevector("latex")
    qc.draw()
                
                
            

5.7 Estimasi Fase Kuantum

Selain QFT, estimasi fase kuantum atau Quantum Phase Estimation (QPE) juga merupakan salah satu algoritma fundamental dalam komputasi kuantum yang menjadi dasar bagi algoritma kuantum lainnya. QPE bertujuan untuk menemukan fase \(\theta\) dari ekspresi matematika

\[ U \ket{\psi} = e^{2 \pi i \theta} \ket{\psi} \tag{5.7.1} \]

dengan \(U\), \(\ket{\psi}\), dan \(e^{2 \pi i \theta}\) berturut-turut adalah operasi unitary, vektor eigen, dan nilai eigen. Implementasi QPE terdiri atas empat tahapan, yakni inisialisasi, operasi \(U\) terkontrol, inverse QFT, serta pengukuran.

5.7.1 Estimasi Fase 1-Qubit

Sebagai dasar dan gambaran awal mengenai estimasi fase, berikut disajikan bahasan tentang estimasi fase 1-qubit. Adapun perhitungan dalam notasi bra-ketnya diuraikan tahap demi tahap sebagai berikut.

Kolom ke-1: Inisialisasi. Terdapat gerbang \(H\) pada qubit \(q_0\) (counting qubit) dan gerbang \(X\) pada qubit \(q_1\) (vektor eigen) sehingga

\[ \ket{\psi_1} = (X \ket{0}) \otimes (H \ket{0}) \tag{5.7.2} \]

atau

\[ \ket{\psi_1} = \ket{1} \big(\frac{1}{\sqrt{2}} (\ket{0} + \ket{1})\big) = \frac{1}{\sqrt{2}} \ket{1}\ket{0} + \frac{1}{\sqrt{2}} \ket{1}\ket{1} \tag{5.7.3} \]

Kolom ke-2: Operasi \(U\) terkontrol. Gerbang kuantum \(CU\) diterapkan pada kedua qubit, dengan \(q_0\) berperan sebagai qubit kontrol dan \(q_1\) sebagai qubit target. Perlu diingat kembali bahwa pada komputasi kuantum, gerbang kontrol hanya akan aktif jika qubit kontrol bernilai \(\ket{1}\), sehingga

\[ \ket{\psi_2} = \frac{1}{\sqrt{2}} \ket{1}\ket{0} + \frac{1}{\sqrt{2}} (U\ket{1})\ket{1} \tag{5.7.4} \]

Dari \((5.7.1)\) maka \((5.7.4)\) menjadi

\[ \ket{\psi_2} = \frac{1}{\sqrt{2}} \ket{1}\ket{0} + \frac{1}{\sqrt{2}} (\lambda\ket{1})\ket{1} \tag{5.7.5} \]

dengan \(\lambda = e^{2 \pi i \theta}\). Persamaan \((5.7.5)\) dapat juga ditulis sebagai

\[ \ket{\psi_2} = \ket{1} \otimes \bigg( \frac{1}{\sqrt{2}} \ket{0} + \frac{1}{\sqrt{2}} \lambda\ket{1}\bigg) \tag{5.7.6} \]

Kolom ke-3: Inverse QFT. Pada tahap ini, inverse QFT diaplikasikan pada qubit \(q_0\). Inverse QFT untuk 1 qubit sama dengan gerbang Hadamard, sehingga

\[ \ket{\psi_3} = \ket{1} \otimes \bigg( \frac{1}{\sqrt{2}} H\ket{0} + \frac{1}{\sqrt{2}} \lambda H\ket{1}\bigg) \tag{5.7.7} \]

atau

\[ \ket{\psi_3} = \ket{1} \otimes \bigg( \frac{1}{\sqrt{2}} \big(\frac{1}{\sqrt{2}} (\ket{0} + \ket{1}) \big) + \frac{1}{\sqrt{2}} \lambda \big(\frac{1}{\sqrt{2}} (\ket{0} - \ket{1}) \big) \bigg) \tag{5.7.8} \]

sehingga

\[ \ket{\psi_3} = \ket{1} \otimes \bigg( \frac{1 + \lambda}{2} \ket{0} + \frac{1 - \lambda}{2} \ket{1} \bigg) \tag{5.7.9} \]

Kolom ke-4: Pengukuran. Pengukuran pada \(q_0\) menghasilkan dua buah probabilitas yakni

\[ p_\ket{0} = \bigg|\frac{1 + \lambda}{2} \bigg|^2, \ p_\ket{1} = \bigg|\frac{1 - \lambda}{2} \bigg|^2 \tag{5.7.10} \]

5.7.2 QPE Gerbang \(T\) - Estimasi Fase 1-Qubit

Salah satu contoh sederhana implementasi QPE yakni penggunaan gerbang \(T\) sebagai operator unitary dalam QPE dan ingin diestimasi nilai fase \(\theta\)-nya. Representasi matematis implementasi QPE ini yaitu

\[ T \ket{1} = e^{2 \pi i \theta} \ket{1} \tag{5.7.11} \]

atau

\[ T \ket{1} = \begin{pmatrix} 1 & 0\\ 0 & e^{\frac{i\pi}{4}} \end{pmatrix} \begin{pmatrix} 0\\ 1 \end{pmatrix} = \begin{pmatrix} 0\\ e^{\frac{i\pi}{4}} \end{pmatrix} = e^{\frac{i\pi}{4}} \ket{1} \tag{5.7.12} \]

Dari \((5.7.12)\) maka nilai eigen \(\lambda = e^{\frac{i\pi}{4}}\) dan fase \(\theta\) yang diinginkan sebesar \(\frac{1}{8}\). Selanjutnya, nilai \(\lambda\) dimasukkan ke dalam \((5.7.10)\) hingga didapat probabilitas \(\ket{0} \approx 0.8536\) dan probabilitas \(\ket{1} \approx 0.1464\).

Probabilitas hasil pengukuran sebagai output sirkuit kuantum QPE gerbang \(T\) dengan estimasi fase 1-qubit yakni {'0': 857, '1': 143}. Hasil pengukuran ini mendekati hasil perhitungan di atas. Perlu dicermati bahwa pada estimasi fase 1-qubit atau dikenal juga presisi 1-qubit, hasil '0' memiliki arti \(\frac{0}{2^1} = 0\) dan '1' berarti \(\frac{1}{2^1} = 0.5\). Nilai 0 dan 0.5 ini tidak lain ialah nilai fase \(\theta\) hasil pengukuran. Fase \(\theta = 0\) sebesar 85.7% dan \(\theta = 0.5\) senilai 14.3%. Estimasi fase 1-qubit masih memberi selisih yang besar karena nilai fase \(\theta\) yang diinginkan adalah \(\frac{1}{8}\) atau 0.125.

5.7.3 QPE Gerbang \(T\) - Estimasi Fase 2-Qubit

Berikutnya, implementasi QPE gerbang \(T\) dengan estimasi fase 2-qubit atau presisi 2-qubit. Probabilitas hasil pengukuran pada sirkuit kuantum yakni {'00': 406, '01': 418, '10': 96, '11': 80}. Maksud dari nilai-nilai ini secara berturut-turut ialah \('00' \to \frac{0}{2^2} = 0 \to 40.6\%\); \('01' \to \frac{1}{2^2} = 0.25 \to 41.8\%\); \('10' \to \frac{2}{2^2} = 0.5 \to 9.6\%\); dan \('11' \to \frac{3}{2^2} = 0.75 \to 8.0\%\). Jika diambil dua probabilitas terbesar, maka nilai fase \(\theta = 0\) senilai 40.6% dan fase \(\theta = 0.25\) sebesar 41.8%.

5.7.4 QPE Gerbang \(T\) - Estimasi Fase 3-Qubit

Terakhir, implementasi QPE gerbang \(T\) dengan estimasi fase 3-qubit atau presisi 3-qubit. Adapun probabilitas hasil pengukuran dari sirkuit kuantum yakni {'001': 1000}. Nilai ini berarti \('001' \to \frac{1}{2^3} = \frac{1}{8} = 0.125 \to 100\%\). Probabilitas nilai fase \(\theta = 0.125\) sebesar 100%. Hasil implementasi ini sama dengan nilai fase yang diinginkan yakni \(\theta = 0.125\).

Code: QPE n-Qubit Phase Estimation
                
    # QPE n-Qubit Phase Estimation
    
    from qiskit import QuantumCircuit
    from qiskit.primitives import StatevectorSampler
    from qiskit.circuit.library import QFT
    from math import pi

    # n-Qubit
    n = 3
    qc = QuantumCircuit(n + 1, n)

    # Eigenvector
    qc.x(n)

    # Initialisation
    for i in range(n):
        qc.h(i)

    # Controlled-U
    theta = pi / 4
    r = 1
    for q in range(n):
        for i in range(r):
            qc.cp(theta, q, n)
        r *= 2
    
    # Inverse QFT
    qc.append(QFT(n, inverse = True), list(range(n)))

    # Measurement
    for i in range(n):
        qc.measure(i, i)

    sampler = StatevectorSampler()
    job = sampler.run([(qc, None, 1000)])
    result = job.result()

    counts = result[0].data.c.get_counts()
    print(counts)

    qc.draw("mpl")
                
                
            
Code: QPE n-Qubit Phase Estimation (Dinfio)
                
    ' QPE n-Qubit Phase Estimation

    import quantum
    
    ' n-Qubit
    n = 3
    qc = quantum_circuit(n + 1, n)
    
    ' Eigenvector
    qc.x(n)
    
    ' Initialisation
    for i, 0, n - 1
        qc.h(i)
    endfor
    
    ' Controlled-U
    theta = pi / 4
    r = 1
    for q, 0, n - 1
        for i, 0, r - 1
            qc.cp(theta, q, n)
        endfor
        r *= 2
    endfor
    
    ' Inverse QFT
    qc.iqft([0, 1, 2])
    
    ' Measurement
    for i, 0, n - 1
        qc.measure(i, i)
    endfor
    
    qc.draw()
    qc.print_classical_bits()
                
                
            

5.8 Algoritma Grover

Bahasan dan implementasi algoritma kuantum yang terakhir yakni algoritma Grover. Adapun contoh kasus yang dipilih yaitu implementasi algoritma Grover dalam menyelesaikan ekspresi logika sederhana (A XOR B) AND B. Secara praktis, algoritma Grover memiliki empat tahapan, yaitu inisialisasi, orakel kuantum, transformasi difusi, serta pengukuran.

5.8.1 Ekspresi Logika

Ekspresi logika (A XOR B) AND B. Ekspresi ini mempunyai dua variabel A dan B serta dua buah klausa dengan operator XOR dan AND. Tabel kebenaran ekspresi logika (A XOR B) AND B disajikan dalam tabel di bawah. Dari tabel terlihat bahwa ekspresi logika akan bernilai true jika A bernilai 0 dan B bernilai 1.

A B A XOR B (A XOR B) AND B
0 0 0 0
1 0 1 0
0 1 1 1
1 1 0 0

5.8.2 Inisialisasi

Langkah pertama implementasi algoritma Grover dalam komputasi kuantum adalah inisialisasi. Ekspresi logika (A XOR B) AND B mempunyai dua variabel A dan B sehingga dibutuhkan dua buah qubit sebagai representasi dari variabel-variabel ini. Qubit \(q_0\) untuk variabel A dan qubit \(q_1\) untuk variabel B. Selain itu, dibutuhkan juga qubit \(q_2\). Qubit \(q_0\) dan \(q_1\) ada di keadaan kuantum \(\ket{0}\) dan qubit \(q_2\) berada di keadaan kuantum \(\ket{1}\). Terakhir, gerbang Hadamard diaplikasikan pada setiap qubit.

5.8.3 Orakel Kuantum

Dalam komputasi kuantum, operator XOR dan AND dapat direpresentasikan oleh gerbang CNOT dan gerbang Toffoli. Dengan mengkombinasikan kedua gerbang kuantum ini maka dapat dibuatlah sirkuit kuantum untuk ekspresi logika (A XOR B) AND B. Pada sirkuit kuantum ini dibutuhkan qubit \(q_3\) sebagai qubit ancilla.

Pertama, diasumsikan bahwa \(q_0\) dan \(q_1\), ada pada keadaan kuantum \(\ket{0}\). Pada kolom ke-1 dari sirkuit terdapat gerbang CNOT yang mana \(q_1\) berlaku sebagai kontrol dan \(q_0\) berperan sebagai target. Karena \(q_1 = \ket{0}\) maka \(q_0\) tetap pada keadaan \(\ket{0}\). Pada kolom ke-2 terdapat gerbang Toffoli dengan \(q_0\) dan \(q_1\) sebagai kontrol dan \(q_3\) sebagai target. Pada kolom ke-2 ini, \(q_0 = q_1 = \ket{0}\) sehingga \(q_3\) tetap pada keadaan kuantum \(\ket{0}\).

Kedua, diasumsikan bahwa \(q_0 = \ket{1}\) dan \(q_1 = \ket{0}\). Setelah diaplikasikan pada gerbang CNOT, \(q_1 = \ket{0}\) dan \(q_0 = \ket{1}\). Jika kedua keadaan kuantum ini diaplikasikan pada gerbang Toffoli, maka \(q_3 = \ket{0}\).

Ketiga, diberikan \(q_0 = \ket{0}\) dan \(q_1 = \ket{1}\). Qubit \(q_0\) akan berubah menjadi \(\ket{1}\) setelah diaplikasikan pada gerbang CNOT dengan \(q_1 = \ket{1}\). Kedua keadaan kuantum ini yaitu \(q_0 = q_1 = \ket{1}\) akan memberikan hasil keluaran gerbang Toffoli \(q_3 = \ket{1}\).

Terakhir, qubit \(q_0\) dan \(q_1\) ada pada keadaan kuantum \(\ket{1}\). Setelah melalui gerbang CNOT, \(q_0\) berubah menjadi \(\ket{0}\) dan \(q_1\) tetap pada keadaan \(\ket{1}\). Kedua keadaan kuantum ini menghasilkan qubit \(q_3\) bernilai \(\ket{0}\) pada gerbang Toffoli.

Langkah berikutnya yaitu memodifikasi sirkuit kuantum sehingga didapatkan orakel kuantum untuk ekspresi logika (A XOR B) AND B yaitu dengan menambahkan gerbang Z pada qubit ancilla \(q_3\) dan melakukan pencerminan gerbang CNOT dan Toffoli.

5.8.4 Transformasi Difusi

Jika ingin dilakukan analisis dengan perhitungan matriks pada transformasi difusi, maka digunakan operasi matriks

\[ 2 \ket{0} \bra{0} - I \tag{5.8.1} \]

Karena merupakan sistem kuantum 2-qubit maka ket \(\ket{0}\) menjadi \(\ket{00}\) dan bra \(\bra{0}\) menjadi \(\bra{00}\) sehingga operasi matriks \((5.8.1)\) berubah menjadi

\[ 2 \ket{00} \bra{00} - I_2 \tag{5.8.2} \]

Representasi matriks dari gerbang kuantum \(CZ(Z \otimes Z)\) dari gambar juga dapat dihitung. Kedua perhitungan menghasilkan matriks yang sama yaitu

\[ 2 \ket{00} \bra{00} - I_2 = CZ(Z \otimes Z) = \begin{pmatrix} 1 & 0 & 0 & 0\\ 0 & -1 & 0 & 0\\ 0 & 0 & -1 & 0\\ 0 & 0 & 0 & -1 \end{pmatrix} \tag{5.8.3} \]

5.8.5 Pengukuran

Pengukuran adalah tahapan keempat implementasi algoritma Grover dalam komputasi kuantum. Dalam pengukuran ini, dibutuhkan dua buah register klasik.

5.8.6 Sirkuit Kuantum dan Kode Program Algoritma Grover

Penggabungan sirkuit inisialisasi, orakel kuantum, transformasi difusi, dan pengukuran akan menghasilkan sirkuit kuantum algoritma Grover sebagai solusi dari implementasi ekspresi logika (A XOR B) AND B. Selanjutnya, sirkuit kuantum ini dapat dijalankan di simulator atau di komputer kuantum riil.

Code: Grover's Algorithm
                
    # Grover's Algorithm
    # Solving "(A XOR B) AND B" logical expression

    from qiskit import QuantumCircuit
    from qiskit.primitives import StatevectorSampler

    qc = QuantumCircuit(4, 2)

    qc.h(0)
    qc.h(1)
    qc.x(2)
    qc.h(2)
    qc.barrier()

    qc.cx(1, 0)
    qc.ccx(0, 1, 3)
    qc.z(3)
    qc.ccx(0, 1, 3)
    qc.cx(1, 0)
    qc.barrier()

    qc.h(0)
    qc.h(1)
    qc.z(0)
    qc.z(1)
    qc.cz(0, 1)
    qc.h(0)
    qc.h(1)
    qc.barrier()

    qc.measure(0, 0)
    qc.measure(1, 1)

    sampler = StatevectorSampler()
    job = sampler.run([(qc, None, 1024)])
    result = job.result()

    counts = result[0].data.c.get_counts()
    print(counts)

    qc.draw("mpl")
                
                
            
Code: Grover's Algorithm (Dinfio)
                
    ' Grover's Algorithm
    ' Solving "(A XOR B) AND B" logical expression

    import quantum
    
    qc = quantum_circuit(4, 2)
    
    qc.h(0)
    qc.h(1)
    qc.x(2)
    qc.h(2)
    qc.barrier()
    
    qc.cx(1, 0)
    qc.ccx(0, 1, 3)
    qc.z(3)
    qc.ccx(0, 1, 3)
    qc.cx(1, 0)
    qc.barrier()
    
    qc.h(0)
    qc.h(1)
    qc.z(0)
    qc.z(1)
    qc.cz(0, 1)
    qc.h(0)
    qc.h(1)
    qc.barrier()
    
    qc.measure(0, 0)
    qc.measure(1, 1)
    
    qc.draw()
    qc.print_classical_bits()
                
                
            

Output kode program, yakni selain gambar sirkuit kuantum, juga didapatkan hasil berupa nilai {'10': 1024}. Maksud dari nilai ini yaitu keadaan kuantum '00', '01', dan '11' bernilai 0, sedangkan '10' bernilai 1 (1024 adalah jumlah shots). Keadaan '10' berarti \(q_1 = 1\) dan \(q_0 = 0\) atau variabel B bernilai 1 serta variabel A bernilai 0. Ini adalah solusi ekspresi logika (A XOR B) AND B dengan menggunakan algoritma Grover.

Bab 6: Informasi Kuantum

— Teleportasi, superdense coding, teorema no-cloning, operasi kuantum, representasi operator-sum, operator Kraus, completely positive, trace-preserving, kanal kuantum, kanal bit flip, kanal fase flip, kanal depolarisasi, operator densitas, peredaman amplitudo, peredaman fase, trace parsial, trace distance, fidelity, entropi, entropi von Neumann, koreksi eror kuantum, kode bit flip 3-qubit.

Tidak seperti sistem klasik, sistem kuantum memungkinkan manipulasi informasi secara unik melalui fenomena seperti superposisi dan entanglement. Hal ini membuka berbagai kemungkinan baru dalam pengolahan dan transmisi informasi yang tidak dapat dicapai oleh sistem klasik.

6.1 Teleportasi

Referensi sirkuit: IBM Quantum.

Teleportasi merupakan salah satu fenomena unik dan sering dibahas dalam komputasi kuantum. Teleportasi memungkinkan pemindahan informasi kuantum dari satu tempat ke tempat lain, tanpa memindahkan partikel fisiknya itu sendiri. Proses ini memanfaatkan prinsip mekanika kuantum, seperti entanglement dan superposisi, yang tidak ada dalam mekanika klasik.

Alice memiliki keadaan kuantum \(\alpha \ket{0} + \beta \ket{1}\) pada qubit \(q_0\) dan akan dikirim ke Bob di qubit \(q_2\). Alice dan Bob juga berbagi entangled state. Adapun protokol teleportasi dan analisis perhitungan bra-ketnya dapat disajikan sebagai berikut.

Keadaan \(\alpha \ket{0} + \beta \ket{1}\) pada qubit \(q_0\) dan entangled state qubit \(q_1\), \(q_2\).

\[ \ket{\psi_i} = (\alpha \ket{0} + \beta \ket{1}) \otimes \frac{1}{\sqrt{2}} (\ket{00} + \ket{11}) \tag{6.1.1*} \]

atau

\[ \ket{\psi_i} = \frac{1}{\sqrt{2}} \bigg( \alpha \ket{0} \ket{00} + \alpha \ket{0} \ket{11} + \beta \ket{1} \ket{00} + \beta \ket{1} \ket{11} \bigg) \tag{6.1.2*} \]

Alice menerapkan gerbang \(CX\) pada \(q_0\) dan \(q_1\), dengan qubit \(q_0\) sebagai kontrol dan qubit \(q_1\) sebagai target.

\[ \ket{\psi_{ii}} = \frac{1}{\sqrt{2}} \bigg( \alpha \ket{0} \ket{00} + \alpha \ket{0} \ket{11} + \beta \ket{1} \ket{10} + \beta \ket{1} \ket{01} \bigg) \tag{6.1.3*} \]

Ia juga menggunakan gerbang \(H\) pada qubit \(q_0\).

\[ \begin{multline} \ket{\psi_{iii}} = \frac{1}{\sqrt{2}} \bigg( \alpha (\frac{\ket{0} + \ket{1}}{\sqrt{2}}) \ket{00} + \alpha (\frac{\ket{0} + \ket{1}}{\sqrt{2}}) \ket{11} \\ + \beta (\frac{\ket{0} - \ket{1}}{\sqrt{2}}) \ket{10} + \beta (\frac{\ket{0} - \ket{1}}{\sqrt{2}}) \ket{01} \bigg) \end{multline} \tag{6.1.4*} \]

atau

\[ \begin{multline} \ket{\psi_{iii}} = \frac{1}{2} \bigg( \alpha \Big( \ket{0} \ket{00} + \ket{1} \ket{00} + \ket{0} \ket{11} + \ket{1} \ket{11} \Big) \\ + \beta \Big( \ket{0} \ket{10} - \ket{1} \ket{10} + \ket{0} \ket{01} - \ket{1} \ket{01} \Big) \bigg) \end{multline} \tag{6.1.5*} \]

Bentuk \((6.1.5^*)\) dapat juga ditulis sebagai

\[ \begin{multline} \ket{\psi_{iii}} = \frac{1}{2} \bigg( \ket{00} (\alpha \ket{0} + \beta \ket{1}) + \ket{01} (\alpha \ket{1} + \beta \ket{0}) + \ket{10} (\alpha \ket{0} - \beta \ket{1}) + \ket{11} (\alpha \ket{1} - \beta \ket{0}) \bigg) \end{multline} \tag{6.1.6*} \]

Alice melakukan pengukuran dan mengirim hasil pengukurannya ke Bob. Adapun hasil pengukurannya yaitu apakah bernilai '00', '01', '10', atau '11'.

Bob melakukan operasi koreksi atas 2-bit informasi yang dikirim Alice. Jika '00', Bob tidak melakukan apa-apa. Jika nilainya '01', '10', atau '11', maka Bob menerapkan masing-masing gerbang \(X\), \(Z\), atau \(ZX\).

Pengukuran '00' \(\to (\alpha \ket{0} + \beta \ket{1}) \), tidak perlu operasi koreksi apa-apa.

Pengukuran '01' \(\to (\alpha \ket{1} + \beta \ket{0}) \), perlu operasi koreksi gerbang \(X\).

\[ X (\alpha \ket{1} + \beta \ket{0}) = \alpha X\ket{1} + \beta X\ket{0} = \alpha \ket{0} + \beta \ket{1} \tag{6.1.7*} \]

Pengukuran '10' \(\to (\alpha \ket{0} - \beta \ket{1}) \), perlu operasi koreksi gerbang \(Z\).

\[ Z (\alpha \ket{0} - \beta \ket{1}) = \alpha Z\ket{0} - \beta Z\ket{1} = \alpha \ket{0} + \beta \ket{1} \tag{6.1.8*} \]

Pengukuran '11' \(\to (\alpha \ket{1} - \beta \ket{0}) \), perlu operasi koreksi gerbang \(ZX\).

\[ ZX (\alpha \ket{1} - \beta \ket{0}) = Z (\alpha X\ket{1} - \beta X\ket{0}) = Z (\alpha \ket{0} - \beta \ket{1}) = \alpha Z \ket{0} - \beta Z \ket{1} = \alpha \ket{0} + \beta \ket{1} \tag{6.1.9*} \]

Akhirnya, Bob mendapatkan \(\alpha \ket{0} + \beta \ket{1}\).

Code: Teleportation
                
    # Teleportation
    
    from qiskit import QuantumCircuit
    from qiskit_aer import StatevectorSimulator
    from qiskit.visualization import plot_bloch_multivector
    import numpy as np
    
    theta = np.pi / 3
    varphi = 0
    
    qc = QuantumCircuit(3, 2)
    
    qc.u(theta, varphi, 0.0, 0)
    qc.barrier()
    
    qc.h(1)
    qc.cx(1, 2)
    
    qc.barrier()
    qc.cx(0, 1)
    qc.h(0)
    
    qc.barrier()
    qc.measure(0, 0)
    qc.measure(1, 1)
    
    with qc.if_test((0, 1)):
        qc.z(2)
    with qc.if_test((1, 1)):
        qc.x(2)
    
    backend = StatevectorSimulator()
    psi = backend.run(qc, shots = 1).result().get_statevector()
     
    plot_bloch_multivector(psi)
    
    qc.draw("mpl")
    
    # Ref.: https://quantum.cloud.ibm.com/learning/en/courses/utility-scale-quantum-computing/teleportation
                
                
            
Code: Teleportation with Bob's Measurement
                
    # Teleportation with Bob's Measurement
    
    from qiskit import QuantumCircuit
    from qiskit_aer import AerSimulator
    from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
    from qiskit_ibm_runtime import Sampler
    from qiskit.visualization import plot_histogram
    import numpy as np
    
    theta = np.pi / 3
    varphi = 0
    
    qc = QuantumCircuit(3, 3)
    
    qc.u(theta, varphi, 0.0, 0)
    qc.barrier()
    
    qc.h(1)
    qc.cx(1, 2)
    qc.barrier()
    
    qc.cx(0, 1)
    qc.h(0)
    qc.barrier()
     
    qc.measure(0, 0)
    qc.measure(1, 1)
    
    with qc.if_test((0, 1)):
        qc.z(2)
    with qc.if_test((1, 1)):
        qc.x(2)
    qc.barrier()
    
    qc.u(theta, varphi, 0.0, 2).inverse()
    qc.measure(2, 2)
    
    backend = AerSimulator()
     
    pm = generate_preset_pass_manager(backend = backend, optimization_level = 1)
    isa_qc = pm.run(qc)
     
    sampler = Sampler(mode = backend)
    job = sampler.run([isa_qc], shots = 1024)
    result = job.result()
     
    counts = result[0].data.c.get_counts()
    print(counts)
     
    plot_histogram(counts)
    
    qc.draw("mpl")
    
    # Ref.: https://quantum.cloud.ibm.com/learning/en/courses/utility-scale-quantum-computing/teleportation
                
                
            

6.2 Superdense Coding

Superdense coding adalah protokol dalam komputasi kuantum yang memungkinkan pengiriman dua bit informasi klasik hanya dengan menggunakan satu qubit, yakni melalui bantuan fenomena entanglement.

Ilustrasi superdense coding dapat diuraikan sebagai berikut. Dua pihak (misalnya, Alice dan Bob) berbagi sepasang qubit yang berada dalam keadaan entangled. Alice mengkodekan dua bit informasi klasik (00, 01, 10, atau 11) dengan melakukan operasi kuantum tertentu pada qubitnya sendiri. Setelah itu, Alice mengirim qubitnya ke Bob, dan Bob merekonstruksi dua bit informasi tersebut lalu mengukur kedua qubit yang ada (miliknya dan yang diterima dari Alice).

Analisis perhitungan bra-ket superdense coding adalah sebagai berikut.

Alice dan Bob berbagi sepasang qubit dalam keadaan entangled:

\[ \ket{\psi_i} = \frac{1}{\sqrt{2}} (\ket{00} + \ket{11}) \tag{6.2.1} \]

Alice mengkodekan dua bit klasik '01'. Ia berarti akan menggunakan gerbang \(X\) untuk proses kode ini.

\[ \ket{\psi_{ii}} = (X \otimes I) \ket{\psi_i} = \frac{1}{\sqrt{2}} (X\ket{00} + X\ket{11}) = \frac{1}{\sqrt{2}} (\ket{10} + \ket{01}) \tag{6.2.2*} \]

Alice mengirimkan qubitnya ke Bob, lalu Bob melakukan proses dekode. Untuk proses ini, Bob menerapkan gerbang \(CX\) lalu \(H\).

\[ \ket{\psi_{iii}} = CX\ket{\psi_{ii}} = CX \bigg( \frac{1}{\sqrt{2}} (\ket{10} + \ket{01}) \bigg) = \frac{1}{\sqrt{2}} (CX\ket{10} + CX\ket{01}) = \frac{1}{\sqrt{2}} (\ket{11} + \ket{01}) \tag{6.2.3*} \]

Lalu

\[ \ket{\psi_{iv}} = (H \otimes I) \ket{\psi_{iii}} = H \bigg( \frac{1}{\sqrt{2}} (\ket{11} + \ket{01}) \bigg) = \frac{1}{\sqrt{2}} (H\ket{11} + H\ket{01}) \tag{6.2.4*} \]

atau

\[ \ket{\psi_{iv}} = \frac{1}{\sqrt{2}} \bigg( \frac{1}{\sqrt{2}}(\ket{0} - \ket{1}) \ket{1} \bigg) + \frac{1}{\sqrt{2}} \bigg( \frac{1}{\sqrt{2}}(\ket{0} + \ket{1}) \ket{1} \bigg) \tag{6.2.5*} \]

diperoleh

\[ \ket{\psi_{iv}} = \frac{1}{2} (\ket{01} - \ket{11}) + \frac{1}{2} (\ket{01} + \ket{11}) = \ket{01} \tag{6.2.6*} \]

Perlu diingat bahwa perhitungan bra-ket di atas dan hasil yang diperoleh adalah dengan menggunakan konvensi big-endian. Hal ini berarti hasil \(\ket{01} \to \ket{q_0q_1}\).

Adapun hasil eksekusi sirkuit kuantum berupa {'10': 1024}. Sirkuit kuantum menggunakan konvensi little-endian, sehingga '10' \(\to \ket{q_1q_0}\).

Selanjutnya, gerbang kuantum \(X\) pada bagian yang diapit oleh barrier sebelah kiri di sirkuit dapat diganti dengan gerbang \(I\), \(Z\), atau \(XZ\) untuk mengirimkan variasi dua bit informasi klasik lainnya.

Code: Superdense Coding
                
    # Superdense Coding
        
    from qiskit import QuantumCircuit
    from qiskit_aer.primitives import SamplerV2

    qc = QuantumCircuit(2)

    qc.h(0)
    qc.cx(0, 1)

    qc.barrier(0, 1)

    qc.x(0)

    qc.barrier(0, 1)

    qc.cx(0, 1)
    qc.h(0)

    qc.measure_all()

    sampler = SamplerV2()
    job = sampler.run([qc])

    result = job.result()
    counts = result[0].data.meas.get_counts()

    print(counts)

    qc.draw("mpl")
                
                
            
Code: Superdense Coding (Dinfio)
                
    ' Superdense Coding
        
    import quantum

    qc = quantum_circuit(2, 2)
    
    qc.h(0)
    qc.cx(0, 1)
    
    qc.barrier(0, 1)
    
    qc.x(0)
    
    qc.barrier(0, 1)
    
    qc.cx(0, 1)
    qc.h(0)
    
    qc.measure(0, 0)
    qc.measure(1, 1)
    
    qc.draw()
    qc.print_classical_bits()
                
                
            

6.3 Teorema No-Cloning

Teorema no-cloning menyatakan bahwa tidak mungkin ada operasi kuantum \(U\) yang dapat menduplikasi keadaan kuantum \(\ket{\psi}\) menjadi \(\ket{\psi}\)\(\ket{\psi}\) atau

\[ U\ket{\psi}\ket{0} = \ket{\psi}\ket{\psi} \tag{6.3.1} \]

Anggap \(U\) pada \((6.3.1)\) ada, maka

\[ U\ket{0}\ket{0} = \ket{0}\ket{0} = \ket{00} \tag{6.3.2} \]

dan

\[ U\ket{1}\ket{0} = \ket{1}\ket{1} = \ket{11} \tag{6.3.3} \]

Uji kasus pada keadaan kuantum \(\ket{\phi}\):

\[ \ket{\phi} = \frac{1}{\sqrt{2}}\ket{0} + \frac{1}{\sqrt{2}}\ket{1} \tag{6.3.4} \]

maka

\[ \ket{\phi}\ket{\phi} = (\frac{1}{\sqrt{2}}\ket{0} + \frac{1}{\sqrt{2}}\ket{1}) \otimes (\frac{1}{\sqrt{2}}\ket{0} + \frac{1}{\sqrt{2}}\ket{1}) \tag{6.3.5} \]

atau

\[ \ket{\phi}\ket{\phi} = \frac{1}{2}\ket{00} + \frac{1}{2}\ket{01} + \frac{1}{2}\ket{10} + \frac{1}{2}\ket{11} \tag{6.3.6} \]

Anggap sekali lagi \(U\) pada \((6.3.1)\) ada dan terapkan \(U\) pada \(\ket{\phi}\), maka

\[ U\ket{\phi}\ket{0} = U\big(\frac{1}{\sqrt{2}}\ket{0} + \frac{1}{\sqrt{2}}\ket{1}\big) \ket{0} = \frac{1}{\sqrt{2}}U(\ket{0}\ket{0}) + \frac{1}{\sqrt{2}}U(\ket{1}\ket{0}) \tag{6.3.7} \]

Gunakan \((6.3.2)\) dan \((6.3.3)\), maka \((6.3.7)\) menjadi

\[ U\ket{\phi}\ket{0} = \frac{1}{\sqrt{2}}\ket{00} + \frac{1}{\sqrt{2}}\ket{11} \tag{6.3.8} \]

Hasil \((6.3.8)\) berbeda dengan nilai sebenarnya yang diharapkan, yakni keadaan kuantum \((6.3.6)\).

6.4 Operasi Kuantum

Operasi kuantum dalam teori informasi kuantum merupakan konsep matematis yang menggambarkan bagaimana informasi kuantum berubah karena proses transformasi atau akibat interaksi dengan lingkungan. Operasi kuantum memetakan keadaan kuantum awal yang biasanya direpresentasikan sebagai matriks densitas \(\rho\) ke keadaan kuantum akhir \(\rho'\) atau

\[ \rho' = \Phi (\rho) \tag{6.4.1} \]

dengan \(\Phi\) adalah operasi kuantum.

Salah satu representasi penting dari operasi kuantum adalah melalui representasi operator-sum, yang menyatakan bahwa operasi kuantum \(\Phi\) dapat ditulis sebagai

\[ \Phi (\rho) = \sum_i A_i \rho A_i^\dagger \tag{6.4.2} \]

dimana \(A_i\) adalah operator Kraus dan \(A_i^\dagger\) ialah adjoinnya.

\[ \sum_i A_i A_i^\dagger = I \tag{6.4.3} \]

Representasi ini memiliki sifat completely positive dan trace-preserving. Completely positive yakni ia memetakan matriks densitas ke matriks densitas lain yang tetap positif. Adapun trace-preserving yaitu total probabilitas keadaan kuantum tetap bernilai 1.

6.5 Kanal Kuantum

Kanal kuantum digunakan untuk memodelkan kebisingan (noise) atau efek lain akibat interaksi dengan lingkungan dalam sistem kuantum. Ia digambarkan dalam kerangka representasi operator-sum. Contoh kanal kuantum yaitu kanal bit flip, kanal fase flip, kanal depolarisasi, peredaman amplitudo, dan peredaman fase.

6.5.1 Kanal Bit Flip

Kanal bit flip menggambarkan kemungkinan qubit berpindah dari \(|0\rangle\) ke \(|1\rangle\) atau sebaliknya dengan probabilitas tertentu. Kanal bit flip memiliki dua operator Kraus, yakni

\[ A_0 = \sqrt{p}I, \ A_1 = \sqrt{1-p}X \tag{6.5.1} \]

sehingga

\[ \Phi (\rho) = A_0 \rho A_0 + A_1 \rho A_1 \tag{6.5.2} \]

atau

\[ \Phi (\rho) = p\rho + (1-p)X\rho X \tag{6.5.3} \]

dengan \((1-p)\) adalah probabilitas flip dan \(X\) representasi matriks dari operator Pauli \((2.3)\).

Contoh: Sebuah qubit dengan keadaan kuantum awal \(\ket{0}\). Bagaimanakah keadaan kuantum qubit tersebut setelah melewati kanal bit flip? Misal nilai \((1-p) = 0.1\).

Dari \((1.11)\) dan \((2.3)\) maka

\[ \Phi(\rho) = 0.9 \begin{pmatrix} 1 & 0\\ 0 & 0 \end{pmatrix} + 0.1 \begin{pmatrix} 0 & 1\\ 1 & 0 \end{pmatrix} \begin{pmatrix} 1 & 0\\ 0 & 0 \end{pmatrix} \begin{pmatrix} 0 & 1\\ 1 & 0 \end{pmatrix} \tag{6.5.4} \]

didapat

\[ \Phi(\rho) = \begin{pmatrix} 0.9 & 0\\ 0 & 0.1 \end{pmatrix} \tag{6.5.5} \]

Hasil \((6.5.5)\) menunjukkan bahwa keadaan kuantum qubit setelah melewati kanal kuantum yakni probabilitas tetap di keadaan \(\ket{0}\) sebesar 0.9 dan probabilitas berubah menjadi keadaan \(\ket{1}\) senilai 0.1. Perlu dipastikan juga bahwa kanal kuantum dianggap valid jika trace-preserving matriks densitas akhir bernilai 1, dalam hal ini \(Tr(\rho') = 0.9 + 0.1 = 1\).

Code: Bit Flip Channel
                
    # Bit Flip Channel
        
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Kraus, DensityMatrix
    import numpy as np
    
    # Bit flip probability
    # (1 - p) = 0.1
    p = 0.9
    
    # Pauli operators I & X
    I = np.array([[1, 0], [0, 1]])
    X = np.array([[0, 1], [1, 0]])
    
    # Kraus operators for bit flip channel
    A0 = np.sqrt(p) * I
    A1 = np.sqrt(1 - p) * X
    
    # Bit flip channel using Kraus operator
    bit_flip_channel = Kraus([A0, A1])
    
    # 1-Qubit quantum state
    qc = QuantumCircuit(1)
    
    # Density matrix
    state = DensityMatrix.from_instruction(qc)
    
    # Apply bit flip channel to state
    output_state = state.evolve(bit_flip_channel)
    
    # Print initial and final density matrices
    print("rho:")
    print(state.data)
    print("\nrho':")
    print(output_state.data)
                
                
            
Code: Bit Flip Channel (Dinfio)
                
    ' Bit Flip Channel
    
    import quantum
    
    qc = quantum_circuit(1)
    
    ' Bit flip probability (1 - p) = 0.1
    p = 0.9
    
    ' Density matrix of ∣0⟩
    rho = qc.density_operator()
    
    ' X gate matrix
    X = gate_x
    
    ' Bit flip channel formula
    Phi = p * rho + (1 - p) * X * rho * X
    
    Phi.print()  
                
                
            

6.5.2 Kanal Fase Flip

Kanal fase flip ialah jenis kanal kuantum yang memodelkan perubahan fase qubit. Kanal fase flip memiliki dua operator Kraus, yaitu

\[ A_0 = \sqrt{p}I, \ A_1 = \sqrt{1-p}Z \tag{6.5.6} \]

sehingga

\[ \Phi (\rho) = A_0 \rho A_0 + A_1 \rho A_1 \tag{6.5.7} \]

atau

\[ \Phi (\rho) = p\rho + (1-p)Z\rho Z \tag{6.5.8} \]

dengan \((1-p)\) ialah probabilitas fase flip dan \(Z\) representasi matriks operator Pauli \((2.5)\).

Contoh: Sebuah qubit dengan keadaan kuantum awal \(\frac{1}{\sqrt{2}}\ket{0} + \frac{1}{\sqrt{2}}\ket{1}\). Tentukan keadaan kuantum qubit setelah melalui kanal fase flip dengan nilai probabilitas \((1-p) = 0.6\).

Matriks densitas keadaan kuantum awal yakni

\[ \rho = \begin{pmatrix} \frac{1}{\sqrt{2}}\\ \frac{1}{\sqrt{2}} \end{pmatrix} \begin{pmatrix} \frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}} \end{pmatrix} = \begin{pmatrix} \frac{1}{2} & \frac{1}{2}\\ \frac{1}{2} & \frac{1}{2} \end{pmatrix} \tag{6.5.9} \]

\[ \Phi(\rho) = 0.4 \begin{pmatrix} \frac{1}{2} & \frac{1}{2}\\ \frac{1}{2} & \frac{1}{2} \end{pmatrix} + 0.6 \begin{pmatrix} 1 & 0\\ 0 & -1 \end{pmatrix} \begin{pmatrix} \frac{1}{2} & \frac{1}{2}\\ \frac{1}{2} & \frac{1}{2} \end{pmatrix} \begin{pmatrix} 1 & 0\\ 0 & -1 \end{pmatrix} \tag{6.5.10} \]

diperoleh

\[ \Phi(\rho) = \begin{pmatrix} 0.5 & -0.1\\ -0.1 & 0.5 \end{pmatrix} \tag{6.5.11} \]

Hasil \((6.5.11)\) menunjukkan bahwa elemen diagonal pada matriks densitas keadaan kuantum setelah melalui kanal fase flip adalah tetap yakni (0.5, 0.5), namun elemen off-diagonal berubah ke (-0.1, -0.1).

Code: Phase Flip Channel
                
    # Phase Flip Channel
        
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Kraus, DensityMatrix
    import numpy as np
    
    # Phase flip probability
    # 1 - p = 0.6
    p = 0.4
    
    # Pauli operators I & Z
    I = np.array([[1, 0], [0, 1]])
    Z = np.array([[1, 0], [0, -1]])
    
    # Kraus operators for phase flip channel
    A0 = np.sqrt(p) * I
    A1 = np.sqrt(1 - p) * Z
    
    # Phase flip channel using Kraus operators
    phase_flip_channel = Kraus([A0, A1])
    
    # 1-Qubit quantum state
    qc = QuantumCircuit(1)
    qc.h(0)
    
    # Density matrix
    state = DensityMatrix.from_instruction(qc)
    
    # Apply phase flip channel to state
    output_state = state.evolve(phase_flip_channel)
    
    # Print initial and final density matrices
    print("rho:")
    print(state.data)
    print("\nrho':")
    print(output_state.data)
                
                
            
Code: Phase Flip Channel (Dinfio)
                
    ' Phase Flip Channel

    import quantum
    
    qc = quantum_circuit(1)
    
    ' Initial state
    qc.h(0)
    
    ' Phase flip probability (1 - p) = 0.6
    p = 0.4
    
    ' Density matrix of H∣0⟩
    rho = qc.density_operator()
    
    ' Z gate matrix
    Z = gate_z
    
    ' Phase flip channel formula
    Phi = p * rho + (1 - p) * Z * rho * Z
    
    Phi.print()  
                
                
            

6.5.3 Kanal Depolarisasi

Kanal depolarisasi adalah kanal kuantum yang, dengan probabilitas tertentu, mengubah suatu keadaan kuantum menjadi keadaan campuran maksimal (completely mixed state).

Kanal depolarisasi mempunyai empat operator Kraus, yakni

\[ A_0 = \sqrt{1-\frac{3p}{4}}I, \ A_1 = \sqrt{\frac{p}{4}}X, \ A_2 = \sqrt{\frac{p}{4}}Y, \ A_3 = \sqrt{\frac{p}{4}}Z \tag{6.5.12} \]

sehingga

\[ \Phi (\rho) = A_0 \rho A_0 + A_1 \rho A_1 + A_2 \rho A_2 + A_3 \rho A_3 \tag{6.5.13} \]

Operator densitas sebuah qubit dapat ditulis sebagai

\[ \rho = \frac{1}{2}(I + r_xX + r_yY + r_zZ) \tag{6.5.14} \]

dengan \(r_x^2 + r_y^2 + r_z^2 = 1\) dan \(r = (r_x, r_y, r_z)\) merupakan vektor Bloch.

Maka

\[ X \rho X = \frac{1}{2}(X^2 + r_xX^2X + r_yXYX + r_zXZX) \tag{6.5.15} \]

atau

\[ X \rho X = \frac{1}{2}(I + r_xX - r_yY - r_zZ) \tag{6.5.16} \]

Dengan cara yang sama, diperoleh

\[ Y \rho Y = \frac{1}{2}(I - r_xX + r_yY - r_zZ) \tag{6.5.17} \]

dan

\[ Z \rho Z = \frac{1}{2}(I - r_xX - r_yY + r_zZ) \tag{6.5.18} \]

Lalu, jumlahkan keempat persamaan \((6.5.14)\), \((6.5.16)\), \((6.5.17)\), dan \((6.5.18)\), didapat

\[ \rho + X \rho X + Y \rho Y + Z \rho Z = 2I \tag{6.5.19} \]

atau

\[ X \rho X + Y \rho Y + Z \rho Z = 2I - \rho \tag{6.5.20} \]

Berikutnya, masukkan \((6.5.12)\) ke \((6.5.13)\)

\[ \Phi (\rho) = (1-\frac{3p}{4})\rho + \frac{p}{4} X \rho X + \frac{p}{4} Y \rho Y + \frac{p}{4} Z \rho Z \tag{6.5.21} \]

atau

\[ \Phi (\rho) = (1-\frac{3p}{4})\rho + \frac{p}{4} \bigg( X \rho X + Y \rho Y + Z \rho Z \bigg) \tag{6.5.22} \]

Substitusikan \((6.5.20)\) ke \((6.5.22)\) sehingga

\[ \Phi (\rho) = (1-\frac{3p}{4})\rho + \frac{p}{4} (2I - \rho) \tag{6.5.23} \]

dan akhirnya diperoleh

\[ \Phi(\rho) = (1-p)\rho + p\frac{I}{2} \tag{6.5.24} \]

dengan \(p\) merupakan probabilitas sistem berubah menjadi completely mixed state dan \(I\) adalah matriks identitas seperti pada \((2.2)\).

Contoh: Sebuah qubit dengan keadaan kuantum awal

\[ \ket{\varphi} = \frac{\sqrt{3}}{2}\ket{0} + \frac{1}{2}\ket{1} \tag{6.5.25} \]

Bagaimanakah keadaan kuantum qubit tersebut setelah melewati kanal depolarisasi? Misal nilai \(p = 0.4\).

Dari \((6.5.25)\) maka

\[ \rho = \ket{\varphi} \bra{\varphi} = \begin{pmatrix} \frac{\sqrt{3}}{2}\\ \frac{1}{2} \end{pmatrix} \begin{pmatrix} \frac{\sqrt{3}}{2} & \frac{1}{2} \end{pmatrix} \tag{6.5.26} \]

atau

\[ \rho = \begin{pmatrix} \frac{3}{4} & \frac{\sqrt{3}}{4} \\ \frac{\sqrt{3}}{4} & \frac{1}{4} \end{pmatrix} \tag{6.5.27} \]

nilai \(\sqrt{3} / 4 \approx 0.433\), sehingga

\[ \Phi (\rho) = (1-0.4) \begin{pmatrix} 0.75 & 0.433\\ 0.433 & 0.25 \end{pmatrix} + 0.4 \bigg( \frac{1}{2} \begin{pmatrix} 1 & 0\\ 0 & 1 \end{pmatrix} \bigg) \tag{6.5.28} \]

atau

\[ \Phi (\rho) = \begin{pmatrix} 0.65 & 0.26\\ 0.26 & 0.35 \end{pmatrix} \tag{6.5.29} \]

Elemen diagonal dari \((6.5.29)\) yaitu (0.65, 0.35) menunjukkan bahwa probabilitas telah bergeser mendekati keadaan campuran maksimal (0.5, 0.5), dibandingkan dengan keadaan awal pada \((6.5.27)\) yakni (0.75, 0.25).

Code: Depolarising Channel
                
    # Depolarising Channel
        
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Kraus, DensityMatrix
    import numpy as np
    
    # Probability
    p = 0.4
    
    # Pauli operators
    I = np.array([[1, 0], [0, 1]])
    X = np.array([[0, 1], [1, 0]])
    Y = np.array([[0, -1j], [1j, 0]])
    Z = np.array([[1, 0], [0, -1]])
    
    # Kraus operators for depolarising channel
    A0 = np.sqrt(1 - 3 * p / 4) * I
    A1 = np.sqrt(p / 4) * X
    A2 = np.sqrt(p / 4) * Y
    A3 = np.sqrt(p / 4) * Z
    
    # Depolarising channel using Kraus operators
    depolarising_channel = Kraus([A0, A1, A2, A3])
    
    # 1-Qubit quantum state
    qc = QuantumCircuit(1)
    qc.ry(np.pi/3, 0)
    
    # Density matrix
    state = DensityMatrix.from_instruction(qc)
    
    # Apply depolarising channel to state
    output_state = state.evolve(depolarising_channel)
    
    # Print initial and final density matrices
    print("rho:")
    print(state.data)
    print("\nrho':")
    print(output_state.data)
                
                
            
Code: Depolarising Channel (Dinfio)
                
    ' Depolarising Channel

    import quantum
    
    qc = quantum_circuit(1)
    
    ' Initial state
    qc.ry(pi / 3, 0)
    
    ' Probability p = 0.4
    p = 0.4
    
    ' Density matrix of Ry∣0⟩
    rho = qc.density_operator()
    
    ' I gate matrix
    I = gate_i
    
    ' Depolarising channel formula
    Phi = (1 - p) * rho + p * I / 2
    
    Phi.print()  
                
                
            

6.5.4 Peredaman Amplitudo

Peredaman amplitudo (amplitude damping) merupakan operasi kuantum yang mendeskripsikan sistem kuantum mengalamai disipasi energi akibat efek berinteraksi dengan lingkungan. Ia menggambarkan proses keadaan kuantum kehilangan energi, misalnya dari keadaan eksitasi \(\ket{1}\) ke keadaan dasar \(\ket{0}\).

Operator Kraus pada peredaman amplitudo yaitu

\[ A_0 = \begin{pmatrix} 1 & 0\\ 0 & \sqrt{1-p} \end{pmatrix}, \ A_1 = \begin{pmatrix} 0 & \sqrt{p}\\ 0 & 0 \end{pmatrix} \tag{6.5.30} \]

dengan \(p\) adalah probabilitas keadaan kuantum kehilangan energi. Kedua operator di atas juga dapat ditulis dalam bentuk basis komputasi, yakni

\[ A_0 = \ket{0} \bra{0} + \sqrt{1-p} \ket{1} \bra{1} , \ A_1 = \sqrt{p} \ket{0} \bra{1} \tag{6.5.31} \]

Contoh: Sebuah qubit dengan keadaan awal \(\ket{\phi} = \ket{1}\). Tentukan keadaan akhir qubit setelah mengalami peredaman amplitudo jika \(p=0.2\).

Nilai operator densitas \(\rho\) yakni

\[ \rho = \ket{\phi} \bra{\phi} = \ket{1} \bra{1} = \begin{pmatrix} 0 & 0\\ 0 & 1 \end{pmatrix} \tag{6.5.32} \]

Dengan \(p=0.2\) maka

\[ A_0 = \begin{pmatrix} 1 & 0\\ 0 & \sqrt{0.8} \end{pmatrix}, \ A_0' = A_0 \tag{6.5.33} \]

dan

\[ A_1 = \begin{pmatrix} 0 & \sqrt{0.2}\\ 0 & 0 \end{pmatrix}, \ A_1' = \begin{pmatrix} 0 & 0\\ \sqrt{0.2} & 0 \end{pmatrix} \tag{6.5.34} \]

sehingga nilai keadaan akhir qubit \(\Phi (\rho) = A_0 \rho A_0' + A_1 \rho A_1' \) yaitu

\[ \Phi \rho = \begin{pmatrix} 1 & 0\\ 0 & \sqrt{0.8} \end{pmatrix} \begin{pmatrix} 0 & 0\\ 0 & 1 \end{pmatrix} \begin{pmatrix} 1 & 0\\ 0 & \sqrt{0.8} \end{pmatrix} + \begin{pmatrix} 0 & \sqrt{0.2}\\ 0 & 0 \end{pmatrix} \begin{pmatrix} 0 & 0\\ 0 & 1 \end{pmatrix} \begin{pmatrix} 0 & 0\\ \sqrt{0.2} & 0 \end{pmatrix} \tag{6.5.35} \]

atau

\[ \Phi \rho = \begin{pmatrix} 0.2 & 0\\ 0 & 0.8 \end{pmatrix} \tag{6.5.36} \]

Hasil \((6.5.36)\) menunjukkan bahwa setelah mengalami peredaman amplitudo, probabilitas keadaan qubit di keadaan \(\ket{1}\) menjadi senilai 0.8.

Code: Amplitude Damping
                
    # Amplitude Damping
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Kraus, DensityMatrix
    import numpy as np
    
    # Probability
    p = 0.2
    
    # Kraus operators for amplitude damping
    A0 = np.array([[1, 0], [0, np.sqrt(1 - p)]])
    A1 = np.array([[0, np.sqrt(p)], [0, 0]])
    
    # Amplitude damping using Kraus operators
    amplitude_damping = Kraus([A0, A1])
    
    # 1-Qubit quantum state
    qc = QuantumCircuit(1)
    qc.x(0)
    
    # Density matrix
    state = DensityMatrix.from_instruction(qc)
    
    # Apply amplitude damping to state
    output_state = state.evolve(amplitude_damping)
    
    # Print initial and final density matrices
    print("rho:")
    print(state.data)
    print("\nrho':")
    print(output_state.data)
                
                
            
Code: Amplitude Damping (Dinfio)
                
    ' Amplitude Damping

    import quantum
    
    qc = quantum_circuit(1)
    
    ' Initial state
    qc.x(0)
    
    ' Probability p = 0.2
    p = 0.2
    
    ' Kraus operators
    A0 = matrix([[1, 0], [0, sqrt(1 - p)]])
    A0_T = transpose(A0)
    A1 = matrix([[0, sqrt(p)], [0, 0]])
    A1_T = transpose(A1)
    
    ' Density matrix of X∣0⟩
    rho = qc.density_operator()
    
    ' Amplitude damping formula
    Phi = A0 * rho * A0_T + A1 * rho * A1_T
    
    Phi.print()  
                
                
            

6.5.5 Peredaman Fase

Peredaman fase (phase damping) adalah proses kuantum yang menggambarkan hilangnya informasi kuantum, yakni informasi tentang fase keadaan kuantum, namun tanpa kehilangan energi.

Peredaman fase memiliki dua operator Kraus, yaitu

\[ A_0 = \begin{pmatrix} 1 & 0\\ 0 & \sqrt{1-p} \end{pmatrix}, \ A_1 = \begin{pmatrix} 0 & 0\\ 0 & \sqrt{p} \end{pmatrix} \tag{6.5.37} \]

Contoh: Sebuah qubit dengan keadaan kuantum awal \(\frac{1}{\sqrt{2}}\ket{0} + \frac{1}{\sqrt{2}}\ket{1}\). Tentukan keadaan kuantum qubit setelah mengalami peredaman fase dengan nilai probabilitas \(p = 0.2\).

Seperti pada \((6.5.9)\), operator densitas \(\rho = \begin{pmatrix} 0.5 & 0.5\\ 0.5 & 0.5 \end{pmatrix}\). Dengan \(p = 0.2\) maka

\[ A_0 = \begin{pmatrix} 1 & 0\\ 0 & \sqrt{0.8} \end{pmatrix}, \ A_0' = A_0 \tag{6.5.38} \]

dan

\[ A_1 = \begin{pmatrix} 0 & 0\\ 0 & \sqrt{0.2} \end{pmatrix}, \ A_1' = A_1 \tag{6.5.39} \]

Nilai keadaan akhir qubit \(\Phi (\rho) = A_0 \rho A_0' + A_1 \rho A_1' \) yakni

\[ \Phi \rho = \begin{pmatrix} 1 & 0\\ 0 & \sqrt{0.8} \end{pmatrix} \begin{pmatrix} 0.5 & 0.5\\ 0.5 & 0.5 \end{pmatrix} \begin{pmatrix} 1 & 0\\ 0 & \sqrt{0.8} \end{pmatrix} + \begin{pmatrix} 0 & 0\\ 0 & \sqrt{0.2} \end{pmatrix} \begin{pmatrix} 0.5 & 0.5\\ 0.5 & 0.5 \end{pmatrix} \begin{pmatrix} 0 & 0\\ 0 & \sqrt{0.2} \end{pmatrix} \tag{6.5.40} \]

atau

\[ \Phi \rho = \begin{pmatrix} 0.5 & 0.447\\ 0.447 & 0.5 \end{pmatrix} \tag{6.5.41} \]

Terlihat pada hasil \((6.5.41)\) bahwa setelah mengalami peredaman fase, elemen off-diagonal berubah menjadi (0.447, 0.447) dari fase awal (0.5, 0.5). Adapun elemen diagonal tetap (0.5, 0.5).

Code: Phase Damping
                
    # Phase Damping
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Kraus, DensityMatrix
    import numpy as np
    
    # Probability
    p = 0.2
    
    # Kraus operators for phase damping
    A0 = np.array([[1, 0], [0, np.sqrt(1 - p)]])
    A1 = np.array([[0, 0], [0, np.sqrt(p)]])
    
    # Phase damping using Kraus operators
    phase_damping = Kraus([A0, A1])
    
    # 1-Qubit quantum state
    qc = QuantumCircuit(1)
    qc.h(0)
    
    # Density matrix
    state = DensityMatrix.from_instruction(qc)
    
    # Apply phase damping to state
    output_state = state.evolve(phase_damping)
    
    # Print initial and final density matrices
    print("rho:")
    print(state.data)
    print("\nrho':")
    print(output_state.data)
                
                
            
Code: Phase Damping (Dinfio)
                
    ' Phase Damping

    import quantum
    
    qc = quantum_circuit(1)
    
    ' Initial state
    qc.h(0)
    
    ' Probability p = 0.2
    p = 0.2
    
    ' Kraus operators
    A0 = matrix([[1, 0], [0, sqrt(1 - p)]])
    A0_T = transpose(A0)
    A1 = matrix([[0, 0], [0, sqrt(p)]])
    A1_T = transpose(A1)
    
    ' Density matrix of H∣0⟩
    rho = qc.density_operator()
    
    ' Phase damping formula
    Phi = A0 * rho * A0_T + A1 * rho * A1_T
    
    Phi.print()  
                
                
            

6.6 Tool Informasi Kuantum

Dalam informasi kuantum, berbagai tool seperti operator densitas, trace, trace parsial, trace distance, dan fidelity memainkan peran dalam analisis sistem kuantum. Operator densitas berfungsi sebagai representasi keadaan kuantum. Trace dan trace parsial memungkinkan analisis sifat kemurnian keadaan serta reduksi ke subsistem, sedangkan trace distance mengukur jarak antara dua keadaan kuantum, dan fidelity menilai tingkat kesamaan antar keadaan.

6.6.1 Operator Densitas

Operator densitas merupakan salah satu tool penting dalam komputasi dan informasi kuantum. Jika pada bab-bab sebelumnya operator densitas bekerja pada sistem kuantum 1-qubit, maka di bagian ini akan disajikan uraian tentangnya untuk n-qubit.

Contoh: Sistem kuantum terdiri atas 2 qubit seperti ditunjukkan pada gambar sirkuit. Akan ditentukan operator densitas dari sistem tersebut.

\[ \ket{\phi} = X \ket{0} \otimes H \ket{0} = \ket{1} \otimes \frac{1}{\sqrt{2}}(\ket{0}+\ket{1}) = \frac{1}{\sqrt{2}}(\ket{10}+\ket{11}) \tag{6.6.1} \]

dan

\[ \bra{\phi} = \frac{1}{\sqrt{2}}(\bra{10}+\bra{11}) \tag{6.6.2} \]

sehingga operator densitasnya

\[ \rho = \ket{\phi} \bra{\phi} = \bigg(\frac{1}{\sqrt{2}}(\ket{10}+\ket{11})\bigg) \bigg(\frac{1}{\sqrt{2}}(\bra{10}+\bra{11})\bigg) \tag{6.6.3} \]

atau

\[ \rho = \frac{1}{2} \big( \ket{10}\bra{10} + \ket{10}\bra{11} + \ket{11}\bra{10} + \ket{11}\bra{11} \big) \tag{6.6.4} \]

Jika ditulis dalam bentuk matriks, maka operator densitasnya adalah

\[ \rho = \frac{1}{2} \begin{pmatrix} 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 1 \\ 0 & 0 & 1 & 1 \\ \end{pmatrix} \tag{6.6.5} \]

Code: Density Operator
                
    # Density Operator
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    import numpy as np
    
    qc = QuantumCircuit(2)
    
    qc.h(0)
    qc.x(1)
    
    # Quantum state
    state = Statevector.from_instruction(qc)
    
    # Density operator
    density_matrix = state.to_operator().data
    
    print("\nrho:")
    print(density_matrix)
                
                
            
Code: Density Operator (Dinfio)
                
    ' Density Operator

    import quantum
    
    qc = quantum_circuit(2)
    
    qc.h(0)
    qc.x(1)
    
    rho = qc.density_operator()
    
    rho.print()
                
                
            

6.6.2 Trace

Tool berikutnya ialah trace. Uraian ini melanjutkan bahasan sebelumnya yakni operator densitas. Akan dicari trace dari operator densitas pada \((6.6.4)\). Persamaan umum trace untuk n-qubit sistem ialah

\[ Tr(\rho) = \sum_{i=0}^{2^n-1} \bra{i}\rho\ket{i} \tag{6.6.6} \]

dengan \(i\) adalah basis komputasi dari n-qubit sistem kuantum. Misal untuk sistem 2-qubit, maka \(i\) terdiri atas 4 basis komputasi yakni \(\ket{00}\), \(\ket{01}\), \(\ket{10}\), dan \(\ket{11}\).

Perlu diingat bahwa nilai \(\braket{00|00}\), \(\braket{01|01}\), \(\braket{10|10}\), dan \(\braket{11|11}\) adalah 1. Adapun yang lainnya adalah 0. Nilai trace per basis komputasi yakni

\[ \bra{00}\rho\ket{00} = \frac{1}{2}(0 \cdot 0 + 0 \cdot 0 + 0 \cdot 0 + 0 \cdot 0) = 0 \tag{6.6.7} \]

\[ \bra{01}\rho\ket{01} = \frac{1}{2}(0 \cdot 0 + 0 \cdot 0 + 0 \cdot 0 + 0 \cdot 0) = 0 \tag{6.6.8} \]

\[ \bra{10}\rho\ket{10} = \frac{1}{2}(1 \cdot 1 + 1 \cdot 0 + 0 \cdot 1 + 0 \cdot 0) = \frac{1}{2} \tag{6.6.9} \]

\[ \bra{11}\rho\ket{11} = \frac{1}{2}(0 \cdot 0 + 0 \cdot 1 + 1 \cdot 0 + 1 \cdot 1) = \frac{1}{2} \tag{6.6.10} \]

Maka nilai \(Tr(\rho) = \frac{1}{2} + \frac{1}{2} = 1\). Nilai ini adalah sama dengan nilai penjumlahan elemen diagonal pada matriks (\(6.6.5)\).

Code: Trace
                
    # Trace
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    import numpy as np
    
    qc = QuantumCircuit(2)
    
    qc.h(0)
    qc.x(1)
    
    # Quantum state
    state = Statevector.from_instruction(qc)
    
    # Density operator
    density_matrix = state.to_operator().data
    
    # Trace
    trace = np.trace(density_matrix).real
    
    print("\nTr(rho):")
    print(trace)
                
                
            

6.6.3 Trace Parsial

Ambil sebuah contoh sistem kuantum, misal sistem AB, yang mana ia terdiri atas subsistem A dan subsistem B. Saat hanya ingin mengetahui keadaan subsistem B, maka subsistem A "dihilangkan" dan hanya menyisakan matriks densitas subsistem B. Dalam komputasi dan informasi kuantum, proses ini dikenal sebagai trace parsial.

Persamaan trace parsial dari kondisi diatas yaitu

\[ \rho_B = Tr_A(\rho_{AB}) \tag{6.6.11} \]

Contoh: Digunakan kembali sirkuit kuantum seperti pada subbab sebelumnya. Dari sirkuit, akan dilakukan trace parsial pada qubit \(q_0\) sehingga didapatkan matriks densitas qubit \(q_1\) atau

\[ \rho_{q_1} = Tr_{q_0}(\rho_{q_1q_0}) \tag{6.6.12} \]

Vektor \(\ket{\phi}\) senilai

\[ \ket{\phi} = X \ket{0} \otimes H \ket{0} = \ket{1} \otimes \frac{1}{\sqrt{2}}(\ket{0}+\ket{1}) = \frac{1}{\sqrt{2}} \ket{1} \otimes (\ket{0}+\ket{1}) \tag{6.6.13} \]

dan

\[ \bra{\phi} = \frac{1}{\sqrt{2}} \bra{1} \otimes (\bra{0}+\bra{1}) \tag{6.6.14} \]

Perlu diingat bahwa

\[ (a \otimes b)(a \otimes b) = aa \otimes bb \tag{6.6.15} \]

dan

\[ (\ket{a} \otimes \ket{b})(\bra{a} \otimes \bra{b}) = \ket{a}\bra{a} \otimes \ket{b}\bra{b} \tag{6.6.16} \]

maka

\[ \rho_{q_1q_0} = \ket{\phi} \bra{\phi} = \frac{1}{2} \ket{1}\bra{1} \otimes (\ket{0}+\ket{1})(\bra{0}+\bra{1}) \tag{6.6.17} \]

atau

\[ \rho_{q_1q_0} = \frac{1}{2} \ket{1}\bra{1} \otimes \big(\ket{0}\bra{0} + \ket{0}\bra{1} + \ket{1}\bra{0} + \ket{1}\bra{1}\big) \tag{6.6.18} \]

Karena trace parsial dilakukan pada qubit \(q_0\) maka basis komputasi untuk trace berupa \(\ket{0}\) dan \(\ket{1}\) serta berlangsung hanya di bagian elemen kanan \(\rho_{q_1q_0}\). Perlu diingat juga bahwa nilai \(\braket{0|0}\) dan \(\braket{1|1}\) adalah 1, adapun yang lainnya ialah 0.

Matriks densitas \(\rho_{q_1}\) dari komponen \(\ket{0}\)

\[ \frac{1}{2} \ket{1}\bra{1} \otimes (1 \cdot 1 + 1 \cdot 0 + 0 \cdot 1 + 0 \cdot 0 ) = \frac{1}{2} \ket{1}\bra{1} \tag{6.6.19} \]

dan komponen \(\ket{1}\)

\[ \frac{1}{2} \ket{1}\bra{1} \otimes (0 \cdot 0 + 0 \cdot 1 + 1 \cdot 0 + 1 \cdot 1 ) = \frac{1}{2} \ket{1}\bra{1} \tag{6.6.20} \]

Maka nilai matriks densitas \(\rho_{q_1}\) adalah \(\frac{1}{2} \ket{1}\bra{1} + \frac{1}{2} \ket{1}\bra{1} = \ket{1}\bra{1}\).

Code: Partial Trace
                
    # Partial Trace
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector, partial_trace
    
    qc = QuantumCircuit(2)
    
    qc.h(0)
    qc.x(1)
    
    # Quantum state
    state = Statevector.from_instruction(qc)
    
    # Partial trace
    reduced_q1 = partial_trace(state, [0])
    
    print(reduced_q1)
                
                
            

6.6.4 Trace Distance

Anggap ada dua keadaan kuantum \(\ket{\psi}\) dan \(\ket{\phi}\), masing-masing memiliki matriks densitas \(\rho\) dan \(\sigma\). Untuk mengukur seberapa beda dua keadaan tersebut, digunakanlah trace distance. Trace distance memiliki persamaan

\[ \delta(\rho, \sigma) = \frac{1}{2}Tr|\rho - \sigma| \tag{6.6.21} \]

dengan \(|\rho - \sigma| = \sqrt{(\rho - \sigma)^\dagger (\rho - \sigma)}\).

Contoh: Dua keadaan kuantum \(\ket{\psi} = \ket{0}\) dan \(\ket{\phi} = H\ket{0}\). Akan ditentukan trace distance dari dua keadaan ini.

Nilai \(\rho = \ket{\psi} \bra{\psi} \) dan \(\sigma = \ket{\phi} \bra{\phi}\) dari kedua keadaan kuantum yakni

\[ \rho = \begin{pmatrix} 1 & 0 \\ 0 & 0 \\ \end{pmatrix}, \ \sigma = \frac{1}{2} \begin{pmatrix} 1 & 1 \\ 1 & 1 \\ \end{pmatrix} \tag{6.6.22} \]

sehingga

\[ \rho - \sigma = \begin{pmatrix} \frac{1}{2} & -\frac{1}{2} \\ -\frac{1}{2} & -\frac{1}{2} \\ \end{pmatrix}, \ (\rho - \sigma)^\dagger = \rho - \sigma \tag{6.6.23} \]

maka

\[ (\rho - \sigma)^\dagger (\rho - \sigma) = \begin{pmatrix} \frac{1}{2} & -\frac{1}{2} \\ -\frac{1}{2} & -\frac{1}{2} \\ \end{pmatrix} \begin{pmatrix} \frac{1}{2} & -\frac{1}{2} \\ -\frac{1}{2} & -\frac{1}{2} \\ \end{pmatrix} = \begin{pmatrix} \frac{1}{2} & 0 \\ 0 & \frac{1}{2} \\ \end{pmatrix} = \frac{1}{2} \begin{pmatrix} 1 & 0 \\ 0 & 1 \\ \end{pmatrix} \tag{6.6.24} \]

dan

\[ |\rho - \sigma| = \sqrt{\frac{1}{2}} \begin{pmatrix} 1 & 0 \\ 0 & 1 \\ \end{pmatrix} = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 0 \\ 0 & 1 \\ \end{pmatrix} \tag{6.6.25} \]

Nilai

\[ Tr |\rho - \sigma| = \frac{1}{\sqrt{2}} (1 + 1) = \frac{2}{\sqrt{2}} \tag{6.6.26} \]

Akhirnya

\[ \delta (\rho, \sigma) = \frac{1}{2} \cdot \frac{2}{\sqrt{2}} = \frac{1}{\sqrt{2}} \tag{6.6.27} \]

Code: Trace Distance
                
    # Trace Distance
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    import numpy as np
    
    # |ψ⟩ = |0⟩
    psi = QuantumCircuit(1)
    state_psi = Statevector.from_instruction(psi)
    
    # |ϕ⟩ = H|0⟩
    phi = QuantumCircuit(1)
    phi.h(0)
    state_phi = Statevector.from_instruction(phi)
    
    # rho & sigma
    rho = state_psi.to_operator()
    sigma = state_phi.to_operator()
    
    # rho - sigma
    diff = rho.data - sigma.data
    
    # |rho - sigma| = sqrt((rho - sigma)^dagger (rho - sigma))
    diff_dagger = np.conjugate(diff.T)
    diff_square = np.dot(diff_dagger, diff)
    eigenvals = np.linalg.eigvals(diff_square)
    sqrt_diff_square = np.sqrt(np.abs(eigenvals))
    abs_diff = np.diag(sqrt_diff_square)
    
    # Trace distance
    trace_distance = 0.5 * np.trace(np.abs(abs_diff))
    
    print(f"Trace distance: {trace_distance:.4f}")
                
                
            

6.6.5 Fidelity

Selanjutnya, untuk mengukur kesamaan antara dua keadaan kuantum, berapa besar dua keadaan saling mirip, digunakan fidelity.

\[ F (\rho, \sigma) = \Big(Tr \sqrt{\rho^{1/2} \sigma \rho^{1/2}} \Big) ^ 2 \tag{6.6.28} \]

Jika keadaan murni, seperti pada \((1.15)\) maka \(\rho^2 = \rho\) dan \(\sigma^2 = \sigma\) diperoleh

\[ F (\rho, \sigma) = \Big( Tr \sqrt{ (\ket{\psi}\bra{\psi}) (\ket{\phi}\bra{\phi}) (\ket{\psi}\bra{\psi}) } \Big) ^ 2 \tag{6.6.29} \]

Karena \(\braket{\phi|\psi} = \braket{\psi|\phi}^* \) maka

\[ \braket{\psi|\phi} \braket{\phi|\psi} = \braket{\psi|\phi} \braket{\psi|\phi}^* = |\braket{\psi|\phi}|^2 \tag{6.6.30} \]

dan diperoleh

\[ F (\rho, \sigma) = \Big( |\braket{\psi|\phi}| Tr \sqrt{ \ket{\psi}\bra{\psi} } \Big) ^ 2 = |\braket{\psi|\phi}| ^ 2 \tag{6.6.31} \]

Dengan contoh \(\ket{\psi}=\ket{0}\) dan \(\ket{\phi}=H\ket{0}\) maka nilai fidelity kedua keadaan kuantum yakni sebesar \(\frac{1}{2}\).

Code: Fidelity
                
    # Fidelity
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector, state_fidelity
    import numpy as np
    
    # |ψ⟩ = |0⟩
    psi = QuantumCircuit(1)
    state_psi = Statevector.from_instruction(psi)
    
    # |ϕ⟩ = H|0>
    phi = QuantumCircuit(1)
    phi.h(0)
    state_phi = Statevector.from_instruction(phi)
    
    # Fidelity
    fidelity = state_fidelity(state_psi, state_phi)
    
    print("Fidelity between |ψ⟩ and |ϕ⟩:", fidelity)
                
                
            
Code: Fidelity (Dinfio)
                
    ' Fidelity

    import quantum
     
    qc1 = quantum_circuit(1)
    qc2 = quantum_circuit(1)
     
    qc1.h(0)
     
    psi = qc1.statevector()
    phi = qc2.statevector()
     
    f = fidelity(psi, phi)
    writeln(f)
                
                
            

6.7 Entropi

Pada pembahasan sebelumnya, seperti pada estimasi fase kuantum atau pada subbab lainnya, telah disinggung secara sekilas tentang nilai eigen. Di subbab ini, sebelum membahas entropi, akan diulas kembali sajian tentang topik tersebut. Nilai eigen sengaja diulas lagi karena urgensinya dalam perhitungan komputasi dan informasi kuantum, termasuk entropi.

Dalam komputasi dan informasi kuantum, nilai eigen suatu keadaan kuantum dihitung dari matriks densitas keadaan kuantum tersebut. Adapun persamaannya ialah

\[ \text{det}(\rho - \lambda I) = 0 \tag{6.7.1} \]

dengan \(\rho\), \(\lambda\), \(I\) berturut-turut adalah matriks densitas, nilai eigen, dan matriks identitas.

Contoh: Akan dicari nilai eigen dari keadaan kuantum \(\ket{0}\).

Telah diketahui dari sebelumnya, bahwa matriks densitas dari keadaan kuantum \(\ket{0}\) yakni \(\rho = \begin{pmatrix} 1 & 0 \\ 0 & 0 \\ \end{pmatrix}\) dan matriks identitas \(I = \begin{pmatrix} 1 & 0 \\ 0 & 1 \\ \end{pmatrix}\) maka

\[ \rho - \lambda I = \begin{pmatrix} 1 & 0 \\ 0 & 0 \\ \end{pmatrix} - \lambda \begin{pmatrix} 1 & 0 \\ 0 & 1 \\ \end{pmatrix} = \begin{pmatrix} 1 - \lambda & 0 \\ 0 & -\lambda \\ \end{pmatrix} \tag{6.7.2} \]

sehingga nilai determinan

\[ \text{det}(\rho - \lambda I) = (1 - \lambda)(-\lambda) - (0 \cdot 0) = (1 - \lambda)(-\lambda) = 0 \tag{6.7.3} \]

Didapat \(\lambda_1 = 1\) dan \(\lambda_2 = 0\).

Code: Eigenvalue
                
    # Eigenvalue
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    import numpy as np
    
    # Quantum state
    psi = QuantumCircuit(1)
    state_psi = Statevector.from_instruction(psi)
    
    # Density matrix
    rho = state_psi.to_operator()
    
    # Eigenvalue
    eigenvalues = np.linalg.eigvals(rho.data)
    
    print("rho:")
    print(rho.data)
    print("\neigenvalue:", eigenvalues)
                
                
            

Entropi ialah konsep penting dalam informasi kuantum. Entropi mengukur seberapa banyak ketidakpastian atau variasi informasi yang ada pada sistem kuantum. Dalam informasi kuantum, entropi dapat digambarkan oleh matriks densitas dan dikenal sebagai entropi von Neumann. Adapun persamaannya yakni

\[ S (\rho) = -Tr(\rho \log \rho) \tag{6.7.4} \]

atau dalam bentuk nilai eigen sebagai

\[ S (\rho) = - \sum_i \lambda_i \log \lambda_i \tag{6.7.5} \]

Contoh: Akan dicari entropi dari keadaan kuantum \(\ket{0}\).

Dari sebelumnya, diperoleh nilai eigen dari keadaan kuantum \(\ket{0}\) yakni \(\lambda_1 = 1\) dan \(\lambda_2 = 0\) maka nilai entropinya yaitu

\[ S (\rho) = - (1 \cdot \log 1 + 0 \cdot \log 0) = 0 \tag{6.7.6} \]

Nilai ini didapat dari pendekatan limit suku kedua \(\lim_{x \to 0} x \log x = 0\).

Code: Entropy
                
    # Entropy
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    import numpy as np
    
    # von Neumann entropy
    def von_neumann_entropy(rho):
        eigenvalues = np.linalg.eigvals(rho.data)
        eigenvalues = eigenvalues[np.abs(eigenvalues) > 1e-10]
        entropy = -np.sum(eigenvalues * np.log2(eigenvalues))
        return entropy
    
    # Quantum state
    psi = QuantumCircuit(1)
    state_psi = Statevector.from_instruction(psi)
    rho = state_psi.to_operator()
    
    # Entropy
    entropy_psi = von_neumann_entropy(rho)
    
    print(f"Entropy: {entropy_psi:.4f}")
                
                
            

6.8 Koreksi Eror Kuantum

Koreksi eror kuantum atau Quantum Error Correction (QEC) adalah teknik dalam komputasi kuantum untuk melindungi informasi kuantum dari eror yang disebabkan oleh kebisingan (noise) atau gangguan lingkungan, seperti dekoherensi dan interaksi dengan lingkungan eksternal. Karena qubit sangat sensitif terhadap gangguan, eror dapat dengan mudah terjadi, sehingga diperlukan metode untuk mendeteksi dan memperbaiki eror tanpa merusak informasi kuantum.

Salah satu contoh teknik QEC ialah kode bit flip 3-qubit. Apa itu kode bit flip 3-qubit dan bagaimana langkah-langkah implementasinya? Berikut adalah uraiannya.

Kode bit flip 3-qubit adalah kode QEC sederhana yang digunakan untuk melindungi informasi kuantum dari eror bit flip (perubahan qubit dari \(\ket{0}\) ke \(\ket{1}\) atau sebaliknya). Kode ini adalah salah satu contoh dasar dalam komputasi kuantum untuk memahami prinsip QEC. Adapun prinsip kode bit flip 3-qubit yakni mengkodekan qubit ke dalam tiga qubit logikal.

Referensi sirkuit: IBM Quantum & https://arxiv.org/abs/2304.08678.

Tahapan kode bit flip 3-qubit adalah sebagai berikut. (1) Qubit dengan vektor \(\ket{\psi} = \alpha \ket{0} + \beta \ket{1}\); (2) Enkode \(\ket{\psi}\) menjadi 3-qubit keadaan logikal \(\ket{\psi}_l = \alpha \ket{000} + \beta \ket{111}\); (3) Simulasi eror. Akibat eror, vektor \(\ket{\psi}_l\) berubah; (4) Dua qubit ancila. Dua qubit bantuan untuk tujuan cek paritas; (5) Sirkuit stabilisator pertama. Mengukur paritas antara dua qubit pertama; (6) Sirkuit stabilisator kedua. Mengukur paritas antara dua qubit kedua; (7) Deteksi eror. Pengukuran dua bit sindrom \(c_1c_0\); (8) Koreksi eror. Koreksi berdasarkan bit \(c_1c_0\); (9) Vektor logikal kembali ke semula \(\ket{\psi}_l = \alpha \ket{000} + \beta \ket{111}\); (10) Pengukuran vektor \(\ket{\psi}\).

\(e\) \(c_1c_0\) bit flip \(c\)
\(I \otimes I \otimes I\) \(00\) - \(I \otimes I \otimes I\)
\(I \otimes I \otimes X\) \(01\) \(q_0\) \(I \otimes I \otimes X\)
\(I \otimes X \otimes I\) \(11\) \(q_1\) \(I \otimes X \otimes I\)
\(X \otimes I \otimes I\) \(10\) \(q_2\) \(X \otimes I \otimes I\)

Contoh: Saat \(c_1c_0\) bernilai '01' maka kode mendeteksi ada eror bit flip pada qubit \(q_0\). Vektor logikal yang semula \(\ket{\psi}_l = \alpha \ket{000} + \beta \ket{111}\) berubah menjadi \(\ket{\psi}_l = \alpha \ket{001} + \beta \ket{110}\) akibat eror tersebut. Oleh karenanya, perlu dilakukan koreksi pada gerbang \(c\) yakni berupa \(I \otimes I \otimes X\). Koreksi ini mengembalikan vektor logikal kembali ke keadaan kuantum semula \(\ket{\psi}_l = \alpha \ket{000} + \beta \ket{111}\).

Code: Quantum Error Correction 3-Qubit Bit Flip Code
                
    # Quantum Error Correction 3-Qubit Bit Flip Code
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from qiskit.primitives import StatevectorSampler
    
    qc = QuantumCircuit(5, 2)
    
    # Psi state goes here
    # e.g. qc.h(0)
    
    # Encode logical state
    qc.cx(0, 1)
    qc.cx(0, 2)
    
    # Bit flip error goes here
    # e.g. qc.x(0)
    
    # First stabiliser
    qc.cx(0, 3)
    qc.cx(1, 3)
    
    # Second stabiliser
    qc.cx(1, 4)
    qc.cx(2, 4)
    
    # Error detection
    qc.measure(3, 0)
    qc.measure(4, 1)
    
    # Error correction goes here
    # e.g. qc.x(0)
    
    state = Statevector(qc)
    
    sampler = StatevectorSampler()
    job = sampler.run([(qc, None, 1000)])
    result = job.result()
    
    counts = result[0].data.c.get_counts()
    
    print(counts)
    qc.draw("mpl")
    
    # Ref.: https://quantum.cloud.ibm.com/learning/en/courses/foundations-of-quantum-error-correction
            https://arxiv.org/abs/2304.08678
                
                
            

Bab 7: Algoritma Kuantum Variasional

— Variational quantum algorithm, Hamiltonian, prinsip variasional, noisy intermediate-scale quantum, nilai ekspektasi, energi dasar, variational quantum eigensolver, ansatz, parameterised quantum circuit, optimasi, iterasi, quantum approximate optimisation algorithm, cost Hamiltonian, mixing Hamiltonian, Max-Cut problem, graf.

Algoritma kuantum variasional atau Variational Quantum Algorithm (VQA) adalah algoritma kuantum yang memanfaatkan prinsip variasional pada mekanika kuantum. Ia memanfaatkan perangkat kuantum skala menengah Noisy Intermediate-Scale Quantum, NISQ. VQA juga dikenal sebagai algoritma hibrida karena menggabungkan komputasi kuantum dan komputasi klasik.

7.1 Hamiltonian dan Prinsip Variasional

Hamiltonian merupakan sebuah konsep dalam mekanika kuantum yang digunakan untuk menggambarkan energi suatu sistem kuantum. Ia dapat juga didefinisikan sebagai operator matematis yang merepresentasikan energi sistem

\[ H \ket{\psi} = E \ket{\psi} \tag{7.1.1} \]

dengan \(H\), \(\ket{\psi}\), \(E\) berturut-turut ialah Hamiltonian, state kuantum, dan energi sistem.

Dalam konteks komputasi kuantum, Hamiltonian direpresentasikan sebagai kombinasi linier dari operator Pauli (seperti \(X\), \(Y\), \(Z\)) atau operator lain yang dapat diimplementasikan pada komputasi kuantum.

Prinsip variasional menyatakan bahwa untuk Hamiltonian \(H\), dan sembarang state kuantum \(\ket{\psi}\), nilai ekspektasi (expectation value) energi adalah selalu lebih besar atau sama dengan energi dasar (ground state energy) \(E_0\). Secara matematis, nilai ekspektasi energi dapat ditulis sebagai

\[ \langle H \rangle = \bra{\psi} H \ket{\psi} \tag{7.1.2} \]

sehingga prinsip variasional berupa

\[ \langle H \rangle \ge E_0 \tag{7.1.3} \]

Code: Expectation value ⟨Z⟩
                
    # Expectation Value ⟨Z⟩
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector, SparsePauliOp
    import numpy as np
    
    # Create a quantum circuit
    qc = QuantumCircuit(1)
    
    qc.x(0)  # Apply X gate to flip |0⟩ → |1⟩
    
    # Define the observable: Pauli Z operator on qubit 0
    observable = SparsePauliOp(["Z"])
    
    # Simulate the final statevector of the circuit
    psi = Statevector.from_instruction(qc) # Result: |ψ⟩ = |1⟩
    
    # Compute the expectation value ⟨ψ|Z|ψ⟩
    expectation_value = np.real(psi.expectation_value(observable))
    
    # Print the result
    print(f"Final state: {psi}")
    print(f"Expectation value ⟨Z⟩ = {expectation_value:.4f}")
                
                
            

Lalu, bagaimanakah prinsip variasional dapat dimanfaatkan dalam komputasi kuantum? VQE dan QAOA adalah contohnya.

7.2 Variational Quantum Eigensolver

Variational Quantum Eigensolver (VQE) adalah contoh dari VQA. VQE digunakan untuk mencari energi dasar atau energi terendah dari Hamiltonian \(H\) dengan meminimalkan nilai ekspektasi energi \(\langle H \rangle\). Dalam implementasinya, VQE memiliki lima langkah, yakni Hamiltonian, ansatz, nilai ekspektasi, optimasi, dan iterasi.

Contoh: VQE akan digunakan untuk menemukan energi terendah dari Hamiltonian sederhana, misalnya Hamiltonian Pauli seperti \(H=Z\), dengan \(Z\) adalah matriks Pauli \(Z\). Energi terendah yang diharapkan yakni bernilai -1 (nilai eigen dari operator \(Z\)).

Code: Eigenvalue of Pauli Z
                
    # Eigenvalue of Pauli Z
    
    from qiskit.quantum_info import SparsePauliOp
    import numpy as np
    
    op = SparsePauliOp("Z")
    op.to_matrix()
    
    result = np.linalg.eigh(op.to_matrix())
    print("Eigenvalues:", result.eigenvalues)
                
                
            

Langkah 1: Hamiltonian. Dari contoh dipilih Hamiltonian \(H=Z\), sehingga nilai ekspektasi energi menjadi

\[ \langle Z \rangle = \bra{\psi} Z \ket{\psi} \tag{7.2.1} \]

Langkah 2: Ansatz. Ansatz adalah sirkuit kuantum parametrik atau Parameterised Quantum Circuit (PQC) yang membentuk state kuantum \(\ket{\psi(\theta)}\), sebuah state kuantum dengan parameter \(\theta\). Misal dipilih state kuantum dengan gerbang \(R_y(\theta)\) pada \(\ket{0}\) maka

\[ \ket{\psi(\theta)} = R_y(\theta) \ket{0} \tag{7.2.2} \]

Dengan

\[ R_y(\theta) = \begin{pmatrix} \cos(\theta / 2) & -\sin(\theta / 2) \\ \sin(\theta / 2) & \cos(\theta / 2) \\ \end{pmatrix} \tag{7.2.3} \]

maka

\[ R_y(\theta) \ket{0} = \begin{pmatrix} \cos(\theta / 2) & -\sin(\theta / 2) \\ \sin(\theta / 2) & \cos(\theta / 2) \\ \end{pmatrix} \begin{pmatrix} 1 \\ 0 \\ \end{pmatrix} = \begin{pmatrix} \cos(\theta / 2) \\ \sin(\theta / 2) \\ \end{pmatrix} \tag{7.2.4} \]

didapat

\[ \ket{\psi(\theta)} = \cos(\theta / 2) \ket{0} + \sin(\theta / 2) \ket{1} \tag{7.2.5} \]

dan

\[ \bra{\psi(\theta)} = \cos(\theta / 2) \bra{0} + \sin(\theta / 2) \bra{1} \tag{7.2.6} \]

Langkah 3: Nilai ekspektasi. Persamaan nilai ekspektasi energi menjadi

\[ \langle Z \rangle = \bra{\psi(\theta)} Z \ket{\psi(\theta)} \tag{7.2.7} \]

atau

\[ \langle Z \rangle = \big(\cos(\theta / 2) \bra{0} + \sin(\theta / 2) \bra{1}\big) Z \big(\cos(\theta / 2) \ket{0} + \sin(\theta / 2) \ket{1}\big) \tag{7.2.8} \]

Dengan \(Z\ket{0}=\ket{0}\) dan \(Z\ket{1}=-\ket{1}\) maka

\[ \langle Z \rangle = \big(\cos(\theta / 2) \bra{0} + \sin(\theta / 2) \bra{1}\big) \big(\cos(\theta / 2) \ket{0} - \sin(\theta / 2) \ket{1}\big) \tag{7.2.9} \]

Menggunakan kaidah ortogonal dan ortonormal seperti pada \(1.7\), \(1.8\), \(1.9\), dan \(1.10\), maka diperoleh

\[ \langle Z \rangle = \cos^2(\theta / 2) - \sin^2(\theta / 2) = \cos(\theta) \tag{7.2.10} \]

Langkah 4: Optimasi. Fungsi \(\cos(\theta) = -1\) saat \(\theta = \pi\), maka nilai energi terendah

\[ \langle Z \rangle = \cos(\pi) = -1 \tag{7.2.11} \]

dan dengan \(\theta = \pi\), maka state kuantum-nya adalah

\[ \ket{\psi(\pi)} = \cos(\pi / 2) \ket{0} + \sin(\pi / 2) \ket{1} = \ket{1} \tag{7.2.12} \]

Langkah 5: Iterasi. Proses di atas diulang secara iteratif. Komputer kuantum menghitung nilai ekspektasi energi dan komputer klasik menyesuaikan \(\theta\) menggunakan algoritma optimasi hingga didapat energi terendah.

Code: VQE of Pauli Z
                
    # VQE of Pauli Z
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import SparsePauliOp
    from qiskit.primitives import StatevectorEstimator
    import numpy as np
    
    # Define the Hamiltonian: H = Z
    hamiltonian = SparsePauliOp.from_list([("Z", 1.0)])
    
    # Create the ansatz: Ry(theta)|0>
    def create_ansatz(theta):
        qc = QuantumCircuit(1)
        qc.ry(theta, 0)
        return qc
    
    # Function to compute the expectation value of energy
    def compute_expectation(theta):
        circuit = create_ansatz(theta)
        estimator = StatevectorEstimator()
        job = estimator.run([(circuit, hamiltonian)])
        result = job.result()[0].data.evs
        return result
    
    # Classical optimisation
    theta = 0.01  # Initial theta value (avoid 0 to prevent zero gradient)
    learning_rate = 1.0  # Larger learning rate
    max_iterations = 100  # Maximum number of iterations
    tolerance = 1e-3  # Convergence tolerance
    
    for i in range(max_iterations):
        # Compute energy (expectation value)
        energy = compute_expectation(theta)
        
        # Compute gradient of cos(theta)
        gradient = -np.sin(theta)
        
        # Update theta using gradient descent
        theta_new = theta - learning_rate * gradient
        
        # Check for convergence
        if abs(energy - (-1.0)) < tolerance:
            print(f"Convergence achieved at iteration {i + 1}")
            break
        
        theta = theta_new
        
        # Print progress every iteration
        if (i + 1) % 1 == 0:
            print(f"Iteration {i + 1}: Energy = {energy:.4f}, Theta = {theta:.4f}")
    
    # Final result
    final_energy = compute_expectation(theta)
    print(f"\nVQE Results:")
    print(f"Lowest energy: {final_energy:.4f}")
    print(f"Optimal theta parameter: {theta:.4f}")
                
                
            
Code: VQE of Pauli Z Using Numerical Gradient
                
    # VQE of Pauli Z Using Numerical Gradient
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import SparsePauliOp
    from qiskit.primitives import StatevectorEstimator
    import numpy as np
    
    # Define the Hamiltonian: H = Z
    hamiltonian = SparsePauliOp.from_list([("Z", 1.0)])
    
    # Create the ansatz: Ry(theta)|0>
    def create_ansatz(theta):
        qc = QuantumCircuit(1)
        qc.ry(theta, 0)
        return qc
    
    # Function to compute the expectation value of energy
    def compute_expectation(theta):
        circuit = create_ansatz(theta)
        estimator = StatevectorEstimator()
        job = estimator.run([(circuit, hamiltonian)])
        result = job.result()[0].data.evs
        return result
    
    # Function to compute the numerical gradient using finite difference
    def compute_numerical_gradient(theta, epsilon):
        # Compute expectation values at theta + epsilon and theta - epsilon
        energy_plus = compute_expectation(theta + epsilon)
        energy_minus = compute_expectation(theta - epsilon)
        # Approximate gradient: (f(theta + epsilon) - f(theta - epsilon)) / (2 * epsilon)
        gradient = (energy_plus - energy_minus) / (2 * epsilon)
        return gradient
    
    # Classical optimisation using gradient descent
    theta = 0.01  # Initial theta value (avoid 0 to prevent slow convergence)
    learning_rate = 1  # Learning rate
    max_iterations = 100  # Maximum number of iterations
    tolerance = 1e-3  # Convergence tolerance
    epsilon = 0.01  # Small perturbation for finite difference
    
    for i in range(max_iterations):
        # Compute energy (expectation value)
        energy = compute_expectation(theta)
        
        # Compute numerical gradient
        gradient = compute_numerical_gradient(theta, epsilon)
        
        # Update theta using gradient descent
        theta_new = theta - learning_rate * gradient
        
        # Check for convergence
        if abs(energy - (-1.0)) < tolerance:
            print(f"Convergence achieved at iteration {i + 1}")
            break
        
        theta = theta_new
        
        # Print progress every iteration
        if (i + 1) % 1 == 0:
            print(f"Iteration {i + 1}: Energy = {energy:.4f}, Theta = {theta:.4f}")
    
    # Final result
    final_energy = compute_expectation(theta)
    print(f"\nVQE Results:")
    print(f"Lowest energy: {final_energy:.4f}")
    print(f"Optimal theta parameter: {theta:.4f}")
                
                
            

7.3 Quantum Approximate Optimisation Algorithm

Quantum Approximate Optimisation Algorithm (QAOA) ialah algoritma kuantum yang digunakan untuk menyelesaikan masalah optimasi secara aproksimasi. QAOA menggabungkan komputasi kuantum dan klasik, serta memiliki dua Hamiltonian, yakni cost Hamiltonian dan mixing Hamiltonian.

Contoh: QAOA akan diterapkan pada Max-Cut problem dengan graf sederhana. Graf sederhana berbentuk segitiga (tiga simpul dan tiga sisi).

Langkah 1: Graf. Simpul {\(A, B, C\)}. Sisi {\((A, B), (B, C), (C, A)\)}.

Langkah 2: Cost Hamiltonian \(H_C\). \(H_C\) didesain untuk mendapatkan energi rendah pada partisi yang memotong banyak sisi.

\[ H_C = \frac{1}{2} \sum_{(i,j) \epsilon E} (1 - Z_i Z_j) \tag{7.3.1} \]

dimana \(Z_i\) adalah operator Pauli \(Z\) pada qubit ke-\(i\) dan \(E\) himpunan sisi graf. Pada graf segitiga, nilai \(H_C\) menjadi

\[ H_C = \frac{1}{2} \big( (1 - Z_A Z_B) + (1 - Z_B Z_C) + (1 - Z_C Z_A)\big) \tag{7.3.2} \]

Langkah 3: Mixing Hamiltonian \(H_M\). Persamaan \(H_M\) yakni

\[ H_M = \sum_i X_i \tag{7.3.3} \]

dengan \(X_i\) ialah operator Pauli \(X\) pada qubit ke-\(i\).

Langkah 4: Sirkuit kuantum QAOA. Pertama, superposisi 3 qubit. Lalu gerbang unitary untuk cost Hamiltonian dengan parameter \(\gamma\)

\[ U(H_C, \gamma) = \exp(-i \gamma H_C) \tag{7.3.4} \]

Gerbang ini berupa \(\exp(-i \gamma (\frac{1 - Z_iZ_j}{2}))\) untuk di setiap sisi \((i, j)\). Berikutnya, gerbang unitary dengan parameter \(\beta\) untuk mixing Hamiltonian

\[ U(H_M, \beta) = \exp(-i \beta H_M) \tag{7.3.5} \]

Saat implementasi, kedua gerbang unitary tersebut berupa \(CX_{i \to j} (I \otimes R_{z_j}) CX_{i \to j}\) dan \(R_{x_j}\).

Langkah 5: Optimasi dan iterasi. Iterasi \(\gamma\) dan \(\beta\) hingga didapatkan nilai ekspektasi jumlah sisi yang dipotong maksimal.

Contoh perhitungan notasi bra-ket nilai ekspektasi. Dipilih partisi dengan \(A=1\), \(B=0\), dan \(C=1\). Partisi ini diwakili oleh state kuantum \(\ket{101}\). Perlu diingat kembali bahwa \(Z\ket{0}=0\) dan \(Z\ket{1}=-1\). Nilai ekspektasi untuk sisi (\(A,B)\), sisi (\(B,C)\), dan sisi (\(C,A)\) berturut-turut adalah

\[ \begin{aligned} \langle H_{(A,B)} \rangle &= \bra{101} \frac{1}{2}(1 - Z_A Z_B) \ket{101} \\ &= \frac{1}{2} \bra{101} I \ket{101} - \frac{1}{2} \bra{101} Z_A Z_B \ket{101} \\ &= \frac{1}{2} \cdot 1 - \frac{1}{2} \bra{101} (Z_A \ket{1} \otimes Z_B \ket{0} \otimes \ket{1} ) \\ &= \frac{1}{2} - \frac{1}{2} \bra{101} (- \ket{1} \otimes \ket{0} \otimes \ket{1} ) \\ &= \frac{1}{2} - \frac{1}{2} \bra{101} (-\ket{101}) \\ &= \frac{1}{2} + \frac{1}{2} \braket{101|101} \\ &= \frac{1}{2} + \frac{1}{2} \cdot 1\\ &= 1 \end{aligned} \tag{7.3.6} \]

\[ \begin{aligned} \langle H_{(B,C)} \rangle &= \bra{101} \frac{1}{2}(1 - Z_B Z_C) \ket{101} \\ &= \frac{1}{2} \bra{101} I \ket{101} - \frac{1}{2} \bra{101} Z_B Z_C \ket{101} \\ &= \frac{1}{2} \cdot 1 - \frac{1}{2} \bra{101} (\ket{1} \otimes Z_B \ket{0} \otimes Z_C \ket{1} ) \\ &= \frac{1}{2} - \frac{1}{2} \bra{101} (\ket{1} \otimes \ket{0} \otimes -\ket{1} ) \\ &= \frac{1}{2} - \frac{1}{2} \bra{101} (-\ket{101}) \\ &= \frac{1}{2} + \frac{1}{2} \braket{101|101} \\ &= \frac{1}{2} + \frac{1}{2} \cdot 1\\ &= 1 \end{aligned} \tag{7.3.7} \]

\[ \begin{aligned} \langle H_{(C,A)} \rangle &= \bra{101} \frac{1}{2}(1 - Z_C Z_A) \ket{101} \\ &= \frac{1}{2} \bra{101} I \ket{101} - \frac{1}{2} \bra{101} Z_C Z_A \ket{101} \\ &= \frac{1}{2} \cdot 1 - \frac{1}{2} \bra{101} (Z_A \ket{1} \otimes \ket{0} \otimes Z_C \ket{1} ) \\ &= \frac{1}{2} - \frac{1}{2} \bra{101} (-\ket{1} \otimes \ket{0} \otimes -\ket{1} ) \\ &= \frac{1}{2} - \frac{1}{2} \bra{101} (\ket{101}) \\ &= \frac{1}{2} - \frac{1}{2} \braket{101|101} \\ &= \frac{1}{2} - \frac{1}{2} \cdot 1\\ &= 0 \end{aligned} \tag{7.3.8} \]

Jadi nilai ekspektasi total yaitu 1 + 1 + 0 = 2. Nilai ekspektasi 2 menunjukkan bahwa state kuantum \(\ket{101}\) memotong 2 sisi, yakni sisi (\(A,B)\) dan sisi (\(B,C)\). Nilai ini adalah nilai optimal Max-Cut untuk graf segitiga.

Code: QAOA for Max-Cut on a Triangle Graph
                
    # QAOA for Max-Cut on a Triangle Graph
    
    from qiskit import QuantumCircuit, QuantumRegister
    from qiskit.quantum_info import Statevector, Operator
    import numpy as np
    from scipy.optimize import minimize
    
    # Define the graph and problem Hamiltonian
    edges = [(0, 1), (1, 2), (2, 0)]
    num_qubits = 3
    
    # Function to create Z_i Z_j operator for edge (i, j)
    def zizj_operator(i, j, num_qubits):
        # Identity and Pauli-Z matrices
        I = np.eye(2)
        Z = np.array([[1, 0], [0, -1]])
        # Initialise operator as identity for all qubits
        op = 1
        for k in range(num_qubits):
            if k == i or k == j:
                op = np.kron(op, Z)
            else:
                op = np.kron(op, I)
        return op
    
    # Problem Hamiltonian: H_C = sum_{(i,j)} 0.5 * (I - Z_i Z_j)
    H_C = np.zeros((2 ** num_qubits, 2 ** num_qubits))
    for i, j in edges:
        H_C += 0.5 * (np.eye(2 ** num_qubits) - zizj_operator(i, j, num_qubits))
    
    # Function to create QAOA circuit
    def create_qaoa_circuit(gamma, beta):
        # Initialise circuit with 3 qubits
        qr = QuantumRegister(num_qubits, 'q')
        qc = QuantumCircuit(qr)
        
        # Initialise superposition state |+>^3
        for i in range(num_qubits):
            qc.h(i)
        
        # Cost operator: U(H_C, gamma) = exp(-i * gamma * H_C)
        for i, j in edges:
            # Apply e^(-i * gamma * 0.5 * (I - Z_i Z_j)) = e^(-i * gamma/2) * e^(i * gamma/2 * Z_i Z_j)
            # Constant factor e^(-i * gamma/2) is ignored as it does not affect measurements
            qc.cx(i, j)
            qc.rz(gamma, j)
            qc.cx(i, j)
        
        # Mixing operator: U(H_M, beta) = exp(-i * beta * sum(X_i))
        for i in range(num_qubits):
            qc.rx(2 * beta, i)  # exp(-i * beta * X) = Rx(2 * beta)
        
        return qc
    
    # Function to compute expectation value 
    def compute_expectation(params):
        gamma, beta = params
        # Create QAOA circuit
        qc = create_qaoa_circuit(gamma, beta)
        # Simulate state using StatevectorSimulator
        state = Statevector.from_instruction(qc)
        # Compute expectation value: 
        expectation = state.expectation_value(Operator(H_C)).real
        # Return negative for minimisation to maximise the number of cut edges
        return -expectation
    
    # Optimise gamma and beta parameters
    # Use scipy.optimize.minimize to find optimal parameters
    initial_params = [0.5, 0.5]  # Initial values for gamma and beta
    result = minimize(compute_expectation, initial_params, method='COBYLA')
    
    # Print results
    optimal_params = result.x
    optimal_value = -result.fun  # Optimal expectation value (negated due to minimisation)
    print(f"Optimal parameters: gamma = {optimal_params[0]:.4f}, beta = {optimal_params[1]:.4f}")
    print(f"Optimal expectation value: {optimal_value:.4f}")
    
    # Simulate final state to see probabilities
    qc_optimal = create_qaoa_circuit(optimal_params[0], optimal_params[1])
    state_optimal = Statevector.from_instruction(qc_optimal)
    probabilities = state_optimal.probabilities_dict()
    
    # Print state probabilities
    print("\nState probabilities:")
    for state, prob in probabilities.items():
        print(f"State |{state}> : {prob:.4f}")
                
                
            

Bab 8: Pembelajaran Mesin Kuantum

— Quantum machine learning, model kuantum, trainable, optimisable, expressivity, trainability, peta fitur kuantum, vektor fitur, ruang fitur kuantum, enkode basis, enkode amplitudo, enkode sudut, enkode fase, enkode sudut padat, kernel kuantum, quantum support vector machine, quantum support vector classifier, quantum support vector regressor, regresi ridge, variational quantum circuit, variational quantum classifier, variational quantum regressor, RealAmplitudes, quantum neural network, perceptron kuantum, quantum convolutional neural network.

Pembelajaran mesin kuantum atau Quantum Machine Learning (QML) adalah pembelajaran mesin yang memanfaatkan sifat unik dalam komputasi kuantum seperti superposisi, entanglement, dan paralelisme kuantum. Dalam implementasinya, QML dilakukan dengan pendekatan hibrid, yakni penggabungan antara komputasi kuantum dengan komputasi klasik.

8.1 Parameterised Quantum Circuit

Taksonomi pembelajaran mesin dapat dikategorikan menjadi empat macam, yakni CC, CQ, QC, dan QQ. Huruf C berarti klasik dan Q adalah kuantum. Huruf yang di depan mencerminkan data dan huruf di belakang menandakan proses. Pada bahasan ini, akan difokuskan pada CQ, yakni pembelajaran mesin dengan data klasik menggunakan proses kuantum (QML dengan data klasik).

Referensi diagram: https://arxiv.org/abs/2205.09510.

QML dengan data klasik - selanjutnya disebut sebagai QML, dapat dimodelkan melalui Parameterised Quantum Circuit (PQC). PQC ini akan sedikit diulas kembali walau sebelumnya telah dibahas secara sekilas pada bab tentang Variational Quantum Algorithm (VQA). Secara matematis, state kuantum PQC dapat ditulis sebagai

\[ \ket{\psi(x,\theta)} = U(x, \theta) \ket{0} \tag{8.1.1} \]

dimana \(U(x,\theta)\) adalah gerbang kuantum unitary dengan input data klasik \(x\) dan parameter \(\theta\). Adapun nilai ekspektasi energi sebesar

\[ \langle H \rangle = \bra{\psi(x,\theta)} H \ket{\psi(x,\theta)} \tag{8.1.2} \]

dengan parameter \(\theta\) telah dilakukan proses optimasi secara klasikal.

PQC berisi gerbang-gerbang kuantum dengan parameter-parameter yang dapat diatur sehingga memungkinkan adanya proses optimasi sesuai dengan nilai ekspektasi yang diharapkan. Dengan konsep hibrid, komputasi dilakukan di sirkuit kuantum, sedangkan optimasi dilakukan oleh komputer klasik. Parameterisasi memungkinkan dibangunnya model kuantum (quantum model) yang dapat dilatih (trainable) dan dioptimalkan (optimisable). Adapun sifat atau karakteristik PQC, dua diantaranya ditentukan oleh ukuran expressivity dan trainability yang ia miliki.

8.2 Peta Fitur Kuantum

Peta fitur kuantum (quantum feature map) merupakan teknik enkode data dalam QML. Sebuah proses atau tahapan awal QML. Ia memetakan data klasik (vektor fitur) atau feature vector ke ruang fitur kuantum (quantum feature space) sedemikian rupa sehingga data tersebut dapat diproses atau ditransformasi dalam sirkuit kuantum. Beberapa contoh jenis peta fitur kuantum diantaranya yaitu enkode basis, enkode amplitudo, enkode sudut, enkode fase, serta enkode sudut padat.

8.2.1 Enkode Basis

Enkode basis (basis encoding) mengkodekan data klasik ke dalam bentuk basis komputasi. Data biner 0 dikodekan menjadi \(\ket{0}\) dan biner 1 ke \(\ket{1}\).

Contoh: Data klasik \([1, 1, 0]\). Hasil enkode basis berupa \(\ket{110}\).

Code: Basis Encoding [1, 1, 0]
                
    # Basis Encoding [1, 1, 0]
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from qiskit.visualization import plot_histogram
    import numpy as np
    
    # Binary data to be encoded
    data = [1, 1, 0]  # Will be encoded as |110⟩ in big-endian
    
    # Create a quantum circuit with 3 qubits
    qc = QuantumCircuit(3)
    
    # Function to perform basis encoding with big-endian ordering
    def basis_encoding(data, circuit, qubits):
        n = len(data)
        for i, bit in enumerate(data):
            if bit == 1:
                circuit.x(qubits[n-1-i])  # Apply X gate on qubit (n-1-i) for bit 1 (flips |0⟩ to |1⟩)
    
    # Apply basis encoding
    basis_encoding(data, qc, range(3))
    
    # Display the quantum circuit
    print("Quantum Circuit:")
    print(qc)
    
    # Simulate the statevector to obtain the quantum state
    state = Statevector.from_instruction(qc)
    
    # Display the quantum state
    print("\nQuantum State:")
    print(state.data)
    
    # Simulate measurement outcomes (probabilities)
    counts = state.probabilities_dict()
    print("\nMeasurement Outcomes (Probabilities):")
    print(counts)
    
    # Plot histogram of results
    plot_histogram(counts)
                
                
            

8.2.2 Enkode Amplitudo

Enkode amplitudo (amplitude encoding) mengkodekan data klasik ke dalam amplitudo state kuantum. Ia memetakan data klasik (vektor fitur)

\[ x = [x_1, x_2, ..., x_N] \tag{8.2.1} \]

ke bentuk

\[ \ket{\psi} = x_{n_1} \ket{0} + x_{n_2} \ket{1} + \ ... \ + x_{n_N} \ket{N-1} \tag{8.2.2} \]

dengan total probabilitas adalah 1.

Vektor pada \((8.2.1)\) di atas memiliki norma

\[ ||x|| = \sqrt{x_1^2 + x_2^2 + \ ... \ + x_N^2} \tag{8.2.3} \]

sehingga vektor ternormalisasinya yakni

\[ x_\text{normal} = \bigg[\frac{x_1}{||x||}, \frac{x_2}{||x||}, \ ..., \frac{x_N}{||x||} \bigg] \tag{8.2.4} \]

Contoh: Data klasik \([0.5, 0.5]\). Hasil enkode amplitudo berupa \(\ket{\psi} = \sqrt{0.5} \ket{0} + \sqrt{0.5} \ket{1}\).

Code: Amplitude Encoding [0.5, 0.5]
                
    # Amplitude Encoding [0.5, 0.5]
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from qiskit.visualization import plot_histogram
    import numpy as np
    
    # Classical data to be encoded
    data = np.array([0.5, 0.5])  # Vector [0.5, 0.5]
    
    # Normalise the data to ensure valid quantum state (sum of probabilities = 1)
    norm = np.sqrt(np.sum(data ** 2))
    data_normalized = data / norm
    
    # Create a quantum circuit with 1 qubit (since 2^1 = 2 states can encode 2 values)
    qc = QuantumCircuit(1)
    
    # Function for amplitude encoding
    def amplitude_encoding(data, circuit):
        # Initialise the qubit to the desired state using initialize()
        circuit.initialize(data, [0])
    
    # Apply amplitude encoding
    amplitude_encoding(data_normalized, qc)
    
    # Display the quantum circuit
    print("Quantum Circuit:")
    print(qc)
    
    # Simulate the statevector to obtain the quantum state
    state = Statevector.from_instruction(qc)
    
    # Display the quantum state
    print("\nQuantum State:")
    print(state.data)
    
    # Simulate measurement outcomes (probabilities)
    counts = state.probabilities_dict()
    print("\nMeasurement Outcomes (Probabilities):")
    print(counts)
    
    # Plot histogram of results
    plot_histogram(counts)
                
                
            

8.2.3 Enkode Sudut

Enkode sudut (angle encoding) mengkodekan data klasik ke dalam sudut rotasi gerbang kuantum, seperti gerbang \(R_x\), \(R_y\), atau \(R_z\). Dengan kata lain, data tersebut dilakukan transformasi rotasi sudut di dalam ruang kuantum-nya. Besar sudut rotasi \(\theta\) adalah senilai dengan data klasiknya (feature value).

Pada enkode sudut ini akan dipilih gerbang \(R_y(\theta)\) sebagai gerbang transformasinya

\[ R_y(\theta) \ket{0} = \cos(\theta/2) \ket{0} + \sin(\theta/2) \ket{1} \tag{8.2.5} \]

Contoh: Data klasik \([\pi/2, \pi/2]\). Pada contoh ini, gerbang transformasi berupa \(R_y(\pi/2)\) dan \(R_y(\pi/2)\) serta menghasilkan output enkode sudut \(\ket{\psi} = \frac{1}{2} \ket{00} + \frac{1}{2} \ket{01} + \frac{1}{2} \ket{10} + \frac{1}{2} \ket{11}\).

Code: Angle Encoding [pi/2, pi/2]
                
    # Angle Encoding [pi/2, pi/2]
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from qiskit.visualization import plot_histogram
    import numpy as np
    
    # Classical data to be encoded
    data = np.array([np.pi/2, np.pi/2])
    
    # Create a quantum circuit with 2 qubits (one qubit per feature)
    qc = QuantumCircuit(2)
    
    # Function for angle encoding using Ry gates
    def angle_encoding(data, circuit, qubits):
        for i, value in enumerate(data):
            circuit.ry(value, qubits[i])  # Apply Ry rotation with the feature value as the angle
    
    # Apply angle encoding
    angle_encoding(data, qc, range(2))
    
    # Display the quantum circuit
    print("Quantum Circuit:")
    print(qc)
    
    # Simulate the statevector to obtain the quantum state
    state = Statevector.from_instruction(qc)
    
    # Display the quantum state
    print("\nQuantum State:")
    print(state.data)
    
    # Simulate measurement outcomes (probabilities)
    counts = state.probabilities_dict()
    print("\nMeasurement Outcomes (Probabilities):")
    print(counts)
    
    # Plot histogram of results
    plot_histogram(counts)
                
                
            

8.2.4 Enkode Fase

Enkode fase (phase encoding) mengkodekan data klasik ke dalam ruang kuantum dengan cara memetakannya ke fase relatif dari state kuantum. Dalam enkode fase biasanya digunakan gerbang \(H\) dan \(P(\theta)\) sebagai transformasinya.

\[ P(\theta) = \begin{pmatrix} 1 & 0\\ 0 & e^{i\theta} \end{pmatrix} \tag{8.2.6} \]

Contoh: Data klasik \([\pi/2]\). Pada contoh encoding ini, gerbang kuantum yang digunakan berupa \(H\) dan \(P(\pi/2)\) serta menghasilkan output enkode fase \(\ket{\psi} = \frac{1}{\sqrt{2}} \ket{0} + \frac{1}{\sqrt{2}} i\ket{1}\).

Code: Phase Encoding [pi/2]
                
    # Phase Encoding [pi/2]
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from qiskit.visualization import plot_histogram
    import numpy as np
    
    # Classical data to be encoded
    data = np.array([np.pi/2])  # Vector [pi/2]
    
    # Create a quantum circuit with 1 qubit (one qubit per feature)
    qc = QuantumCircuit(1)
    
    # Function for phase encoding using P gates
    def phase_encoding(data, circuit, qubits):
        for i in qubits:
            circuit.h(i)  # Apply Hadamard to create superposition
        for i, value in enumerate(data):
            circuit.p(value, qubits[i])  # Apply phase gate P with feature value as the phase
    
    # Apply phase encoding
    phase_encoding(data, qc, range(1))
    
    # Display the quantum circuit
    print("Quantum Circuit:")
    print(qc)
    
    # Simulate the statevector to obtain the quantum state
    state = Statevector.from_instruction(qc)
    
    # Display the quantum state
    print("\nQuantum State:")
    print(state.data)
    
    # Simulate measurement outcomes (probabilities)
    counts = state.probabilities_dict()
    print("\nMeasurement Outcomes (Probabilities):")
    print(counts)
    
    # Plot histogram of results
    plot_histogram(counts)
                
                
            

8.2.5 Enkode Sudut Padat

Enkode sudut padat (dense angle encoding, DAE) merupakan kombinasi enkode sudut dan enkode fase. DAE memungkinkan encoding dua vektor hanya ke dalam satu buah qubit. Untuk DAE, digunakan gerbang \(R_y(\theta)\) untuk vektor 1 dan \(R_z(\phi)\) untuk vektor 2.

Contoh: Data klasik \([\pi/2, 2\pi/3]\). "Ilustrasi" vektor 1 (warna teal) adalah \(\ket{\psi_1} = R_y(\pi/2)\ket{0}\) dan vektor 2 (merah muda) berupa \(\ket{\psi_2} = R_z(2\pi/3) R_y(\pi/2) \ket{0}\).

Code: Dense Angle Encoding [pi/2, 2pi/3]
                
    # Dense Angle Encoding [pi/2, 2pi/3]
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from math import pi
    import numpy as np
    import matplotlib.pyplot as plt
    from qiskit.visualization.bloch import Bloch
    
    # First circuit: Ry(π/2) on a single qubit
    qc1 = QuantumCircuit(1)
    qc1.ry(np.pi / 2, 0)
    
    # Second circuit: Ry(π/2) followed by Rz(2π/3) on a single qubit
    qc2 = QuantumCircuit(1)
    qc2.ry(np.pi / 2, 0)
    qc2.rz(2 * np.pi / 3, 0)
    
    # Simulate statevectors for both circuits
    state1 = Statevector.from_instruction(qc1)
    state2 = Statevector.from_instruction(qc2)
    
    # Store states
    states = state1, state2
    
    # Display quantum states
    print("Quantum State for qc1 (Ry(π/2)):")
    print(state1.data)
    print("\nQuantum State for qc2 (Ry(π/2) + Rz(2π/3)):")
    print(state2.data)
    
    # Calculate Bloch vector coordinates
    # For a state |ψ⟩ = a|0⟩ + b|1⟩, Bloch coordinates are:
    # x = 2 * Re(a * b), y = 2 * Im(a * b), z = |a|^2 - |b|^2
    def bloch_coordinates(state):
        a, b = state.data[0], state.data[1]  # Amplitudes for |0⟩ and |1⟩
        x = 2 * np.real(np.conj(a) * b)
        y = 2 * np.imag(np.conj(a) * b)
        z = np.abs(a) ** 2 - np.abs(b) ** 2
        return [x, y, z]
    
    bloch1 = bloch_coordinates(state1)
    bloch2 = bloch_coordinates(state2)
    
    # Display Bloch coordinates
    print("\nBloch Coordinates for qc1 (Ry(π/2)): ", bloch1)
    print("Bloch Coordinates for qc2 (Ry(π/2) + Rz(2π/3)): ", bloch2)
    
    # Create a single Bloch sphere with two vectors
    fig = plt.figure()
    ax = fig.add_subplot(111, projection = '3d')
    bloch = Bloch(axes = ax)
    
    # Add vectors for both states
    bloch.add_vectors([bloch1, bloch2])  # Add both vectors as a list
    bloch.vector_color = ['#008080', '#C11C84']  # Set colors: blue for qc1, red for qc2
    
    # Render the Bloch sphere
    bloch.show()
    plt.show()
                
                
            

8.3 Kernel Kuantum

Pada subbab sebelumnya telah dijelaskan bahwa data klasik agar dapat diproses dalam sirkuit kuantum, ia terlebih dahulu dilakukan enkode melalui peta fitur kuantum. Peta fitur kuantum memetakan data klasik ke ruang fitur kuantum.

Anggap \(\phi\) adalah peta fitur kuantum dari data klasik \(x\) dan \(x'\), maka kernel kuantum dapat didefinisikan secara matematis sebagai

\[ k(x, x') = |\braket{\phi(x)|\phi(x')}|^2 \tag{8.3.1} \]

Kernel kuantum merupakan hasil inner product dari dua state kuantum hasil enkode data klasik \(x\) dan \(x'\) yakni state \(\ket{\phi(x)}\) dan \(\ket{\phi(x')}\). Adapun salah satu fungsi kernel kuantum dalam komputasi kuantum yaitu digunakan untuk mengukur kemiripan antara dua data dalam ruang fitur kuantum. (Reminder: \(\braket{0|0} = \braket{1|1} = 1\) dan \(\braket{0|1} = \braket{1|0} = 0\)).

Contoh: Akan dicari kernel kuantum \(k\) dari data klasik \(x = [x_1]\) dan \(x' = [x_2]\). Peta fitur kuantum dipilih tipe enkode sudut \(R_y\).

\[ \ket{\phi(x)} = R_y(x_1) \ket{0} = \cos(x_1/2) \ket{0} + \sin(x_1/2) \ket{1} \tag{8.3.2} \]

dan

\[ \ket{\phi(x')} = R_y(x_2) \ket{0} = \cos(x_2/2) \ket{0} + \sin(x_2/2) \ket{1} \tag{8.3.3} \]

maka

\[ k(x, x') = |\cos(x_1/2) \cos(x_2/2) + \sin(x_1/2) \sin(x_2/2)|^2 \tag{8.3.4} \]

Ambil \(x = [0.3]\) dan \(x' = [0.4]\) maka

\[ k(x, x') = |\cos(0.3/2) \cos(0.4/2) + \sin(0.3/2) \sin(0.4/2)|^2 = 0.9975 \tag{8.3.5} \]

dan matriks kernel kuantum-nya adalah

\[ k = \begin{pmatrix} k(x, x) & k(x, x')\\ k(x', x) & k(x', x') \end{pmatrix} = \begin{pmatrix} 1 & 0.9975\\ 0.9975 & 1 \end{pmatrix} \tag{8.3.6} \]

dimana \(k(x', x) = k(x, x')\) merupakan sifat simetri.

Bola Bloch berikut menyajikan "ilustrasi" seberapa dekat dua data tersebut dalam ruang fitur kuantum.

Adapun sirkuit kuantum implementasi kernel kuantum dari contoh di atas adalah sebagai berikut. Implementasi menggunakan metode tes SWAP. Qubit \(q_0\) adalah ancilla, sedang \(q_1\) dan \(q_2\) untuk representasi data \(x_1\) dan \(x_2\). Kolom kiri pada sirkuit (dipisahkan oleh barrier) merupakan peta fitur kuantum tipe dekode sudut dengan gerbang \(R_y\). Kolom tengah ialah tes SWAP. Tujuan tes ini yakni ketika ancilla bernilai 1, maka state di \(q_1\) dan \(q_2\) ditukar. Kolom kanan adalah pengukuran.

Hasil pengukuran ancilla memiliki persamaan

\[ P(0) = \frac{1 + |\braket{\phi(x)|\phi(x')}|^2}{2} \tag{8.3.7} \]

sehingga nilai kernel kuantum \(k\) sebesar

\[ |\braket{\phi(x)|\phi(x')}|^2 = 2 P(0) - 1 \tag{8.3.8} \]

Code: Quantum Kernel
                
    # Quantum Kernel
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    import numpy as np
    
    # Data input
    x = [0.3]
    x_prime = [0.4]
    data = [x, x_prime]
    
    # Initialise kernel matrix
    n = len(data)
    kernel_matrix = np.zeros((n, n))
    
    # Calculate kernel for each pair
    for i in range(n):
        for j in range(i, n):
            # Create quantum circuit for state |phi(x_i)>
            qc_i = QuantumCircuit(1)
            qc_i.ry(data[i][0], 0)
            state_i = Statevector.from_instruction(qc_i)
    
            # Create quantum circuit for state |phi(x_j)>
            qc_j = QuantumCircuit(1)
            qc_j.ry(data[j][0], 0)
            state_j = Statevector.from_instruction(qc_j)
    
            # Calculate inner product and kernel
            inner_product = np.abs(state_i.inner(state_j)) ** 2
            kernel_matrix[i, j] = inner_product
            kernel_matrix[j, i] = inner_product  # Symmetry
    
    # Print kernel matrix
    print("Quantum Kernel Matrix:")
    print(kernel_matrix)
                
                
            
Code: Quantum Kernel Circuit
                
    # Quantum Kernel Circuit
    
    from qiskit import QuantumCircuit
    from qiskit.primitives import StatevectorSampler
    
    # Classical data
    x1 = 0.3
    x2 = 0.4
    
    # n Qubit quantum system
    qc = QuantumCircuit(3, 1)
    
    # Quantum feature map - angle encoding
    qc.ry(x1, 1)
    qc.ry(x2, 2)
    
    # Barrier
    qc.barrier()
    
    # SWAP test
    qc.h(0)
    qc.cswap(0, 1, 2)
    qc.h(0)
    
    # Barrier
    qc.barrier()
    
    # Measurement on ancilla
    qc.measure(0, 0)
    
    # Running on simulator
    sampler = StatevectorSampler()
    job = sampler.run([(qc, None, 1024)])
    result = job.result()
    
    # Measurement counts
    counts = result[0].data.c.get_counts()
    
    # Total shots
    shots = sum(counts.values())
    
    # Probability P(|0>)
    p0 = counts.get('0', 0) / shots
    
    # Kernel value k = |<ψ_x|ψ_x'>|^2
    k = 2 * p0 - 1
    
    print(f"{k:.4f}")
    qc.draw("mpl", style = {"backgroundcolor": "#f5f5f5"})
                
                
            

8.4 Quantum Support Vector Machine

Quantum Support Vector Machine (QSVM) merupakan algoritma QML yang menggunakan kernel kuantum sebagai input pada SVM klasik untuk menentukan support vector, menghitung bias, dan melakukan prediksi label atau regresi. Dengan kata lain, QSVM menggabungkan komputasi kuantum dengan komputasi klasik.

Sisi komputasi kuantum yakni pada bagian peta fitur kuantum yang memetakan data klasik ke ruang fitur kuantum, melakukan inner product state kuantum hasil enkode, hingga didapat kernel kuantum beserta matriksnya. Selebihnya, SVM klasik yang berperan sampai didapat prediksi label atau regresinya.

8.4.1 Quantum Support Vector Classifier

Quantum Support Vector Classifier (QSVC) merupakan QSVM untuk tugas klasifikasi. Berikut disajikan dua contoh QSVC untuk data linier dan non linier. Jumlah data klasik masing-masing contoh adalah 6 data, oleh karenanya matriks kernel kuantum-nya berdimensi 6 x 6. Adapun peta fitur kuantum yang dipilih yakni tipe enkode sudut dengan gerbang rotasi \(R_y\).

Code: QSVC
                
    # QSVC
    
    import numpy as np
    import matplotlib.pyplot as plt
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from scipy.optimize import minimize
    
    # Classical data
    X = np.array([[0.2, 0.7], [0.4, 0.5], [0.3, 0.6], [0.8, 0.3], [0.7, 0.6], [0.5, 0.8]])
    y = np.array([-1, -1, -1, 1, 1, 1])
    n = len(X)
    
    # Quantum feature map: angle encoding
    def angle_encoding_circuit(x):
        qc = QuantumCircuit(len(x))
        for i in range(len(x)):
            qc.ry(x[i], i)
        return qc
    
    # Quantum kernel: |⟨ψ(x1)|ψ(x2)⟩|^2
    def quantum_kernel(x1, x2):
        sv1 = Statevector(angle_encoding_circuit(x1))
        sv2 = Statevector(angle_encoding_circuit(x2))
        return np.abs(sv1.data.conj().dot(sv2.data)) ** 2
    
    # Compute the kernel matrix
    K = np.zeros((n,n))
    for i in range(n):
        for j in range(n):
            K[i,j] = quantum_kernel(X[i], X[j])
    
    print("Quantum kernel matrix:")
    print(np.round(K, 3))
    
    # Dual SVM objective function
    def dual_objective(alpha):
        return 0.5 * np.sum(alpha[:,None] * alpha[None,:] * y[:,None] * y[None,:] * K) - np.sum(alpha)
    
    # Constraint: sum of α_i * y_i = 0
    constraints = {'type':'eq', 'fun': lambda a: np.dot(a, y)}
    
    # Bound: α_i >= 0
    bounds = [(0, None) for _ in range(n)]
    
    # Initial guess
    alpha0 = np.zeros(n)
    
    # Optimise alpha
    res = minimize(dual_objective, alpha0, bounds = bounds, constraints = constraints)
    alpha = res.x
    print("Alpha vector:", np.round(alpha, 3))
    
    # Compute bias b
    tol = 1e-5
    sv_idx = np.where(alpha > tol)[0]
    
    b = 0
    for k in sv_idx:
        b += y[k] - np.sum(alpha * y * K[:,k])
    b /= len(sv_idx)
    print("Bias b:", b)
    
    # QSVC prediction
    def predict(x_new):
        s = np.sum(alpha * y * np.array([quantum_kernel(X[i], x_new) for i in range(n)]))
        s += b
        return np.sign(s)
    
    preds = [predict(X[i]) for i in range(n)]
    print("QSVC predictions:", preds)
    print("Actual labels       :", y.tolist())
    
    # Plot QSVC decision boundary
    x_min, x_max = 0, 1
    y_min, y_max = 0, 1
    xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
                         np.linspace(y_min, y_max, 200))
    
    grid_points = np.c_[xx.ravel(), yy.ravel()]
    Z = np.array([predict(p) for p in grid_points]).reshape(xx.shape)
    
    plt.figure(figsize = (5,4), facecolor = '#f5f5f5')
    plt.contourf(xx, yy, Z, alpha = 0.2, colors = ['#C11C84','#008080'])
    plt.scatter(X[y == -1][:,0], X[y == -1][:,1], color = '#C11C84', label = 'Class -1')
    plt.scatter(X[y == 1][:,0], X[y == 1][:,1], color = '#008080', label = 'Class 1')
    plt.scatter(X[sv_idx][:,0], X[sv_idx][:,1], s = 150, facecolors = 'none', edgecolors = 'k', label = 'Support Vector')
    
    plt.xlabel('x1')
    plt.ylabel('x2')
    plt.title('QSVC')
    plt.legend()
    plt.show()
                
                
            
Code: QSVC Non Linear Data
                
    # QSVC Non Linear Data
    
    import numpy as np
    import matplotlib.pyplot as plt
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from scipy.optimize import minimize
    
    # Classical data
    X = np.array([
        [0.1, 0.1],
        [0.9, 0.9],
        [0.1, 0.9],
        [0.9, 0.1],
        [0.5, 0.2],
        [0.2, 0.8]
    ])
    y = np.array([-1, -1, 1, 1, -1, 1])
    n = len(X)
    
    # Quantum feature map: angle encoding
    def angle_encoding_circuit(x):
        qc = QuantumCircuit(len(x))
        for i in range(len(x)):
            qc.ry(x[i], i)
        return qc
    
    # Quantum kernel: |⟨ψ(x1)|ψ(x2)⟩|^2
    def quantum_kernel(x1, x2):
        sv1 = Statevector(angle_encoding_circuit(x1))
        sv2 = Statevector(angle_encoding_circuit(x2))
        return np.abs(sv1.data.conj().dot(sv2.data)) ** 2
    
    # Compute the kernel matrix
    K = np.zeros((n,n))
    for i in range(n):
        for j in range(n):
            K[i,j] = quantum_kernel(X[i], X[j])
    
    print("Quantum kernel matrix:")
    print(np.round(K, 3))
    
    # Dual SVM objective function
    def dual_objective(alpha):
        return 0.5 * np.sum(alpha[:,None] * alpha[None,:] * y[:,None] * y[None,:] * K) - np.sum(alpha)
    
    # Constraint: sum of α_i * y_i = 0
    constraints = {'type':'eq', 'fun': lambda a: np.dot(a, y)}
    
    # Bound: α_i >= 0
    bounds = [(0, None) for _ in range(n)]
    
    # Initial guess
    alpha0 = np.zeros(n)
    
    # Optimise alpha
    res = minimize(dual_objective, alpha0, bounds = bounds, constraints = constraints)
    alpha = res.x
    print("Alpha vector:", np.round(alpha, 3))
    
    # Compute bias b
    tol = 1e-5
    sv_idx = np.where(alpha > tol)[0]
    
    b = 0
    for k in sv_idx:
        b += y[k] - np.sum(alpha * y * K[:,k])
    b /= len(sv_idx)
    print("Bias b:", b)
    
    # QSVC prediction
    def predict(x_new):
        s = np.sum(alpha * y * np.array([quantum_kernel(X[i], x_new) for i in range(n)]))
        s += b
        return np.sign(s)
    
    preds = [predict(X[i]) for i in range(n)]
    print("QSVC predictions:", preds)
    print("Actual labels       :", y.tolist())
    
    # Plot QSVC decision boundary
    x_min, x_max = 0, 1
    y_min, y_max = 0, 1
    xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
                         np.linspace(y_min, y_max, 200))
    
    grid_points = np.c_[xx.ravel(), yy.ravel()]
    Z = np.array([predict(p) for p in grid_points]).reshape(xx.shape)
    
    plt.figure(figsize = (5,4), facecolor = '#f5f5f5')
    plt.contourf(xx, yy, Z, alpha = 0.2, colors = ['#C11C84','#008080'])
    plt.scatter(X[y == -1][:,0], X[y == -1][:,1], color = '#C11C84', label = 'Class -1')
    plt.scatter(X[y == 1][:,0], X[y == 1][:,1], color = '#008080', label = 'Class 1')
    plt.scatter(X[sv_idx][:,0], X[sv_idx][:,1], s = 150, facecolors = 'none', edgecolors = 'k', label = 'Support Vector')
    
    plt.xlabel('x1')
    plt.ylabel('x2')
    plt.title('QSVC Non Linear Data')
    plt.legend()
    plt.show()
                
                
            

8.4.2 Quantum Support Vector Regressor

Quantum Support Vector Regressor (QSVR) merupakan jenis QSVM untuk tujuan regresi. Berikut disajikan contoh perhitungan sederhana QSVR dari dua titik data yakni \(x_1 = [0, 0]\), target \(y_1 = 0\) dan \(x_2 = [1, 1]\), target \(y_2 = 1\). QSVR menggunakan enkode sudut \(R_y\) pada sistem dua qubit dan regresi ridge dengan \(\lambda = 0.01\).

Tahap 1: Enkode sudut \(R_y\). State \(\ket{\psi_1} = R_y(x_1) \ket{0} \otimes R_y(x_1) \ket{0}\) dan \(\ket{\psi_2} = R_y(x_2) \ket{0} \otimes R_y(x_2) \ket{0}\). Dengan

\[ R_y(\theta) \ket{0} = \cos(\theta/2) \ket{0} + \sin(\theta/2) \ket{1} \tag{8.4.1} \]

maka

\[ \ket{\psi_1} = R_y(0) \ket{0} \otimes R_y(0) \ket{0} = \ket{00} \tag{8.4.2} \]

dan

\[ \ket{\psi_2} = R_y(1) \ket{0} \otimes R_y(1) \ket{0} \approx 0.7701 \ket{00} + 0.4207 \ket{01} + 0.4207 \ket{10} + 0.2298 \ket{11} \tag{8.4.3} \]

Tahap 2: Kernel kuantum. Nilai kernel \(k(0,0)\), \(k(1,1)\), \(k(0,1)\), dan \(k(1,0)\) berturut-turut yaitu

\[ k(0,0) = |\braket{\psi_1 | \psi_1}| ^ 2 = 1 \ (\text{normal}) \tag{8.4.4} \]

\[ k(1,1) = |\braket{\psi_2 | \psi_2}| ^ 2 = 1 \ (\text{normal}) \tag{8.4.5} \]

\[ k(0,1) = k(1,0) = |\braket{\psi_1 | \psi_2}| ^ 2 = 0.7701 ^ 2 \approx 0.5931 \ (\text{simetri}) \tag{8.4.6} \]

Diperoleh matriks kernel

\[ k = \begin{pmatrix} 1 & 0.5931 \\ 0.5931 & 1 \end{pmatrix} \tag{8.4.7} \]

Tahap 3: Regresi ridge. Tiga parameter dalam regresi ridge:

\[ \alpha = (k + \lambda I) ^ {-1} y, \quad \lambda = 0.01, \quad y = \begin{pmatrix} 0 \\ 1 \end{pmatrix} \tag{8.4.8} \]

Didapat nilai \(\alpha_1 \approx -0.888\) dan \(\alpha_2 \approx 1.511\).

Tahap 4: Fungsi prediksi \(f(x)\). Persamaan fungsi prediksi yakni

\[ f(x) = \sum_{i = 1} ^ N \alpha_i k(x, x_i) \tag{8.4.9} \]

sehingga

\[ f(x) = \alpha_1 k(x, x_1) + \alpha_2 k(x, x_2) \tag{8.4.10} \]

Tahap 5: Contoh prediksi.
Contoh 1: Titik data \(x = [0, 0]\). Nilai kernel \(k(x, x_1) = 1\) dan \(k(x, x_2) = 0.5931\), sehingga

\[ f([0, 0]) = (-0.888)(1) + (1.511)(0.5931) \approx -0.888 + 0.896 = 0.008 \tag{8.4.11} \]

Contoh 2: Titik data \(x = [1, 1]\). Nilai kernel \(k(x, x_1) = 0.5931\) dan \(k(x, x_2) = 1\), sehingga

\[ f([1, 1]) = (-0.888)(0.5931) + (1.511)(1) \approx -0.527 + 1.511 = 0.984 \tag{8.4.12} \]

Code: QSVR
                
    # QSVR
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    import numpy as np
    
    # Define the data
    x1 = [0.0, 0.0]   # target y1 = 0
    x2 = [1.0, 1.0]   # target y2 = 1
    y = np.array([0.0, 1.0])
    lam = 0.01        # ridge regularisation parameter
    
    print("Data points:")
    print("x1 =", x1, " → y1 = 0")
    print("x2 =", x2, " → y2 = 1")
    print("λ =", lam)
    print()
    
    # Encode each input x as Ry(x[0]) ⊗ Ry(x[1])
    
    # Encode x1 = [0,0]
    qc1 = QuantumCircuit(2)
    qc1.ry(x1[0], 0)   # Ry(0) on qubit 0
    qc1.ry(x1[1], 1)   # Ry(0) on qubit 1
    psi1 = Statevector.from_instruction(qc1)  # |00⟩
    
    # Encode x2 = [1,1]
    qc2 = QuantumCircuit(2)
    qc2.ry(x2[0], 0)   # Ry(1) on qubit 0
    qc2.ry(x2[1], 1)   # Ry(1) on qubit 1
    psi2 = Statevector.from_instruction(qc2)
    
    print("Quantum states created:")
    print("|ψ₁⟩ = Ry(0)|0⟩ ⊗ Ry(0)|0⟩ = |00⟩")
    print("|ψ₂⟩ = Ry(1)|0⟩ ⊗ Ry(1)|0⟩ → mixed state")
    print()
    
    # Compute the quantum kernel matrix k_ij = |⟨ψ_i|ψ_j⟩|²
    k11 = np.abs(psi1.inner(psi1)) ** 2   # 1.0
    k12 = np.abs(psi1.inner(psi2)) ** 2   # overlap between x1 and x2
    k21 = k12
    k22 = np.abs(psi2.inner(psi2)) ** 2   # 1.0
    
    k = np.array([[k11, k12],
                  [k21, k22]])
    
    print("Quantum kernel matrix k:")
    print(np.round(k, 4))
    print()
    
    # Ridge regression → α = (k + λI)⁻¹ y
    I = np.eye(2)
    alpha = np.linalg.inv(k + lam * I) @ y
    
    print("Ridge regression coefficients α:")
    print("α₁ =", np.round(alpha[0], 4))
    print("α₂ =", np.round(alpha[1], 4))
    print()
    
    # Define prediction function f(x)
    # f(x) = α₁ k(x,x₁) + α₂ k(x,x₂)
    
    def predict(x):
        # Encode new input x
        qc = QuantumCircuit(2)
        qc.ry(x[0], 0)
        qc.ry(x[1], 1)
        psi_x = Statevector.from_instruction(qc)
        
        # Compute kernel elements
        kx1 = np.abs(psi1.inner(psi_x)) ** 2
        kx2 = np.abs(psi2.inner(psi_x)) ** 2
        
        # Final prediction
        return alpha[0] * kx1 + alpha[1] * kx2
    
    # Test predictions
    print("Predictions f(x):")
    print("f([0.0, 0.0]) =", np.round(predict([0.0, 0.0]), 4)) # ~0.008
    print("f([1.0, 1.0]) =", np.round(predict([1.0, 1.0]), 4)) # ~0.984
    print("f([0.5, 0.5]) =", np.round(predict([0.5, 0.5]), 4))   
    print("f([0.0, 1.0]) =", np.round(predict([0.0, 1.0]), 4))   
    print("f([1.0, 0.0]) =", np.round(predict([1.0, 0.0]), 4))   
                
                
            

8.5 Variational Quantum Circuit

Variational Quantum Circuit (VQC) adalah algoritma QML dengan sirkuit variasional yang digunakan untuk tugas klasifikasi atau regresi. Sama seperti VQE dan QAOA, VQC merupakan algoritma hibrida yakni gabungan komputasi kuantum dan komputasi klasik, dengan optimasi parameter sirkuit kuantum hingga didapatkan prediksi yang sesuai. Adapun bagian atau tahapan VQC meliputi data klasik, peta fitur kuantum, ansatz, optimasi, serta pengukuruan nilai ekspektasi.

8.5.1 Variational Quantum Classifier

Variational Quantum Classifier (VQC) merupakan jenis VQC untuk tugas klasifikasi, seperti memprediksi label 0 atau 1 dari data input.

Contoh: VQC akan digunakan untuk memprediksi label klasifikasi data \(x = [x_1, x_2]\) dengan kondisi berikut

\[ \text{prediksi label} = \begin{cases} 0 & \text{jika } x_1 + x_2 \le 1 \\ 1 & \text{jika } x_1 + x_2 > 1 \end{cases} \tag{8.5.1} \]

Misal dipilih data \(x = [0.7, 0.8]\) dan akan diprediksi dengan VQC 2-qubit dan VQC 1-qubit.

8.5.1.a VQC 2-Qubit

Tahap 1: Data klasik. Data klasik \(x = [0.7, 0.8]\). Dari data didapat \(0.7+0.8=1.5\) sehingga ia lebih dari 1, maka true label bernilai 1.

Tahap 2: Peta fitur kuantum - enkode sudut. Dibutuhkan dua qubit untuk peta fitur kuantum ini yakni \(q_0\) dan \(q_1\). Selanjutnya, diterapkan gerbang kuantum \(R_y(x_1)\) pada \(q_0\) dan \(R_y(x_2)\) pada \(q_1\), atau gerbang \(R_y(0.7)\) pada \(q_0\) serta \(R_y(0.8)\) pada \(q_1\).

Tahap 3: Ansatz. Ansatz terdiri dari gerbang \(CX\) dengan qubit \(q_0\) sebagai kontrol dan \(q_1\) sebagai target, serta gerbang rotasi dengan parameter \(\theta\) atau \(R_y(\theta)\) pada \(q_0\). Pada contoh ini, nilai \(\theta\) telah ditentukan sebesar \(\pi\). Adapun pada proses komputasi sebenarnya, nilai parameter \(\theta\) didapat dengan menggunakan teknik optimasi.

Tahap 4: Nilai Ekspektasi. Pengukuran nilai ekspektasi \(\langle Z \rangle\) dari state kuantum akhir pada qubit \(q_0\).

Hubungan nilai ekspektasi \(\langle Z \rangle\) dengan probabilitas \(P\). Kedua besaran tersebut memiliki hubungan yakni

\[ \langle Z \rangle\ = P(\ket{0}) \cdot (+1) + P(\ket{1}) \cdot (-1) \tag{8.5.2} \]

dengan \(P(\ket{0}) + P(\ket{1}) = 1\). Selanjutnya, persamaan di atas dapat ditulis sebagai

\[ \langle Z \rangle\ = P(\ket{0}) - P(\ket{1}) = P(\ket{0}) - (1 - P(\ket{0})) = 2 P(\ket{0}) - 1 \tag{8.5.3} \]

atau

\[ P(\ket{0}) = \frac{1 + \langle Z \rangle}{2} \tag{8.5.4} \]

Jika \(\langle Z \rangle\ < 0\) maka \(P(\ket{0}) < 0.5\). Ini berarti probabilitas menemukan state lebih besar ke \(\ket{1}\), prediksi label pada VQC diklasifikasikan 1. Sebaliknya, jika \(\langle Z \rangle\ \ge 0\) maka \(P(\ket{0}) \ge 0.5\), prediksi label VQC diberi 0.

State kuantum dari sirkuit kuantum di atas yakni

\[ \ket{\psi} = -0.1335307 \ket{00} + 0.86521956 \ket{01} - 0.3158298 \ket{10} + 0.36580896 \ket{11} \tag{8.5.5} \]

sehingga didapat nilai ekspektasi \(\langle Z \rangle\) pada qubit \(q_0\) sebesar

\[ \langle Z \rangle = \bra{\psi} Z \ket{\psi} = -0.7648421872844884 \tag{8.5.6} \]

Nilai ekspektasi \(\langle Z \rangle\) \(< 0\), maka hasil prediksi label VQC ialah 1. Hal ini sesuai dengan true label dari \(x = [0.7, 0.8]\) yang bernilai 1.

Lalu, bagaimanakah jika ansatz dikembangkan dan dilakukan optimasi parameter \(\theta\)-nya? Berikut ialah salah satu contohnya. Ada beberapa gerbang yang ditambahkan pada ansatz sedemikian rupa sehingga sirkuit menjadi lebih ekspresif.

Adapun nilai ekspektasi dari sirkuit ini yaitu

\[ \langle Z \rangle = -0.9999999960339767 \tag{8.5.7} \]

Code: 2-Qubit VQC
                
    # 2-Qubit VQC
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Operator, Statevector
    import numpy as np
    
    # Data input
    x = [0.7, 0.8] # Features
    theta = np.pi # Variational parameter, explicitly set to pi
    
    # 2-Qubit quantum system
    qc = QuantumCircuit(2)
    
    # Feature map
    qc.ry(x[0], 0)
    qc.ry(x[1], 1)
    
    # Ansatz
    qc.cx(0, 1)
    qc.ry(theta, 0)
    
    # Statevector
    statevector = Statevector.from_instruction(qc)
    
    # Expectation value
    i_z = Operator([[1, 0, 0, 0],
                    [0, -1, 0, 0],
                    [0, 0, 1, 0],
                    [0, 0, 0, -1]])
    expectation_value = statevector.expectation_value(i_z).real
    
    # Print results
    print("Statevector:", statevector.data)
    print(f"Expectation value  on q0: {expectation_value}")
    print(f"Probability of |0> for q0: {(1 + expectation_value) / 2}")
    print(f"Predicted label: {1 if expectation_value < 0 else 0}")
                
                
            
Code: Optimised 2-Qubit VQC
                
    # Optimised 2-Qubit VQC
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Operator, Statevector
    from qiskit.visualization import circuit_drawer
    import numpy as np
    from scipy.optimize import minimize
    import matplotlib.pyplot as plt
    
    # Input data
    x = [0.7, 0.8]  # Features
    
    # Operator for expectation value (I ⊗ Z)
    i_z = Operator([[1, 0, 0, 0],
                    [0, -1, 0, 0],
                    [0, 0, 1, 0],
                    [0, 0, 0, -1]])
    
    # Function to create the quantum circuit with an enhanced ansatz
    def create_circuit(theta, x):
        qc = QuantumCircuit(2)
        
        # Feature map
        qc.ry(x[0], 0)  # Rotation Y on qubit 0 based on feature x[0]
        qc.ry(x[1], 1)  # Rotation Y on qubit 1 based on feature x[1]
        
        # Enhanced ansatz
        qc.cx(0, 1)         # CNOT for entanglement
        qc.ry(theta[0], 0)  # Y rotation on qubit 0
        qc.rz(theta[1], 0)  # Z rotation on qubit 0
        qc.ry(theta[2], 1)  # Y rotation on qubit 1
        qc.cx(1, 0)         # Additional CNOT for entanglement
        qc.rz(theta[3], 1)  # Z rotation on qubit 1
        
        return qc
    
    # Objective function for optimisation (maximising expectation value)
    def objective_function(theta, x):
        qc = create_circuit(theta, x)
        statevector = Statevector.from_instruction(qc)
        expectation_value = statevector.expectation_value(i_z).real
        return expectation_value  # Negative to maximise expectation value
    
    # Initialise parameters randomly for robust optimisation
    initial_theta = np.random.uniform(0, 2 * np.pi, 4)
    print("Initial theta parameters:", initial_theta)
    
    # Optimise theta parameters using COBYLA
    result = minimize(objective_function, initial_theta, args = (x,), method = 'COBYLA', options = {'maxiter': 1000})
    
    # Optimised theta parameters
    optimal_theta = result.x
    print("Optimised theta parameters:", optimal_theta)
    
    # Create circuit with optimised parameters
    qc_optimal = create_circuit(optimal_theta, x)
    
    # Calculate statevector and expectation value
    statevector_optimal = Statevector.from_instruction(qc_optimal)
    expectation_value_optimal = statevector_optimal.expectation_value(i_z).real
    
    # Print results
    print("Statevector:", statevector_optimal.data)
    print("Expectation value for q0:", expectation_value_optimal)
    print("Probability of |0> for q0:", (1 + expectation_value_optimal) / 2)
    print("Predicted label:", 1 if expectation_value_optimal < 0 else 0)
    
    # Visualise the circuit and save it as a file
    qc_optimal.draw('mpl', style={"backgroundcolor": "#f5f5f5"})
                
                
            

8.5.1.b VQC 1-Qubit

Jika pada VQC 2-qubit menggunakan enkode sudut, maka pada VQC 1-qubit ini di dalam tahapan peta fitur kuantum-nya memakai enkode amplitudo. Data klasik atau vektor \(x = [0.7, 0.8]\) terdiri atas 2 data, maka cukup dibutuhkan sebanyak \(\log_2 2 = 1\) qubit saja. Adapun ansatz berupa gerbang \(R_y(\theta)\) dengan nilai \(\theta\) yang telah ditentukan yakni \(\theta=\pi/2\). Terakhir, dilakukan pengukuran nilai ekspektasi energi yaitu \(\bra{\psi_q} Z \ket{\psi_q}\).

State kuantum dari sirkuit di atas yakni

\[ \ket{\psi_q} = -0.0665 \ket{0} + 0.9978 \ket{1} \tag{8.5.8} \]

sehingga nilai ekspektasi energinya sebesar

\[ \langle Z \rangle = -0.9912 \tag{8.5.9} \]

Nilai \(\langle Z \rangle\ < 0\) maka \(P(\ket{0}) < 0.5\), berarti probabilitas menemukan state lebih besar ke \(\ket{1}\), prediksi label pada VQC adalah 1.

Selanjutnya, disajikan satu contoh VQC 1-qubit versi hasil pengembangan di bagian ansatz, yaitu dengan menambahkan gerbang \(R_z\) serta melakukan optimasi pada parameter \(\theta\).

Code: 1-Qubit VQC
                
    # 1-Qubit VQC
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Operator, Statevector
    from qiskit.circuit.library import Initialize
    import numpy as np
    
    # Feature vector
    x = np.array([0.7, 0.8])
    
    # Normalise the vector
    norm = np.linalg.norm(x)
    normalised_x = x / norm
    
    # 1-Qubit quantum circuit
    qc = QuantumCircuit(1)
    
    # Quantum feature map: Amplitude encoding
    qc.initialize(normalised_x, 0)  # Encode [x1, x2] into |0⟩ and |1⟩
    
    # Initial gate after normalisation
    init_gate = Initialize(normalised_x)
    
    # Variational layer: Parameterised circuit (Ansatz)
    qc.ry(np.pi/2, 0)  # Ry gate with pi/2
    
    # Statevector
    statevector = Statevector.from_instruction(qc)
    
    # Expectation value
    Z = Operator([[1, 0], [0, -1]])
    expectation_value = statevector.expectation_value(Z).real
    
    # Print results
    print("Original vector:", x)
    print("Normalised vector:", normalised_x)
    print("Statevector:", statevector.data)
    print(f"Expectation value: {expectation_value}")
    print(f"Probability of |0> : {(1 + expectation_value) / 2}")
    print(f"Predicted label: {1 if expectation_value < 0 else 0}")
    
    # Draw quantum circuit
    qc.draw("mpl", style={"backgroundcolor": "#f5f5f5"})
                
                
            
Code: Optimised 1-Qubit VQC
                
    # Optimised 1-Qubit VQC
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Operator, Statevector
    from qiskit.circuit.library import Initialize
    import numpy as np
    from scipy.optimize import minimize
    
    # Feature vector for encoding
    x = np.array([0.7, 0.8])
    
    # Normalise the feature vector
    norm = np.linalg.norm(x)
    normalised_x = x / norm
    
    # Define a function to create the quantum circuit with a parameterised ansatz
    def create_circuit(params):
        # Initialise a 1-qubit quantum circuit
        qc = QuantumCircuit(1)
        # Encode the normalised feature vector using amplitude encoding
        qc.initialize(normalised_x, 0)
        
        # Apply a parameterised ansatz: Ry and Rz gates
        qc.ry(params[0], 0)  # Ry gate with parameter theta_1
        qc.rz(params[1], 0)  # Rz gate with parameter theta_2
        
        return qc
    
    # Define the cost function for optimisation
    def cost_function(params):
        # Create the circuit with the given parameters
        qc = create_circuit(params)
        # Compute the statevector of the circuit
        statevector = Statevector.from_instruction(qc)
        # Define the Pauli-Z operator
        Z = Operator([[1, 0], [0, -1]])
        # Calculate the expectation value of Z
        expectation = statevector.expectation_value(Z).real
        return expectation  # Objective: minimise the expectation value
    
    # Set initial parameters for optimisation (randomly chosen)
    initial_params = np.random.uniform(0, 2 * np.pi, 2)
    
    # Perform optimisation using the COBYLA algorithm
    result = minimize(cost_function, initial_params, method = 'COBYLA', options = {'maxiter': 100})
    
    # Extract the optimised parameters
    optimal_params = result.x
    
    # Create the circuit with optimised parameters
    qc_opt = create_circuit(optimal_params)
    # Compute the statevector for the optimised circuit
    statevector_opt = Statevector.from_instruction(qc_opt)
    # Define the Pauli-Z operator for expectation value
    Z = Operator([[1, 0], [0, -1]])
    # Calculate the optimised expectation value
    expectation_value_opt = statevector_opt.expectation_value(Z).real
    
    # Print results
    print("Original vector:", x)
    print("Normalised vector:", normalised_x)
    print("Optimised parameters:", optimal_params)
    print("Optimised statevector:", statevector_opt.data)
    print(f"Optimised expectation value: {expectation_value_opt}")
    print(f"Probability of |0⟩: {(1 + expectation_value_opt) / 2}")
    print(f"Predicted label: {1 if expectation_value_opt < 0 else 0}")
    
    # Draw the optimised circuit
    qc_opt.draw("mpl", style={"backgroundcolor": "#f5f5f5"})
                
                
            

8.5.1.c VQC N Poin Vektor

Dua subsubbab sebelumnya telah menyajikan VQC untuk 1 poin vektor, dimana untuk yang pertama menggunakan VQC 2-qubit dengan peta fitur kuantum tipe enkode sudut dan untuk yang kedua melalui VQC 1-qubit berjenis enkode amplitudo pada peta fitur kuantum-nya.

Disini, akan diberikan satu contoh VQC 1-qubit untuk N poin vektor. Di VQC ini, peta fitur kuantum berupa gerbang \(R_y(x_1 * \pi)\) dan \(R_z(x_2 * \pi)\), sedangkan ansatz-nya terdiri atas gerbang \(R_y(\theta_1)\) dan \(R_z(\theta_2)\).

Code: 1-Qubit VQC for N Vector Points
                
    # 1-Qubit VQC for N Vector Points
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Operator, Statevector
    import numpy as np
    from scipy.optimize import minimize
    import matplotlib.pyplot as plt
    
    # Generate synthetic dataset: 1000 data points with 2D feature vectors and binary labels
    np.random.seed(42)  # For reproducibility
    n_samples = 1000
    X = np.random.uniform(0, 1, (n_samples, 2))  # 1000 random 2D vectors
    true_labels = np.array([0 if x[0] + x[1] <= 1 else 1 for x in X])  # Label 0 if x1 + x2 <= 1, else 1
    
    # Define a function to create the quantum circuit with angle encoding and parameterised ansatz
    def create_circuit(feature_vector, params):
        # Initialise a 1-qubit quantum circuit
        qc = QuantumCircuit(1)
        # Angle encoding: encode x1 and x2 into rotations
        qc.ry(feature_vector[0] * np.pi, 0)  # Ry(x1 * pi) on qubit 0
        qc.rz(feature_vector[1] * np.pi, 0)  # Rz(x2 * pi) on qubit 0
        
        # Parameterised ansatz: Ry, Rz gates
        qc.ry(params[0], 0)  # Ry gate with parameter theta_1
        qc.rz(params[1], 0)  # Rz gate with parameter theta_2
        
        return qc
    
    # Define a function to compute the expectation value for a single data point
    def compute_expectation(feature_vector, params):
        # Create the circuit with the given parameters and feature vector
        qc = create_circuit(feature_vector, params)
        # Compute the statevector of the circuit
        statevector = Statevector.from_instruction(qc)
        # Define the Pauli-Z operator
        Z = Operator([[1, 0], [0, -1]])
        # Calculate the expectation value of Z
        return statevector.expectation_value(Z).real
    
    # Define the loss function: Binary cross-entropy
    def loss_function(params, X, true_labels):
        loss = 0
        for x, true_label in zip(X, true_labels):
            z = compute_expectation(x, params)
            prob_one = (1 - z) / 2  # P(label=1) = (1 - ) / 2
            # Avoid log(0) by clipping probabilities
            prob_one = np.clip(prob_one, 1e-10, 1 - 1e-10)
            loss += - (true_label * np.log(prob_one) + (1 - true_label) * np.log(1 - prob_one))
        return loss / len(X)
    
    # Optimise a single set of parameters for the entire dataset
    initial_params = np.zeros(2)  # Start with zeros for stability
    result = minimize(lambda params: loss_function(params, X, true_labels), 
                     initial_params, method = 'BFGS', options = {'maxiter': 300})
    optimal_params = result.x
    
    # Print optimised parameters and minimum loss
    print("Optimised parameters:", optimal_params)
    print("Minimum loss: {:.4f}".format(result.fun))
    
    # Predict labels for all data points using the optimised parameters
    predicted_labels = []
    for x in X:
        z = compute_expectation(x, optimal_params)
        predicted_label = 1 if z < 0 else 0
        predicted_labels.append(predicted_label)
    
    predicted_labels = np.array(predicted_labels)
    
    # Calculate accuracy
    accuracy = np.mean(predicted_labels == true_labels) * 100
    
    # Print results
    print("Dataset size:", n_samples)
    print("Accuracy of predictions: {:.2f}%".format(accuracy))
    
    # Plotting: True labels (left) and predicted labels (right)
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize = (9, 4))
    
    # Set background colour for figure and axes
    fig.patch.set_facecolor('#f5f5f5')
    ax1.set_facecolor('#f5f5f5')
    ax2.set_facecolor('#f5f5f5')
    
    for ax in [ax1, ax2]:
        for spine in ax.spines.values():
            spine.set_color('#111')
        ax.xaxis.label.set_color('#111')
        ax.yaxis.label.set_color('#111')
        ax.tick_params(colors = '#111')
        ax.title.set_color('#111')
    
    # Plot 1: True labels
    for x, label in zip(X, true_labels):
        color = '#008080' if label == 0 else '#C11C84'
        ax1.scatter(x[0], x[1], c = color, s = 20)
    ax1.set_xlabel('x1')
    ax1.set_ylabel('x2')
    ax1.set_title('True Labels, Dataset of 1000 Points')
    ax1.grid(True)
    
    # Plot 2: Predicted labels
    for x, pred in zip(X, predicted_labels):
        color = '#008080' if pred == 0 else '#C11C84'
        ax2.scatter(x[0], x[1], c = color, s = 20)
    ax2.set_xlabel('x1')
    ax2.set_ylabel('x2')
    ax2.set_title(f'Predicted Labels, {accuracy:.2f}% Accuracy')
    ax2.grid(True)
    
    plt.tight_layout()
    plt.show()
    
    # Draw the circuit for the last data point as an example
    qc_example = create_circuit(X[-1], optimal_params)
    qc_example.draw("mpl", style = {"backgroundcolor": "#f5f5f5"})
                
                
            

8.5.2 Variational Quantum Regressor

Variational Quantum Regressor (VQR) adalah jenis Variational Quantum Circuit untuk tujuan regresi. Berikut disajikan contoh sederhana VQR. Diberikan dua titik data yakni \(x_1 = [0, 0]\) dan \(x_2 = [1, 1]\) dengan target \(y_1 = 0\) dan \(y_2 = 1\). Sirkuit VQR akan digunakan untuk memprediksi regresi pada dua titik data ini serta pada satu titik data tes, misal \([0.5, 0.5]\).

Alurnya yaitu VQR menggunakan sirkuit sistem dua qubit, enkode sudut \(R_x\) dan \(R_y\), ansatz dengan RealAmplitudes, prediksi dengan nilai ekspektasi \(\langle Z \rangle\) pada \(q_0\), dan optimasi parameter \(\theta\) dengan teknik optimasi. Sirkuit implementasi VQR ini disajikan dalam gambar (bagian atas).

RealAmplitudes sendiri ialah ansatz kuantum parametrik dalam pustaka Qiskit Circuit Library yang didesain khusus untuk melatih model kuantum variasional (variational quantum models) seperti VQE dan VQR. Sirkuit kuantum hasil decompose RealAmplitudes ditunjukkan pada gambar (bagian bawah).

Code: VQR
                
    # VQR
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    from qiskit.circuit.library import RealAmplitudes
    from scipy.optimize import minimize
    import numpy as np
    import matplotlib.pyplot as plt
    
    # Training data
    X_train = np.array([[0.0, 0.0], [1.0, 1.0]])
    y_train = np.array([0.0, 1.0])
    
    # Scale features to [0, π] for rotation gates
    X_train_scaled = X_train * np.pi
    
    # Test point (added at the end)
    x_test = np.array([0.5, 0.5])        # Mid-point between [0,0] and [1,1]
    x_test_scaled = x_test * np.pi
    # y_test_expected = 0.5              # Linear interpolation expectation
    
    # Encoding circuit: RX(x0) on q0, RY(x1) on q1
    def encoding_circuit(x):
        qc = QuantumCircuit(2)
        qc.rx(x[0], 0)
        qc.ry(x[1], 1)
        return qc
    
    # Ansatz: Hardware-efficient (RealAmplitudes, 1 repetition)
    ansatz = RealAmplitudes(num_qubits = 2, reps = 1, entanglement = 'linear')
    print(f"Ansatz depth: {ansatz.depth()}, parameters: {ansatz.num_parameters}")
    
    # Full variational circuit: encoding + ansatz
    def vqr_circuit(x, theta):
        qc = encoding_circuit(x)
        qc = qc.compose(ansatz.assign_parameters(theta))
        return qc
    
    # Prediction:  on qubit 0 → scaled to [0,1]
    def predict(x, theta):
        qc = vqr_circuit(x, theta)
        sv = Statevector.from_instruction(qc)
        probs = sv.probabilities()
        # _q0 = P(|q0 = 0>) - P(|q0 = 1>) = P00 + P10 - (P01 + P11)
        exp_z_q0 = probs[0] + probs[2] - probs[1] - probs[3]
        return (exp_z_q0 + 1.0) / 2.0  # Map from [-1,1] to [0,1]
    
    # Loss function: Mean Squared Error
    def loss(theta):
        predictions = np.array([predict(x, theta) for x in X_train_scaled])
        return np.mean((predictions - y_train) ** 2)
    
    # Optimisation using COBYLA
    print("\nStarting parameter optimisation...")
    initial_theta = np.random.uniform(0, 2 * np.pi, ansatz.num_parameters)
    result = minimize(
        loss, initial_theta,
        method = 'COBYLA',
        options = {'maxiter': 300, 'tol': 1e-10}
    )
    
    optimal_theta = result.x
    final_loss = result.fun
    
    print(f"Optimisation complete.")
    print(f"Final loss (MSE): {final_loss:.2e}")
    print(f"Optimal parameters: {np.round(optimal_theta, 4)}")
    
    # Final predictions on training data
    pred_0 = predict(X_train_scaled[0], optimal_theta)
    pred_1 = predict(X_train_scaled[1], optimal_theta)
    
    print(f"\n--- Training Results ---")
    print(f"Input [0.0, 0.0] → Prediction: {pred_0:.6f} (target: 0.0)")
    print(f"Input [1.0, 1.0] → Prediction: {pred_1:.6f} (target: 1.0)")
    
    # Test point prediction
    pred_test = predict(x_test_scaled, optimal_theta)
    print(f"\n--- Test Point ---")
    print(f"Input {x_test} → Prediction: {pred_test:.6f} (expected ~0.5)")
    
    # Draw and display the quantum circuit
    print(f"\nGenerating quantum circuit diagram...")
    circuit_with_params = vqr_circuit(x_test_scaled, optimal_theta)
    
    # Draw using matplotlib
    fig = circuit_with_params.draw('mpl')
    plt.tight_layout()
    plt.show()
    
    # Optional: save to file
    fig.savefig("vqr_circuit.png")
    print("Circuit diagram saved as 'vqr_circuit.png'")
                
                
            
Code: RealAmplitudes
                
    # RealAmplitudes
    
    from qiskit.circuit.library import RealAmplitudes

    ansatz = RealAmplitudes(num_qubits = 2, reps = 1, entanglement = 'linear')
    ansatz.decompose().draw('mpl')
                
                
            

8.6 Quantum Neural Network

Jaringan saraf tiruan kuantum (Quantum Neural Network, QNN) adalah algoritma pembelajaran mesin kuantum (QML) yang memanfaatkan PQC. Sirkuit QNN berupa lapisan gerbang kuantum yang dikenal sebagai perceptron kuantum, menyerupai struktur dan fungsi perceptron pada jaringan saraf klasik.

Contoh: Implementasi QNN untuk memodelkan dan memprediksi hasil fungsi XOR. Guna implementasi contoh ini, akan digunakan QNN 2-qubit dan QNN 1-qubit.

\(x_1\) \(x_2\) \(x_1 ⊕ x_2\)
0 0 0
0 1 1
1 0 1
1 1 0

8.6.1 QNN 2-Qubit

QNN 2-qubit memiliki dua buah qubit untuk proses peta fitur kuantum, ansatz, optimasi, dan pengukuran.

Referensi sirkuit: MathWorks.

Qubit \(q_0\) dan \(q_1\) sebagai input, qubit \(q_1\) juga untuk output. Gerbang \(R_x(\pi * x_1)\) pada pada \(q_0\) dan \(R_x(\pi * x_2)\) di \(q_1\) adalah peta fitur kuantum jenis enkode sudut, dan gerbang kuantum selebihnya merupakan ansatz. Parameter \(\theta\) didapat dengan teknik optimasi. Adapun nilai ekspektasi diukur pada state kuantum akhir qubit \(q_1\) atau

\[ \langle Z \rangle = \bra{\psi_{q_1}} Z \ket{\psi_{q_1}} \tag{8.6.1} \]

Berikut disajikan tiga kode Qiskit implementasi QNN 2-qubit dari contoh di atas. Kode pertama, nilai parameter \(\theta\) telah ditentukan yakni sebesar \(\frac{\pi}{6}\). Untuk kode kedua dan ketiga, parameter \(\theta\) dicari dengan teknik optimasi. Adapun visualisasi heatmap implementasi QNN-nya disajikan pada kode ketiga.

Code: 2-Qubit QNN for XOR
                
    # 2-Qubit QNN for XOR

    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Operator, Statevector
    import numpy as np
    
    # Data input for XOR
    inputs = [(0, 0), (0, 1), (1, 0), (1, 1)]
    
    # Define Pauli-Z and identity matrices
    Z = np.array([[1, 0], [0, -1]])
    I = np.eye(2)
    
    # Calculate Z ⊗ I for expectation value
    ZI = np.kron(Z, I)
    ZI_operator = Operator(ZI)
    
    print("Quantum VQC XOR with Statevector and Expectation Value\n")
    
    for x1, x2 in inputs:
        # Initialise quantum circuit with 2 qubits
        qc = QuantumCircuit(2)
    
        # Feature map: Angle encoding
        qc.rx(np.pi * x1, 0)  # Rx rotation based on x1
        qc.rx(np.pi * x2, 1)  # Rx rotation based on x2
    
        # Ansatz: CNOT gates and Ry rotation
        qc.cx(0, 1)  # CNOT from qubit 0 to qubit 1
        qc.ry(np.pi/6, 1)  # Ry(π/6) rotation on qubit 1
    
        # Create statevector from circuit
        statevector = Statevector.from_instruction(qc)
    
        # Calculate expectation value 
        expectation_value = statevector.expectation_value(ZI_operator).real
    
        # Print results
        print(f"Input: ({x1}, {x2})")
        print("Statevector:", statevector.data)
        print(f"Expectation value on q1: {expectation_value}")
        print(f"Probability of |0> for q1: {(1 + expectation_value) / 2}")
        print(f"Predicted label: {1 if expectation_value < 0 else 0}\n") 
                
                
            
Code: Optimised 2-Qubit QNN for XOR
                
    # Optimised 2-Qubit QNN for XOR

    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Operator, Statevector
    import numpy as np
    from scipy.optimize import minimize
    
    # Data input for XOR
    inputs = [(0, 0), (0, 1), (1, 0), (1, 1)]
    
    # Define Pauli-Z and identity matrices
    Z = np.array([[1, 0], [0, -1]])
    I = np.eye(2)
    
    # Calculate Z ⊗ I for expectation value
    ZI = np.kron(Z, I)
    ZI_operator = Operator(ZI)
    
    # Function to create circuit with theta
    def get_circuit(x1, x2, theta):
        # Initialise quantum circuit with 2 qubits
        qc = QuantumCircuit(2)
    
        # Feature map: Angle encoding
        qc.rx(np.pi * x1, 0)  # Rx rotation based on x1
        qc.rx(np.pi * x2, 1)  # Rx rotation based on x2
    
        # Ansatz: CNOT gates and Ry rotation
        qc.cx(0, 1) # CNOT from qubit 0 to qubit 1
        qc.ry(theta, 1)  # Theta as a variable parameter
        return qc
    
    # Cost function for optimisation
    def cost(params):
        theta = params[0]
        expectation_values = []
        
        # Calculate expectation values for all inputs
        for x1, x2 in inputs:
            qc = get_circuit(x1, x2, theta)
            statevector = Statevector.from_instruction(qc)
            expectation_value = statevector.expectation_value(ZI_operator).real
            expectation_values.append(expectation_value)
        
        # Objective: maximise separation between input groups
        # For example, push (0,0) and (1,1) to positive expectation values,
        # and (0,1), (1,0) to negative expectation values
        # Cost = - (variance of expectation values) to encourage separation
        variance = np.var(expectation_values)
        return -variance  # Negative to maximise variance
    
    # Run optimisation
    initial_params = [0.0]  # Initial guess for theta
    result = minimize(cost, initial_params, method = 'COBYLA', tol = 1e-6)
    optimal_theta = result.x[0]
    
    print(f"Optimal theta: {optimal_theta}")
    print(f"Minimum cost (negative variance): {result.fun}\n")
    
    print("Quantum VQC XOR with Statevector and Expectation Value after Unsupervised Optimisation\n")
    
    for x1, x2 in inputs:
        qc = get_circuit(x1, x2, optimal_theta)
        statevector = Statevector.from_instruction(qc)
        expectation_value = statevector.expectation_value(ZI_operator).real
    
        # Print results
        print(f"Input: ({x1}, {x2})")
        print("Statevector:", statevector.data)
        print(f"Expectation value on q1: {expectation_value}")
        print(f"Probability of |0> for q1: {(1 + expectation_value) / 2}")
        # Predict cluster based on threshold on expectation value
        print(f"Predicted cluster: {1 if expectation_value < 0 else 0}\n")
                
                
            
Code: Visualisation of 2-Qubit QNN for XOR
                
    # Visualisation of 2-Qubit QNN for XOR

    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Operator, Statevector
    import numpy as np
    from scipy.optimize import minimize
    import matplotlib.pyplot as plt
    
    # Data input for XOR (for reference)
    inputs = [(0, 0), (0, 1), (1, 0), (1, 1)]
    
    # Define Pauli-Z and identity matrices
    Z = np.array([[1, 0], [0, -1]])
    I = np.eye(2)
    
    # Calculate Z ⊗ I for expectation value
    ZI = np.kron(Z, I)
    ZI_operator = Operator(ZI)
    
    # Function to create circuit with theta and continuous input
    def get_circuit(x1, x2, theta):
        # Initialise quantum circuit with 2 qubits
        qc = QuantumCircuit(2)
        
        # Feature map: Angle encoding
        qc.rx(np.pi * x1, 0)  # Rx rotation based on x1
        qc.rx(np.pi * x2, 1)  # Rx rotation based on x2
    
        # Ansatz: CNOT gates and Ry rotation
        qc.cx(0, 1) # CNOT from qubit 0 to qubit 1
        qc.ry(theta, 1)  # Theta as a variable parameter
        return qc
    
    # Cost function for optimisation
    def cost(params):
        theta = params[0]
        expectation_values = []
        
        # Calculate expectation values for all inputs
        for x1, x2 in inputs:
            qc = get_circuit(x1, x2, theta)
            statevector = Statevector.from_instruction(qc)
            expectation_value = statevector.expectation_value(ZI_operator).real
            expectation_values.append(expectation_value)
        
        # Objective: maximise separation between input groups
        # For example, push (0,0) and (1,1) to positive expectation values,
        # and (0,1), (1,0) to negative expectation values
        # Cost = - (variance of expectation values) to encourage separation
        variance = np.var(expectation_values)
        return -variance  # Negative to maximise variance
    
    # Run optimisation
    initial_params = [0.0]  # Initial guess for theta
    result = minimize(cost, initial_params, method = 'COBYLA', tol = 1e-6)
    optimal_theta = result.x[0]
    
    print(f"Optimal theta: {optimal_theta}")
    print(f"Minimum cost (negative variance): {result.fun}\n")
    
    # Create grid for heatmap
    n_points = 50  # Grid resolution
    x1_range = np.linspace(0, 1, n_points)
    x2_range = np.linspace(0, 1, n_points)
    X1, X2 = np.meshgrid(x1_range, x2_range)
    Z = np.zeros((n_points, n_points))  # To store probability of |1>
    
    # Calculate probabilities for each point in the grid
    for i in range(n_points):
        for j in range(n_points):
            qc = get_circuit(X1[i, j], X2[i, j], optimal_theta)
            statevector = Statevector.from_instruction(qc)
            expectation_value = statevector.expectation_value(ZI_operator).real
            Z[i, j] = (1 - expectation_value) / 2  # Probability of |1>
    
    # Create heatmap
    plt.figure(figsize = (6, 4))
    plt.gcf().set_facecolor('#f5f5f5')
    plt.contourf(X1, X2, Z, cmap = 'viridis', levels = 50)
    plt.colorbar(label = 'Probability of |1> (XOR output)')
    plt.scatter([0, 0, 1, 1], [0, 1, 0, 1], c = 'red', marker = 'x', s = 100, label = 'XOR Inputs')
    plt.xlabel('x1')
    plt.ylabel('x2')
    plt.title('2-Qubit QNN for XOR')
    plt.legend()
    plt.grid(True)
    plt.show()
    
    # Print results for discrete inputs
    print("Quantum VQC XOR with Statevector and Expectation Value after Unsupervised Optimisation\n")
    for x1, x2 in inputs:
        qc = get_circuit(x1, x2, optimal_theta)
        statevector = Statevector.from_instruction(qc)
        expectation_value = statevector.expectation_value(ZI_operator).real
        print(f"Input: ({x1}, {x2})")
        print("Statevector:", statevector.data)
        print(f"Expectation value on q1: {expectation_value}")
        print(f"Probability of |1> for q1: {(1 - expectation_value) / 2}")
        print(f"Predicted cluster: {1 if expectation_value < 0 else 0}\n")
                
                
            

8.6.2 QNN 1-Qubit

QNN 1-qubit hanya membutuhkan satu buah qubit untuk implementasi pemodelan dan prediksi hasil fungsi XOR. Gerbang \(H\), gerbang \(R_z\), dan gerbang \(R_x\).

Referensi sirkuit: I. Grossu.

Gerbang \(H\) untuk superposisi, dan gerbang \(R_z(\theta_1 * x_1 + \alpha)\) serta \(R_x(\theta_2 * x_2 + \alpha)\) dua-duanya berperan sekaligus sebagai peta fitur kuantum juga ansatz. Ia disebut peta fitur kuantum karena pada gerbang rotasinya ada variabel \(x\), yakni \([x_1, x_2]\). Variabel \(x\) dipetakan ke ruang fitur kuantum. Ia juga dinamakan ansatz karena memiliki variabel parametrik \(\theta\) dan \(\alpha\) guna optimasi. Adapun nilai ekspektasi sebesar

\[ \langle Z \rangle = \bra{\psi_{q}} Z \ket{\psi_{q}} \tag{8.6.2} \]

Code: 1-Qubit QNN for XOR
                
    # Code: 1-Qubit QNN for XOR 
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Operator, Statevector
    import numpy as np
    
    # Data input for XOR
    inputs = [(0, 0), (0, 1), (1, 0), (1, 1)]
    
    # Define Pauli-Z
    Z = np.array([[1, 0], [0, -1]])
    
    print("Quantum VQC XOR with Statevector and Expectation Value\n")
    
    # Parameters
    theta1 = np.pi
    theta2 = np.pi
    alpha = - np.pi / 2
    
    for x1, x2 in inputs:
        # Initialise quantum circuit
        qc = QuantumCircuit(1)
    
        # Superposition
        qc.h(0)
    
        # Feature map and ansatz
        qc.rz(theta1 * x1 + alpha, 0)
        qc.rx(theta2 * x2 + alpha, 0)
    
        # Create statevector from circuit
        statevector = Statevector.from_instruction(qc)
    
        # Calculate expectation value 
        expectation_value = statevector.expectation_value(Z).real
    
        # Print results
        print(f"Input: ({x1}, {x2})")
        print("Statevector:", statevector.data)
        print(f"Expectation value: {expectation_value}")
        print(f"Probability of |0>: {(1 + expectation_value) / 2}")
        print(f"Predicted label: {1 if expectation_value < 0 else 0}\n")
                
                
            
Code: Optimised 1-Qubit QNN for XOR
                
    # Code: Optimised 1-Qubit QNN for XOR
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector
    import numpy as np
    from scipy.optimize import minimize
    
    # XOR input data (binary combinations)
    inputs = [(0, 0), (0, 1), (1, 0), (1, 1)]
    
    # Pauli-Z observable
    Z = np.array([[1, 0], [0, -1]])
    
    # Function to construct the quantum circuit
    def get_circuit(x1, x2, theta1, theta2, alpha):
        qc = QuantumCircuit(1)
        qc.h(0)  # Create superposition
        qc.rz(theta1 * x1 + alpha, 0) # Feature map and ansatz
        qc.rx(theta2 * x2 + alpha, 0) # Feature map and ansatz
        return qc
    
    # Cost function
    def cost(params):
        theta1, theta2, alpha = params
    
        # Group inputs: same vs different values
        same_inputs = [(0, 0), (1, 1)]      # Inputs where x₁ == x₂
        diff_inputs = [(0, 1), (1, 0)]      # Inputs where x₁ ≠ x₂
    
        exp_same = []
        exp_diff = []
    
        # Calculate expectation values for inputs with same values
        for x1, x2 in same_inputs:
            qc = get_circuit(x1, x2, theta1, theta2, alpha)
            statevector = Statevector.from_instruction(qc)
            ev = statevector.expectation_value(Z).real
            exp_same.append(ev)
    
        # Calculate expectation values for inputs with differing values
        for x1, x2 in diff_inputs:
            qc = get_circuit(x1, x2, theta1, theta2, alpha)
            statevector = Statevector.from_instruction(qc)
            ev = statevector.expectation_value(Z).real
            exp_diff.append(ev)
    
        # Aim: maximise separation between the two groups
        mean_same = np.mean(exp_same)
        mean_diff = np.mean(exp_diff)
    
        separation = (mean_same - mean_diff) ** 2  # Larger is better
        cost_value = -separation  # Minimise negative separation
    
        return cost_value
    
    # Perform parameter optimisation
    initial_params = np.random.uniform(-np.pi, np.pi, 3)
    result = minimise = minimize(cost, initial_params, method = 'COBYLA', tol = 1e-6)
    
    # Extract optimal parameters
    theta1_opt, theta2_opt, alpha_opt = result.x
    
    # Report optimisation result
    print(f"Optimisation successful: {result.success}")
    print(f"Number of function evaluations: {result.nfev}")
    print(f"Optimal θ₁: {theta1_opt}")
    print(f"Optimal θ₂: {theta2_opt}")
    print(f"Optimal α : {alpha_opt}")
    print(f"Final cost value (−separation²): {result.fun:.6f}\n")
    
    # Evaluate final circuit behaviour for all XOR inputs
    print("Final circuit behaviour for all XOR input combinations:\n")
    
    for x1, x2 in inputs:
        qc = get_circuit(x1, x2, theta1_opt, theta2_opt, alpha_opt)
        statevector = Statevector.from_instruction(qc)
        ev = statevector.expectation_value(Z).real
        prob_0 = (1 + ev) / 2  # Probability of measuring |0⟩
        predicted_label = 1 if ev < 0 else 0
    
        print(f"Input: ({x1}, {x2})")
        print(f"  Expectation value ⟨Z⟩: {ev:.4f}")
        print(f"  Probability of measuring |0⟩: {prob_0:.4f}")
        print(f"  Predicted label: {predicted_label}\n")
                
                
            
Code: Visualisation of 1-Qubit QNN for XOR
                
    # Code: Visualisation of 1-Qubit QNN for XOR
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Operator, Statevector
    import numpy as np
    from scipy.optimize import minimize
    import matplotlib.pyplot as plt
    
    # XOR input data
    inputs = [(0, 0), (0, 1), (1, 0), (1, 1)]
    
    # Pauli-Z observable operator
    Z_op = np.array([[1, 0], [0, -1]])
    
    # Function to build a quantum circuit with parameters theta1, theta2, alpha
    def get_circuit(x1, x2, theta1, theta2, alpha):
        qc = QuantumCircuit(1)
        qc.h(0) # Superposition
        qc.rz(theta1 * x1 + alpha, 0) # Feature map and ansatz
        qc.rx(theta2 * x2 + alpha, 0) # Feature map and ansatz
        return qc
    
    # Cost function
    def cost(params):
        theta1, theta2, alpha = params
        same_group = [(0, 0), (1, 1)]     # Inputs with x1 == x2
        diff_group = [(0, 1), (1, 0)]     # Inputs with x1 ≠ x2
    
        exp_same = []
        exp_diff = []
    
        for x1, x2 in same_group:
            qc = get_circuit(x1, x2, theta1, theta2, alpha)
            statevector = Statevector.from_instruction(qc)
            ev = statevector.expectation_value(Z_op).real
            exp_same.append(ev)
    
        for x1, x2 in diff_group:
            qc = get_circuit(x1, x2, theta1, theta2, alpha)
            statevector = Statevector.from_instruction(qc)
            ev = statevector.expectation_value(Z_op).real
            exp_diff.append(ev)
    
        # Maximise the squared difference between the group means
        separation = (np.mean(exp_same) - np.mean(exp_diff)) ** 2
        return -separation  # Negated for minimisation
    
    # Optimisation of parameters
    initial_params = np.random.uniform(-np.pi, np.pi, 3)
    print(f"Initial parameters: {initial_params}")
    result = minimize(cost, initial_params, method = 'COBYLA', tol = 1e-6)
    
    # Optimisation results
    print(f"\nOptimisation successful: {result.success}")
    print(f"Number of function evaluations: {result.nfev}")
    optimal_theta1, optimal_theta2, optimal_alpha = result.x
    print(f"Optimal θ₁: {optimal_theta1}")
    print(f"Optimal θ₂: {optimal_theta2}")
    print(f"Optimal α : {optimal_alpha}")
    print(f"Maximum group separation²: {-result.fun:.6f}\n")
    
    # Create input grid for visualisation
    n_points = 50
    x1_range = np.linspace(0, 1, n_points)
    x2_range = np.linspace(0, 1, n_points)
    X1, X2 = np.meshgrid(x1_range, x2_range)
    Z_grid = np.zeros((n_points, n_points))  # Holds output probabilities for |1⟩
    
    # Evaluate the circuit on each point in the grid
    for i in range(n_points):
        for j in range(n_points):
            qc = get_circuit(X1[i, j], X2[i, j], optimal_theta1, optimal_theta2, optimal_alpha)
            statevector = Statevector.from_instruction(qc)
            exp_val = statevector.expectation_value(Z_op).real
            Z_grid[i, j] = (1 - exp_val) / 2  # Probability of measuring |1⟩
    
    # Plot visualisation with light background
    plt.figure(figsize = (6, 4))
    plt.gcf().set_facecolor('#f5f5f5')  # Figure background
    plt.gca().set_facecolor('#f5f5f5')  # Plot area background
    contour = plt.contourf(X1, X2, Z_grid, cmap = 'YlGnBu', levels = 50)
    plt.colorbar(contour, label='Probability of |1⟩ (XOR output)')
    plt.scatter([0, 0, 1, 1], [0, 1, 0, 1], c = 'red', marker = 'x', s = 100, label = 'XOR Inputs')
    plt.xlabel('x1')
    plt.ylabel('x2')
    plt.title('1-Qubit QNN for XOR')
    plt.legend()
    plt.grid(True)
    plt.show()
    
    # Final predictions for all XOR inputs using optimised parameters
    print("Final predictions using optimised parameters:\n")
    for x1, x2 in inputs:
        qc = get_circuit(x1, x2, optimal_theta1, optimal_theta2, optimal_alpha)
        statevector = Statevector.from_instruction(qc)
        exp_val = statevector.expectation_value(Z_op).real
        prob_1 = (1 - exp_val) / 2
        predicted_label = 1 if exp_val < 0 else 0
        print(f"Input: ({x1}, {x2})")
        print(f"  Statevector: {statevector.data}")
        print(f"  Expectation value ⟨Z⟩: {exp_val:.4f}")
        print(f"  Probability of |1⟩: {prob_1:.4f}")
        print(f"  Predicted label: {predicted_label}\n")
                
                
            

8.7 Quantum Convolutional Neural Network

Pada subbab Quantum Convolutional Neural Network (QCNN) ini akan disajikan implementasi QCNN pada dataset untuk klasifikasi pola gambar 3×3 pixel. Adapun dataset yang dimaksud yakni dataset "3x3 Pixel Pattern Classification Dataset: 12 Adjacent Pair Classes" — https://doi.org/10.5281/zenodo.17576072. Sebuah dataset untuk klasifikasi pola gambar kecil (3×3 pixel), di mana setiap gambar merepresentasikan pola hubungan antara pasangan piksel yang berdekatan (adjacent pair). Total ada 12 kategori kelas yang berbeda, berdasarkan pola adjacency (kedekatan) di dalam blok 3×3 tersebut.

Untuk contoh implementasi QCNN ini dipilih dua kelas, yakni kelas 1 (gambar atas paling kiri - Horizontal r1c1 - r1c2) dan kelas 2 (gambar atas nomor 2 dari kiri - Horizontal r1c2 - r1c3). Masing-masing kelas terdiri atas 80 data latih dan 20 data tes.

Gambar di atas menunjukkan sirkuit kuantum implementasi QCNN untuk klasifikasi pola gambar 3x3 pixel. Sirkuit terdiri atas 9 qubit, enkode sudut \(R_y\), lapisan konvolusi pertama (melibatkan gerbang \(CX\) dan \(R_y\)), serta lapisan konvolusi kedua (gerbang \(CX\) dan \(R_z\)). Untuk implementasi ini, nilai parameter optimal \(\theta\) telah ditentukan sebelumnya, yakni \(\pi / 4\) untuk lapisan pertama dan \(\pi / 3\) untuk lapisan kedua. Detail langkah demi langkah implementasi QCNN disajikan dalam kode berikut.

Code: QCNN for 3x3 Pixel Pattern Classification
                
    # Code: QCNN for 3x3 Pixel Pattern Classification
    
    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Statevector, Pauli, SparsePauliOp
    from PIL import Image
    import numpy as np
    import glob, os
    
    # Load image and convert to feature vector
    def load_image_as_features(path, size=(3, 3)):
        """
        Open a greyscale image, resize to `size`, normalise to [0, 1]
        and scale to [0, π] (suitable for RY rotations).
        """
        img = Image.open(path).convert('L')          # convert to greyscale
        img = img.resize(size, Image.NEAREST)        # keep pixel-perfect values
        arr = np.asarray(img, dtype = np.float32) / 255.0
        return (arr.flatten() * np.pi)               # feature vector in [0, π]
    
    # Construct a minimal QCNN circuit
    def build_qcnn_circuit(features):
        """
        Create a QuantumCircuit with:
          • RY rotations according to the feature vector
          • a chain of CX + RY(π/4) gates
          • a second layer of CX + RZ(π/3) gates (stride 3)
        """
        n = len(features)
        qc = QuantumCircuit(n, name = 'QCNN')
    
        # Encode features
        for i, f in enumerate(features):
            qc.ry(f, i)
    
        # First convolution layer
        for i in range(n - 1):
            qc.cx(i, i + 1)
            qc.ry(np.pi / 4, i + 1)
    
        qc.barrier()
    
        # Second convolution layer (stride 3)
        for i in range(0, n, 3):
            if i + 2 < n:
                qc.cx(i, i + 2)
                qc.rz(np.pi / 3, i)
    
        return qc
    
    # Expectation value of Pauli-Z on a single qubit
    def expectation_Z(state, qubit, num_qubits):
        """
        Compute ⟨ψ| Z_q |ψ⟩ where Z_q acts on `qubit` and identity elsewhere.
        """
        op = SparsePauliOp(Pauli('I' * qubit + 'Z' + 'I' * (num_qubits - qubit - 1)))
        return np.real(state.expectation_value(op))
    
    # Full QCNN forward pass for one image
    def qcnn_output_from_image(img_path):
        """
        Return the average ⟨Z⟩ over selected observable qubits.
        For 9-pixel images we measure qubits 0, 3 and 6.
        """
        features = load_image_as_features(img_path)
        qc = build_qcnn_circuit(features)
        state = Statevector.from_instruction(qc)
    
        obs_qubits = [0, 3, 6] if len(features) >= 9 else [0]
        return np.mean([expectation_Z(state, q, len(features)) for q in obs_qubits])
    
    # Dataset paths
    train_class1_folder = ".../train/h_r1c1_r1c2"   # class 1
    train_class2_folder = ".../train/h_r1c2_r1c3"   # class 2
    test_folder         = ".../test/..."            # unlabelled
    
    train_class1_images = glob.glob(os.path.join(train_class1_folder, "*.png"))
    train_class2_images = glob.glob(os.path.join(train_class2_folder, "*.png"))
    test_images         = glob.glob(os.path.join(test_folder,         "*.png"))
    
    print(f"Train class 1: {len(train_class1_images)} images")
    print(f"Train class 2: {len(train_class2_images)} images")
    print(f"Test (unlabelled): {len(test_images)} images")
    
    # Compute QCNN outputs for training data
    print("\nComputing QCNN outputs for training images...")
    train_outputs_class1 = [qcnn_output_from_image(p) for p in train_class1_images]
    train_outputs_class2 = [qcnn_output_from_image(p) for p in train_class2_images]
    
    class_means = {
        1: np.mean(train_outputs_class1),
        2: np.mean(train_outputs_class2)
    }
    print("Class centroid (mean) outputs:", class_means)
    
    # QCNN outputs for test images
    print("\nProcessing test images...")
    test_outputs = [qcnn_output_from_image(p) for p in test_images]
    
    # Classification with OOD threshold
    threshold = 0.3          # maximum distance to a centroid to be considered known
    predictions = []
    
    for out in test_outputs:
        dist1 = abs(out - class_means[1])
        dist2 = abs(out - class_means[2])
        min_dist = min(dist1, dist2)
    
        if min_dist > threshold:
            pred = 0                     # out-of-distribution / unknown
        else:
            pred = 1 if dist1 < dist2 else 2
        predictions.append(pred)
    
    # Display results
    results = [(os.path.basename(p), pred) for p, pred in zip(test_images, predictions)]
    
    print("\n=== QCNN Test Predictions ===")
    for name, pred_lbl in results:
        if pred_lbl == 0:
            label_str = "Unknown ?"
        else:
            label_str = f"Class {pred_lbl}"
        print(f"{name:25s} | Predicted: {label_str}")
                
                
            

Referensi

D. McMahon, Quantum Computing Explained, 1st ed., New Jersey: John Wiley & Sons, Inc., 2008.
E. Rieffel dan W. Polak, Quantum Computing A Gentle Introduction, 1st ed., Cambridge: The MIT Press, 2011.
M. Nielsen dan I. Chuang, Quantum Computation and Quantum Information, Cambridge: Cambridge University Press, 2010.
N. Zettili, Quantum Mechanics Concepts and Applications, 2nd ed., West Sussex: John Wiley & Sons, Ltd, 2009.
S. Kurgalin dan S. Borzunov, Concise Guide to Quantum Computing Algorithms, Exercises, and Implementations, Cham: Springer, 2021.
I. Grossu, Single Qubit Neural Quantum Circuit for Solving Exclusive-OR, MethodsX 8, 101573, 2021.
A. Chatterjee, K. Phalak, dan S. Ghosh, Quantum Error Correction For Dummies, arXiv:2304.08678.
E. Strubell, An Introduction to Quantum Algorithms, Github. URL: https://strubell.github.io.
M. Christandl, M. Ozols, M. Walter, dan F. Witteveen, Quantum Information Theory. URL: https://qi.rub.de.
O. Simeone, An Introduction to Quantum Machine Learning for Engineers, arXiv:2205.09510.
IBM Quantum Learning. URL: https://learning.quantum.ibm.com.
MathWorks, Solve XOR Problem Using Quantum Neural Network (QNN). URL: https://www.mathworks.com.
M. Nur Hidayat dan M. Faruq Nuruddinsyah, 3x3 Pixel Pattern Classification Dataset: 12 Adjacent Pair Classes [Dataset]. Pi Quantum. URL: https://doi.org/10.5281/zenodo.17576072.

© 2021-2026 Mirza & Faruq