Udostępnij za pośrednictwem


Deklaracje specjalizacji

Jak wyjaśniono w sekcji dotyczącej deklaracji wywoływanych, nie ma obecnie powodu jawnego deklarowania specjalizacji funkcji. W tym temacie opisano operacje i opisano sposób deklarowania niezbędnych specjalizacji do obsługi niektórych funktorów.

Jest to dość powszechny problem w obliczeniach kwantowych, który wymaga przymiotu danej transformacji. Wiele algorytmów kwantowych wymaga zarówno operacji, jak i jej przylegania do wykonywania obliczeń. Q# Wykorzystuje obliczenia symboliczne, które mogą automatycznie generować odpowiednią implementację przyleganą dla określonej implementacji treści. Ta generacja jest możliwa nawet w przypadku implementacji, które swobodnie mieszają obliczenia klasyczne i kwantowe. Istnieją jednak pewne ograniczenia, które mają zastosowanie w tym przypadku. Na przykład automatyczne generowanie nie jest obsługiwane ze względu na wydajność, jeśli implementacja korzysta ze zmiennych modyfikowalnych. Ponadto każda operacja wywoływana w obrębie treści generuje odpowiednie elementy, które muszą obsługiwać Adjoint sam functor.

Mimo że nie można łatwo cofnąć pomiarów w przypadku wielu kubitów, można połączyć miary, aby zastosowane przekształcenie było unitarne. W tym przypadku oznacza to, że mimo że implementacja ciała zawiera pomiary, które samodzielnie nie obsługują Adjoint funktora, ciało w całości jest przylegające. Niemniej jednak automatyczne generowanie implementacji sąsiedniej zakończy się niepowodzeniem w tym przypadku. Z tego powodu można ręcznie określić implementację. Kompilator automatycznie generuje zoptymalizowane implementacje dla typowych wzorców, takich jak sprzężenia. Niemniej jednak jawna specjalizacja może być pożądana do ręcznego zdefiniowania bardziej zoptymalizowanej implementacji. Istnieje możliwość jawnego określenia dowolnej implementacji i dowolnej liczby implementacji.

Uwaga

Poprawność takiej ręcznie określonej implementacji nie jest weryfikowana przez kompilator.

W poniższym przykładzie deklaracja operacji SWAP, która wymienia stan dwóch kubitów q1 i q2, deklaruje jawną specjalizację dla swojej wersji sąsiedniej i kontrolowanej wersji. Chociaż implementacje dla Adjoint SWAP elementów i Controlled SWAP są w ten sposób zdefiniowane przez użytkownika, kompilator nadal musi wygenerować implementację dla kombinacji obu funktorów (Controlled Adjoint SWAPco jest takie samo jak Adjoint Controlled SWAP).

    operation SWAP (q1 : Qubit, q2 : Qubit) : Unit
    is Adj + Ctl { 

        body ... {
            CNOT(q1, q2);
            CNOT(q2, q1);
            CNOT(q1, q2);
        }

        adjoint ... { 
            SWAP(q1, q2);
        }

        controlled (cs, ...) { 
            CNOT(q1, q2);
            Controlled CNOT(cs, (q2, q1));
            CNOT(q1, q2);            
        } 
    }

Dyrektywy automatycznego generowania

Podczas określania sposobu generowania określonej specjalizacji kompilator określa priorytety implementacji zdefiniowanych przez użytkownika. Oznacza to, że jeśli specjalizacja sąsiada jest zdefiniowana przez użytkownika, a kontrolowana specjalizacja jest generowana automatycznie, kontrolowana specjalizacja sąsiada jest generowana na podstawie sąsiada zdefiniowanego przez użytkownika i na odwrót. W tym przypadku obie specjalizacje są zdefiniowane przez użytkownika. Ponieważ automatyczne generowanie implementacji przylegającej podlega większemu ograniczeniu, kontrolowanej specjalizacji przylegającej domyślnie generuje kontrolowaną specjalizację jawnie zdefiniowanej implementacji specjalizacji przylegającej.

W przypadku SWAP implementacji lepszym rozwiązaniem jest przyleganie kontrolowanej specjalizacji, aby uniknąć niepotrzebnego klimatyzacji wykonywania pierwszego i ostatniego CNOT stanu kubitów kontrolnych. Dodanie jawnej deklaracji kontrolowanej wersji adjoint, która określa odpowiednią dyrektywę generowania wymusza kompilator generowania kontrolowanej specjalizacji przylegania na podstawie ręcznie określonej implementacji kontrolowanej wersji. Taka jawna deklaracja specjalizacji, która ma być generowana przez kompilator, przyjmuje formularz

    controlled adjoint invert;

i jest wstawiany wewnątrz deklaracji .SWAP Z drugiej strony wstawianie wiersza

    controlled adjoint distribute;

wymusza generowanie specjalizacji przez kompilator na podstawie zdefiniowanej (lub wygenerowanej) specjalizacji przylegania. Aby uzyskać więcej informacji, zobacz tę propozycję wnioskowania częściowej specjalizacji .

W przypadku operacji SWAPistnieje lepsza opcja. SWAP jest samozadowoleniem, czyli jest jego własną odwrotnością; -zdefiniowana implementacja adjoint jedynie wywołuje treść SWAP. Wyrażasz to za pomocą dyrektywy

    adjoint self;

Zadeklarowanie sąsiedniej specjalizacji w ten sposób zapewnia, że kontrolowana specjalizacja sąsiada, która jest automatycznie wstawiana przez kompilator, jedynie wywołuje kontrolowaną specjalizację.

Istnieją następujące dyrektywy generowania i są prawidłowe:

Specjalizacji Dyrektywy
body Specjalizacji: -
adjoint Specjalizacji: self, invert
controlled Specjalizacji: distribute
controlled adjoint Specjalizacji: self, invert, distribute

To, że wszystkie dyrektywy generowania są prawidłowe dla kontrolowanej specjalizacji sąsiada, nie jest przypadkiem; tak długo, jak funktory dojeżdżają do pracy, zestaw prawidłowych dyrektyw generowania do implementowania specjalizacji dla kombinacji funktorów jest zawsze związkiem zestawu prawidłowych generatorów dla każdego z nich.

Oprócz wcześniej wymienionych dyrektyw dyrektywa auto jest prawidłowa dla wszystkich specjalizacji z wyjątkiem body; wskazuje, że kompilator powinien automatycznie wybrać odpowiednią dyrektywę generowania. Deklaracja

    operation DoNothing() : Unit {
        body ... { }
        adjoint auto;
        controlled auto;
        controlled adjoint auto;
    }

jest równoważny

    operation DoNothing() : Unit 
    is Adj + Ctl { }

Adnotacja is Adj + Ctl w tym przykładzie określa charakterystykę operacji, która zawiera informacje o tym, co functors dana operacja obsługuje.

Chociaż w celu zapewnienia czytelności zaleca się dodawanie adnotacji do każdej operacji z pełnym opisem jej właściwości, kompilator automatycznie wstawia lub kończy adnotację na podstawie jawnie zadeklarowanych specjalizacji. Z drugiej strony kompilator generuje również specjalizacje, które nie zostały zadeklarowane jawnie, ale muszą istnieć na podstawie cech z adnotacjami. Mówimy, że dana adnotacja niejawnie zadeklarowała te specjalizacje. Kompilator automatycznie generuje niezbędne specjalizacje, jeśli może, wybierając odpowiednią dyrektywę. Q# w ten sposób obsługuje wnioskowanie zarówno cech operacji, jak i istniejących specjalizacji na podstawie adnotacji (częściowych) i jawnie zdefiniowanych specjalizacji.

W pewnym sensie specjalizacje są podobne do poszczególnych przeciążeń dla tego samego wywołania, z zastrzeżeniem, że niektóre ograniczenia mają zastosowanie do przeciążeń, które można zadeklarować.