Skip to main content
IBM Quantum Platform

Instruções Singleton

qiskit.circuit.singleton

A mecânica deste módulo serve para definir subclasses de Instruction e Gate que, ao serem instanciadas, retornam preferencialmente uma instância singleton imutável compartilhada. XGateTomando como exemplo, o resultado final para o usuário é o seguinte:

  • Existe uma classe regular chamada XGate, que deriva de Gate.
  • Fazer algo como XGate(label="my_gate") gera um objeto cujo tipo é exatamente XGate, e toda a mutabilidade funciona exatamente como esperado; todos os métodos remetem exatamente àqueles definidos por XGate, Gate, ou seus pais.
  • Isso XGate() gera um objeto singleton cujo tipo é uma classe sintética _SingletonXGate , que deriva de XGate mas sobrescreve __setattr__() para se tornar imutável. O próprio objeto possui exatamente os mesmos atributos de instância que XGate() teria se não houvesse o tratamento de singleton. Este objeto retornará a si mesmo em copy(), deepcopy() e percorrerá todo o caminho de ida e volta por pickle.

MeasureO mesmo pode se aplicar, por exemplo, a, exceto que ela é uma subclasse apenas de Instruction , e não de Gate.

Nota

As classes deste módulo são para uso avançado, pois estão intimamente ligadas ao coração do modelo de dados do Qiskit para circuitos.

Do ponto de vista de um autor de biblioteca, o mínimo necessário para aprimorar um Gate ou Instruction com esse comportamento é herdar de SingletonGate (SingletonInstruction) em vez de Gate (Instruction), e fazer com que o __init__ método tenha valores padrão para todos os seus argumentos (esses serão o estado da instância singleton). Por exemplo:

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

assert XGate() is XGate()

Interface

As classes públicas correspondem às classes Instruction padrão e Gate, respectivamente, e são subclasses delas.

SingletonInstruction

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

GitHub

Bases: Instruction, _SingletonBase

Uma classe base a ser usada para Instruction objetos que, por padrão, são instâncias singleton.

Essa classe deve ser usada para classes de instrução que tenham definições fixas e não contenham nenhum estado exclusivo. O exemplo canônico de algo assim é Measure que tem uma definição imutável e qualquer instância de Measure é a mesma. O uso de instruções singleton como classe base para esses tipos de classes de portas oferece uma grande vantagem no espaço ocupado pela memória de várias instruções.

No entanto, a exceção a ser levada em conta nessa classe são os Instruction atributos label , que podem ser definidos de maneira diferente para instâncias específicas de portões. Para que SingletonInstruction a utilização seja correta, a configuração desses atributos não está disponível; eles só podem ser definidos no momento da criação ou em um objeto que tenha sido especificamente tornado mutável por meio de to_mutable(). Se algum desses atributos for utilizado durante a criação, em vez de se usar uma única instância global compartilhada do mesmo gate, será criada uma nova instância separada.

SingletonGate

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

GitHub

Bases: Gate, _SingletonBase

Uma classe base a ser usada para Gate objetos que, por padrão, são instâncias singleton.

Essa classe é muito semelhante à SingletonInstruction, exceto que também implica uma semântica unitária Gate . As mesmas ressalvas relativas à definição de atributos nessa classe também se aplicam aqui.

SingletonControlledGate

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

GitHub

Bases: ControlledGate, _SingletonBase

Uma classe base a ser usada para ControlledGate objetos que, por padrão, são instâncias singleton

Essa classe é muito semelhante à SingletonInstruction, exceto que também implica uma semântica unitária ControlledGate . As mesmas ressalvas relativas à definição de atributos nessa classe também se aplicam aqui.

Ao herdar de uma dessas classes, a classe produzida terá uma instância de singleton criada com antecedência que será retornada sempre que a classe for construída com argumentos que foram definidos como singletons. Normalmente, esse será o padrão. Essas instâncias são imutáveis; tentativas de modificar suas propriedades gerarão TypeError.

Todas as subclasses de Instruction possuem uma mutable propriedade. Para a maioria das instruções, isso é True, enquanto que, para as instâncias singleton, é False. É possível usar o to_mutable() método para obter uma versão da instrução que esteja sob controle e que possa ser alterada com segurança.

As instâncias singleton não são instâncias exatas de sua classe base; elas são subclasses especiais que não podem construir novos objetos. Isso significa que:

type(XGate()) is not XGate

Você não deve contar com type um valor exato; em vez disso, use isinstance() isso para a verificação de tipo. Se você precisar recuperar de forma confiável a classe base de um Instruction, consulte o Instruction.base_class atributo; as instâncias singleton definem isso corretamente. Na maioria dos casos em que se utiliza o Qiskit, Instruction.name é um indicador mais adequado do que uma instrução “significa” em um circuito.

Derivando novos singletons

O exemplo mais simples de derivação de uma nova instrução singleton é simplesmente herdar da base correta e fornecer um método __init__() que tenha padrões imutáveis para quaisquer argumentos. Por exemplo:

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

A instância singleton usará todos os padrões do construtor.

Você também pode derivar de uma instrução que é, por si só, um singleton. A natureza de singleton da classe será herdada, embora as instâncias de singleton das duas classes sejam diferentes:

class MyOtherInstruction(MyInstruction):
    pass

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

Se, por algum motivo, você quiser derivar de SingletonInstructionou de uma das subclasses ou classes relacionadas, mas não quiser que a instância singleton padrão seja criada, por exemplo, se estiver definindo uma nova classe base abstrata, você poderá definir o argumento da palavra-chave create_default_singleton=False na definição da classe:

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

assert NotASingleton() is not NotASingleton()

Se o seu construtor não tiver padrões para todos os seus argumentos, você deverá definir create_default_singleton=False.

As subclasses de SingletonInstruction e as outras classes associadas podem controlar como os argumentos de seus construtores são interpretados, a fim de ajudar o mecanismo de singleton a retornar o singleton mesmo no caso de um argumento opcional ser explicitamente definido com seu valor padrão.

_singleton_lookup_key

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

GitHub

Considerando os argumentos do construtor, retorne uma tupla de chaves que identifique a instância singleton a ser recuperada ou None se os argumentos indicarem que um objeto mutável deve ser criado.

Para fins de desempenho, como um caso especial, esse método não será chamado se o construtor da classe tiver recebido zero argumentos (por exemplo, a construção XGate() não chamará esse método, mas XGate(label=None) chamará), e o singleton padrão será imediatamente retornado.

Esse método estático pode (e provavelmente deve) ser substituído por subclasses. A assinatura derivada deve corresponder à da classe __init__; esse método deve, então, examinar os argumentos para determinar se requer mutabilidade ou qual deve ser a chave de cache (se houver).

A função deve retornar None ou uma chave dict válida (ou seja, com hash e que implemente a igualdade). Retornar None significa que a instância criada deve ser mutável. Nenhum outro processamento baseado em singleton será feito, e a criação da classe prosseguirá como se não houvesse manipulação de singleton. Caso contrário, a chave retornada pode ser qualquer coisa passível de hash e nenhum significado especial é atribuído a ela. Sempre que esse método retornar a mesma chave, a mesma instância singleton será retornada. Sugerimos que você use uma tupla dos valores de todos os argumentos que podem ser definidos, mantendo a natureza de singleton.

Somente as chaves que correspondem aos argumentos padrão ou aos argumentos fornecidos a additional_singletons no momento da criação da classe realmente retornarão singletons; outros valores retornarão uma instância mutável padrão.

Nota

O mecanismo de singleton tratará um retorno sem hash dessa função de forma graciosa, retornando uma instância mutável. As subclasses devem garantir que sua chave seja passível de hash no caminho feliz, mas não precisam verificar manualmente se os argumentos fornecidos pelo usuário são passíveis de hash. Por exemplo, é seguro implementar isso como:

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

mesmo que um usuário possa fornecer algum tipo não-habitável como um dos args.

Isso é definido por todas as portas da biblioteca padrão do Qiskit, de modo que os argumentos de label e de palavras-chave semelhantes sejam ignorados no cálculo da chave se forem seus padrões, ou uma instância mutável seja retornada se não forem.

Você também pode especificar outras combinações de argumentos do construtor para produzir instâncias de singleton, usando o argumento additional_singletons na definição da classe. Isso usa um iterável de tuplas (args, kwargs) e criará singletons equivalentes a cls(*args, **kwargs). Você não precisa tratar o caso dos argumentos padrão com isso. Por exemplo, dada uma definição de 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)

haverá duas instâncias singleton instanciadas. Um corresponde a n=1 e label=None, e o outro a n=2 e label="two". Sempre que o MySingleton for construído com argumentos consistentes com um desses dois casos, o singleton relevante será retornado. Por exemplo:

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

O caso da classe que está sendo instanciada com zero argumentos é tratado especialmente para permitir um caminho absolutamente rápido para o desempenho do loop interno (embora o mecanismo geral não seja desesperadamente lento de qualquer forma).


implementação

Nota

Esta seção é principalmente a documentação do desenvolvedor para o código; nenhum dos mecanismos descritos aqui é público, e não é seguro herdar diretamente de nenhum deles.

Há vários aspectos a serem abordados aqui. O comportamento de fazer com que XGate() retorne algum objeto singleton que seja uma instância (inexata) de XGate , mas sem chamar __init__ , exige que sobrescrevamos type.__call__. Isso significa que XGate deve ter uma metaclasse que defina __call__ para retornar a instância singleton.

Em seguida, precisamos garantir que haja uma instância singleton para que o XGate() retorne. Isso pode ser feito dinamicamente em cada chamada (ou seja, verificar se a instância existe e criá-la se não existir), mas como também queremos que essa instância seja muito especial, é mais fácil conectá-la e criá-la durante a definição do objeto do tipo XGate . Isso também tem a vantagem de não precisarmos tornar o objeto singleton selecionável; só precisamos especificar de onde recuperá-lo durante o unpickle, porque a criação do objeto do tipo base recriará o singleton.

Queremos que a instância singleton:

  • ser imutável; ele deve rejeitar todas as tentativas de sofrer mutação.
  • têm exatamente o mesmo estado que um XGate() teria se não houvesse manipulação de singleton.

Fazemos isso em um procedimento de três etapas:

  1. Antes de criar quaisquer singletons, definimos separadamente as substituições necessárias para tornar um Instruction e um Gate imutáveis. Essas são _SingletonInstructionOverrides e as outras _*Overrides classes.
  2. Enquanto criamos o objeto do tipo XGate , também criamos dinamicamente uma subclasse dele que tem as substituições imutáveis em sua ordem de resolução de método no local correto. Eles substituem os métodos/propriedades padrão que são definidos na porta mutável (não tentamos substituir nenhum caso em que o objeto de tipo que estamos criando tenha métodos adicionais no local).
  3. Não podemos instanciar essa nova subclasse, pois quando ela chamar XGate.__init__, tentará definir alguns atributos, que serão rejeitados pela imutabilidade. Em vez disso, primeiro criamos uma instância completamente regular do XGate e, em seguida, alteramos dinamicamente seu tipo para a classe singleton, congelando-a.

Poderíamos fazer isso inteiramente dentro do mecanismo de metaclasse, mas isso exigiria que o site XGate fosse definido como algo do tipo:

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

o que é extremamente inconveniente (ou teríamos que fazer _SingletonMeta um monte de introspecções delicadas). abc.ABCEm vez disso, usamos o padrão /abc.ABCMeta para definir uma classe intermediária concreta (SingletonGate neste XGate caso) que define a metaclasse, seleciona as substituições a serem aplicadas e possui um __init_subclass__() método que aplica as etapas de criação da subclasse singleton descritas acima. As substituições estão em classes separadas para que as instâncias *mutáveis *XGate não as tenham em suas próprias ordens de resolução de métodos; fazer isso é mais fácil de implementar, mas exige que todos os setters e checkers tenham que se adaptar em tempo de execução para tentar validar se é permitido alterar a instância.

Por fim, para realmente montar toda essa estrutura, a base é _SingletonMeta, que é uma metaclasse compatível com qualquer metaclasse de Instruction. Isso define o __call__() mecanismo que substitui type.__call__ a chamada original para retornar as instâncias singleton. O outro componente disso é o __new__(), que é chamado (de forma não trivial) durante a criação de SingletonGate e SingletonInstruction , com seu overrides argumento-chave definido para definir o __init_subclass__ dessas classes com as propriedades acima. Usamos a metaclasse para adicionar esse método dinamicamente, pois a __init_subclass__() “machinery” deve ser abstrata, capturando o overrides e a classe base, mas ainda assim capaz de chamar super. É mais prático fazer isso dinamicamente, capturando a variável de classe desejada e usando a forma com dois argumentos de super, já que a forma sem argumentos realiza uma introspecção automática com base no local onde a função que a contém foi definida.

Para lidar com vários singletons, é necessário armazenar os argumentos de inicialização de alguma forma, a fim de permitir que o to_mutable() método e o pickling sejam definidos. Fazemos isso por meio de um dicionário de consulta no objeto do tipo singleton. Logicamente, trata-se de um atributo de instância, mas, como precisamos aplicar dinamicamente o tipo dinâmico _Singleton a uma instância do tipo base, isso se torna bastante complexo; ou exigimos que o tipo base já possua um dicionário de instâncias, ou corremos o risco de comprometer o __slots__ layout durante a substituição. Como os singletons têm duração de vida que se estende até a coleta de lixo do objeto-tipo de sua classe base, podemos simular esse dicionário de instâncias usando um dicionário de objetos-tipo que mapeia ponteiros de instância para os dados que queremos armazenar. Uma alternativa seria criar um novo objeto de tipo para cada singleton individual que capture (ou armazene) os argumentos do inicializador, mas os objetos de tipo são bastante pesados e, de qualquer forma, o princípio é basicamente o mesmo.

Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.