Gerenciamento de memória do Quantum

Um programa sempre é iniciado sem qubits, o que significa que valores do tipo Qubit não podem ser passados como argumentos de ponto de entrada. Essa restrição é intencional, pois a finalidade do Q# é raciocinar sobre um programa e expressá-lo em sua totalidade. Nesse caso, um programa aloca e libera qubits, ou memória quântica, ao longo do processo. Nesse sentido, Q# modela o computador quântico como um heap qubit.

Em vez de dar suporte a instruções allocate e release separadas para a memória quântica, o Q# dá suporte à alocação de memória quântica em forma de instruções de bloco, em que a memória é acessível somente no escopo dessa instrução de bloco. O bloco de instruções pode ser definido implicitamente ao alocar qubits para a duração do escopo atual, o que é descrito em mais detalhes nas seções sobre as instruções use e borrow. A tentativa de acessar os qubits alocados após o encerramento da instrução resulta em uma exceção de runtime.

Q# tem duas instruções use e borrow, que criam instâncias de valores de qubit, de matrizes de qubits ou de qualquer combinação dessas opções. Você só pode usar essas instruções dentro das operações. Elas coletam os valores das instâncias de qubit criadas, os associam às variáveis especificadas na instrução e depois executam um bloco de instruções. No final do bloco, as variáveis associadas saem do escopo e não são mais definidas.

O Q# faz a distinção entre a alocação de qubits limpos e sujos. Os qubits limpos são desentrelaçados e não são usados por outra parte da computação. Os qubits sujos são aqueles cujo estado é desconhecido e podem até ser entrelaçados com outras partes da memória do processador quântico.

Instrução use

Os qubits limpos são alocados pela instrução use.

  • A instrução consiste na palavra-chave use seguida por uma associação e um bloco de instrução opcional.
  • Se um bloco de instrução estiver presente, os qubits só estarão disponíveis dentro desse bloco. Caso contrário, os qubits estarão disponíveis até o final do escopo atual.
  • A vinculação segue o mesmo padrão das instruções let: um único símbolo ou uma tupla de símbolos, seguido por um sinal de igual = e uma única tupla ou uma tupla correspondente de inicializadores.

Os inicializadores estão disponíveis para um único qubit, indicado como Qubit(), ou uma matriz de qubits, Qubit[n], em que n é uma expressão Int. Por exemplo,

use qubit = Qubit();
// ...

use (aux, register) = (Qubit(), Qubit[5]);
// ...

use qubit = Qubit() {
    // ...
}

use (aux, register) = (Qubit(), Qubit[5]) {
    // ...
}

Os qubits têm a garantia de estar em um estado |0⟩ na alocação. Eles são liberados no final do escopo e precisam estar em um estado |0⟩ após a versão. Esse requisito não é imposto ao compilador, pois isso exigiria uma avaliação simbólica que rapidamente fica muito cara e inviável. Em execuções em simuladores, o requisito pode ser imposto pelo runtime. Em processadores quânticos, o requisito não pode ser imposto em tempo de execução; um qubit não medido pode ser redefinido para |0⟩ por meio da transformação unitária. Se isso não for feito, o comportamento será incorreto.

A instrução use aloca os qubits do heap de qubit livre do processador quântico e retorna-os ao heap, no máximo, até o final do escopo ao qual os qubits estão associados.

Instrução borrow

A instrução borrow permite acesso a qubits que já estão alocados, mas que não estão em uso no momento. Esses qubits podem estar em um estado qualquer e precisam voltar ao mesmo estado quando a instrução borrow é encerrada. Alguns algoritmos quânticos podem usar qubits sem depender do estado exato e sem exigir que eles sejam desentrelaçados com o restante do sistema. Ou seja, eles exigem qubits adicionais temporariamente, mas podem garantir que esses qubits sejam retornados exatamente ao estado original seja ele qual for.

Quando há qubits em uso, mas não processados durante a execução de uma sub-rotina, esses qubits podem ser emprestados a um algoritmo desse tipo para que não seja necessário alocar mais memória quântica. O empréstimo em vez da alocação pode reduzir significativamente os requisitos gerais de memória quântica de um algoritmo e é um exemplo quântico de uma compensação típica de espaço-tempo.

Uma instrução borrow segue o mesmo padrão já descrito para uma instrução use, com os mesmos inicializadores disponíveis. Por exemplo,

borrow qubit = Qubit();
// ...

borrow (aux, register) = (Qubit(), Qubit[5]);
// ...

borrow qubit = Qubit() {
    // ...
}

borrow (aux, register) = (Qubit(), Qubit[5]) {
    // ...
}

Os qubits emprestados estão em um estado desconhecido e sai do escopo no final do bloco de instrução. Aquele que pega o empréstimo se compromete a deixar os qubits no mesmo estado em que estavam quando foram emprestados, ou seja, espera-se que o estado no início e no final do bloco de instruções seja o mesmo.

A instrução borrow recupera os qubits em uso que com certeza não serão usados ​​pelo programa desde o momento em que o qubit for associado até a última vez em que ele for usado. Se não houver qubits suficientes disponíveis para o empréstimo, os qubits serão alocados do heap e retornados a ele, como uma instrução use.

Observação

Entre os casos de uso conhecidos de qubits sujos estão as implementações de portas CNOT de vários controles que exigem apenas poucos qubits e implementações de incrementadores. Este artigo sobre fatoração com qubits mostra um exemplo de algoritmo que utiliza qubits emprestados.