Créez votre propre simulateur Q # - Partie 1

Les simulateurs sont une fonctionnalité particulièrement polyvalente du QDK. Ils vous permettent d'effectuer diverses tâches dans un programme Q # sans le modifier. Ces tâches incluent la simulation d'état complet , l'estimation des ressources ou la simulation de traçage . La nouvelle interface IQuantumProcessorfacilite la création de vos propres simulateurs et leur intégration dans vos projets Q #.



Cet article est le premier d'une série sur cette interface. Nous commencerons par implémenter un simulateur réversible comme premier exemple, que nous développerons dans les prochains articles de blog. Simulateur réversible peut simuler des programmes quantiques qui correspondent à des opérations classiques: X, CNOT,CCNOT(Porte de Toffoli) ou X-opérations contrôlées aléatoirement. Puisqu'un simulateur réversible peut représenter un état quantique en attribuant une valeur booléenne à chaque qubit, il peut même exécuter des programmes quantiques de milliers de qubits. Ce simulateur est très utile pour tester les opérations quantiques qui évaluent les fonctions booléennes.







Implémentation du simulateur en C #



Cet article de blog met en évidence les extraits de code de base. Le code source complet se trouve dans le référentiel d'exemples Microsoft QDK.


Vous commencez à écrire votre propre simulateur en étendant la classe QuantumProcessorBase:



class ReversibleSimulatorProcessor : QuantumProcessorBase {
    private IDictionary<Qubit, bool> simulationValues = new Dictionary<Qubit, bool>();

    //       (intrinsic operations)...
}


Le dictionnaire qui stockera la valeur de simulation actuelle pour chaque qubit du programme a déjà été ajouté à la classe. Les états quantiques classiques | 0⟩ et | 1⟩ sont représentés respectivement par des valeurs booléennes false et true. Les simulateurs définissent les opérations internes dans un programme Q #. QuantumProcessorBasecontient une méthode pour chaque opération interne que vous pouvez remplacer pour définir son comportement dans le nouveau simulateur. Notez que les méthodes non implémentées lèveront une exception par défaut. Cela vous aidera à identifier les cas où l'opération doit encore être implémentée et informera l'utilisateur du simulateur que l'opération en ligne n'est pas prise en charge par le simulateur. Par exemple, un simulateur réversible ne peut pas être utilisé pour simuler des programmes quantiques contenant des opérations non classiques telles que H ou T.



Commençons par initialiser faussement les qubits nouvellement alloués en remplaçant la méthode OnAllocateQubits. De même, les qubits sont supprimés du dictionnaire lorsqu'ils sont libérés. Dans ce cas, OnReleaseQubits est appelé.



public override void OnAllocateQubits(IQArray qubits) {
    foreach (var qubit in qubits) {
        simulationValues[qubit] = false;
    }
}

public override void OnReleaseQubits(IQArray qubits) {
    foreach (var qubit in qubits) {
        simulationValues.Remove(qubit);
    }
}


Avec ces deux opérations, il est garanti que les valeurs de simulation sont disponibles lorsque l'opération est appliquée à certains qubit, et qu'il n'y a plus de valeurs de simulation dans le dictionnaire pour les qubits qui ne sont pas dans la région courante.



Comme étape suivante, implémentons les actions d'opérations classiques. Dans ce cas, deux méthodes suffisent: la méthode X est appelée lorsque l'opération X est simulée, et la méthode ControlledX est appelée lorsque l'opération X est simulée, qui comprend également CNOT et CCNOT. L'opération X inverse la valeur de simulation du qubit, alors que dans le cas d'une opération X contrôlée arbitrairement, le qubit cible est inversé si et seulement si tous les qubits de contrôle sont mis à true.



public override void X(Qubit qubit) {
    simulationValues[qubit] = !simulationValues[qubit];
}

public override void ControlledX(IQArray controls, Qubit qubit) {
    simulationValues[qubit] ^= And(controls);
}


Enfin, la mesure d'un qubit dans le programme Q # renvoie un résultat (un ou zéro), qui peut être calculé en fonction de la valeur de simulation actuelle du qubit. Nous implémentons également une méthode de réinitialisation qui est appelée lorsqu'une opération de réinitialisation est appelée dans un programme Q #. Ce dernier ne supprime pas le qubit de la région actuelle, mais réinitialise la valeur de simulation à sa valeur initiale, qui est fausse.



public override Result M(Qubit qubit) {
    return simulationValues[qubit] ? Result.One : Result.Zero;
}

public override void Reset(Qubit qubit) {
    simulationValues[qubit] = false;
}


Le ReversibleSimulatorProcessor peut être utilisé comme simulateur en instanciant un QuantumProcessorDispatcher. Le style de conception recommandé est de fournir une classe personnalisée pour le simulateur:



public class ReversibleSimulator : QuantumProcessorDispatcher {
    public ReversibleSimulator() : base(new ReversibleSimulatorProcessor()) {}
}


Utilisation du nouveau simulateur



Essayons le nouveau simulateur sur l'opération Q # suivante:



operation ApplyMajority(a : Qubit, b : Qubit, c : Qubit, f : Qubit) : Unit {
    within {
        CNOT(b, a);
        CNOT(b, c);
    } apply {
        CCNOT(a, c, f);
        CNOT(b, f);
    }
}


Nous écrivons également une opération qui effectue une opération majoritaire en fournissant trois entrées booléennes:



operation RunMajority(a : Bool, b : Bool, c : Bool) : Bool {
    using ((qa, qb, qc, f) = (Qubit(), Qubit(), Qubit(), Qubit())) {
        within {
            ApplyPauliFromBitString(PauliX, true, [a, b, c], [qa, qb, qc]);
        } apply {
            ApplyMajority(qa, qb, qc, f);
        }
        return MResetZ(f) == One;
    }
}


Enfin, vous pouvez tout rassembler en appelant une opération Q # avec un nouveau simulateur dans le programme hôte C #. L'exemple suivant évalue l'opération principale pour toutes les différentes destinations d'entrée et affiche tous les résultats de la simulation:



public static void Main(string[] args) {
    var sim = new ReversibleSimulator();
    var bits = new[] {false, true};

    foreach (var a in bits) {
        foreach (var b in bits) {
            foreach (var c in bits) {
                var f = RunMajority.Run(sim, a, b, c).Result;
                Console.WriteLine($"Majority({a,5}, {b,5}, {c,5})  =  {f,5}");
            }
        }
    }
}


Prêt à écrire votre propre simulateur?



Nous espérons que cet article vous inspirera à créer votre propre simulateur. Dans les étapes suivantes, nous discuterons comment améliorer les performances de l'implémentation actuelle (par exemple, en n'utilisant pas de dictionnaire pour stocker les valeurs de simulation), comment le transformer en un projet Q # autonome, comment fournir des actions personnalisées pour les opérations non intrinsèques et comment fournir des opérations de diagnostic qui aide au débogage.



All Articles