Ćwiczenie — szacowanie zasobów pod kątem rzeczywistego problemu

Ukończone

W poprzedniej lekcji przedstawiono sposób korzystania z narzędzia do szacowania zasobów usługi Azure Quantum oraz sposobu eksplorowania danych wyjściowych.

W tej lekcji oszacujesz zasoby wymagane do liczenia 2048-bitowej liczby całkowitej za pomocą algorytmu Shora. Algorytm faktorowania Shora jest jednym z najbardziej znanych algorytmów kwantowych. Oferuje szybkość wykładniczą w przypadku dowolnego znanego klasycznego algorytmu faktorowania.

Kryptografia klasyczna używa metod fizycznych lub matematycznych, takich jak trudności obliczeniowe, do wykonania zadania. Popularnym protokołem kryptograficznym jest schemat Rivest-Shamir-Adleman (RSA), który opiera się na założeniu, że trudno jest znaleźć czynniki liczby głównej bardzo dużej liczby całkowitej na komputerze klasycznym.

Algorytm Shora sugeruje, że wystarczająco duże komputery kwantowe mogą przerwać kryptografię klucza publicznego. Aby ocenić lukę w zabezpieczeniach kryptografii klucza publicznego, ważne jest oszacowanie zasobów wymaganych do uruchomienia algorytmu Shora.

W poniższym ćwiczeniu obliczysz szacowane zasoby dla algorytmu Shora, aby uwzględnić liczbę całkowitą 2048-bitową. W przypadku tej aplikacji obliczasz szacunkowe zasoby fizyczne bezpośrednio z wstępnie skompilowanych oszacowań zasobów logicznych. W przypadku budżetu błędów należy użyć $\epsilon = 1/3$.

Pisanie algorytmu Shora

Aby utworzyć algorytm Shora w języku Q#, wykonaj następujące kroki:

  1. Otwórz program Visual Studio Code (VS Code).

  2. Otwórz menu Widok , a następnie wybierz pozycję Paleta poleceń. Zostanie wyświetlone pole wejściowe.

  3. W polu wejściowym wprowadź i wybierz pozycję Utwórz: Nowy Jupyter Notebook.

  4. Zapisz notes jako ShorRE.ipynb.

  5. W pierwszej komórce zaimportuj pakiet qsharp i funkcję EstimateDetails.

    import qsharp
    from qsharp_widgets import EstimateDetails
    
  6. Dodaj nową komórkę, a następnie skopiuj następujący kod algorytmu Shora do tej komórki:

    %%qsharp
    open Microsoft.Quantum.Arrays;
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Convert;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Measurement;
    open Microsoft.Quantum.Unstable.Arithmetic;
    open Microsoft.Quantum.ResourceEstimation;
    
    operation RunProgram() : Unit {
        let bitsize = 31;
    
        // When choosing parameters for `EstimateFrequency`, make sure that
        // generator and modules are not co-prime
        let _ = EstimateFrequency(11, 2^bitsize - 1, bitsize);
    }
    
    
    // In this sample we concentrate on costing the `EstimateFrequency`
    // operation, which is the core quantum operation in Shor's algorithm, and
    // we omit the classical pre- and post-processing.
    
    /// # Summary
    /// Estimates the frequency of a generator
    /// in the residue ring Z mod `modulus`.
    ///
    /// # Input
    /// ## generator
    /// The unsigned integer multiplicative order (period)
    /// of which is being estimated. Must be co-prime to `modulus`.
    /// ## modulus
    /// The modulus which defines the residue ring Z mod `modulus`
    /// in which the multiplicative order of `generator` is being estimated.
    /// ## bitsize
    /// Number of bits needed to represent the modulus.
    ///
    /// # Output
    /// The numerator k of dyadic fraction k/2^bitsPrecision
    /// approximating s/r.
    operation EstimateFrequency(
        generator : Int,
        modulus : Int,
        bitsize : Int
    )
    : Int {
        mutable frequencyEstimate = 0;
        let bitsPrecision =  2 * bitsize + 1;
    
        // Allocate qubits for the superposition of eigenstates of
        // the oracle that is used in period finding.
        use eigenstateRegister = Qubit[bitsize];
    
        // Initialize eigenstateRegister to 1, which is a superposition of
        // the eigenstates we are estimating the phases of.
        // We first interpret the register as encoding an unsigned integer
        // in little endian encoding.
        ApplyXorInPlace(1, eigenstateRegister);
        let oracle = ApplyOrderFindingOracle(generator, modulus, _, _);
    
        // Use phase estimation with a semiclassical Fourier transform to
        // estimate the frequency.
        use c = Qubit();
        for idx in bitsPrecision - 1..-1..0 {
            within {
                H(c);
            } apply {
                // `BeginEstimateCaching` and `EndEstimateCaching` are the operations
                // exposed by the Azure Quantum Resource Estimator. These will instruct
                // resource counting such that the if-block will be executed
                // only once, its resources will be cached, and appended in
                // every other iteration.
                if BeginEstimateCaching("ControlledOracle", SingleVariant()) {
                    Controlled oracle([c], (1 <<< idx, eigenstateRegister));
                    EndEstimateCaching();
                }
                R1Frac(frequencyEstimate, bitsPrecision - 1 - idx, c);
            }
            if MResetZ(c) == One {
                set frequencyEstimate += 1 <<< (bitsPrecision - 1 - idx);
            }
        }
    
        // Return all the qubits used for oracles eigenstate back to 0 state
        // using Microsoft.Quantum.Intrinsic.ResetAll.
        ResetAll(eigenstateRegister);
    
        return frequencyEstimate;
    }
    
    /// # Summary
    /// Interprets `target` as encoding unsigned little-endian integer k
    /// and performs transformation |k⟩ ↦ |gᵖ⋅k mod N ⟩ where
    /// p is `power`, g is `generator` and N is `modulus`.
    ///
    /// # Input
    /// ## generator
    /// The unsigned integer multiplicative order ( period )
    /// of which is being estimated. Must be co-prime to `modulus`.
    /// ## modulus
    /// The modulus which defines the residue ring Z mod `modulus`
    /// in which the multiplicative order of `generator` is being estimated.
    /// ## power
    /// Power of `generator` by which `target` is multiplied.
    /// ## target
    /// Register interpreted as LittleEndian which is multiplied by
    /// given power of the generator. The multiplication is performed modulo
    /// `modulus`.
    internal operation ApplyOrderFindingOracle(
        generator : Int, modulus : Int, power : Int, target : Qubit[]
    )
    : Unit
    is Adj + Ctl {
        // The oracle we use for order finding implements |x⟩ ↦ |x⋅a mod N⟩. We
        // also use `ExpModI` to compute a by which x must be multiplied. Also
        // note that we interpret target as unsigned integer in little-endian
        // encoding by using the `LittleEndian` type.
        ModularMultiplyByConstant(modulus,
                                    ExpModI(generator, power, modulus),
                                    target);
    }
    
    /// # Summary
    /// Performs modular in-place multiplication by a classical constant.
    ///
    /// # Description
    /// Given the classical constants `c` and `modulus`, and an input
    /// quantum register (as LittleEndian) |𝑦⟩, this operation
    /// computes `(c*x) % modulus` into |𝑦⟩.
    ///
    /// # Input
    /// ## modulus
    /// Modulus to use for modular multiplication
    /// ## c
    /// Constant by which to multiply |𝑦⟩
    /// ## y
    /// Quantum register of target
    internal operation ModularMultiplyByConstant(modulus : Int, c : Int, y : Qubit[])
    : Unit is Adj + Ctl {
        use qs = Qubit[Length(y)];
        for (idx, yq) in Enumerated(y) {
            let shiftedC = (c <<< idx) % modulus;
            Controlled ModularAddConstant([yq], (modulus, shiftedC, qs));
        }
        ApplyToEachCA(SWAP, Zipped(y, qs));
        let invC = InverseModI(c, modulus);
        for (idx, yq) in Enumerated(y) {
            let shiftedC = (invC <<< idx) % modulus;
            Controlled ModularAddConstant([yq], (modulus, modulus - shiftedC, qs));
        }
    }
    
    /// # Summary
    /// Performs modular in-place addition of a classical constant into a
    /// quantum register.
    ///
    /// # Description
    /// Given the classical constants `c` and `modulus`, and an input
    /// quantum register (as LittleEndian) |𝑦⟩, this operation
    /// computes `(x+c) % modulus` into |𝑦⟩.
    ///
    /// # Input
    /// ## modulus
    /// Modulus to use for modular addition
    /// ## c
    /// Constant to add to |𝑦⟩
    /// ## y
    /// Quantum register of target
    internal operation ModularAddConstant(modulus : Int, c : Int, y : Qubit[])
    : Unit is Adj + Ctl {
        body (...) {
            Controlled ModularAddConstant([], (modulus, c, y));
        }
        controlled (ctrls, ...) {
            // We apply a custom strategy to control this operation instead of
            // letting the compiler create the controlled variant for us in which
            // the `Controlled` functor would be distributed over each operation
            // in the body.
            //
            // Here we can use some scratch memory to save ensure that at most one
            // control qubit is used for costly operations such as `AddConstant`
            // and `CompareGreaterThenOrEqualConstant`.
            if Length(ctrls) >= 2 {
                use control = Qubit();
                within {
                    Controlled X(ctrls, control);
                } apply {
                    Controlled ModularAddConstant([control], (modulus, c, y));
                }
            } else {
                use carry = Qubit();
                Controlled AddConstant(ctrls, (c, y + [carry]));
                Controlled Adjoint AddConstant(ctrls, (modulus, y + [carry]));
                Controlled AddConstant([carry], (modulus, y));
                Controlled CompareGreaterThanOrEqualConstant(ctrls, (c, y, carry));
            }
        }
    }
    
    /// # Summary
    /// Performs in-place addition of a constant into a quantum register.
    ///
    /// # Description
    /// Given a non-empty quantum register |𝑦⟩ of length 𝑛+1 and a positive
    /// constant 𝑐 < 2ⁿ, computes |𝑦 + c⟩ into |𝑦⟩.
    ///
    /// # Input
    /// ## c
    /// Constant number to add to |𝑦⟩.
    /// ## y
    /// Quantum register of second summand and target; must not be empty.
    internal operation AddConstant(c : Int, y : Qubit[]) : Unit is Adj + Ctl {
        // We are using this version instead of the library version that is based
        // on Fourier angles to show an advantage of sparse simulation in this sample.
    
        let n = Length(y);
        Fact(n > 0, "Bit width must be at least 1");
    
        Fact(c >= 0, "constant must not be negative");
        Fact(c < 2 ^ n, $"constant must be smaller than {2L ^ n}");
    
        if c != 0 {
            // If c has j trailing zeroes than the j least significant bits
            // of y will not be affected by the addition and can therefore be
            // ignored by applying the addition only to the other qubits and
            // shifting c accordingly.
            let j = NTrailingZeroes(c);
            use x = Qubit[n - j];
            within {
                ApplyXorInPlace(c >>> j, x);
            } apply {
                IncByLE(x, y[j...]);
            }
        }
    }
    
    /// # Summary
    /// Performs greater-than-or-equals comparison to a constant.
    ///
    /// # Description
    /// Toggles output qubit `target` if and only if input register `x`
    /// is greater than or equal to `c`.
    ///
    /// # Input
    /// ## c
    /// Constant value for comparison.
    /// ## x
    /// Quantum register to compare against.
    /// ## target
    /// Target qubit for comparison result.
    ///
    /// # Reference
    /// This construction is described in [Lemma 3, arXiv:2201.10200]
    internal operation CompareGreaterThanOrEqualConstant(c : Int, x : Qubit[], target : Qubit)
    : Unit is Adj+Ctl {
        let bitWidth = Length(x);
    
        if c == 0 {
            X(target);
        } elif c >= 2 ^ bitWidth {
            // do nothing
        } elif c == 2 ^ (bitWidth - 1) {
            ApplyLowTCNOT(Tail(x), target);
        } else {
            // normalize constant
            let l = NTrailingZeroes(c);
    
            let cNormalized = c >>> l;
            let xNormalized = x[l...];
            let bitWidthNormalized = Length(xNormalized);
            let gates = Rest(IntAsBoolArray(cNormalized, bitWidthNormalized));
    
            use qs = Qubit[bitWidthNormalized - 1];
            let cs1 = [Head(xNormalized)] + Most(qs);
            let cs2 = Rest(xNormalized);
    
            within {
                for i in IndexRange(gates) {
                    (gates[i] ? ApplyAnd | ApplyOr)(cs1[i], cs2[i], qs[i]);
                }
            } apply {
                ApplyLowTCNOT(Tail(qs), target);
            }
        }
    }
    
    /// # Summary
    /// Internal operation used in the implementation of GreaterThanOrEqualConstant.
    internal operation ApplyOr(control1 : Qubit, control2 : Qubit, target : Qubit) : Unit is Adj {
        within {
            ApplyToEachA(X, [control1, control2]);
        } apply {
            ApplyAnd(control1, control2, target);
            X(target);
        }
    }
    
    internal operation ApplyAnd(control1 : Qubit, control2 : Qubit, target : Qubit)
    : Unit is Adj {
        body (...) {
            CCNOT(control1, control2, target);
        }
        adjoint (...) {
            H(target);
            if (M(target) == One) {
                X(target);
                CZ(control1, control2);
            }
        }
    }
    
    
    /// # Summary
    /// Returns the number of trailing zeroes of a number
    ///
    /// ## Example
    /// ```qsharp
    /// let zeroes = NTrailingZeroes(21); // = NTrailingZeroes(0b1101) = 0
    /// let zeroes = NTrailingZeroes(20); // = NTrailingZeroes(0b1100) = 2
    /// ```
    internal function NTrailingZeroes(number : Int) : Int {
        mutable nZeroes = 0;
        mutable copy = number;
        while (copy % 2 == 0) {
            set nZeroes += 1;
            set copy /= 2;
        }
        return nZeroes;
    }
    
    /// # Summary
    /// An implementation for `CNOT` that when controlled using a single control uses
    /// a helper qubit and uses `ApplyAnd` to reduce the T-count to 4 instead of 7.
    internal operation ApplyLowTCNOT(a : Qubit, b : Qubit) : Unit is Adj+Ctl {
        body (...) {
            CNOT(a, b);
        }
    
        adjoint self;
    
        controlled (ctls, ...) {
            // In this application this operation is used in a way that
            // it is controlled by at most one qubit.
            Fact(Length(ctls) <= 1, "At most one control line allowed");
    
            if IsEmpty(ctls) {
                CNOT(a, b);
            } else {
                use q = Qubit();
                within {
                    ApplyAnd(Head(ctls), a, q);
                } apply {
                    CNOT(q, b);
                }
            }
        }
    
        controlled adjoint self;
    }
    

Szacowanie wymagań dotyczących zasobów algorytmu Shora

Masz teraz kod algorytmu Shora. Nawet jeśli nie rozumiesz dokładnie, jak działa kod, nadal możesz przekazać algorytm do narzędzia do szacowania zasobów, aby ocenić, jak można go uruchomić na komputerze kwantowym. Aby rozpocząć, szacuj zasoby wymagane do uruchomienia algorytmu z wartościami domyślnymi parametrów narzędzia do szacowania zasobów.

Dodaj nową komórkę, a następnie skopiuj i uruchom następujący kod w tej komórce:

result = qsharp.estimate("RunProgram()")

EstimateDetails(result)

Funkcja qsharp.estimate tworzy obiekt wynikowy zawierający informacje z narzędzia do szacowania zasobów. Przekazujemy result do funkcji EstimateDetails, która wyświetla zestaw tabel na listach rozwijanych zawierających dane wyjściowe z narzędzia do szacowania zasobów (Resource Estimator).

Na przykład rozwiń grupę Parametry kubitu logicznego , aby zobaczyć, że odległość kodu wynosi 21, a liczba kubitów fizycznych to 882.

Parametr kubitu logicznego Wartość
Schemat QEC kod_powierzchniowy
Odległość kodu dwadzieścia jeden
Kubity fizyczne 882
Czas cyklu logicznego 8 mikrosektów
Szybkość błędów kubitu logicznego 3.00e-13
Wstępna przeprawa 0.03
Próg korekty błędu 0,01
Formuła czasu cyklu logicznego (4 * twoQubitGateTime + 2 * oneQubitMeasurementTime) * codeDistance
Formuła kubitów fizycznych 2 * codeDistance * codeDistance

Wizualizowanie diagramu przestrzeni

Niektóre zagadnienia dotyczące zasobów, które mogą mieć wpływ na projekt algorytmu, obejmują rozkład fizycznych kubitów i liczbę kubitów potrzebnych dla fabryk T. Aby zwizualizować tę dystrybucję i lepiej zrozumieć szacowane wymagania dotyczące przestrzeni algorytmu, skorzystaj z pakietu qsharp-widgets.

Dodaj nową komórkę, a następnie skopiuj i uruchom następujący kod w tej komórce:

from qsharp_widgets import SpaceChart

SpaceChart(result)

Ta implementacja algorytmu Shora wymaga w sumie 829 766 kubitów fizycznych do uruchomienia, z czego 196 686 to kubity algorytmu, a 633 080 to kubity fabryki T.

Zrzut ekranu przedstawiający diagram przestrzeni narzędzia do szacowania zasobów.

Porównanie oszacowań zasobów dla różnych technologii kubitów

Narzędzie do szacowania zasobów umożliwia uruchamianie wielu konfiguracji parametrów docelowych i porównywanie wyników dla każdej konfiguracji. Jest to przydatne, gdy chcesz porównać koszt różnych modeli kubitów, schematów QEC lub budżetów błędów.

Możesz również utworzyć listę parametrów szacowania przy użyciu obiektu EstimatorParams. Aby porównać oszacowania, dodaj nową komórkę, a następnie skopiuj i uruchom następujący kod w tej komórce:

from qsharp.estimator import EstimatorParams, QubitParams, QECScheme, LogicalCounts

labels = ["Gate-based µs, 10⁻³", "Gate-based µs, 10⁻⁴", "Gate-based ns, 10⁻³", "Gate-based ns, 10⁻⁴", "Majorana ns, 10⁻⁴", "Majorana ns, 10⁻⁶"]

params = EstimatorParams(6)
params.error_budget = 0.333
params.items[0].qubit_params.name = QubitParams.GATE_US_E3
params.items[1].qubit_params.name = QubitParams.GATE_US_E4
params.items[2].qubit_params.name = QubitParams.GATE_NS_E3
params.items[3].qubit_params.name = QubitParams.GATE_NS_E4
params.items[4].qubit_params.name = QubitParams.MAJ_NS_E4
params.items[4].qec_scheme.name = QECScheme.FLOQUET_CODE
params.items[5].qubit_params.name = QubitParams.MAJ_NS_E6
params.items[5].qec_scheme.name = QECScheme.FLOQUET_CODE

qsharp.estimate("RunProgram()", params=params).summary_data_frame(labels=labels)

Tabela jest pobierana jako dane wyjściowe zawierające oszacowania zasobów dla każdego modelu:

Model kubitu Kubity logiczne Głębokość logiczna Stany T Odległość kodu Fabryki T Ułamek fabryki T Kubity fizyczne Środowisko uruchomieniowe fizyczne
Oparte na bramie μs, 10⁻³ 223 3,64 mln 4,70 mln 17 13 40.54 % 216,77 tys. 10 godzin
Oparte na bramie μs, 10⁻⁴ 223 3,64 mln 4,70 mln 9 14 43.17 % 63,57 tys. 5 godzin
Ns oparte na bramie, 10⁻³ 223 3,64 mln 4,70 mln 17 16 69.08 % 416.89k 25 s
Ns oparte na bramie, 10⁻⁴ 223 3,64 mln 4,70 mln 9 14 43.17 % 63,57 tys. 13 s
Majorana ns, 10⁻⁴ 223 3,64 mln 4,70 mln 9 19 82.75 % 501.48k 10 sekund
Majorana ns, 10⁻⁶ 223 3,64 mln 4,70 mln 5 13 31.47 % 42,96 tys. 5 s

Wyodrębnianie oszacowań zasobów z liczby zasobów logicznych

Jeśli znasz już pewne oszacowania dla operacji, narzędzie do szacowania zasobów umożliwia uwzględnienie znanych szacunków do ogólnego kosztu programu, co skraca czas wykonywania narzędzia do szacowania zasobów. LogicalCounts Użyj klasy , aby wyodrębnić oszacowania zasobów logicznych z wstępnie obliczonych wartości szacowania zasobów.

Dodaj nową komórkę, a następnie skopiuj i uruchom następujący kod w tej komórce:

logical_counts = LogicalCounts({
    'numQubits': 12581,
    'tCount': 12,
    'rotationCount': 12,
    'rotationDepth': 12,
    'cczCount': 3731607428,
    'measurementCount': 1078154040})

logical_counts.estimate(params).summary_data_frame(labels=labels)

Na wartości w nowej tabeli porównania mają wpływ wstępnie obliczone wartości przekazane do klasy LogicalCounts.

Model kubitu Kubity logiczne Głębokość logiczna Stany T Odległość kodu Fabryki T Ułamek fabryki T Kubity fizyczne Środowisko uruchomieniowe fizyczne
Oparte na bramie μs, 10⁻³ 25481 1.2e+10 1.5e+10 27 13 0,6% 37,38 mln 6 lat
Oparte na bramie μs, 10⁻⁴ 25481 1.2e+10 1.5e+10 13 14 0,8% 8,68 mln 3 lata
Ns oparte na bramie, 10⁻³ 25481 1.2e+10 1.5e+10 27 15 1,3% 37,65 mln 2 dni
Ns oparte na bramie, 10⁻⁴ 25481 1.2e+10 1.5e+10 13 18 1,2% 8,72 mln 18 godz.
Majorana ns, 10⁻⁴ 25481 1.2e+10 1.5e+10 15 15 1,3% 26,11 mln 15 godzin
Majorana ns, 10⁻⁶ 25481 1.2e+10 1.5e+10 7 13 0,5% 6,25 mln 7 godzin

Podsumowanie

W najgorszym scenariuszu komputer kwantowy, który korzysta z kubitów bazujących na bramkach (kubitów, które mają czasy operacji w reżimie mikrosekund, takich jak nadprzewodzące kubity) i kodu QEC dla powierzchni, potrzebowałby sześciu lat i 37,38 miliona kubitów, aby rozłożyć na czynniki pierwsze 2048-bitową liczbę całkowitą z wykorzystaniem algorytmu Shora.

Jeśli używasz tego samego kodu powierzchni z inną technologią kubitu, na przykład kubitów jonowych opartych na bramkach, liczba kubitów nie zmienia się zbytnio, ale czas wykonania wynosi w najgorszym scenariuszu dwa dni, a w optymistycznym 18 godzin. Jeśli zmienisz technologię kubitu i kod QEC, na przykład na kubity oparte na majoranie, możesz rozłożyć na czynniki 2048-bitową liczbę całkowitą z algorytmem Shora w ciągu kilku godzin, z użyciem tablicy 6,25 miliona kubitów w najlepszym przypadku.

Z eksperymentu wynika, że komputer kwantowy z kubitami Majorana i kodem QEC Floquet jest najlepszym wyborem do uruchomienia algorytmu Shora do faktoryzacji liczby całkowitej 2048-bitowej.