Skip to content

Instantly share code, notes, and snippets.

@denkspuren
Last active April 26, 2022 13:03
Show Gist options
  • Save denkspuren/ec0671457c3d884482edada5db37e60e to your computer and use it in GitHub Desktop.
Save denkspuren/ec0671457c3d884482edada5db37e60e to your computer and use it in GitHub Desktop.
Ein Simulator für Quantenschaltungen
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Ein Simulator für Quantenschaltungen\n",
"\n",
"Dominikus Herzberg, V1.0"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Einleitung\n",
"\n",
"Es ist überraschenderweise gar nicht so schwer, einen Simulator für Schaltungen aus Quantengattern zu bauen. Der entscheidende operative Teil des Simulators besteht aus 5 Zeilen Python-Code, es ist die Methode `op` der Klasse `QState`. Diese Kompaktheit ist zum einen dem `numpy`-Paket zu verdanken, das leistungsfähige Operationen zum numerischen Rechnen mit Vektoren und Matrizen zur Verfügung stellt. Mit diesen Ausdrucksmitteln ist man nah an der Mathematik zur Quantenmechanik dran. Zum anderen ist die Kompaktheit ein Zeichen dafür, wie einfach die zugrunde liegenden Prinzipien des Rechnens mit Quanten sind.\n",
"\n",
"### Ein Quantensimulator ist einfach, das Quantenrechnen nicht\n",
"\n",
"Das soll jedoch nicht über zwei Dinge hinwegtäuschen:\n",
"\n",
"1. Das heißt nicht, dass es einfach wäre, die Berechnungen intellektuell nachzuvollziehen. Ganz im Gegenteil, das Quantencomputing bleibt schwer verständlich in den Auswirkungen seiner Rechenprozesse. Die Berechnungsschritte sorgen für eine Verteilung und Verrechnung von Quantenzuständen im Zustandsraum der beteiligten Quantenbits (Qubits). Erst im Moment einer Beobachtung kollabieren die mit den Quantenzuständen assoziierten Qubits zu binären Werten, womit sich auch die Quantenzustände verändern. Es hilft nicht, Beobachtungen zu Zwischenständen vorzunehmen, was in einem Simulator ja geht, weil die Beobachtung einen informationsreichen Zustandsraum auf eine informationsarme binäre Sicht reduziert. Damit ist absolut nichts gewonnen, um die Fortsetzung mit der nächsten Rechenoperation zu verstehen.\n",
"\n",
"2. Der Quantensimulator verdichtet physikalische Realitäten auf eine informatische Sicht, wo es nichts anderes gibt als Quantenbits, den dazu aufgespannten Zustandsraum und Quantenoperatoren. Das Rechnen mit Qubits stellt sich als vollkommen deterministischer, nach strengen Regeln ablaufender Rechenprozess im Zustandsraum dar, der -- und das ist eine Besonderheit -- sogar reversibel ist. Was man dabei leicht aus dem Auge verliert, ist, dass es dazu eine physikalische Interpretation und physikalische Effekte gibt. Zum Beispiel kann man verschränkte Qubits räumlich weit voneinander trennen, sie bleiben dennoch miteinander \"verbunden\", um es vorsichtig auszudrücken. Sowas kennt man bei klassischen Schaltgliedern nicht. Die Bits, die man miteinander verrechnet, müssen sozusagen sichtbar miteinander verdrahtet sein. Bei verschränkten Qubits bleibt das physikalische Mysterium, wie diese spukhafte Fernwirkung wohl zustande kommen mag. Im Quantensimulator stellt sich die Frage nach dem Ort der Qubits nicht, man verrechnet einfach die Quantenzustände. Ein anderes physikalisches Phänomen bleibt zumindest in einem idealen Quantensimulator unberücksichtigt: die Störeinflüsse, denen Quanten ausgesetzt sind. Ohne Störungen ergibt sich mit jedem Rechenschritt ein klares Bild der Wahrscheinlichkeitsverteilung im Zustandsraum des Quantensystems. In der Realität ist das nicht so. Möchte man der Realität diesbezüglich näher kommen, muss man diese Störungen als Rauschvorgang in dem Modell mit berücksichtigen.\n",
"\n",
"### Qubits sind nur Eingangstore zum Zustandsraum\n",
"\n",
"Ich habe schon mehrfach Quantenbits unterschieden von dem von ihnen aufgespannten Zustandsraum. Das sauber voneinander zu trennen ist der entscheidende Schritt, um einen Quantencomputer zu verstehen. Bei klassischen Bits ist diese Unterscheidung hinfällig. Der Zustand eines Bit-Registers (ein Register ist schlicht eine Folge von Bits) ist an den Bits abzulesen. Nicht so bei den Qubits. Zwar sind die Qubits der Gegenstand von Quantenoperatoren, doch darüber verändert man keine Qubits, sondern den Zustandsraum der Qubits. Oder anders ausgedrückt: Über die Qubits hat man lediglich eine Art Eingangstor zum Zustandsraum. Man wendet die Operatoren bzw. die Quantengatter, wenn man so möchte, nacheinander am Eingangstor an -- einen Ausgang gibt es nicht. Hat man etwa drei Qubits, dann sind damit $2^3 = 8$ Zustände verbunden, die sich gemäß des Operators bzw. Quantengatters ändern. An den Qubits, am Eingang, gibt es nichts zu sehen. Erst wenn man einen Beobachtungsoperator anwendet, geben die Qubits am Eingang einen Zufallswert in Abhängigkeit vom Zustandsraum aus, was gleichzeitig den Zustandsraum verändert.\n",
"\n",
"### Verständniszugang über klassische Bitschaltungen\n",
"\n",
"Um in diese Denke hineinzufinden, betrachten wir zunächst klassische Bitschaltungen als Zustandsraum und definieren dafür Schaltungsoperatoren wie `NOT` und `AND`. Mit binären Schaltungen und Schaltungsgattern ist jede Informatikerin und jeder Informatiker vertraut. Hat man das Denken im Zustandsraum erfasst und den dazugehörigen Python-Code verstanden, ist es faszinierend zu sehen, wie leicht der Übergang zu einem Simulator für Quantenschaltungen ist. Lediglich die Datentypen müssen auf komplexe Zahlen angepasst werden. Voila!\n",
"\n",
"### Die Inspiration zu diesem Text\n",
"\n",
"Die Inspiration zu diesem Notebook hat ein Blogpost von Aws Albarghouthi geliefert. Albarghouthi ist Informatik-Professor an der Universität von Wisconsin-Madison (USA). Ich folge dem Aufbau seines Blogposts, von klassischen Schaltgliedern auszugehen. Sein Python-Code war mir eine große Hilfe, die Vorlage schimmert noch durch. Allerdings habe ich das Design für den Quantensimulator verändert (u.a. finde ich das Method-Chaining eleganter), eine Ausgabe hinzugefügt und die Möglichkeit ergänzt, die Wahrscheinlichkeitsverteilung zu plotten. In den entscheidenden fünf Zeilen Code ist Albarghouthi ein Fehler unterlaufen, den ich behoben habe.\n",
"\n",
"> [Albarghouthi, Aws](https://pages.cs.wisc.edu/~aws/) (2022): A Quantum Circuit Simulator in 27 Lines of Python. Blogpost, 2021-08-05. [online](https://barghouthi.github.io/2021/08/05/quantum/) (Abruf: 2022-04-18)\n",
"\n",
"Da ich Sie gerne ermuntern möchte, diesen Simulator auszubauen und weiterzuentwickeln, habe ich dieses Notebook unter die _Creative Commons_-Lizenz [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.de) gestellt. Kurz gesagt: Sie dürfen die Inhalte bearbeiten und teilen, sofern Sie mich als Urheber nennen, die Verwertung nicht kommerziell ist und Sie das Werk unter die gleiche Lizenzbedingung stellen.\n",
"\n",
"Ich freue mich, wenn Sie mir Korrekturen, Verbesserungen oder Richtigstellungen per Email zusenden. Ebenso sind Vorschläge und Anregungen herzlich willkommen.\n",
"\n",
"-- [Dominikus Herzberg](https://www.thm.de/mni/dominikus-herzberg), Technische Hochschule Mittelhessen\n",
"\n",
"![image](https://licensebuttons.net/l/by-nc-sa/3.0/88x31.png)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Ein Simulator für klassische Logikgatter\n",
"\n",
"Im Quantencomputing unterscheidet man die Qubits von dem durch sie aufgespannten Zustandsraum. Weil -- und das ist das Interessante -- Zustände möglich sind, die sich nicht aus einer Kombination der Qubits ableiten lassen. Da soll es also hingehen. Um sich in diese Unterscheidung einzudenken, beginnen wir damit, uns das an klassischen Bits klarzumachen."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Ein Register aus $n$ Bits hat $2^n$ Zustände\n",
"\n",
"Ein klassisches Bit, so wie wir es aus der Informatik kennen, hat entweder den Wert 0 oder den Wert 1. Ein Register aus $n$ Bits hat $2^n$ Zustände; dieser Zustandsraum setzt sich zusammen aus allen möglichen Wertkombinationen für diese Bits, d.h. den Binärwerten von 0 bis $2^n - 1$. Die aufgeführten Register aus $n$ Bits haben die folgenden Zustandsräume:\n",
"\n",
"* $n = 1$: 0, 1\n",
"* $n = 2$: 00, 01, 10, 11\n",
"* $n = 3$: 000, 001, 010, 011, 100, 101, 110, 111\n",
"* usw.\n",
"\n",
"In etwas ungewöhnlicher Weise kann man diesen Zustandsraum durch einen Vektor darstellen, wobei jede Position im Vektor einem binären Wert entspricht. Durch eine 1 wird markiert, welchen Binärwert der Zustandsvektor repräsentiert. Im Beispiel für $n = 2$ zeigt der Vektor den binären Wert `10`, dh. 2 an. Das ist der dritte Wert von oben:\n",
"\n",
"$$\n",
"\\begin{matrix}00\\quad\\rightarrow\\quad\\\\\n",
" 01\\quad\\rightarrow\\quad\\\\\n",
" 10\\quad\\rightarrow\\quad\\\\\n",
" 11\\quad\\rightarrow\\quad\\end{matrix}\n",
"\\begin{pmatrix}0 \\\\ 0 \\\\ 1 \\\\ 0\\end{pmatrix}\n",
"$$\n",
"\n",
"In Python kann man einen solchen Vektor mit Hilfe des Pakets zum numerischen Rechnen namens `numpy` erstellen. Es wird ein Vektor mit $2^2$ (in Python `2**2`) Null-Werten aufgesetzt; die Werte sind Ganzzahl-Typen (_integer_) und werden intern mit 32 Bit kodiert.\n",
"\n",
"Doch zunächst muss das `numpy`-Paket importiert werden. Es ist üblich, es unter der Abkürzung `np` einzubinden."
]
},
{
"cell_type": "code",
"execution_count": 81,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": 82,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([0, 0, 1, 0], dtype=int32)"
]
},
"execution_count": 82,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bitstates = np.zeros(2**2, dtype = np.int32)\n",
"bitstates[2] = 1\n",
"bitstates"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sie ahnen vielleicht, wie man mit dieser Notation vollkommen irrwitzige Zustände für ein Bitregister aufsetzen kann. Wenn beispielsweise alle Werte im Zustandsvektor 0 sind, oder wenn sich mehrere Zustandswerte überlagern. Bei Qubits spielt das tatsächlich eine Rolle, bei klassischen Bits geht das natürlich nicht.\n",
"\n",
"Gießen wir die Erzeugung eines solchen Zustandsraums in Code. Die Klasse `CState` (das `C` steht für _classical_) bekommt zur Initialisierung eines Zustandsobjekts die Anzahl der Bits als `n` übergeben."
]
},
{
"cell_type": "code",
"execution_count": 83,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([1, 0, 0, 0, 0, 0, 0, 0], dtype=int32)"
]
},
"execution_count": 83,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class CState:\n",
" def __init__(self, n):\n",
" self.n = n\n",
" self.state = np.zeros(2**self.n, dtype=np.int32)\n",
" self.state[0] = 1 # Initialisierung auf Wert 0\n",
"\n",
"CState(3).state"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Das `NOT`-Gatter für ein 1-Bit-Register\n",
"\n",
"Ein `NOT`-Schaltglied arbeitet auf einem Bit. Es genügt, sich ein 1-Bit-Register zu veranschaulichen mit einem Vektor für die zwei Zustände, die den Werten 0 und 1 zugeordnet sind, hier $a$ und $b$. Entweder ist $a = 1$ und $b = 0$, oder es ist $a = 0$ und $b = 1$. Andere Kombinationen von $a$ und $b$ kann es nicht geben. \n",
"\n",
"$$\n",
"\\begin{matrix}0\\quad\\rightarrow\\quad\\\\\n",
" 1\\quad\\rightarrow\\quad\\\\\n",
"\\end{matrix}\n",
"\\begin{pmatrix}a \\\\ b\\end{pmatrix}\n",
"$$\n",
"\n",
"Die `NOT`-Operation besteht darin, den Zustandsvektor $\\begin{pmatrix}1 \\\\ 0\\end{pmatrix}$ in $\\begin{pmatrix}0 \\\\ 1\\end{pmatrix}$ bzw. den Zustandsvektor $\\begin{pmatrix}0 \\\\ 1\\end{pmatrix}$ in $\\begin{pmatrix}1 \\\\ 0\\end{pmatrix}$ zu überführen. Oder allgemeiner: Ein `NOT` wandelt den Zustand $\\begin{pmatrix}a \\\\ b\\end{pmatrix}$ in den Zustand $\\begin{pmatrix}b \\\\ a\\end{pmatrix}$; vergessen Sie dabei nicht: die Zustände $\\begin{pmatrix}0 \\\\ 0\\end{pmatrix}$ und $\\begin{pmatrix}1 \\\\ 1\\end{pmatrix}$ sind ungültige Zustände.\n",
"\n",
"Das `NOT`-Schaltglied lässt sich als Matrix darstellen und die `NOT`-Operation als die Multiplikation der Matrix mit dem Zustandsvektor. Es ergibt sich ein Ergebnisvektor, bei dem $a$ und $b$ die Plätze getauscht haben, wie das Rechenbeispiel belegt.\n",
"\n",
"$$\n",
"\\begin{pmatrix}0 & 1 \\\\ 1 & 0\\end{pmatrix}\n",
"\\cdot\n",
"\\begin{pmatrix}a \\\\ b\\end{pmatrix}\n",
"=\n",
"\\begin{pmatrix}0\\cdot a + 1\\cdot b \\\\ 1\\cdot a + 0\\cdot b\\end{pmatrix}\n",
"=\n",
"\\begin{pmatrix}b \\\\ a\\end{pmatrix}\n",
"$$\n",
"\n",
"In Python stellt sich das wie folgt dar; da links von dem `@`-Zeichen eine Matrix steht, steht `@` für die Matrizenmultiplikation."
]
},
{
"cell_type": "code",
"execution_count": 84,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([0, 1])"
]
},
"execution_count": 84,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"NOT = np.array([[0, 1], [1, 0]])\n",
"NOT @ CState(1).state"
]
},
{
"cell_type": "code",
"execution_count": 85,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([1, 0])"
]
},
"execution_count": 85,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"NOT @ NOT @ CState(1).state"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `NOT` im 2-Bit-Register\n",
"\n",
"Das war `NOT` angewendet auf ein 1-Bit-Register. Wenn sich `NOT` auf das erste und/oder zweite Bit in einem 2-Bit-Register beziehen soll, dann braucht es etwas Matrizen-Magie, um eine entsprechende 4x4-Matrix aus der 2x2-Matrix $\\begin{pmatrix}0 & 1 \\\\ 1 & 0\\end{pmatrix}$ für das `NOT` vorzubereiten. Das geht mit Hilfe der Identitätsmatrix und des Kronecker-Produkts.\n",
"\n",
"Warum es bei einem 2-Bit-Register einer 4x4-Matrix bedarf? Weil ein 2-Bit-Register einen Zustandsvektor der Größe 4 hat, der die vier möglichen Werte abbildet. Insofern muss die Matrix 4x4 Werte haben, damit sich bei der Multiplikation wieder ein Zustandsvektor der Größe 4 ergibt. Lineare Algebra, Sie erinnern sich? ;-)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Die Identitätsmatrix verhält sich neutral\n",
"\n",
"Machen wir uns zunächst klar, dass die Identitätsmatrix den Zustandsvektor nicht verändert; daher auch ihr Name."
]
},
{
"cell_type": "code",
"execution_count": 86,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 0, 0, 0],\n",
" [0, 1, 0, 0],\n",
" [0, 0, 1, 0],\n",
" [0, 0, 0, 1]], dtype=int32)"
]
},
"execution_count": 86,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"op0 = np.eye(4, dtype=np.int32) # Eine Identitätsmatrix der Größe 4\n",
"op0"
]
},
{
"cell_type": "code",
"execution_count": 87,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1 0 0 0]\n",
"[0 1 0 0]\n",
"[0 0 1 0]\n",
"[0 0 0 1]\n"
]
}
],
"source": [
"# Die Identitätsmatrix verhält sich neutral; nichts passiert\n",
"print(op0 @ np.array([1, 0, 0, 0])) # 00 [1, 0, 0, 0] -> 00 [1, 0, 0, 0]\n",
"print(op0 @ np.array([0, 1, 0, 0])) # 01 [0, 1, 0, 0] -> 01 [0, 1, 0, 0]\n",
"print(op0 @ np.array([0, 0, 1, 0])) # 10 [0, 0, 1, 0] -> 10 [0, 0, 1, 0]\n",
"print(op0 @ np.array([0, 0, 0, 1])) # 11 [0, 0, 0, 1] -> 11 [0, 0, 0, 1]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Erstes Bit auf `NOT`\n",
"\n",
"Wenn sich das `NOT` in einem 2-Bit-Register nur auf das erste Bit auswirken soll, muss irgendwie die `NOT`-Matrix (2x2) für das erste Bit mit einer Identitätsmatrix (2x2) für das zweite Bit zu einer 4x4-Matrix verrechnet werden. Genau das leistet das sogenannte Konecker-Produkt zweier Matrizen. Das [Beispiel auf Wikipedia](https://de.wikipedia.org/wiki/Kronecker-Produkt#Beispiel) (Abruf: 2022-04-19) macht einem die Funktionsweise intuitiv klar. Angewendet auf unser Beispiel sieht das wie folgt aus:\n",
"\n",
"$$\n",
"NOT \\otimes I_2 = \\begin{pmatrix} 0 & 1 \\\\ 1 & 0 \\end{pmatrix}\\otimes \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}\n",
"=\\begin{pmatrix} \n",
"0 \\cdot \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix} & 1 \\cdot \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix} \\\\\\\\\n",
"1 \\cdot \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix} & 0 \\cdot \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}\n",
"\\end{pmatrix}\n",
"=\\begin{pmatrix} 0 & 0 & \\!\\!\\! & 1 & 0 \\\\ 0 & 0 & \\!\\!\\! & 0 & 1 \\\\[0.6em] 1 & 0 & \\!\\!\\! & 0 & 0 \\\\ 0 & 1 & \\!\\!\\! & 0 & 0\\end{pmatrix}\n",
"$$"
]
},
{
"cell_type": "code",
"execution_count": 88,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0, 0, 1, 0],\n",
" [0, 0, 0, 1],\n",
" [1, 0, 0, 0],\n",
" [0, 1, 0, 0]])"
]
},
"execution_count": 88,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"op1 = np.kron(NOT, np.eye(2, dtype=np.int32)) # 2 Bits, erstes NOT\n",
"op1"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Überzeugen Sie sich selbst, dass die vier möglichen Fälle allesamt korrekt abgehandelt werden."
]
},
{
"cell_type": "code",
"execution_count": 89,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0 0 1 0]\n",
"[0 0 0 1]\n",
"[1 0 0 0]\n",
"[0 1 0 0]\n"
]
}
],
"source": [
"# Erstes Bit wird durch NOT invertiert\n",
"print(op1 @ np.array([1, 0, 0, 0])) # 00 [1, 0, 0, 0] -> 10 [0, 0, 1, 0]\n",
"print(op1 @ np.array([0, 1, 0, 0])) # 01 [0, 1, 0, 0] -> 11 [0, 0, 0, 1]\n",
"print(op1 @ np.array([0, 0, 1, 0])) # 10 [0, 0, 1, 0] -> 00 [1, 0, 0, 0]\n",
"print(op1 @ np.array([0, 0, 0, 1])) # 11 [0, 0, 0, 1] -> 01 [0, 1, 0, 0]\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Zweites Bit auf `NOT`\n",
"\n",
"**Aufgabe**: Rechnen Sie per Hand das Kronecker-Produkt $I_2 \\otimes NOT$ aus und vollziehen Sie die nachstehenden Codezeilen nach."
]
},
{
"cell_type": "code",
"execution_count": 90,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0, 1, 0, 0],\n",
" [1, 0, 0, 0],\n",
" [0, 0, 0, 1],\n",
" [0, 0, 1, 0]])"
]
},
"execution_count": 90,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"op2 = np.kron(np.eye(2, dtype=np.int32), NOT) # 2 Bits, zweites NOT\n",
"op2"
]
},
{
"cell_type": "code",
"execution_count": 91,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0 1 0 0]\n",
"[1 0 0 0]\n",
"[0 0 0 1]\n",
"[0 0 1 0]\n"
]
}
],
"source": [
"# Zweites Bit wird durch NOT invertiert\n",
"print(op2 @ np.array([1, 0, 0, 0])) # 00 [1, 0, 0, 0] -> 01 [0, 1, 0, 0]\n",
"print(op2 @ np.array([0, 1, 0, 0])) # 01 [0, 1, 0, 0] -> 00 [1, 0, 0, 0]\n",
"print(op2 @ np.array([0, 0, 1, 0])) # 10 [0, 0, 1, 0] -> 11 [0, 0, 0, 1]\n",
"print(op2 @ np.array([0, 0, 0, 1])) # 11 [0, 0, 0, 1] -> 10 [0, 0, 1, 0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Erstes und zweites Bit auf `NOT`\n",
"\n",
"Sehen Sie, wie $NOT \\otimes NOT$ in der ergebenden Matrix ein `NOT` im `NOT` abbildet? Man braucht für die Rechnung kein Papier und Stift. Vollziehen Sie auch hier die Ausführungen eigenständig nach."
]
},
{
"cell_type": "code",
"execution_count": 92,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0, 0, 0, 1],\n",
" [0, 0, 1, 0],\n",
" [0, 1, 0, 0],\n",
" [1, 0, 0, 0]])"
]
},
"execution_count": 92,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"op3 = np.kron(NOT, NOT) # 2 Bits, erstes und zweites Bit NOT\n",
"op3"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Alternativ hätte man `op3` auch so, nämlich als $(NOT \\otimes I_2) \\cdot (I_2 \\otimes NOT)$ ermitteln können:"
]
},
{
"cell_type": "code",
"execution_count": 93,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0, 0, 0, 1],\n",
" [0, 0, 1, 0],\n",
" [0, 1, 0, 0],\n",
" [1, 0, 0, 0]])"
]
},
"execution_count": 93,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"op1 @ op2"
]
},
{
"cell_type": "code",
"execution_count": 94,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0 0 0 1]\n",
"[0 0 1 0]\n",
"[0 1 0 0]\n",
"[1 0 0 0]\n"
]
}
],
"source": [
"# Erstes und zweites Bit werden durch NOT invertiert\n",
"print(op3 @ np.array([1, 0, 0, 0])) # 00 [1, 0, 0, 0] -> 11 [0, 0, 0, 1]\n",
"print(op3 @ np.array([0, 1, 0, 0])) # 01 [0, 1, 0, 0] -> 10 [0, 0, 1, 0]\n",
"print(op3 @ np.array([0, 0, 1, 0])) # 10 [0, 0, 1, 0] -> 01 [0, 1, 0, 0]\n",
"print(op3 @ np.array([0, 0, 0, 1])) # 11 [0, 0, 0, 1] -> 00 [1, 0, 0, 0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `NOT` im 3-Bit-Register\n",
"\n",
"Bei einem 3-Bit-Register wird es ein wenig interessanter. Letztlich sieht man bestätigt, was sich bereits beim 2-Bit-Register gezeigt hat."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Drittes Bit auf `NOT`\n",
"\n",
"Nun gilt es $I_2 \\otimes I_2 \\otimes NOT$ zu berechnen. Oder kürzer: $I_4 \\otimes NOT$."
]
},
{
"cell_type": "code",
"execution_count": 95,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0 1 0 0 0 0 0 0]\n",
"[1 0 0 0 0 0 0 0]\n",
"[0 0 0 1 0 0 0 0]\n",
"[0 0 1 0 0 0 0 0]\n",
"[0 0 0 0 0 1 0 0]\n",
"[0 0 0 0 1 0 0 0]\n",
"[0 0 0 0 0 0 0 1]\n",
"[0 0 0 0 0 0 1 0]\n"
]
},
{
"data": {
"text/plain": [
"array([[0, 1, 0, 0, 0, 0, 0, 0],\n",
" [1, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 1, 0, 0, 0, 0],\n",
" [0, 0, 1, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 1, 0, 0],\n",
" [0, 0, 0, 0, 1, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 1],\n",
" [0, 0, 0, 0, 0, 0, 1, 0]])"
]
},
"execution_count": 95,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"op4 = np.kron(np.eye(4, dtype=np.int32), NOT) # 2 bits first, 3rd bit\n",
"print(op4 @ np.array([1,0,0,0,0,0,0,0])) # 000 -> 001\n",
"print(op4 @ np.array([0,1,0,0,0,0,0,0])) # 001 -> 000\n",
"print(op4 @ np.array([0,0,1,0,0,0,0,0])) # 010 -> 011\n",
"print(op4 @ np.array([0,0,0,1,0,0,0,0])) # 011 -> 010\n",
"print(op4 @ np.array([0,0,0,0,1,0,0,0])) # 100 -> 101\n",
"print(op4 @ np.array([0,0,0,0,0,1,0,0])) # 101 -> 100\n",
"print(op4 @ np.array([0,0,0,0,0,0,1,0])) # 110 -> 111\n",
"print(op4 @ np.array([0,0,0,0,0,0,0,1])) # 111 -> 110\n",
"op4"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Zweites Bit auf `NOT`\n",
"\n",
"In diesem Beispiel steht das `NOT` in der Mitte zweier Identitätsmatrizen: $I_2 \\otimes NOT \\otimes I_2$"
]
},
{
"cell_type": "code",
"execution_count": 96,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0 0 1 0 0 0 0 0]\n",
"[0 0 0 1 0 0 0 0]\n",
"[1 0 0 0 0 0 0 0]\n",
"[0 1 0 0 0 0 0 0]\n",
"[0 0 0 0 0 0 1 0]\n",
"[0 0 0 0 0 0 0 1]\n",
"[0 0 0 0 1 0 0 0]\n",
"[0 0 0 0 0 1 0 0]\n"
]
},
{
"data": {
"text/plain": [
"array([[0, 0, 1, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 1, 0, 0, 0, 0],\n",
" [1, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 1, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 1, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 1],\n",
" [0, 0, 0, 0, 1, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 1, 0, 0]])"
]
},
"execution_count": 96,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"op5 = np.kron(np.kron(np.eye(2, dtype=np.int32), NOT), np.eye(2, dtype=np.int32))\n",
"print(op5 @ np.array([1,0,0,0,0,0,0,0])) # 000 -> 010\n",
"print(op5 @ np.array([0,1,0,0,0,0,0,0])) # 001 -> 011\n",
"print(op5 @ np.array([0,0,1,0,0,0,0,0])) # 010 -> 000\n",
"print(op5 @ np.array([0,0,0,1,0,0,0,0])) # 011 -> 001\n",
"print(op5 @ np.array([0,0,0,0,1,0,0,0])) # 100 -> 110\n",
"print(op5 @ np.array([0,0,0,0,0,1,0,0])) # 101 -> 111\n",
"print(op5 @ np.array([0,0,0,0,0,0,1,0])) # 110 -> 100\n",
"print(op5 @ np.array([0,0,0,0,0,0,0,1])) # 111 -> 101\n",
"op5"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Der Simulator-Code für Logikgatter\n",
"\n",
"Wir übernehmen den obigen Code von `CState`, namentlich die Methode `__init__`, und ergänzen eine Methode `op` für _operation_. Diese Methode nimmt eine Matrix als Operanden entgehen. Aus den obigen Beobachtungen schließen wir verallgemeinernd, dass Matrizen nicht nur auf einem Bit in dem Zustandsraum operieren können (in dem Fall ist eine 2x2-Matrix erforderlich, wie bei `NOT`), sondern dass das auch auf zwei oder mehr aufeinanderfolgenden Bits möglich ist mit ensprechend größeren Matrizen (4x4, 8x8, 16x16 usw.). Mit dem Parameter `i` kann angegeben werden, ab welchem Anfangsbit die Matrix angewendet wird. Die Zählung beginnt mit 0, wie das so üblich ist in der Informatik.\n",
"\n",
"Im allgemeinen Fall gibt es eine linke und eine rechte Identitätsmatrix, `eyeL` und `eyeR`, und dazwischen die Matrix (`operator`). Die einzige Schwierigkeit besteht darin, die Größen der Identitätsmatrizen korrekt zu berechnen. Dafür bedarf es der Logarithmus-Funktion zur Basis 2, weshalb die `math`-Bibliothek zunächst zu importieren ist.\n",
"\n",
"Die Methode `__repr__` bietet eine auskunftsreichere Repräsentation von `CState`-Objekten an."
]
},
{
"cell_type": "code",
"execution_count": 97,
"metadata": {},
"outputs": [],
"source": [
"import math"
]
},
{
"cell_type": "code",
"execution_count": 98,
"metadata": {},
"outputs": [],
"source": [
"class CState:\n",
" def __init__(self, n):\n",
" self.n = n\n",
" self.state = np.zeros(2**n, dtype=np.int32)\n",
" self.state[0] = 1 # Initialisierung auf Wert 0\n",
" def op(self, operator, i = 0):\n",
" eyeL = np.eye(2**i, dtype=np.int32)\n",
" eyeR = np.eye(2**(self.n - i - int(math.log(operator.shape[0], 2))), dtype=np.int32)\n",
" self.state = np.kron(np.kron(eyeL, operator), eyeR) @ self.state\n",
" return self\n",
" def __repr__(self):\n",
" return \"%s(%s): %s\" % (self.__class__.__name__, self.n, self.state)"
]
},
{
"cell_type": "code",
"execution_count": 99,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"CState(2): [1 0 0 0]"
]
},
"execution_count": 99,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"CState(2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Definition eines `AND`-Gatters\n",
"\n",
"Nachfolgend ist ein `AND` definiert. Gewöhnlicherweise kennen Sie das `AND`-Schaltglied als eines, das zwei Bit-Eingänge verarbeitet und einen Bit-Ausgang mit dem Ergebnis hat. Hier ist ein wenig anders vorzugehen. Sie betrachten ein zwei Bit-Register, d.h. vor und nach der Anwendung einer Operation liegen nachwievor zwei Bits vor. Die Anzahl der Bits ändert sich nicht. Man muss sich also darauf einigen, auf welches der beiden Bits das Ergebnis gespielt wird und wie mit dem anderen Bit zu verfahren ist; man kann es beispielsweise unverändert lassen.\n",
"\n",
"**Aufgabe**: Schauen Sie sich an, wie das hier definierte `AND`-Gatter funktioniert und beschreiben Sie seine Funktionsweise."
]
},
{
"cell_type": "code",
"execution_count": 100,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 0, 1, 0],\n",
" [0, 1, 0, 0],\n",
" [0, 0, 0, 0],\n",
" [0, 0, 0, 1]], dtype=int32)"
]
},
"execution_count": 100,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"AND = np.array([[1,0,1,0],[0,1,0,0],[0,0,0,0],[0,0,0,1]], dtype=np.int32)\n",
"AND"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Mit dem `NOT`-Operator kann man jedes Bit auf einen gewünschten Wert setzen. Zur Erinnerung: Die `op`-Methode erlaubt die Angabe, auf welches Bit sich die Anwendung eines Operators bezieht.\n",
"\n",
"Wie Sie sehen, entwickle ich den Schaltungsaufbau anhand vier separater Ausdrücke, die die Schaltungsfolge des vorhergehenden Ausdrucks aufreifen und erweitern. Man muss sich daran gewöhnen, wie die Eins im Zustandsvektor lediglich hin- und herhüpft, um den aktuellen Zustand des Quantenregisters abzubilden."
]
},
{
"cell_type": "code",
"execution_count": 101,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CState(2): [1 0 0 0]\n",
"CState(2): [0 0 1 0]\n",
"CState(2): [0 0 0 1]\n",
"CState(2): [0 0 0 1]\n"
]
}
],
"source": [
"print(CState(2)) # Initialized to 00\n",
"print(CState(2).op(NOT)) # First bit fliped: 10\n",
"print(CState(2).op(NOT).op(NOT,1)) # Second bit flipped: 11\n",
"print(CState(2).op(NOT).op(NOT,1).op(AND)) # AND on both: 11"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Aufgabe**: Definieren Sie ein `ODER`-Gatter, welches das Ergebnis in das gleiche Bit wie `AND` überträgt. Zeigen Sie zusammen mit einem `NOT` auf, dass das `ODER`-Gatter funktioniert."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Simulator für Quantenschaltungen\n",
"\n",
"Sie haben bis hierher tapfer durchgehalten. Jetzt kommt das eigentlich Ungeheuerliche und irgendwie auch Faszinierende: Wir müssen in dem Code für `CState` lediglich das `dtype=int32` auf `dtype=complex` anpassen, also komplexe Zahlenwerte zulassen, ... und der Simulator für Quantenschaltungen ist fertig! Natürlich braucht es dazu noch Quantengatter, die ebenfalls Matrizen sind, aber eben mit komplexen Zahlenwerten.\n",
"\n",
"Aus diesem Grund importieren wir wie gehabt `math`, ein paar Methoden aus `cmath` für komplexe Zahlen und zudem `matplotlib.pyplot`, um den Zustandsraum mit den jewiligen Zustands-Wahrscheinlichkeiten zu visualisieren.\n"
]
},
{
"cell_type": "code",
"execution_count": 102,
"metadata": {},
"outputs": [],
"source": [
"from cmath import isclose, phase, pi\n",
"import math\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Der Simulator-Code\n",
"\n",
"Qubits werden als $|0\\rangle$ bzw. $|1\\rangle$ dargestellt. Ein Quantenregister aus $n=3$ Qubits hat -- wie im klassischen Fall -- einen Zustandsraum von $2^n$ Werten und zwar: $|000\\rangle$, $|001\\rangle$, $|010\\rangle$, $|011\\rangle$, $|100\\rangle$, $|101\\rangle$, $|110\\rangle$, $|111\\rangle$.\n",
"\n",
"Konnte eben noch der klassische Zustandsvektor lediglich eine einzige Eins beheimaten, sind nun Quantengatter in Matrizenform erlaubt, die Werte im Zustandsraum verteilen. Die Summe der Betragsquadrate von den Zustandswerten im Zustandsraum muss exakt 1 ergeben. Das prüft übrigens die Methode `isvalid` im Code.\n",
"\n",
"Eine Matrix operiert wie bei den Logikgattern auf einem oder mehr Qubits. Wenn eine Matrix $k$ Quibits adressiert, dann hat die Matrix die Größe $2^k \\times 2^k$. Die Matrix muss eine [unitäre Matrix](https://de.wikipedia.org/wiki/Unit%C3%A4re_Matrix) sein. Das überprüft der Code nicht.\n",
"\n",
"Der sonstige Code, der hinzugekommen ist, hat für die Simulation keine weitere Funktion, sondern dient nur zu Darstellungszwecken:\n",
"\n",
"Die Methode `__repr__` ist angepasst, um Einblicke in den Zustandsvektor zu gewähren: die komplexen Zahlen eines Zustands werden in voller Genauigkeit in kartesischen Koordination angegeben und ergänzt um eine gerundete Darstellung in Polarkoordinaten. Der Polarwinkel ist in Grad angegeben, da das meist leichter zu interpretieren ist. Zusätzlich wird die Wahrscheinlichkeit für den Zustand angegeben.\n",
"\n",
"Die Methode `show` erlaubt ein `print` eines `QState`-Objekts (dafür wird `__repr__` bemüht), ohne damit das [Method-Chaining](https://en.wikipedia.org/wiki/Method_chaining) zu unterbrechen. Es ist eines Design-Entscheidung von mir, mit Method-Chaining zu arbeiten.\n",
"\n",
"Die Methode `plot` erstellt ein Balkendiagramm zur Wahrscheinlichkeitsverteilung der Zustände im Zustandsvektor."
]
},
{
"cell_type": "code",
"execution_count": 103,
"metadata": {},
"outputs": [],
"source": [
"class QState:\n",
" def __init__(self, n):\n",
" self.n = n\n",
" self.state = np.zeros(2**n, dtype=complex)\n",
" self.state[0] = 1 # Initialisierung auf Wert 0\n",
" \n",
" def op(self, operator, i = 0):\n",
" eyeL = np.eye(2**i, dtype=complex)\n",
" eyeR = np.eye(2**(self.n - i - int(math.log(operator.shape[0], 2))), dtype=complex)\n",
" self.state = np.kron(np.kron(eyeL, operator), eyeR) @ self.state\n",
" return self\n",
" \n",
" def isvalid(self):\n",
" return isclose(self.state @ np.conjugate(self.state), 1.0)\n",
" \n",
" def __repr__(self):\n",
" repr = f\"{self.__class__.__name__}({self.n}) valid = {self.isvalid()}:\"\n",
" for i in range(0, self.state.shape[0]):\n",
" num = self.state[i]\n",
" repr += f\"\\n{i:2d} |{i:0{self.n}b}⟩ \"\n",
" repr += f\"{num.real if isclose(num.imag, 0.0) else (f'{num.imag}i' if isclose(num.real, 0.0) else f'{num.real} + {num.imag}i')} \"\n",
" repr += f\"({abs(num):.4f} * e^{phase(num) / pi * 180.0:.2f}°) \"\n",
" repr += f\"{abs(num * num):.2%}\"\n",
" return repr\n",
" \n",
" def show(self):\n",
" print(self)\n",
" return self\n",
" \n",
" def plot(self):\n",
" names = list(map(lambda x: f'|{x}⟩', range(0, self.state.shape[0])))\n",
" values = list(map(lambda x: abs(x), self.state * self.state))\n",
" plt.bar(names, values)\n",
" plt.suptitle(f'{self.__class__.__name__}({self.n})')\n",
" plt.show()\n",
" return self"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Spezial-Operator `SWAP`\n",
"\n",
"Um die Methode `op` einfach zu halten, gibt man für einen Operator an, von welchem Bit an er wirkt. Natürlich passt das nicht immer. Manchmal arbeitet ein Operator bzw. ein Quantengatter beispielsweise auf zwei Qubits, die aber nicht hintereinander aufgereiht sind. Mit `SWAP` kann man zwei aufeinander folgende Qubits tauschen und sich so die Qubit-Folge geeignet arrangieren. Das ist zwar etwas mühsam, aber derzeit der Preis für eine so kurze `op`-Methode. "
]
},
{
"cell_type": "code",
"execution_count": 104,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 0, 0, 0],\n",
" [0, 0, 1, 0],\n",
" [0, 1, 0, 0],\n",
" [0, 0, 0, 1]])"
]
},
"execution_count": 104,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"SWAP = np.array([[1,0,0,0],\n",
" [0,0,1,0],\n",
" [0,1,0,0],\n",
" [0,0,0,1]])\n",
"SWAP"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Quantengatter\n",
"\n",
"Die implementierten Quantengatter orientieren sich an der [Liste der Quantengatter](https://de.wikipedia.org/wiki/Liste_der_Quantengatter) aus Wikipedia. Schaltungen werden mit diesen Quantengattern realisiert."
]
},
{
"cell_type": "code",
"execution_count": 105,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 0, 0, 0],\n",
" [0, 1, 0, 0],\n",
" [0, 0, 0, 1],\n",
" [0, 0, 1, 0]])"
]
},
"execution_count": 105,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"CNOT = np.array([[1,0,0,0],\n",
" [0,1,0,0],\n",
" [0,0,0,1],\n",
" [0,0,1,0]])\n",
"CNOT"
]
},
{
"cell_type": "code",
"execution_count": 106,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1.+0.j, 0.+0.j],\n",
" [0.+0.j, 0.+1.j]])"
]
},
"execution_count": 106,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"S = np.array([[1,0],\n",
" [0,1j]])\n",
"S"
]
},
{
"cell_type": "code",
"execution_count": 107,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1. +0.j , 0. +0.j ],\n",
" [0. +0.j , 0.70710678+0.70710678j]])"
]
},
"execution_count": 107,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"inv_sqrt2 = 1 / 2**0.5 # 1 durch Wurzel aus Zwei\n",
"T = np.array([[1,0],\n",
" [0, inv_sqrt2 + inv_sqrt2 * 1j]])\n",
"T"
]
},
{
"cell_type": "code",
"execution_count": 108,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 0.70710678, 0.70710678],\n",
" [ 0.70710678, -0.70710678]])"
]
},
"execution_count": 108,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"H = inv_sqrt2 * np.array([[1, 1],\n",
" [1,-1]])\n",
"H"
]
},
{
"cell_type": "code",
"execution_count": 109,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0, 1],\n",
" [1, 0]])"
]
},
"execution_count": 109,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"PAULIX = np.array([[0,1],\n",
" [1,0]])\n",
"PAULIX"
]
},
{
"cell_type": "code",
"execution_count": 110,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0.+0.j, 0.-1.j],\n",
" [0.+1.j, 0.+0.j]])"
]
},
"execution_count": 110,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"PAULIY = np.array([[0,0-1j],\n",
" [0+1j,0]])\n",
"PAULIY"
]
},
{
"cell_type": "code",
"execution_count": 111,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 1, 0],\n",
" [ 0, -1]])"
]
},
"execution_count": 111,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"PAULIZ = np.array([[1,0],\n",
" [0,-1]])\n",
"PAULIZ"
]
},
{
"cell_type": "code",
"execution_count": 112,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0.70710678+0.j , 0. -0.70710678j],\n",
" [0. -0.70710678j, 0.70710678+0.j ]])"
]
},
"execution_count": 112,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"XROT = inv_sqrt2 * np.array([[1,0-1j],[0-1j,1]])\n",
"XROT"
]
},
{
"cell_type": "code",
"execution_count": 113,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 0.70710678, -0.70710678],\n",
" [ 0.70710678, 0.70710678]])"
]
},
"execution_count": 113,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"YROT = inv_sqrt2 * np.array([[1,-1],[1,1]])\n",
"YROT"
]
},
{
"cell_type": "code",
"execution_count": 114,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 1, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 1, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 1, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 1, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 1],\n",
" [0, 0, 0, 0, 0, 0, 1, 0]])"
]
},
"execution_count": 114,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"TOFFOLI = np.array([[1,0,0,0,0,0,0,0],\n",
" [0,1,0,0,0,0,0,0],\n",
" [0,0,1,0,0,0,0,0],\n",
" [0,0,0,1,0,0,0,0],\n",
" [0,0,0,0,1,0,0,0],\n",
" [0,0,0,0,0,1,0,0],\n",
" [0,0,0,0,0,0,0,1],\n",
" [0,0,0,0,0,0,1,0]])\n",
"TOFFOLI"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Simulationsbeispiel\n",
"\n",
"In dem nachfolgenden Beispiel sehen Sie den Simulator für Quantenschaltungen in Aktion."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### ERP-Paar\n",
"\n",
"Die Quantentheorie hat zur Zeit ihrer Entstehung (oder sollte man eher sagen: ihrer Entdeckung?) die größten Physiker herausgefordert. Das tut sie übrigens immer noch. Albert Einstein wollte damals den Beweis antreten, dass etwas mit der Quantentheorie nicht stimmt. Zusammen mit Boris Podolsky und Nathan Rosen veröffentlichte er 1935 einen Artikel dazu. Darin spielt ein Quantenzustand eine Rolle, der ihnen zu Ehren als ERP-Paar und bisweilen auch als Bell-Zustand bezeichnet wird. Einstein und seine Kollegen argumentierten, dass die Verschränkung zweier Quanten gegen das Lokalitätsprinzip verstößt -- und deshalb die Quantentheorie fehlerhaft sei. Sie hatten unrecht mit der Folgerung. Die Quantentheorie ist korrekt, sie ist die wohl am besten experimentell bestätigte Theorie der Physik. Es ist ihre Besonderheit, dass sie gegen das Lokalitätsprinzip verstoßen kann, und das ist die neue Erkenntnis. Wie man sich das vorstellen und erklären soll, das verrät einem die Quantentheorie leider nicht.\n",
"\n",
"Es ist einfach, mit dem Simulator ein ERP-Paar bzw. einen [Bell-Zustand](https://en.wikipedia.org/wiki/Bell_state) zu erzeugen. Am Ende hat man ein verschränktes Quantenpaar.\n",
"\n",
"Wenn Sie diese erste Simulation sehen, dann können Sie das vielleicht rein rechnerisch nachvollziehen, aber so richtig verstehen und deuten können Sie das ohne weitere Kenntnisse des Quantencomputings vermutlich nicht. Es sei an die Einleitung erinnert: Nur weil man mit einem Quantensimulator arbeitet hat man die Quantenwelt noch lange nicht verstanden. Willkommen im Club!"
]
},
{
"cell_type": "code",
"execution_count": 115,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"QState(2) valid = True:\n",
" 0 |00⟩ 1.0 (1.0000 * e^0.00°) 100.00%\n",
" 1 |01⟩ 0.0 (0.0000 * e^0.00°) 0.00%\n",
" 2 |10⟩ 0.0 (0.0000 * e^0.00°) 0.00%\n",
" 3 |11⟩ 0.0 (0.0000 * e^0.00°) 0.00%\n"
]
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"QState(2) valid = True:\n",
" 0 |00⟩ 0.7071067811865475 (0.7071 * e^0.00°) 50.00%\n",
" 1 |01⟩ 0.0 (0.0000 * e^0.00°) 0.00%\n",
" 2 |10⟩ 0.7071067811865475 (0.7071 * e^0.00°) 50.00%\n",
" 3 |11⟩ 0.0 (0.0000 * e^0.00°) 0.00%\n"
]
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"QState(2) valid = True:\n",
" 0 |00⟩ 0.7071067811865475 (0.7071 * e^0.00°) 50.00%\n",
" 1 |01⟩ 0.0 (0.0000 * e^0.00°) 0.00%\n",
" 2 |10⟩ 0.0 (0.0000 * e^0.00°) 0.00%\n",
" 3 |11⟩ 0.7071067811865475 (0.7071 * e^0.00°) 50.00%"
]
},
"execution_count": 115,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"QState(2).plot().show().op(H).plot().show().op(CNOT).plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Erweiterungen\n",
"\n",
"Eventuell werde ich den Code mit der Zeit selber erweitern. Kommen Sie mir gerne zuvor. Hier ein paar Anregungen:\n",
"\n",
"* Der Code benötigt noch einige exemplarische Beispiele als Testfälle\n",
"* Anzeige, welche Bits miteinander verschränkt sind.\n",
"* Freie Konfiguration der Bits zu einem Operator, bspw. `QState(4).op(CNOT,1,3)` oder `QState(4).op(CNOT,3,0)`; das erste Bit gibt das Control-Bit, das zweite das Target-Bit an.\n",
"* Messung von Quantenbits ergänzen\n",
"* Rauschmodell berücksichtigen"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*Hinweis*: Zum Verständnis der Methode `__repr__` in `QState` mögen folgende Quellen hilfreich sein:\n",
"\n",
"* Format-Templates, siehe [Python-Doku](https://docs.python.org/3/library/string.html#format-string-syntax) (Abruf: 2022-04-18)\n",
"* Formatierung einer Binärzahl, siehe [Stackoverflow](https://stackoverflow.com/questions/16926130/convert-to-binary-and-keep-leading-zeros) (Abruf: 2022-04-18)\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"interpreter": {
"hash": "b0fa6594d8f4cbf19f97940f81e996739fb7646882a419484c72d19e05852a7e"
},
"kernelspec": {
"display_name": "Python 3.9.10 64-bit",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.12"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}
@denkspuren
Copy link
Author

denkspuren commented Apr 25, 2022

Die Weiterentwicklung des Simulators findet im GitHub-Repo https://github.com/denkspuren/qcsim statt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment