Tutorial: Erkunden der Verschränkung mit Q#

In diesem Tutorial erfahren Sie, wie Sie ein Q#-Programm schreiben, mit dem Qubits bearbeitet und gemessen werden können. Darüber hinaus werden die Auswirkungen von Superposition und Verschränkung veranschaulicht.

Sie schreiben eine Anwendung mit dem Namen Bell, um die Quantenverschränkung zu veranschaulichen. Der Name „Bell“ bezieht sich auf die Bell-Zustände. Hierbei handelt es sich um spezifische Quantenzustände von zwei Qubits, die zum Darstellen der einfachsten Beispiele für Überlagerungen und Quantenverschränkungen verwendet werden.

Voraussetzungen

Sie können den Vorgang auch ohne Installation des QDK durchführen, indem Sie sich die Übersichten zur Q# Programmiersprache und die wichtigsten Konzepte des Quantencomputings ansehen.

In diesem Tutorial lernen Sie Folgendes:

  • Erstellen und Kombinieren von Vorgängen in Q#
  • Erstellen Sie Vorgänge, um Qubits in Superposition zu versetzen, verschränken und zu messen.
  • Veranschaulichen der Quantenverschränkung mit einem Q# Programm, das in einem Simulator ausgeführt wird.

Veranschaulichen des Qubit-Verhaltens per QDK

Klassische Bits enthalten einen einzelnen binären Wert (0 oder 1). Der Zustand eines Qubits kann dagegen eine Superposition sein (also 0 und 1). Konzeptuell können Sie sich den Zustand eines Qubits als eine Richtung im abstrakten Raum vorstellen (auch als Vektor bezeichnet). Ein Qubitzustand kann eine beliebige der möglichen Richtungen aufweisen. Die beiden klassischen Zustände sind die beiden Richtungen, die für die einhundertprozentige Chance einer Messung von 0 und die einhundertprozentige Chance einer Messung von 1 stehen.

Mit dem Messvorgang wird ein binäres Ergebnis erzeugt und ein Qubit-Zustand geändert. Bei der Messung wird ein binärer Wert erzeugt (0 oder 1). Das Qubit geht von einem Überlagerungszustand (beliebige Richtung) in einen der klassischen Zustände über. Danach führt die Wiederholung der gleichen Messung ohne Eingriff zu demselben binären Ergebnis.

Mehrere Qubits können verschränkt sein. Bei der Messung eines verschränkten Qubits wird unsere Kenntnis des Zustands der anderen Qubits ebenfalls aktualisiert.

Schreiben der Q# Anwendung

Das Ziel ist das Festlegen eines spezifischen Quantenzustands für zwei Qubits. Hiermit soll demonstriert werden, wie Sie mit Q# bei Qubits vorgehen, um den Zustand zu ändern und die Auswirkungen von Superposition und Verschränkung zu veranschaulichen. Der Aufbau erfolgt Stück für Stück, um Zustände, Vorgänge und Messungen von Qubits zu demonstrieren.

Initialisieren von Qubits mithilfe von Messungen

Im folgenden Codeausschnitt wird die Verwendung von Qubits in Q# veranschaulicht. In dem Code werden die beiden Vorgänge M und X eingeführt, die den Zustand eines Qubits transformieren.

Der definierte Vorgang SetQubitState verwendet ein Qubit als Parameter sowie einen weiteren Parameter (desired), der den gewünschten Zustand für das Qubit darstellt. Der Vorgang SetQubitState führt eine Messung für das Qubit durch, indem der Vorgang M verwendet wird. In Q# wird für eine Qubit-Messung stets entweder Zero oder One zurückgegeben. Wenn die Messung einen Wert zurückgibt, der nicht einem gewünschten Wert entspricht, kehrt SetQubitState das Qubit um. Der Vorgang X wird ausgeführt, mit dem der Qubit-Zustand in einen neuen Zustand versetzt wird, bei dem die Wahrscheinlichkeiten, dass für eine Messung Zero und One ausgegeben wird, umgekehrt sind. Auf diese Weise versetzt SetQubitState das Zielqubit immer in den gewünschten Zustand.

Ersetzen Sie den Inhalt von Program.qs durch den folgenden Code.

   namespace Bell {
       open Microsoft.Quantum.Intrinsic;
       open Microsoft.Quantum.Canon;

       operation SetQubitState(desired : Result, q1 : Qubit) : Unit {
           if desired != M(q1) {
               X(q1);
           }
       }
   }

Dieser Vorgang kann jetzt aufgerufen werden, um ein Qubit auf einen klassischen Zustand festzulegen, indem entweder immer Zero oder immer One zurückgegeben wird. Zero und One sind Konstanten, die die beiden einzigen möglichen Ergebnisse der Messung eines Qubits darstellen.

Mit dem Vorgang SetQubitState wird das Qubit gemessen. Wenn sich das Qubit im gewünschten Zustand befindet, wird es von SetQubitState unverändert gelassen. Andernfalls wird der Qubit-Zustand durch Ausführen des Vorgangs X in den gewünschten Zustand versetzt.

Informationen zu Q# Vorgängen

Ein Vorgang Q# ist eine Quantenunterroutine. Dies bedeutet, dass es sich um eine aufrufbare Routine handelt, die Aufrufe für andere Quantenvorgänge enthält.

Die Argumente für eine Operation werden als Tupel angegeben, in Klammern eingeschlossen.

Der Rückgabetyp der Operation wird nach einem Doppelpunkt angegeben. In diesem Fall hat die Operation SetQubitState keinen Rückgabewert, daher ist sie markiert als Unit zurückgebend. Dies ist die Q# Entsprechung von unit in F#, das in etwa analog zu void in C# und einem leeren Tupel in Python ist ((), dargestellt vom Tuple[()] Hinweis zum Typ).

Sie haben in Ihrem ersten Q# Vorgang zwei Quantenvorgänge verwendet:

  • Den Vorgang M, mit dem der Zustand des Qubits gemessen wird
  • Den Vorgang X, mit dem der Zustand eines Qubits umgekehrt wird

Mit einem Quantenvorgang wird der Zustand eines Qubits transformiert. Analog zu klassischen Logikgattern wird anstelle von Vorgängen auch von Quantengattern gesprochen. Diese Bezeichnung stammt aus den Anfängen des Quantencomputings, als Algorithmen lediglich ein theoretisches Konstrukt waren und in Form von Diagrammen visualisiert wurden – ähnlich wie bei einem Schaltplan im Bereich des klassischen Computings.

Zählen von Messergebnissen

Zur Veranschaulichung der Auswirkungen des Vorgangs SetQubitState wird dann der Vorgang TestBellState hinzugefügt. Für diesen Vorgang wird Zero oder One als Eingabe verwendet, und der Vorgang SetQubitState wird einige Male mit dieser Eingabe aufgerufen. Es wird gezählt, wie oft bei der Messung des Qubits Zero und wie oft One zurückgegeben wird. Bei der ersten Simulation des Vorgangs TestBellState wird natürlich erwartet, dass in der Ausgabe für alle Messungen des Qubits mit Zero als Parametereingabe Zero und für alle Messungen eines Qubits mit One als Parametereingabe One zurückgegeben wird. Später wird Code zu TestBellState hinzugefügt, um Superposition und Verschränkung zu demonstrieren.

Fügen Sie der Program.qs-Datei die folgende Operation hinzu, innerhalb des Namespaces, nach dem Ende der SetQubitState-Operation:

   operation TestBellState(count : Int, initial : Result) : (Int, Int) {

       mutable numOnes = 0;
       use qubit = Qubit();
       for test in 1..count {
           SetQubitState(initial, qubit);
           let res = M(qubit);

           // Count the number of ones we saw:
           if res == One {       
               set numOnes += 1;
           }
       }

       SetQubitState(Zero, qubit);

       

       // Return number of times we saw a |0> and number of times we saw a |1>
       Message("Test results (# of 0s, # of 1s): ");
       return (count - numOnes, numOnes);
   }

Beachten Sie, dass vor return eine Zeile hinzugefügt wurde, um mit der Funktion (Message)[microsoft.quantum.intrinsic.message] eine erläuternde Nachricht in der Konsole auszugeben.

Diese Operation (TestBellState) iteriert count Iterationen lang in einer Schleife, legt einen angegebenen initial-Wert für ein Qubit fest, und misst (M) dann das Ergebnis. Sie führt eine Statistik zur Anzahl der gemessenen Nullen und Einsen und gibt diese an den Aufrufer zurück. Sie führt eine weitere erforderliche Operation aus. Sie setzt das Qubit auf einen bekannten Zustand (Zero) zurück, bevor sie es zurückgibt, was es anderen ermöglicht, dieses Qubit in einem bekannten Zustand zuzuweisen. Dies ist aufgrund der use-Anweisung erforderlich.

Informationen zu Variablen in Q#

Standardmäßig sind Variablen in Q# unveränderlich. Ihr Wert kann nach dem Einbinden nicht geändert werden. Das let-Schlüsselwort wird verwendet, um die Bindung einer unveränderlichen Variable anzugeben. Operationsargumente sind immer unveränderlich.

Wenn Sie eine Variable benötigen, deren Wert geändert werden kann, wie etwa numOnes im Beispiel, können Sie die Variable mit dem Schlüsselwort mutable deklarieren. Der Wert einer veränderlichen Variable kann mithilfe einer set-Anweisung geändert werden.

In beiden Fällen wird der Typ einer Variablen vom Compiler abgeleitet. Q# erfordert keine Typanmerkungen für Variablen.

Informationen zu use Anweisungen in Q#

Die use Anweisung ist zudem kennzeichnend für Q#. Sie wird verwendet, um Qubits für die Verwendung in einem Codeblock zuzuweisen. In Q# werden alle Qubits dynamisch zugeordnet und freigegeben. Sie stellen keine festen Ressourcen dar, die für die gesamte Dauer eines komplexen Algorithmus vorliegen. Eine use-Anweisung weist eine Reihe Qubits am Anfang zu und gibt diese Qubits am Ende des Blocks wieder frei.

Führen Sie den Code in der Eingabeaufforderung aus

Zum Ausführen des Codes müssen wir dem Compiler mitteilen, welcher Aufruf ausgeführt werden soll, wenn wir den Befehl dotnet run eingeben. Dies erfolgt durch eine einfache Änderung in der Q# Datei, durch Hinzufügen einer Zeile in der @EntryPoint() dem Aufruf direkt vorausgeht: in diesem Fall dem TestBellState Vorgang. Der vollständige Code sollte so lauten:

namespace Bell {
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Intrinsic;

    operation SetQubitState(desired : Result, target : Qubit) : Unit {
        if desired != M(target) {
            X(target);
        }
    }

    @EntryPoint()
    operation TestBellState(count : Int, initial : Result) : (Int, Int) {

        mutable numOnes = 0;
        use qubit = Qubit();
        for test in 1..count {
            SetQubitState(initial, qubit);
            let res = M(qubit);
            

            // Count the number of ones we saw:
            if res == One {
                  set numOnes += 1;
            }
        }

        SetQubitState(Zero, qubit);
        

    // Return number of times we saw a |0> and number of times we saw a |1>
    Message("Test results (# of 0s, # of 1s): ");
    return (count - numOnes, numOnes);
    }
}

Zum Ausführen des Programms müssen wir die count und initial Argumente aus der Eingabeaufforderung angeben. Wählen wir beispielsweise count = 1000 und initial = One. Geben Sie den folgenden Befehl ein:

dotnet run --count 1000 --initial One

Sie sollten zudem die folgende Ausgabe beobachten:

Test results (# of 0s, # of 1s):
(0, 1000)

Wenn Sie es mit initial = Zero probieren, sollten Sie Folgendes beobachten:

dotnet run --count 1000 --initial Zero
Test results (# of 0s, # of 1s):
(1000, 0)

Vorbereiten von Überlagerung

Wir sehen uns nun an, wie in Q# die Überlagerung von Qubits erzielt wird. Sie wissen bereits, dass der Zustand eines Qubits den Überlagerungswert 0 und 1 aufweisen kann. Hierzu kann der Vorgang Hadamard verwendet werden. Wenn das Qubit einen der klassischen Zustände aufweist (bei dem für eine Messung immer Zero oder immer One zurückgegeben wird), versetzt der Vorgang Hadamard bzw. H das Qubit in einen Zustand, in dem für eine Messung des Qubits in 50 % der Fälle Zero und in 50 % der Fälle One zurückgegeben wird. Konzeptuell können Sie sich das Qubit als Kombination von Zero und One vorstellen. Bei der Simulation des Vorgangs TestBellState werden Zero und One in den Ergebnissen ungefähr mit der gleichen Häufigkeit zurückgegeben.

X kehrt den Qubitzustand um

Versuchen Sie zunächst nur, das Qubit umzukehren. Wenn sich das Qubit im Zustand Zero befindet, kehrt es sich zu One um (und umgekehrt). Dies wird dadurch erreicht, dass wir den Vorgang X durchführen, bevor die Messung in TestBellState erfolgt:

X(qubit);
let res = M(qubit);

Jetzt sind die Ergebnisse umgekehrt:

dotnet run --count 1000 --initial One
Test results (# of 0s, # of 1s):
(1000, 0)
dotnet run --count 1000 --initial Zero
Test results (# of 0s, # of 1s):
(0, 1000)

Erkunden wir nun die Quanteneigenschaften der Qubits.

H bereitet die Überlagerung vor

Hierfür müssen Sie lediglich den Vorgang X in der vorherigen Ausführung durch den Vorgang H (Hadamard-Vorgang) ersetzen. Statt das Qubit vollständig von 0 in 1 umzukehren, wird es nur halb umgekehrt. Die ersetzten Zeilen in TestBellState sehen jetzt so aus:

H(qubit);
let res = M(qubit);

Jetzt werden die Ergebnisse interessanter:

dotnet run --count 1000 --initial One
Test results (# of 0s, # of 1s):
(496, 504)
dotnet run --count 1000 --initial Zero
Test results (# of 0s, # of 1s):
(506, 494)

Bei jeder Messung wird ein klassischer Wert angefordert, aber das Qubit befindet sich auf halbem Weg zwischen 0 und 1, also erhalten wir (statistisch) zur Hälfte 0 und zur Hälfte 1. Dies wird als Überlagerung bezeichnet und gibt uns unseren ersten echten Einblick in einen Quantenzustand.

Vorbereiten von Verschränkung

Schauen wir uns nun an, wie Q# verschiedene Möglichkeiten der Verschränkung von Qubits ausdrückt. Legen Sie zuerst das erste Qubit auf den Anfangszustand fest, und verwenden Sie dann den Vorgang H, um eine Superposition zu erreichen. Verwenden Sie anschließend vor der Messung des ersten Qubits einen neuen Vorgang (CNOT), der für Controlled-NOT steht. Das Ergebnis des Ausführens dieses Vorgangs für zwei Qubits ist, dass das zweite Qubit umgekehrt wird, wenn das erste Qubit One ist. Die beiden Qubits sind nun verschränkt. Unsere Statistik für das erste Qubit hat sich nicht geändert (50:50-Wahrscheinlichkeit für Zero oder One nach der Messung), aber bei der Messung des zweiten Qubits wird immer das gleiche Ergebnis wie bei der Messung des ersten Qubits erzielt. Unser CNOT hat die zwei Qubits verschränkt, sodass was immer mit dem einen von ihnen geschieht, auch mit dem anderen geschieht. Wenn Sie die Messungen umkehrten (das zweite Qubit vor dem ersten messen), würde das Gleiche passieren. Die erste Messung wäre zufällig, und die zweite wäre im Gleichschritt mit dem, was bei der ersten herausgefunden wurde.

Ordnen Sie zur Vorbereitung der Verschränkung zunächst in TestBellState anstelle eines einzelnen Qubits zwei Qubits zu:

use (q0, q1) = (Qubit(), Qubit());

Dies ermöglicht es, vor der Messung (M) in TestBellState einen neuen Vorgang (CNOT) hinzuzufügen:

SetQubitState(initial, q0);
SetQubitState(Zero, q1);

H(q0);
CNOT(q0, q1);
let res = M(q0);

Fügen Sie einen weiteren Vorgang vom Typ SetQubitState hinzu, um das erste Qubit zu initialisieren und sicherzustellen, dass es sich immer im Zustand Zero befindet.

Setzen Sie abschließend das zweite Qubit zurück, bevor Sie es freigeben.

SetQubitState(Zero, q0);
SetQubitState(Zero, q1);

Die vollständige Routine sieht jetzt so aus:

    operation TestBellState(count : Int, initial : Result) : (Int, Int) {

        mutable numOnes = 0;
        use (q0, q1) = (Qubit(), Qubit());
        for test in 1..count {
            SetQubitState(initial, q0);
            SetQubitState(Zero, q1);

            H(q0);
            CNOT(q0,q1);
            let res = M(q0);

            // Count the number of ones we saw:
            if res == One {
                set numOnes += 1;
            }
        }

        SetQubitState(Zero, q0);
        SetQubitState(Zero, q1);
        

        // Return number of times we saw a |0> and number of times we saw a |1>
        return (count-numOnes, numOnes);
    }

Wenn Sie sie ausführen, erhalten Sie exakt das gleiche 50:50-Ergebnis wie zuvor. Interessant ist jedoch, wie das zweite Qubit auf die Messung des ersten Qubits reagiert. Diese Statistik wird mit einer neuen Version des Vorgangs TestBellState hinzugefügt:

    operation TestBellState(count : Int, initial : Result) : (Int, Int, Int) {
        mutable numOnes = 0;
        mutable agree = 0;
        use (q0, q1) = (Qubit(), Qubit());
        for test in 1..count {
            SetQubitState(initial, q0);
            SetQubitState(Zero, q1);

            H(q0);
            CNOT(q0, q1);
            let res = M(q0);

            if M(q1) == res {
                set agree += 1;
            }

            // Count the number of ones we saw:
            if res == One {
                set numOnes += 1;
            }
        }

        SetQubitState(Zero, q0);
        SetQubitState(Zero, q1);
        

        // Return times we saw |0>, times we saw |1>, and times measurements agreed
        Message("Test results (# of 0s, # of 1s, # of agreements)");
        return (count-numOnes, numOnes, agree);
    }

Der neue Rückgabewert (agree) hält fest, wenn die Messung des ersten Qubits mit der Messung des zweiten Qubits übereinstimmt.

Wenn Sie den Code ausführen, sieht das Ergebnis wie folgt aus:

dotnet run --count 1000 --initial One
(505, 495, 1000)
dotnet run --count 1000 --initial Zero
Test results (# of 0s, # of 1s, # of agreements)
(507, 493, 1000)

Wie in der Übersicht beschrieben, hat sich die Statistik für das erste Qubit nicht geändert (50:50-Wahrscheinlichkeit für 0 oder 1). Bei der Messung des zweiten Qubits erhalten Sie jedoch aufgrund der Verschränkung immer das gleiche Ergebnis wie beim ersten Qubit.

Nächste Schritte

Im Tutorial zur Grover-Suche wird veranschaulicht, wie Sie die Grover-Suche erstellen und ausführen. Dies ist einer der beliebtesten Algorithmen des Quantencomputings und ein schönes Beispiel für ein Q# Programm, das zum Lösen echter Probleme im Bereich des Quantencomputings eingesetzt werden kann.

In Erste Schritte mit dem Quantum Development Kit finden Sie weitere Möglichkeiten, Q# und Quantenprogrammierung zu lernen.