diff --git a/library/src/lib.rs b/library/src/lib.rs index c0f28dfeda..ce37f66397 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -93,4 +93,8 @@ pub const STD_LIB: &[(&str, &str)] = &[ "qsharp-library-source:legacy_api.qs", include_str!("../std/src/legacy_api.qs"), ), + ( + "qsharp-library-source:DurrHoyerLibrary.qs", + include_str!("../std/src/Std/DurrHoyerLibrary.qs"), + ), ]; diff --git a/library/src/tests/durrhoyerlibrary.rs b/library/src/tests/durrhoyerlibrary.rs new file mode 100644 index 0000000000..e24128c826 --- /dev/null +++ b/library/src/tests/durrhoyerlibrary.rs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use super::test_expression_with_lib; +use qsc::interpret::Value; + +// Library that includes the necessary DurrHoyerAlgorithm implementation +const DURR_HOYER_LIB: &str = include_str!("resources/src/durrhoyerlibrary.qs"); + +#[test] +fn check_durr_hoyer_minimum_test_case_1() { + test_expression_with_lib( + "Test.RunDurrHoyerMinimumUnitTestWithShots(1000)", + DURR_HOYER_LIB, + ); +} + +#[test] +fn check_durr_hoyer_maximum_test_case_3() { + test_expression_with_lib( + "Test.RunDurrHoyerMaximumUnitTestWithShots(1000)", + DURR_HOYER_LIB, + ); +} diff --git a/library/src/tests/resources/src/durrhoyerlibrary.qs b/library/src/tests/resources/src/durrhoyerlibrary.qs new file mode 100644 index 0000000000..40ecbf3910 --- /dev/null +++ b/library/src/tests/resources/src/durrhoyerlibrary.qs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Test { + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Measurement; + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Random; + open Microsoft.Quantum.Core; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.DurrHoyerLibrary; + + // Function to find the maximum element in an array + function MaxIntArray(arr : Int[]) : Int { + mutable max = arr[0]; + for i in arr[1..Length(arr) - 1] { + if (arr[i] > max) { + set max = arr[i]; + } + } + return max; + } + + // Function to compute the probability of finding the minimum index + operation RunDurrHoyerMinimumUnitTestWithShots(shots : Int) : Unit { + // Define test lists for the unit test + let testLists = [ + [5, 3, 1, 2, 4], + [6, 5, 4, 3, 1], + [7, 5, 6, 1, 2] + ]; + + // Expected results (minimum element index for each list) + let expectedMinIndices = [2, 4, 3]; + + // Iterate over test cases + for (list, expectedMinIndex) in Zipped(testLists, expectedMinIndices) { + let maxValue = MaxIntArray(list); + let double : Double = IntAsDouble(maxValue + 1); + let log : Double = Log(double) / Log(2.0); + let nQubits = Ceiling(log); + + // Variable to track how many times we find the correct minimum index + mutable correctCount = 0; + + // Run the Durr-Hoyer algorithm multiple times (shots) + for _ in 1..shots { + let minIndex : Int = DurrHoyerAlgorithm(list, nQubits, "min"); + + // Check if the found index matches the expected minimum index + if (minIndex == expectedMinIndex) { + set correctCount += 1; + } + } + + // Calculate the probability of finding the correct minimum + let probability = IntAsDouble(correctCount) / IntAsDouble(shots); + + // Assert that the probability is above 50% + Assert(probability > 0.5, $"Probability of finding the minimum for list {list} is less than 50%. Found: {probability * 100.0}%"); + + // Optionally print debugging info + Message($"List: {list}"); + Message($"Probability of finding the minimum is {probability * 100.0}%"); + } + } +} diff --git a/library/std/src/Std/DurrHoyerLibrary.qs b/library/std/src/Std/DurrHoyerLibrary.qs new file mode 100644 index 0000000000..f77a1b58a1 --- /dev/null +++ b/library/std/src/Std/DurrHoyerLibrary.qs @@ -0,0 +1,226 @@ +open Microsoft.Quantum.Intrinsic; +open Microsoft.Quantum.Canon; +open Microsoft.Quantum.Math; +open Microsoft.Quantum.Measurement; +open Microsoft.Quantum.Arrays; +open Microsoft.Quantum.Convert; +open Microsoft.Quantum.Random; +open Microsoft.Quantum.Core; +open Microsoft.Quantum.Diagnostics; + +function CountElements(list : Int[], threshold : Int, comparisonType : String) : Int { + mutable count = 0; + + for element in list { + if (comparisonType == "min" and element < threshold) { + set count += 1; + } elif (comparisonType == "max" and element > threshold) { + set count += 1; + } + } + + return count; +} + +/// Converts an integer to its binary representation as an array of Results. +/// The least significant bit is at index 0. +function ConvertToBinary(value : Int, length : Int) : Result[] { + // Validate input + if (length <= 0) { + fail "Length must be a positive integer."; + } + + // Ensure the value fits within the specified length + let maxVal = (1 <<< length) - 1; + if (value < 0 or value > maxVal) { + fail $"Value {value} cannot be represented with {length} bits."; + } + + // Initialize the binary array with default values + mutable binary : Result[] = []; + + // Generate the binary array + for i in 0..length - 1 { + let bitValue = value &&& (1 <<< i); // Extract the i-th bit + let res = if (bitValue != 0) { One } else { Zero }; // Determine Result + // Correct syntax to assign to the array + set binary += [res]; + + } + + // Return the constructed binary array + return binary; +} +function ResultAsInt(r : Result) : Int { + if (r == One) { + return 1; + } else { + return 0; + } +} + +function BitsToInt(bits : Result[]) : Int { + mutable result = 0; + for i in 0..Length(bits) - 1 { + if (bits[i] == One) { + set result += (1 <<< i); + } + } + return result; +} + +// Oracle that marks elements less than the threshold through Most Signficant Bit comparision +operation OracleLessThan(threshold : Int, inputQubits : Qubit[], auxQubit : Qubit) : Unit is Adj + Ctl { + // Convert the threshold to binary and compare + let thresholdBits = ConvertToBinary(threshold, Length(inputQubits)); + for i in 0..Length(thresholdBits) - 1 { + if (thresholdBits[i] == Zero) { + // Most Signficant Bit comparision, if There is a zero when the bits are compared we have something less than + X(inputQubits[i]); // Flip qubits that should be zero in the threshold + } + } + + // Controlled-Z gate to flip the phase of the state if the element is less than the threshold + Controlled Z(inputQubits, auxQubit); + + // Undo the X operations to revert qubits + for i in 0..Length(thresholdBits) - 1 { + if (thresholdBits[i] == Zero) { + X(inputQubits[i]); + } + } +} + +// Oracle that marks elements more than the threshold through Most Signficant Bit comparision +operation OracleMoreThan(threshold : Int, inputQubits : Qubit[], auxQubit : Qubit) : Unit is Adj + Ctl { + // Convert the threshold to binary and compare + let thresholdBits = ConvertToBinary(threshold, Length(inputQubits)); + for i in 0..Length(thresholdBits) - 1 { + if (thresholdBits[i] == One) { + // Most Signficant Bit comparision, if tbere is a one when the bits are compared we have something more than + X(inputQubits[i]); // Flip qubits that should be zero in the threshold + } + } + + // Controlled-Z gate to flip the phase of the state if the element is less than the threshold + Controlled Z(inputQubits, auxQubit); + + // Undo the X operations to revert qubits + for i in 0..Length(thresholdBits) - 1 { + if (thresholdBits[i] == One) { + X(inputQubits[i]); + } + } +} + +// Diffusion operator (Grover's diffusion) +operation DiffusionOperator(qubits : Qubit[]) : Unit { + ApplyToEach(H, qubits); + ApplyToEach(X, qubits); + Controlled Z(qubits[0..Length(qubits) - 2], qubits[Length(qubits) - 1]); + ApplyToEach(X, qubits); + ApplyToEach(H, qubits); +} + +// Grover iteration with the oracle and diffusion operator for min +operation GroverIterationMin(threshold : Int, inputQubits : Qubit[], auxQubit : Qubit) : Unit { + OracleLessThan(threshold, inputQubits, auxQubit); + DiffusionOperator(inputQubits); +} + +// Grover iteration with the oracle and diffusion operator for max +operation GroverIterationMax(threshold : Int, inputQubits : Qubit[], auxQubit : Qubit) : Unit { + OracleMoreThan(threshold, inputQubits, auxQubit); + DiffusionOperator(inputQubits); +} + +// Dürr-Høyer for finding min or max algorithm +operation DurrHoyerAlgorithm(list : Int[], nQubits : Int, type : String) : Int { + mutable candidateMin = DrawRandomInt(0, Length(list) - 1); // Random initial candidate + let listSize = Length(list); + + use inputQubits = Qubit[nQubits] { + use auxQubit = Qubit() { + // Create a superposition of all states + ApplyToEach(H, inputQubits); + + // Continue Grover search until no better candidate is found + mutable betterCandidateFound = true; + mutable iterationCount = 1; // Track the iteration count manually + mutable optimalIterations = 5; + mutable validIterations = 0; + + while (validIterations < optimalIterations) { + set betterCandidateFound = false; + + // Calculate M: the number of elements smaller than the current candidate (for min) + let M = CountElements(list, list[candidateMin], type); + + // If there are no more elements smaller/larger, return the candidate + if (M == 0) { + Message("No more elements to compare, search complete."); + ResetAll(inputQubits + [auxQubit]); // Ensure qubits are reset before returning + return candidateMin; + } + + // Calculate the optimal number of Grover iterations + let N = Length(list); + let optimalIterations = Round((PI() / 4.0) * Sqrt(IntAsDouble(N) / IntAsDouble(M))); + + // Perform Grover iterations for min or max + for i in 1..optimalIterations { + if (type == "min") { + GroverIterationMin(list[candidateMin], inputQubits, auxQubit); + } else { + GroverIterationMax(list[candidateMin], inputQubits, auxQubit); + } + + // Measure qubits and convert to an integer index + mutable results = []; + for qubit in inputQubits { + let result = Measure([PauliZ], [qubit]); + set results += [result]; + + // Reset qubit if it is in the |1⟩ state + if (result == One) { + X(qubit); + } + } + + let candidateIndex = BitsToInt(results); + + // Check if the new candidate is valid and within bounds + if (candidateIndex >= 0 and candidateIndex < listSize) { + let candidateValue = list[candidateIndex]; + + // Update the candidate if a better one is found + if (type == "min" and candidateValue < list[candidateMin]) { + OracleLessThan(list[candidateMin], inputQubits, auxQubit); // Mark the last candidate + set candidateMin = candidateIndex; + set betterCandidateFound = true; + } elif (type == "max" and candidateValue > list[candidateMin]) { + OracleMoreThan(list[candidateMin], inputQubits, auxQubit); // Mark the last candidate + set candidateMin = candidateIndex; + set betterCandidateFound = true; + } + set validIterations += 1; + + // Output intermediate results for debugging + Message($"Iteration {validIterations}: Measured index = {candidateIndex}, Value = {candidateValue}"); + } + // Reset all qubits to |0⟩ before returning + ResetAll(inputQubits + [auxQubit]); + + } + + } + + // Reset all qubits to |0⟩ before returning + ResetAll(inputQubits + [auxQubit]); + + // Return the found minimum or maximum index + return candidateMin; + } + } +} +export DurrHoyerAlgorithm;