From 1ff82ea9f8632a8b8d8a16081f33191619ab4acf Mon Sep 17 00:00:00 2001 From: Jan Gukelberger Date: Sun, 22 Sep 2019 05:27:10 +0200 Subject: [PATCH] Oracle emulation sample (#175) * Add oracle emulation sample. * Documentation improvements. * Add reference to Readme. * Rename EmulateOracle to PermutationOracle and provide a default implementation (which just fails). * Address stylistic comments from code review. * Update to QDK 0.7. * Align definition of emulated oracle with the approach taken in EstimateFrequencyA. * Update to latest QDK API; improve comments. --- Samples/src/OracleEmulation/Driver.cs | 53 +++++ Samples/src/OracleEmulation/Operations.qs | 173 ++++++++++++++ .../OracleEmulation/OracleEmulation.csproj | 15 ++ .../src/OracleEmulation/PermutationOracle.cs | 222 ++++++++++++++++++ .../src/OracleEmulation/PermutationOracle.qs | 42 ++++ Samples/src/OracleEmulation/README.md | 19 ++ 6 files changed, 524 insertions(+) create mode 100644 Samples/src/OracleEmulation/Driver.cs create mode 100644 Samples/src/OracleEmulation/Operations.qs create mode 100644 Samples/src/OracleEmulation/OracleEmulation.csproj create mode 100644 Samples/src/OracleEmulation/PermutationOracle.cs create mode 100644 Samples/src/OracleEmulation/PermutationOracle.qs create mode 100644 Samples/src/OracleEmulation/README.md diff --git a/Samples/src/OracleEmulation/Driver.cs b/Samples/src/OracleEmulation/Driver.cs new file mode 100644 index 000000000000..0bc13e36adad --- /dev/null +++ b/Samples/src/OracleEmulation/Driver.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Quantum.Simulation.Simulators; +using Microsoft.Quantum.Extensions.Oracles; + +namespace Microsoft.Quantum.Samples.OracleEmulation +{ + class Driver + { + public static void Pause() + { + System.Console.WriteLine("\n\nPress any key to continue...\n\n"); + System.Console.ReadKey(); + } + + static void Main(string[] args) + { + // We begin by defining a quantum simulator to be our target machine + using (var qsim = new QuantumSimulator()) + { + #region Simple oracles + + // Create an oracle from a C# lambda. + // The result is an operation with signature + // (Qubit[], Qubit[]) => Unit + // that can be passed to Q#. + var oracle = EmulatedOracleFactory.Create(qsim, (x, y) => 42 ^ y); + + // Provide the definition of an oracle that has been declared in + // Q#, replacing the stub body defined in Operations.qs. This + // way, the `HalfAnswer` oracle is accessible via the + // `OracleEmulation` namespace and does not have to be passed to + // operations depending on it (unlike the oracle created above). + EmulatedOracleFactory.Register(qsim, (x, y) => 21 ^ y); + + // Execute the simple oracles and print the results. + RunConstantOracles.Run(qsim, oracle).Wait(); + Pause(); + + #endregion + + #region Emulated arithmetic + + // Run the demo for emulated arithmetic. + RunAddOracle.Run(qsim).Wait(); + Pause(); + + #endregion + } + } + } +} \ No newline at end of file diff --git a/Samples/src/OracleEmulation/Operations.qs b/Samples/src/OracleEmulation/Operations.qs new file mode 100644 index 000000000000..0913405e06e3 --- /dev/null +++ b/Samples/src/OracleEmulation/Operations.qs @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +namespace Microsoft.Quantum.Samples.OracleEmulation +{ + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Arithmetic; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Extensions.Oracles; + + + ////////////////////////////////////////////////////////////////////////// + // Defining and using simple emulated oracles //////////////////////////// + ////////////////////////////////////////////////////////////////////////// + + // Declare an oracle to be implemented in C#. See the + // `PermutationOracle.Register` call in the driver for its implementation. + operation HalfAnswer(x: Qubit[], y: Qubit[]) : Unit { + // Since we are here only interested in the emulation feature, we do not + // provide a native Q# implementation. In general, providing a Q# + // implementation is encouraged because it allows for resource counting + // and running on target machines without emulation capabilities. + body (...) + { + fail "not implemented"; + } + adjoint auto; + } + + // Define a simple permutation function that is used below to create + // another oracle. + function DoubleAnswerFunc(x: Int, y: Int) : Int { + return 84 ^^^ y; + } + + // Measure and print the result. + operation MeasureAndDisplay(message: String, register: Qubit[]) : Unit { + let answer = MeasureInteger(LittleEndian(register)); + Message($"{message}{answer}."); + } + + // # Summary + // Here we demonstrate the use of three simple oracles. Each oracle ignores + // the content of the first register and XOR's a constant number into the + // second register. + // + // # Input + // ## oracle + // A quantum operation that implements an oracle + // $$ + // \begin{align} + // O: \ket{x}\ket{y} \rightarrow \ket{x}\ket{f(x, y)}. + // \end{align} + // $$ + operation RunConstantOracles (oracle: ((Qubit[], Qubit[]) => Unit)) : Unit { + Message("Querying the oracles..."); + + // Prepare a one-qubit register `flag` and an eight-qubit register + // `result`. Since all the oracles here ignore the flag, its length and + // state do not matter. + using ((flag, result) = (Qubit(), Qubit[8])) { + H(flag); + + // Apply an oracle that was passed explicitly by the C# driver. + oracle([flag], result); + MeasureAndDisplay("The answer is ", result); + + // Apply an oracle that was declared above and implemented in the C# + // driver. + HalfAnswer([flag], result); + MeasureAndDisplay("Half the answer is ", result); + + // Apply an oracle defined in terms of a Q# permutation function. + PermutationOracle(DoubleAnswerFunc, [flag], result); + MeasureAndDisplay("Twice the answer is ", result); + + // Apply an oracle to a superposition in result. + for(i in 1..5) { + H(result[7]); + // Before the oracle is queried, the state of the result register is + // $\ket{y} = \ket{0} + \ket{128}$. + // The oracle will map this to + // $\ket{y'} = \ket{42 \oplus 0} + \ket{42 \oplus 128} = \ket{42} + \ket{170}$. + oracle([flag], result); + MeasureAndDisplay("The answer might be ", result); + } + + Reset(flag); + } + } + + + ////////////////////////////////////////////////////////////////////////// + // Emulated arithmetic operations //////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + + // Prepare two `LittleEndian` registers in a computational basis state. + operation PrepareSummands(numbers: (Int, Int), registers: (Qubit[], Qubit[])) : (LittleEndian, LittleEndian) { + let x = LittleEndian(Fst(registers)); + let y = LittleEndian(Snd(registers)); + ApplyXorInPlace(Fst(numbers), x); + ApplyXorInPlace(Snd(numbers), y); + return (x, y); + } + + // Measure and check that M(x) + y_init == M(y). + operation MeasureAndCheckAddResult(y_init: Int, x: LittleEndian, y: LittleEndian): (Int, Int) { + let mx = MeasureInteger(x); + let my = MeasureInteger(y); + Message($"Computed {mx} + {y_init} = {my} mod {2^8}"); + EqualityFactI((mx + y_init) % 2^8, my, "sum is wrong"); + return (mx, my); + } + + // Modular addition of 8-bit integers. + function ModAdd8(x: Int, y: Int) : Int { + return (x + y) % (1 <<< 8); + } + + // Here we demonstrate how to define and use emulated arithmetic operations. + // Our example is modular addition of 8-bit integers as implemented by the + // one-line function above. This defines already a permutation function on + // the computational basis states of two 8-qubit registers: + // $\ket{m}\ket{n} \rightarrow \ket{m}\ket{m + n \mod 8}$. + // We can hence directly turn this function into an emulated oracle. + operation RunAddOracle() : Unit { + Message("Running emulated addition..."); + + // Turn the permutation function into an oracle operation acting on two + // quantum registers. + let adder = PermutationOracle(ModAdd8, _, _); + let width = 8; + + // Two integers to initialize the registers. + let numbers = (123, 234); + + // Write the numbers into registers and add them. + using (registers = (Qubit[width], Qubit[width])) { + // Prepare two `LittleEndian` registers, initialized to the values + // in `numbers`. + let (x, y) = PrepareSummands(numbers, registers); + + // Apply the add oracle. Note that the oracle expects two plain + // `Qubit[]` registers, so the `LittleEndian` variables `x`, `y` + // need to be unwrapped with the `!` operator. + adder(x!, y!); + + // Measure the registers. Check that the addition was performed and + // the input register `x` has not been changed. + let (mx, my) = MeasureAndCheckAddResult(Snd(numbers), x, y); + EqualityFactI(mx, Fst(numbers), "x changed!"); + } + + // Now do two additions in superposition. + for(i in 1..5) { + using (registers = (Qubit[width], Qubit[width])) { + // Prepare x in the superposition $\ket{x} = \ket{123} + \ket{251}$. + let (x, y) = PrepareSummands(numbers, registers); + H(x![7]); + + // Apply the add oracle. + adder(x!, y!); + + // Measure the registers. Check that the addition was performed and + // the input register `x` has collapsed into either 123 or 251. + let (mx, my) = MeasureAndCheckAddResult(Snd(numbers), x, y); + Fact(mx == Fst(numbers) or mx == (Fst(numbers) + 2^7) % 2^width, "x changed!"); + } + } + } +} diff --git a/Samples/src/OracleEmulation/OracleEmulation.csproj b/Samples/src/OracleEmulation/OracleEmulation.csproj new file mode 100644 index 000000000000..6125e6d4fe14 --- /dev/null +++ b/Samples/src/OracleEmulation/OracleEmulation.csproj @@ -0,0 +1,15 @@ + + + + Exe + netcoreapp2.0 + x64 + Microsoft.Quantum.Samples.OracleEmulation.Driver + + + + + + + + diff --git a/Samples/src/OracleEmulation/PermutationOracle.cs b/Samples/src/OracleEmulation/PermutationOracle.cs new file mode 100644 index 000000000000..280c0637e031 --- /dev/null +++ b/Samples/src/OracleEmulation/PermutationOracle.cs @@ -0,0 +1,222 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Microsoft.Quantum.Simulation; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace Microsoft.Quantum.Extensions.Oracles +{ + /// + /// This class provides the infrastructure to define and efficiently + /// apply permutation oracles within a (full state) simulator. + /// + public class OracleEmulator + { + /// + /// The main entry point for emulation of a permutation oracle: Apply + /// the permutation defined by the oracle function + /// f: (x, y) -> (x, f(x, y)). + /// + public static void ApplyOracle(QuantumSimulator simulator, Func oracle, + IQArray xbits, IQArray ybits, bool adjoint = false) + { + var permutation = BuildPermutationTable(oracle, (int)xbits.Length, (int)ybits.Length); + ApplyOracle(simulator, permutation, xbits, ybits, adjoint); + } + + /// + /// Apply a permutation defined by a permutation table. This overload + /// allows for perfomance optimizations like reuse of permutation + /// tables. + /// + public static void ApplyOracle(QuantumSimulator simulator, Int64[] permutation, + IQArray xbits, IQArray ybits, bool adjoint = false) + { + simulator.CheckQubits(xbits, "x"); + simulator.CheckQubits(ybits, "y"); + Debug.Assert(CheckPermutation(permutation)); + var qbits = QArray.Add(xbits, ybits).GetIds(); + if (adjoint) + AdjPermuteBasisTable(simulator.Id, (uint)qbits.Length, qbits, permutation.LongLength, permutation); + else + PermuteBasisTable(simulator.Id, (uint)qbits.Length, qbits, permutation.LongLength, permutation); + } + + /// + /// Build a permutation table for nx- and ny-qubit registers from a + /// permutation function. + /// + public static Int64[] BuildPermutationTable(Func oracle, int nx, int ny) + { + Int64 xmask = (1L << nx) - 1L; + Int64 ymask = ((1L << ny) - 1L) << nx; + Int64 table_size = 1L << (nx + ny); + + var permutation = new Int64[table_size]; + for (Int64 state = 0; state < table_size; ++state) + { + Int64 x = state & xmask; + Int64 y = (state & ymask) >> nx; + Int64 z = oracle(x, y); + Int64 result = x | (z << nx); + permutation[state] = result; + } + + return permutation; + } + + /// + /// Check whether the given permutation table is actually bijective, + /// i.e. a valid permutation. + /// + public static bool CheckPermutation(Int64[] permutation) + { + var mapped = new BitArray(permutation.Length); + for (int i = 0; i < permutation.Length; ++i) + { + var j = (int)permutation[i]; + Debug.Assert(j >= 0 && j < permutation.Length); + mapped[j] = true; + } + for (int i = 0; i < permutation.Length; ++i) + { + if (!mapped[i]) + return false; + } + return true; + } + + // Entry points to the simulator backend + [DllImport(QuantumSimulator.QSIM_DLL_NAME, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PermuteBasis")] + private static extern void PermuteBasisTable(uint id, uint num_qbits, [In] uint[] qbits, long table_size, [In] long[] permutation_table); + [DllImport(QuantumSimulator.QSIM_DLL_NAME, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl, EntryPoint = "AdjPermuteBasis")] + private static extern void AdjPermuteBasisTable(uint id, uint num_qbits, [In] uint[] qbits, long table_size, [In] long[] permutation_table); + } + + + /// + /// Extension of the PermutationOracle operation defined in + /// PermutationOracle.qs with an emulated version. + /// + public partial class PermutationOracle + { + + /// + /// Native emulation of permutation oracles when run on a full state + /// simulator. Directly permutes the basis state amplitudes in the wave + /// function of the simulator, rather than computing and applying a + /// sequence of gates with the same effect. + /// + public class Native : PermutationOracle + { + private QuantumSimulator Simulator { get; } + + public Native(QuantumSimulator m) : base(m) + { + this.Simulator = m; + } + + /// + /// Overrides the body to do the emulation. + /// + public override Func<(ICallable, IQArray, IQArray), QVoid> Body => (_args) => + { + var (oracle, xbits, ybits) = _args; + OracleEmulator.ApplyOracle(this.Simulator, (x, y) => oracle.Apply((x, y)), xbits, ybits, adjoint: false); + return QVoid.Instance; + }; + + /// + /// Overrides the adjoint body to do the emulation. + /// + public override Func<(ICallable, IQArray, IQArray), QVoid> AdjointBody => (_args) => + { + var (oracle, xbits, ybits) = _args; + OracleEmulator.ApplyOracle(this.Simulator, (x, y) => oracle.Apply((x, y)), xbits, ybits, adjoint: true); + return QVoid.Instance; + }; + } + } + + + /// + /// Factory class facilitating the creation of emulated permutation oracles + /// from C# code. + /// + public class EmulatedOracleFactory + { + /// + /// Create an oracle Operation that applies a permutation to the basis + /// states of two registers. + /// + public static Adjointable<(IQArray, IQArray)> Create(QuantumSimulator simulator, Func permutation) + { + return new PermutationOracleImpl(simulator, permutation); + } + + /// + /// Register a permutation oracle as the implementation of the + /// operation "Op", which is typically a Q# declaration of the form + /// operation MyOracle(xbits : Qubit[], ybits : Qubit[]) : Unit + /// { + /// body intrinsic; + /// adjoint intrinsic; + /// } + /// + public static void Register(QuantumSimulator simulator, Func permutation) + { + PermutationOracleImpl.RegisterPermutation(permutation); + simulator.Register(typeof(Op), typeof(PermutationOracleImpl), typeof(ICallable)); + } + + + // Infrastructure to allow for programmatic definition and registration of new oracles. + private class PermutationOracleImpl : Adjointable<(IQArray, IQArray)>, ICallable + { + private static Dictionary> registered_permutations = new Dictionary>(); + public static void RegisterPermutation(Func permutation) + { + registered_permutations[typeof(Op)] = permutation; + } + + private QuantumSimulator Simulator { get; } + private Func Permutation { get; } + + public PermutationOracleImpl(QuantumSimulator m) : base(m) + { + this.Simulator = m; + this.Permutation = registered_permutations[typeof(Op)]; ; + } + + public PermutationOracleImpl(QuantumSimulator m, Func permutation) : base(m) + { + this.Simulator = m; + this.Permutation = permutation; + } + + string ICallable.FullName => $"PermutationOracleImpl<{typeof(Op)}>"; + + public override void Init() { } + + public override Func<(IQArray, IQArray), QVoid> Body => (_args) => + { + var (xbits, ybits) = _args; + OracleEmulator.ApplyOracle(this.Simulator, this.Permutation, xbits, ybits, adjoint: false); + return QVoid.Instance; + }; + + public override Func<(IQArray, IQArray), QVoid> AdjointBody => (_args) => + { + var (xbits, ybits) = _args; + OracleEmulator.ApplyOracle(this.Simulator, this.Permutation, xbits, ybits, adjoint: true); + return QVoid.Instance; + }; + } + } +} diff --git a/Samples/src/OracleEmulation/PermutationOracle.qs b/Samples/src/OracleEmulation/PermutationOracle.qs new file mode 100644 index 000000000000..82ca7bbb7aac --- /dev/null +++ b/Samples/src/OracleEmulation/PermutationOracle.qs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +namespace Microsoft.Quantum.Extensions.Oracles +{ + /// # Summary + /// Apply a classical permutation oracle to two registers. + /// + /// # Description + /// The effect of the oracle is a permutation of basis states according to + /// the provided classical function: + /// $$ + /// \begin{align} + /// \ket{x}\ket{y}\ket{w} \rightarrow \ket{x}\ket{f(x, y)}\ket{w}, + /// \end{align} + /// $$ + /// with registers x, y, w and the oracle function f. + /// + /// # Input + /// ## oracle + /// A function that defines the action of the oracle on the computational + /// basis states of the two registers x, y. The mapping + /// $$ + /// \begin{align} + /// $(x, y) \rightarrow (x, z=f(x, y))$ + /// \end{align} + /// $$ + /// must be a bijective mapping on the basis states. + /// ## xbits + /// Input register x. + /// ## ybits + /// Output register y. + operation PermutationOracle(oracle : ((Int, Int) -> Int), xbits : Qubit[], ybits : Qubit[]) : Unit + { + body (...) + { + fail "not implemented for general target machines yet"; + } + adjoint auto; + } +} diff --git a/Samples/src/OracleEmulation/README.md b/Samples/src/OracleEmulation/README.md new file mode 100644 index 000000000000..b0bf2b0bc40c --- /dev/null +++ b/Samples/src/OracleEmulation/README.md @@ -0,0 +1,19 @@ +# Oracle Emulation Sample # + +This sample describes how to create and use emulated permutation oracles with the full state simulator of the Quantum Development Kit. Emulated oracles directly permute the wavefunction in the simulator. They allow for rapid prototyping and testing of quantum algorithms that involve calls to classical functions on a superposition of input arguments. An important use case are arithmetic operations on quantum registers. Emulation is not applicable to quantum hardware and hence specific to the quantum simulator. + +See [Häner et al., High Performance Emulation of Quantum Circuits (2016)](https://arxiv.org/abs/1604.06460) for a general explanation of oracle emulation. + +## Running the Sample ## + +Open the `QsharpSamples.sln` solution in Visual Studio and set the .csproj file in the manifest as the startup project. +Press Start in Visual Studio to run the sample. + +## Manifest ## + +- [Operations.qs](./Operations.qs): The main Q# example code implementing quantum operations for this sample. +- [Driver.cs](./Driver.cs): C# code to interact with and print out results of the Q# operations for this sample. Also contains two examples for creating an oracle from a C# function. +- [Emulator.cs](./Emulator.cs): An extension of the QDK's quantum simulator with convenience functions to create and apply emulated oracles. +- [Emulator.qs](./Emulator.qs): The Q# interface for permutation oracles that can be emulated. +- [OracleEmulation.csproj](./OracleEmulation.csproj): Main C# project for the sample. +