Skip to main content
IBM Quantum Platform

Istruzioni singleton

qiskit.circuit.singleton

I meccanismi descritti in questo modulo servono a definire sottoclassi di Instruction e Gate che, al momento dell'istanziazione, restituiscono preferibilmente un'istanza singleton immutabile condivisa. XGatePrendendo ad esempio, il risultato finale per l'utente è il seguente:

  • Esiste una classe regolare denominata XGate, che deriva da Gate.
  • Eseguendo un'operazione del tipo XGate(label="my_gate") si ottiene un oggetto il cui tipo è esattamente XGate, e la mutabilità funziona esattamente come previsto; tutti i metodi rimandano esattamente a quelli definiti da XGate, Gate, o dai genitori.
  • L'esecuzione di questa operazione XGate() produce un oggetto singleton il cui tipo è una classe sintetica _SingletonXGate , che deriva XGate da ma sovrascrive __setattr__() per rendersi immutabile. L'oggetto stesso presenta esattamente gli stessi attributi di istanza che XGate() avrebbe se non fosse presente alcuna gestione del singleton. Questo oggetto verrà restituito in copy(), deepcopy() e compirà un ciclo completo attraverso pickle.

MeasureLo stesso vale, ad esempio, per, tranne per il fatto che si tratta di una sottoclasse solo di Instruction , e non di Gate.

Nota

Le classi di questo modulo sono destinate a un uso avanzato, perché sono strettamente legate al cuore del modello di dati di Qiskit per i circuiti.

Dal punto di vista di un autore di librerie, il minimo necessario per potenziare un Gate o Instruction un con questo comportamento è ereditare da SingletonGate (SingletonInstruction) anziché da Gate (Instruction), e fare in modo che il __init__ metodo abbia valori predefiniti per tutti i suoi argomenti (che corrisponderanno allo stato dell'istanza singleton). Ad esempio:

class XGate(SingletonGate):
    def __init__(self, label=None):
        super().__init__("x", 1, [], label=label)

assert XGate() is XGate()

Interfaccia

Le classi pubbliche corrispondono rispettivamente alle classi Instruction standard e Gate, e ne sono sottoclassi.

SingletonInstruction

class qiskit.circuit.singleton.SingletonInstruction(*args, _force_mutable=False, **kwargs)

GitHub

Basi: Instruction, _SingletonBase

Una classe base da utilizzare per Instruction gli oggetti che, per impostazione predefinita, sono istanze singleton.

Questa classe deve essere utilizzata per le classi di istruzioni che hanno definizioni fisse e non contengono uno stato unico. L'esempio canonico di una cosa del genere è Measure che ha una definizione immutabile e qualsiasi istanza di Measure è la stessa. L'uso di istruzioni singleton come classe base per questi tipi di gate offre un grande vantaggio nell'ingombro in memoria di istruzioni multiple.

L'eccezione da tenere presente con questa classe, tuttavia, riguarda gli Instruction attributi label che possono essere impostati in modo diverso per specifiche istanze di porte. Per garantire SingletonInstruction un utilizzo corretto, la modifica di questi attributi non è consentita; essi possono essere impostati solo al momento della creazione oppure su un oggetto che sia stato specificatamente reso modificabile tramite to_mutable(). Se durante la creazione viene utilizzato uno qualsiasi di questi attributi, invece di utilizzare un'unica istanza globale condivisa dello stesso gate, verrà creata una nuova istanza separata.

SingletonGate

class qiskit.circuit.singleton.SingletonGate(*args, _force_mutable=False, **kwargs)

GitHub

Basi: Gate, _SingletonBase

Una classe base da utilizzare per Gate gli oggetti che, per impostazione predefinita, sono istanze singleton.

Questa classe è molto simile a SingletonInstruction, tranne per il fatto che implica anche una semantica unitaria Gate . Anche in questo caso valgono le stesse avvertenze relative all'impostazione degli attributi in quella classe.

SingletonControlledGate

class qiskit.circuit.singleton.SingletonControlledGate(*args, _force_mutable=False, **kwargs)

GitHub

Basi: ControlledGate, _SingletonBase

Una classe base da utilizzare per ControlledGate gli oggetti che, per impostazione predefinita, sono istanze singleton

Questa classe è molto simile a SingletonInstruction, tranne per il fatto che implica anche una semantica unitaria ControlledGate . Anche in questo caso valgono le stesse avvertenze relative all'impostazione degli attributi in quella classe.

Quando si eredita da una di queste classi, la classe prodotta avrà un'istanza di singleton creata con bramosia, che sarà restituita ogni volta che la classe viene costruita con argomenti che sono stati definiti singleton. In genere si tratta delle impostazioni predefinite. Queste istanze sono immutabili; il tentativo di modificare le loro proprietà solleverà TypeError.

Tutte le sottoclassi di Instruction possiedono una mutable proprietà. Per la maggior parte delle istruzioni è True, mentre per le istanze singleton è False. È possibile utilizzare il to_mutable() metodo per ottenere una versione dell'istruzione di cui si detiene il controllo e che può essere modificata in tutta sicurezza.

Le istanze singleton non sono istanze esatte della loro classe base; sono sottoclassi speciali che non possono costruire nuovi oggetti. Significa che:

type(XGate()) is not XGate

Non dovresti fare affidamento su type un valore esatto; usa isinstance() invece per il controllo del tipo. Se è necessario recuperare in modo affidabile la classe base da un Instruction, consultare l'attributo Instruction.base_class ; le istanze singleton lo impostano correttamente. Nella maggior parte dei casi in cui si utilizza Qiskit, Instruction.name è un indicatore più adeguato del “significato” di un’istruzione all’interno di un circuito.

Derivazione di nuovi singleton

L'esempio più semplice di derivare una nuova istruzione singleton è semplicemente ereditare dalla base corretta e fornire un metodo __init__() che ha valori predefiniti immutabili per qualsiasi parametro. Ad esempio:

from qiskit.circuit.singleton import SingletonInstruction

class MyInstruction(SingletonInstruction):
    def __init__(self, label=None):
        super().__init__("my_instruction", 1, 0, label=label)

assert MyInstruction() is MyInstruction()
assert MyInstruction(label="some label") is not MyInstruction()
assert MyInstruction(label="some label").mutable

L'istanza singleton utilizzerà tutti i valori predefiniti del costruttore.

Si può anche derivare da un'istruzione che è a sua volta una singleton. La natura singleton della classe sarà ereditata, anche se le istanze singleton delle due classi saranno diverse:

class MyOtherInstruction(MyInstruction):
    pass

assert MyOtherInstruction() is MyOtherInstruction()
assert MyOtherInstruction() is not MyInstruction()

Se per qualche motivo si vuole derivare da SingletonInstructiono da una delle classi o sottoclassi correlate, ma non si vuole creare l'istanza singleton predefinita, ad esempio se si sta definendo una nuova classe base astratta, si può impostare l'argomento chiave create_default_singleton=False nella definizione della classe:

class NotASingleton(SingletonInstruction, create_default_singleton=False):
    def __init__(self):
        return super().__init__("my_mutable", 1, 0, [])

assert NotASingleton() is not NotASingleton()

Se il costruttore non ha valori predefiniti per tutti i suoi argomenti, è necessario impostare create_default_singleton=False.

Le sottoclassi di SingletonInstruction e le altre classi associate possono controllare il modo in cui vengono interpretati gli argomenti del loro costruttore, in modo da consentire al meccanismo singleton di restituire il singleton anche nel caso in cui un argomento opzionale sia esplicitamente impostato sul suo valore predefinito.

_singleton_lookup_key

static SingletonInstruction._singleton_lookup_key(*_args, **_kwargs)

GitHub

Dati gli argomenti del costruttore, restituisce una tupla di chiavi che identifica l'istanza singleton da recuperare, oppure None se gli argomenti implicano che deve essere creato un oggetto mutabile.

Per le prestazioni, come caso speciale, questo metodo non sarà chiamato se al costruttore della classe sono stati dati zero argomenti (ad esempio, il costruttore XGate() non chiamerà questo metodo, ma XGate(label=None) sì) e sarà immediatamente restituito il singleton predefinito.

Questo metodo statico può (e probabilmente dovrebbe) essere sovrascritto dalle sottoclassi. La firma derivata deve corrispondere a quella della classe __init__; questo metodo deve quindi esaminare gli argomenti per determinare se richiede la mutabilità o quale debba essere la chiave di cache (se esiste).

La funzione deve restituire None o una chiave dict valida (cioè hashable e che implementa l'uguaglianza). La restituzione di None significa che l'istanza creata deve essere mutabile. Non saranno eseguite ulteriori elaborazioni basate su singleton e la creazione della classe procederà come se non ci fosse la gestione dei singleton. In caso contrario, la chiave restituita può essere qualsiasi cosa che possa essere sottoposta a hash e non le viene attribuito alcun significato speciale. Ogni volta che questo metodo restituisce la stessa chiave, verrà restituita la stessa istanza singleton. Si consiglia di utilizzare una tupla dei valori di tutti gli argomenti che possono essere impostati, mantenendo la natura di singleton.

Solo le chiavi che corrispondono agli argomenti predefiniti o a quelli dati a additional_singletons al momento della creazione della classe restituiranno effettivamente dei singleton; gli altri valori restituiranno un'istanza mutabile standard.

Nota

Il macchinario dei singleton gestirà con grazia un ritorno non hashashable da questa funzione, restituendo un'istanza mutabile. Le sottoclassi devono assicurarsi che la loro chiave sia hashable nel percorso felice, ma non devono verificare manualmente che gli argomenti forniti dall'utente siano hashable. Ad esempio, è possibile implementare questo come:

@staticmethod
def _singleton_lookup_key(*args, **kwargs):
    return None if kwargs else args

anche se l'utente potrebbe indicare un tipo non hashappabile come uno dei args.

Questo parametro viene impostato da tutti i gate della libreria standard di Qiskit, in modo che gli argomenti delle parole chiave label e simili vengano ignorati nel calcolo della chiave se sono quelli predefiniti, oppure venga restituita un'istanza mutabile se non lo sono.

È possibile specificare anche altre combinazioni di argomenti del costruttore per cui produrre istanze singleton, utilizzando l'argomento additional_singletons nella definizione della classe. Prende un iterabile di tuple (args, kwargs) e costruisce singleton equivalenti a cls(*args, **kwargs). Non è necessario gestire il caso degli argomenti predefiniti. Ad esempio, data una definizione di classe:

class MySingleton(SingletonGate, additional_singletons=[((2,), {"label": "two"})]):
    def __init__(self, n=1, label=None):
        super().__init__("my", n, [], label=label)

    @staticmethod
    def _singleton_lookup_key(n=1, label=None):
        return (n, label)

saranno istanziate due istanze singleton. Uno corrisponde a n=1 e label=None, l'altro a n=2 e label="two". Ogni volta che MySingleton viene costruito con argomenti coerenti con uno di questi due casi, verrà restituito il singleton corrispondente. Ad esempio:

assert MySingleton() is MySingleton(1, label=None)
assert MySingleton(2, "two") is MySingleton(n=2, label="two")

Il caso in cui la classe venga istanziata con zero argomenti viene gestito in modo particolare per consentire un percorso assolutamente veloce per le prestazioni dell'inner-loop (anche se il macchinario generale non è comunque disperatamente lento).


Implementazione

Nota

Questa sezione è principalmente la documentazione del codice per gli sviluppatori; nessuno dei macchinari qui descritti è pubblico e non è sicuro ereditare direttamente da nessuno di essi.

Ci sono diversi aspetti da affrontare in questo contesto. Il fatto che restituisca XGate() un oggetto singleton che è un'istanza (inesatta) di XGate , senza però chiamare __init__ , ci impone di sovrascrivere type.__call__. Ciò significa che XGate deve avere una metaclasse che definisca __call__ in modo che restituisca l'istanza singleton.

Successivamente, dobbiamo assicurarci che esista un'istanza singleton da restituire a XGate() . Questo può essere fatto dinamicamente a ogni chiamata (cioè controllando se l'istanza esiste e creandola in caso contrario), ma poiché vogliamo che l'istanza sia molto speciale, è più facile agganciarla e crearla durante la definizione dell'oggetto di tipo XGate . Questo ha anche il vantaggio di non dover rendere l'oggetto singleton pickleable; è sufficiente specificare da dove recuperarlo durante l'unpickle, perché la creazione dell'oggetto di tipo base ricreerà il singleton.

Vogliamo che l'istanza singleton sia:

  • essere immutabile; deve rifiutare tutti i tentativi di mutazione.
  • hanno esattamente lo stesso stato che avrebbe un XGate() se non ci fosse la gestione dei singleton.

Lo facciamo con una procedura in tre fasi:

  1. Prima di creare qualsiasi singleton, definiamo separatamente le sovrascritture necessarie per rendere immutabili un Instruction e un Gate . Questa è _SingletonInstructionOverrides e le altre _*Overrides classi.
  2. Mentre creiamo l'oggetto di tipo XGate , creiamo dinamicamente anche una sua sottoclasse che ha le sovrascritte immutabili nel suo ordine di risoluzione dei metodi al posto giusto. Questi metodi sovrascrivono i metodi e le proprietà standard definiti sul gate mutabile (non cerchiamo di sovrascrivere i casi in cui l'oggetto tipo che stiamo creando ha metodi extra inplace).
  3. Non possiamo istanziare questa nuova sottoclasse, perché quando chiama XGate.__init__, tenterà di impostare alcuni attributi, che saranno rifiutati dall'immutabilità. Invece, creiamo prima un'istanza completamente regolare di XGate e poi cambiamo dinamicamente il suo tipo alla classe singleton, congelandola.

Potremmo farlo interamente all'interno del meccanismo delle metaclassi, ma ciò richiederebbe che XGate sia definito come qualcosa di simile:

class XGate(Gate, metaclass=_SingletonMeta, overrides=_SingletonGateOverrides): ...

il che è davvero scomodo (altrimenti dovremmo ricorrere _SingletonMeta a una serie di operazioni di introspezione piuttosto delicate). abc.ABCUtilizziamo invece il modello /abc.ABCMeta che consiste nel definire una classe intermedia concreta (SingletonGate nel XGate caso specifico) che imposta la metaclasse, seleziona le sovrascritture da applicare e dispone di un __init_subclass__() metodo che applica le fasi di creazione della sottoclasse singleton descritte sopra. Le sovrascritture sono contenute in classi separate, in modo che le istanze *mutabili *XGate non le includano nel proprio ordine di risoluzione dei metodi; questa soluzione è più semplice da implementare, ma richiede che tutti i setter e i checker debbano "darsi da fare" in fase di esecuzione per verificare se la modifica dell'istanza è consentita.

Infine, per realizzare effettivamente tutto questo meccanismo, la base è _SingletonMeta, ovvero una metaclasse compatibile con qualsiasi metaclasse di Instruction. Questo definisce il __call__() meccanismo che sovrascrive type.__call__ il comportamento predefinito per restituire le istanze singleton. L'altro elemento che lo caratterizza è il suo __new__(), che viene chiamato (in modo non banale) durante la creazione di SingletonGate e SingletonInstruction con il suo overrides argomento chiave impostato per definire il __init_subclass__ di quelle classi con le proprietà sopra indicate. superUtilizziamo la metaclasse per aggiungere questo metodo in modo dinamico, poiché il __init_subclass__() meccanismo deve rimanere astratto, includendo sia il metodo overrides che la classe base, ma deve comunque poter chiamare il metodo. È più pratico farlo in modo dinamico, chiudendo la variabile di classe desiderata e utilizzando la forma a due argomenti di super, poiché la forma senza argomenti esegue un’introspezione “magica” basata sul punto in cui è stata definita la funzione che la contiene.

Per gestire più singleton è necessario memorizzare in qualche modo gli argomenti di inizializzazione, in modo da consentire la definizione del to_mutable() metodo e il pickling. Lo facciamo utilizzando un dizionario di ricerca sull'oggetto di tipo singleton. Si tratta logicamente di un attributo dell'istanza, ma poiché dobbiamo applicare dinamicamente il tipo dinamico _Singleton a un'istanza del tipo di base, la cosa si complica notevolmente; o richiediamo che il tipo di base disponga già di un dizionario dell'istanza, oppure rischiamo di compromettere il __slots__ layout durante l'applicazione. Poiché i singleton hanno un ciclo di vita che dura fino alla garbage collection dell'oggetto-tipo della loro classe base, possiamo simulare questo dizionario delle istanze utilizzando un dizionario di oggetti-tipo che mappa i puntatori alle istanze ai dati che vogliamo memorizzare. Un'alternativa sarebbe quella di creare un nuovo oggetto di tipo per ogni singolo singleton che racchiuda (o memorizzi) gli argomenti dell'inizializzatore, ma gli oggetti di tipo sono piuttosto pesanti e, in ogni caso, il principio è sostanzialmente lo stesso.

Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.