Självstudie: Skriva och simulera program på qubitnivå i Q#

Välkommen till självstudien för Quantum Development Kit (QDK) om att skriva och simulera ett grundläggande kvantprogram som körs på enskilda kvantbitar.

Även om främst skapades som ett programmeringsspråk på hög nivå för storskaliga kvantprogram, kan det lika enkelt användas för att utforska den lägre nivån av kvantprogram: direkt adressering av specifika Q# kvantbitar. Flexibiliteten i gör att användare kan använda kvantsystem från valfri abstraktionsnivå, och i den här självstudien går vi in på Q# själva kvantbitarna.

Mer specifikt tar den här självstudien en titt under huven på Quantum Fourier Transform, en subrutin som är integrerad i många större kvantalgoritmer.

Observera att den här lågnivåvyn avkvantinformationsbearbetningofta beskrivs i termer av "kvantkretsar", som representerar den sekventiella tillämpningen av grindar på specifika kvantbitar i ett system.

Därför kan de åtgärder med en och flera qubit som vi tillämpar sekventiellt enkelt representeras i ett "kretsdiagram". I det här fallet definierar du en åtgärd för att utföra den fullständiga fouriertransformen med tre kvantbitar, som har Q# följande representation som en krets:


Three qubit quantum Fourier transform circuit diagram

Förutsättningar

I den här självstudien får du lära dig att:

  • Definiera kvantåtgärder i Q#
  • Anropa Q# åtgärder direkt från kommandotolken eller med hjälp av ett klassiskt värdprogram
  • Simulera en kvantåtgärd från kvantbitsallokering till mätutdata
  • Observera hur kvantsystemets simulerade vågfunktion utvecklas under hela åtgärden

Att köra ett kvantprogram med QDK består vanligtvis av två delar:

  1. Själva programmet, som implementeras med hjälp av Q# kvantprogrammeringsspråket, anropas sedan för att köras på en kvantdator eller kvantsimulator. Dessa består av
    • Q# åtgärder: subrutiner som agerar på kvantregister och
    • Q# funktioner: klassiska subrutiner som används i kvantalgoritmen.
  2. Startpunkten som används för att anropa kvantprogrammet och ange den måldator som det ska köras på. Detta kan göras direkt från kommandotolken eller via ett värdprogram som skrivits i ett klassiskt programmeringsspråk som Python eller C#. Den här självstudien innehåller instruktioner för den metod du föredrar.

Allokera kvantbitar och definiera kvantåtgärder

Den första delen av den här självstudien består av att definiera åtgärden , som utför Q# Perform3qubitQFT Fourier-kvanttransformeringen på tre kvantbitar.

Dessutom används funktionen för att observera hur den simulerade vågfunktionen i vårt DumpMachine tre qubitsystem utvecklas under åtgärden.

Det första steget är att skapa Q# projektet och filen. Stegen för detta beror på vilken miljö du använder för att anropa programmet, och du hittar informationen i respektive installationsguide.

I den här självstudien går vi igenom komponenterna i filen steg för steg, men koden är också tillgänglig som ett fullständigt block nedan.

Namnrymder för att få åtkomst till andra Q# åtgärder

I filen definierar du först det NamespaceQFT namnområde som kompilatorn kommer åt. För att den här åtgärden ska kunna använda Q# befintliga åtgärder öppnar du de relevanta Microsoft.Quantum.<> namnrymderna.

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    // operations go here
}

Definiera åtgärder med argument och returer

Definiera sedan Perform3qubitQFT åtgärden:

    operation Perform3qubitQFT() : Unit {
        // do stuff
    }

För tillfället tar åtgärden inga argument och returnerar inte något--- i det här fallet skriver vi att den returnerar ett -objekt som liknar i C# eller en tom Unit void tuppel, Tuple[()] , i Python. Senare kommer den att ändras för att returnera en matris med måttresultat, där Unit ersätts med Result[] .

Allokera qubitar med use

I åtgärden Q# allokerar du ett register med tre qubitar med use nyckelordet :

        use qs = Qubit[3];

        Message("Initial state |000>:");
        DumpMachine();

Med use allokeras qubits automatiskt i tillståndet $\ket {0} $. Du kan kontrollera detta med Message(<string>) hjälp av och , som skriver ut en sträng och DumpMachine() systemets aktuella tillstånd till konsolen.

Anteckning

Funktionerna Message(<string>) DumpMachine() och (från Microsoft.Quantum.Intrinsic respektive ) skrivs ut direkt till Microsoft.Quantum.Diagnostics konsolen. Precis som med en verklig kvantberäkning Q# kan vi inte komma åt kvantbitstillstånd direkt. Men när skriver ut måldatorns aktuella tillstånd kan den ge värdefulla insikter för felsökning och inlärning när den används tillsammans med DumpMachine simulatorn med fullständigt tillstånd.

Tillämpa en enskild qubit och kontrollerade grindar

Tillämpa sedan grindarna som utgör själva åtgärden. Q# innehåller redan många grundläggande kvantgrindar som åtgärder Microsoft.Quantum.Intrinsic i namnområdet och dessa är inget undantag.

I en åtgärd körs de instruktioner som anropar anropningsbara i Q# sekventiell ordning. Därför är den första grinden att tillämpa H (Hadamard) på den första qubiten:


Circuit diagram for three qubit QFT through first Hadamard

För att tillämpa en åtgärd på en specifik qubit från ett register (till exempel en enskild från en Qubit matris ) använder vi Qubit[] standardindex notation. Det gör att H tillämpningen av för den första qubiten i vårt register har formen qs :

            H(qs[0]);

Förutom att tillämpa H Hadamard-grinden på enskilda qubitar består QFT-kretsen främst av kontrollerade R1 rotationer. En åtgärd lämnar i allmänhet $\ket $-komponenten för qubiten oförändrad medan en rotation av R1(θ, <qubit>) {0} $e^{i\theta}$ tillämpas på $\ket {1} $-komponenten.

Kontrollerade åtgärder

Q# gör det mycket enkelt att villkora körningen av en åtgärd på en eller flera kontrollqubitar. I allmänhet föregås anropet bara av Controlled , och åtgärdsargumenten ändras som:

Op(<normal args>) $\to$ Controlled Op([<control qubits>], (<normal args>)) .

Observera att kontrollqubitarna måste anges som en matris, även om det är en enda qubit.

Efter H är de följande grindarna R1 grindarna som fungerar på den första qubiten (och styrs av den andra/tredje):


Circuit diagram for three qubit QFT through first qubit

Du kan anropa dessa med:

            Controlled R1([qs[1]], (PI()/2.0, qs[0]));
            Controlled R1([qs[2]], (PI()/4.0, qs[0]));

Observera att PI() funktionen från Microsoft.Quantum.Math namnområdet används för att definiera rotationerna i termer av pi-radianer. Dessutom dividerar koderna med en (till exempel ) eftersom division med Double 2.0 ett heltal skulle ge ett 2 typfel.

Tips

R1(π/2) och R1(π/4) motsvarar åtgärderna och S T (även i Microsoft.Quantum.Intrinsic ).

När du har H tillämpat relevanta åtgärder och kontrollerade rotationer på den andra och tredje qubits:

            //second qubit:
            H(qs[1]);
            Controlled R1([qs[2]], (PI()/2.0, qs[1]));

            //third qubit:
            H(qs[2]);

Du behöver bara tillämpa en SWAP grind för att slutföra kretsen:

            SWAP(qs[2], qs[0]);

Detta är nödvändigt eftersom typen av Fourier-transformering matar ut kvantbitarna i omvänd ordning, så växlingarna möjliggör sömlös integrering av subrutinen i större algoritmer.

Därför har du slutfört skrivningen av åtgärder på qubitnivå för kvant-Fourier-transformeringen till vår Q# åtgärd:

Three qubit quantum Fourier transform circuit diagram

Qubitarna var dock i tillståndet $\ket $ när vi allokerade dem, så du måste ta bort dem för att kunna återställa {0} det ursprungliga tillståndet.

Avallokera qubitar

Anropa igen för att se tillståndet efter åtgärden och tillämpa slutligen på qubitregistret för att återställa våra DumpMachine() ResetAll qubitar till $\ket $ innan {0} du slutför åtgärden:

            Message("After:");
            DumpMachine();

            ResetAll(qs);

Att kräva att alla frisatta qubits uttryckligen anges till $\ket $ är en grundläggande funktion i , eftersom det gör att andra åtgärder kan känna till deras tillstånd exakt när de börjar använda samma {0} Q# qubitar (en begränsad resurs). Dessutom säkerställer detta att de inte är sammantrasslade med andra qubitar i systemet. Om återställningen inte utförs i slutet av ett use allokeringsblock kan ett körningsfel visas.

Den fullständiga Q# filen bör nu se ut så här:

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    operation Perform3qubitQFT() : Unit {

        use qs = Qubit[3];

        Message("Initial state |000>:");
        DumpMachine();

        //QFT:
        //first qubit:
        H(qs[0]);
        Controlled R1([qs[1]], (PI()/2.0, qs[0]));
        Controlled R1([qs[2]], (PI()/4.0, qs[0]));

        //second qubit:
        H(qs[1]);
        Controlled R1([qs[2]], (PI()/2.0, qs[1]));

        //third qubit:
        H(qs[2]);

        SWAP(qs[2], qs[0]);

        Message("After:");
        DumpMachine();

        ResetAll(qs);

    }
}

När Q# filen och åtgärden är klar är kvantprogrammet redo att anropas och simuleras.

Köra programmet

När du har Q# definierat åtgärden i en fil måste du nu anropa åtgärden och observera eventuella .qs returnerade klassiska data. För tillfället finns det inget som returneras (kom ihåg att vår åtgärd som definieras ovan returnerar ), men när du senare ändrar åtgärden för att returnera en matris med måttresultat ( ), kommer detta Unit Q# att Result[] adresseras.

Programmet är allmänt förekommande i de miljöer som används för att anropa det, men sättet Q# att göra det varierar naturligtvis. Därför följer du bara anvisningarna på fliken som motsvarar din konfiguration: arbeta från programmet eller använda ett Q# värdprogram i Python eller C#.

Om du Q# kör programmet från kommandotolken behöver du bara göra en liten ändring i Q# filen.

Lägg bara @EntryPoint() till i en rad som föregår åtgärdsdefinitionen:

    @EntryPoint()
    operation Perform3qubitQFT() : Unit {
        // ...

Om du vill köra programmet öppnar du terminalen i mappen för projektet och anger

dotnet run

När du är klar bör du se Message DumpMachine utdata och nedan i konsolen.

Initial state |000>:
# wave function for qubits with ids (least to most significant): 0;1;2
|0>:     1.000000 +  0.000000 i  ==     ******************** [ 1.000000 ]     --- [  0.00000 rad ]
|1>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]                   
|2>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]                   
|3>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]                   
|4>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]                   
|5>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]                   
|6>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]                   
|7>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]                   
After:
# wave function for qubits with ids (least to most significant): 0;1;2
|0>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|1>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|2>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|3>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|4>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|5>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|6>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|7>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]

När det anropas i simulatorn med fullständigt tillstånd DumpMachine() tillhandahåller dessa flera representationer av kvanttillståndet vågfunktion. Möjliga tillstånd för ett $n$-qubitsystem kan representeras av $2^n$ beräkningsgrund tillstånd, var och en med en motsvarande komplex koefficient (helt enkelt en amplitud och en fas). Beräkningsgrunds tillstånden motsvarar alla möjliga binära strängar med längden $n$---som är alla möjliga kombinationer av qubit-tillstånd $\ket $ och $\ket $, där varje binär siffra motsvarar en enskild {0} {1} qubit.

Den första raden innehåller en kommentar med DED:erna för motsvarande qubits i sin betydande ordning. Qubit är den "viktigaste" innebär helt enkelt att i den binära representationen av bastillståndsvektorn 2 $\ket{i}$, motsvarar tillståndet för qubit den vänstra 2 siffran. Till exempel består $\ket = \ket $ av qubitar och både i {6} {110} 2 1 $\ket $ och {1} qubit 0 i $\ket {0} $.

Resten av raderna beskriver sannolikhets amplituden för mätning av bastillståndsvektorn $\ket{i}$ i både kartesiska och polära format. I detalj för den första raden i indatatillståndet $\ket {000} $:

  • |0>: Den här raden motsvarar beräkningsgrundtillståndet (eftersom vårt ursprungliga tillstånd efter allokeringen var $\ket $, förväntar vi oss att detta är det enda tillståndet med 0 sannolikhets amplitud i det här {000} läget).
  • 1.000000 + 0.000000 i: sannolikhets amplituden i kartesiskt format.
  • ==: equal -tecknet separerar båda motsvarande representationer.
  • ********************: En grafisk representation av storleken, talet är * proportionellt mot sannolikheten att mäta den här tillståndsvektorn.
  • [ 1.000000 ]: det numeriska värdet för storleken
  • ---: En grafisk representation av amplitudens fas.
  • [ 0.0000 rad ]: det numeriska värdet för fasen (i radianer).

Både storleken och fasen visas med en grafisk representation. Storleksrepresentationen är enkel: den visar en stapel på , och ju högre * sannolikhet, desto större blir fältet. Mer information om fasen finns i Testa och felsöka: dumpa funktioner för möjliga symbolrepresentationer baserat på vinkelintervall.

Utdatan visar alltså att våra programmerade grindar omvandlade vårt tillstånd från

$$ \ket{\psi} _ {initial} = \ket {000} $$

$$ \begin{align} \ket{\psi} _ {final} &= \frac {1} {\sqrt {8} } \left( \ket {000} + \ket {001} + \ket {010} + \ket {011} + \ket + \ket {100} + \ket + \ket {101} + \ket {110} + \ket {111} \right) \ \ &= \frac {1} {\sqrt{2^n}}\sum _ {j=0}^{2^n-1} \ket{j}, \end{align} $$

vilket är exakt beteendet för 3-qubit Fourier-transformen.

Om du är nyfiken på hur andra indata tillstånd påverkas rekommenderar vi att du går runt och använder qubitåtgärder före transformeringen.

Lägga till mått

Tyvärr säger en hörnsten i kvantmekanik att ett verkligt kvantsystem inte kan ha en sådan DumpMachine funktion. I stället extraheras informationen via mätningar, som i allmänhet inte bara ger oss det fullständiga kvanttillståndet, utan även drastiskt kan ändra själva systemet. Det finns många typer av kvantmått, men vi fokuserar på de mest grundläggande: projektiva mätningar på enskilda kvantbitar. Vid mätning i en viss grund (till exempel beräkningsgrunden $ \ket , \ket $) projiceras qubittillståndet till det bastillstånd som { mättes---hence förstör all superposition mellan de {0} {1} } två.

Om du vill implementera mått i Q# ett program använder du åtgärden M (från ), som Microsoft.Quantum.Intrinsic returnerar en Result typ.

Ändra först åtgärden så Perform3QubitQFT att den returnerar en matris med måttresultat, Result[] , i stället för Unit .

    operation Perform3QubitQFT() : Result[] {

Definiera och initiera Result[] matris

Innan du till och med allokerar qubits (till exempel före -instruktionen) deklarerar och binder du matrisen use length-3 (en Result för varje qubit):

        mutable resultArray = new Result[3];

Nyckelordet prefacing gör att variabeln kan rebound senare i mutable resultArray koden---till exempel när du lägger till våra måttresultat.

Utföra mått i en for loop och lägga till resultat i matrisen

Efter fourier-transformeringsåtgärder infogar du följande kod:

            for i in IndexRange(qs) {
                set resultArray w/= i <- M(qs[i]);
            }

Funktionen som anropas på en matris (till exempel vår matris med qubitar, ) returnerar ett intervall IndexRange qs över indexen i matrisen. Här används den i loopen för for att sekventiellt mäta varje qubit med hjälp av M(qs[i]) -instruktionen. Varje uppmätt typ (antingen eller ) läggs sedan till i motsvarande indexposition i med Result Zero en One resultArray update-and-reassign-instruktion.

Anteckning

Syntaxen för den här instruktionen är unik för , men motsvarar den liknande variabelomtilldeling som visas på andra språk Q# resultArray[i] <- M(qs[i]) som F# och R.

Nyckelordet set används alltid för att omtilldela variabler som binds med hjälp av mutable .

Återvända resultArray

När alla tre qubits har mätts och resultaten har lagts till i kan du återställa och ta bort resultArray qubitarna som tidigare. Om du vill returnera måtten infogar du:

        return resultArray;

Förstå effekterna av mätning

Nu ska vi ändra placeringen av våra DumpMachine funktioner för att mata ut tillståndet före och efter mätningarna. Den slutliga åtgärdskoden bör se ut så här:

    operation Perform3QubitQFT() : Result[] {

        mutable resultArray = new Result[3];

        use qs = Qubit[3];

        //QFT:
        //first qubit:
        H(qs[0]);
        Controlled R1([qs[1]], (PI()/2.0, qs[0]));
        Controlled R1([qs[2]], (PI()/4.0, qs[0]));

        //second qubit:
        H(qs[1]);
        Controlled R1([qs[2]], (PI()/2.0, qs[1]));

        //third qubit:
        H(qs[2]);

        SWAP(qs[2], qs[0]);

        Message("Before measurement: ");
        DumpMachine();

        for i in IndexRange(qs) {
            set resultArray w/= i <- M(qs[i]);
        }

        Message("After measurement: ");
        DumpMachine();

        ResetAll(qs);

        return resultArray;

    }

Om du arbetar från kommandotolken visas den returnerade matrisen helt enkelt direkt i konsolen i slutet av körningen. Annars uppdaterar du värdprogrammet för att bearbeta den returnerade matrisen.

Om du vill ha mer förståelse för den returnerade matrisen som ska skrivas ut i konsolen kan du lägga till en till i Message Q# filen precis före return -instruktionen:

        Message("Post-QFT measurement results [qubit0, qubit1, qubit2]: ");
        return resultArray;

Kör projektet så bör dina utdata se ut ungefär så här:

Before measurement: 
# wave function for qubits with ids (least to most significant): 0;1;2
|0>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|1>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|2>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|3>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|4>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|5>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|6>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|7>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
After measurement:
# wave function for qubits with ids (least to most significant): 0;1;2
|0>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|1>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|2>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|3>:     1.000000 +  0.000000 i  ==     ******************** [ 1.000000 ]     --- [  0.00000 rad ]
|4>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|5>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|6>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|7>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]

Post-QFT measurement results [qubit0, qubit1, qubit2]: 
[One,One,Zero]

Dessa utdata illustrerar några olika saker:

  1. Om du jämför det returnerade resultatet med förmätningen visas inte DumpMachine superpositionen efter QFT över bas tillstånd. Ett mått returnerar bara ett enda grundtillstånd, med en sannolikhet som bestäms av amplituden för det tillståndet i systemets vågfunktion.
  2. Från eftermätningen ser vi att mätningen ändrar själva tillståndet och projicerar det från den ursprungliga superpositionen över grundtillstånd till det enskilda bastillståndet som motsvarar det DumpMachine uppmätta värdet.

Om du skulle upprepa den här åtgärden många gånger skulle du se att resultatstatistiken börjar illustrera den lika viktade superpositionen för det post-QFT-tillstånd som ger upphov till ett slumpmässigt resultat på varje bild. Förutom att det är ineffektivt och ändå inte är perfekt, skulle detta dock bara återskapa de relativa amplituderna för grund tillstånden, inte de relativa faserna mellan dem. Det senare är inte ett problem i det här exemplet, men du ser relativa faser om de ges mer komplexa indata till QFT än $\ket {000} $.

Partiella mått

Om du vill utforska hur mätning av endast vissa qubitar i registret kan påverka systemets tillstånd kan du prova att lägga till följande inuti for loopen efter måttlinjen:

                let iString = IntAsString(i);
                Message("After measurement of qubit " + iString + ":");
                DumpMachine();

Observera att du måste lägga IntAsString till för att få åtkomst till funktionen

    open Microsoft.Quantum.Convert;

med resten av open namnområdessatserna.

I de resulterande utdata ser du den gradvisa projektionen i delområden när varje qubit mäts.

Använda Q# biblioteken

Som vi nämnde i introduktionen vilar mycket av kraften i det faktum att det gör att du kan abstrahera bort oron med att hantera Q# enskilda qubits. Om du vill utveckla fullskaliga, tillämpliga kvantprogram skulle det faktiskt göra dig långsammare om en åtgärd utförs före eller H efter en viss rotation.

Biblioteken Q# innehåller QFT-åtgärden, som du bara kan använda och använda för val av antal qubitar. För att göra ett försök definierar du en ny åtgärd i filen som har samma innehåll, men med allt från den första till den ersatta med Q# Perform3QubitQFT två enkla H SWAP rader:

            let register = BigEndian(qs);    //from Microsoft.Quantum.Arithmetic
            QFT(register);                   //from Microsoft.Quantum.Canon

Den första raden skapar helt enkelt ett uttryck för den allokerade matrisen med qubitar, , vilket är vad BigEndian qs QFT-åtgärden tar som ett argument. Detta motsvarar indexordningen för qubitarna i registret.

Om du vill ha åtkomst till dessa åtgärder open lägger du till instruktioner för respektive namnrymd i början av Q# filen:

    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Arithmetic;

Justera nu värdprogrammet så att det anropar namnet på den nya åtgärden (t.ex. PerformIntrinsicQFT ), och ge det en rl.

Om du vill se den verkliga fördelen med Q# att använda biblioteksåtgärder ändrar du antalet qubitar till något annat än 3 :

        mutable resultArray = new Result[4];

        use qs = Qubit[4];
        //...

Du kan därför använda rätt QFT för ett visst antal qubits utan att behöva bekymra dig om röran med nya åtgärder och rotationer på varje H qubit.

Observera att kvantsimulatorn tar exponentiellt mer tid att köra när du ökar antalet kvantbitar---förklara varför vi ser fram emot verklig kvantmaskinvara!