Sdílet prostřednictvím


Deklarace specializace

Jak je vysvětleno v části o volatelných deklaracích, není v současné době důvod explicitně deklarovat specializace pro funkce. Toto téma se týká operací a popisuje, jak deklarovat potřebné specializace pro podporu určitých funktorů.

V kvantových výpočtech je poměrně běžným problémem vyžadovat adjoint dané transformace. Mnoho kvantových algoritmů k provedení výpočtu vyžaduje operaci i její spojení. Q# využívá symbolické výpočty, které mohou automaticky generovat odpovídající adjoint implementace pro konkrétní implementaci těla. Tato generace je možná i pro implementace, které volně kombinují klasické a kvantové výpočty. V tomto případě ale platí určitá omezení. Automatické generování se například nepodporuje z důvodů výkonu, pokud implementace využívá proměnlivé proměnné. Kromě toho každá operace volaná v těle generuje odpovídající adjoint potřeby pro podporu samotného functoru Adjoint .

I když nelze snadno vrátit zpět měření v případě s více qubity, je možné kombinovat měření tak, aby použitá transformace byla jednotná. V tomto případě to znamená, že i když implementace těla obsahuje hodnoty, které samy o sobě Adjoint nepodporují functor, tělo je v celém jeho rozsahu adjointable. Automatické generování adjoint implementace však v tomto případě selže. Z tohoto důvodu je možné implementaci zadat ručně. Kompilátor automaticky generuje optimalizované implementace pro běžné vzory, jako jsou například konjugace. Nicméně explicitní specializace může být žádoucí definovat optimalizovanější implementaci ručně. Je možné explicitně zadat libovolnou implementaci a libovolný počet implementací.

Poznámka

Správnost takové ručně zadané implementace není ověřena kompilátorem.

V následujícím příkladu deklarace pro operaci SWAP, která vyměňuje stav dvou qubitů q1 a q2, deklaruje explicitní specializaci pro její adjoint verzi a její řízenou verzi. Zatímco implementace pro Adjoint SWAP a Controlled SWAP jsou tedy definovány uživatelem, kompilátor stále potřebuje vygenerovat implementaci pro kombinaci obou funktorů (Controlled Adjoint SWAPcož je stejné jako 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);            
        } 
    }

Direktivy automatického generování

Při určování, jak vygenerovat konkrétní specializaci, kompilátor upřednostňuje uživatelem definované implementace. To znamená, že pokud je adjoint specializace definovaná uživatelem a řízená specializace se automaticky vygeneruje, pak se řízená adjointová specializace vygeneruje na základě uživatelem definovaného adjointu a naopak. V tomto případě jsou obě specializace definované uživatelem. Vzhledem k tomu, že automatické generování adjoint implementace podléhá většímu omezení, řízená vedlejší specializace ve výchozím nastavení generuje řízenou specializaci explicitně definované implementace adjoint specializace.

V případě SWAP implementace je lepší možností přidružovat řízenou specializaci, aby nedocházelo k zbytečnému podmiňování provádění prvního a posledního CNOT na stavu řídicích qubitů. Přidání explicitní deklarace pro řízenou adjoint verzi, která určuje vhodnou direktivu generování vynutí kompilátor vygenerovat řízenou adjoint specializaci na základě ručně zadané implementace řízené verze místo. Taková explicitní deklarace specializace, kterou má kompilátor vygenerovat, má podobu

    controlled adjoint invert;

a vkládá se do deklarace .SWAP Na druhé straně vložení čáry

    controlled adjoint distribute;

vynutí, aby kompilátor vygeneroval specializaci na základě definované (nebo vygenerované) vedlejší specializace. Další podrobnosti najdete v tomto návrhu odvozování částečné specializace .

Pro operaci SWAPexistuje lepší možnost. SWAPje vlastní, to znamená, že je to jeho vlastní inverzní; -definovaná implementace adjointu pouze nazývá tělo .SWAP Vyjadřujete to direktivou

    adjoint self;

Deklarace vedlejší specializace tímto způsobem zajistí, že řízená vedlejší specializace, která je automaticky vložena kompilátorem, pouze vyvolá řízenou specializaci.

Existují a jsou platné následující direktivy generování:

Specializace Direktivy
body Specializace: -
adjoint Specializace: self, invert
controlled Specializace: distribute
controlled adjoint Specializace: self, invert, distribute

To, že všechny direktivy generování jsou platné pro řízenou vedlejší specializaci, není náhoda; pokud funktory dojížděly, je sada platných direktiv generování pro implementaci specializace pro kombinaci funktorů vždy sjednocením sady platných generátorů pro každý z nich.

Kromě dříve uvedených direktiv je tato direktiva auto platná pro všechny specializace s výjimkou body; označuje, že kompilátor by měl automaticky vybrat vhodnou direktivu generování. Deklarace

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

je ekvivalentem

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

Poznámka is Adj + Ctl v tomto příkladu určuje charakteristiky operace, které obsahují informace o tom, jaké funktory konkrétní operace podporuje.

Zatímco z důvodu čitelnosti se doporučuje, abyste ke každé operaci anotovali úplným popisem jejich vlastností, kompilátor automaticky vloží nebo dokončí poznámku na základě explicitně deklarovaných specializací. Naopak kompilátor také generuje specializace, které nebyly explicitně deklarovány, ale musí existovat na základě vlastností s poznámkami. Říkáme, že daná poznámka implicitně deklarovala tyto specializace. Kompilátor automaticky vygeneruje potřebné specializace, pokud je to možné, a vybere vhodnou direktivu. Q# podporuje tak odvozování charakteristik operací i stávajících specializací na základě (částečných) poznámek a explicitně definovaných specializací.

V jistém smyslu se specializace podobají jednotlivým přetížením pro stejné volatelné s tím, že platí určitá omezení, na která přetížení můžete deklarovat.