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 SWAP
co 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 SWAP
istnieje 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ć.