Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Durr hoyer library min and max #1936

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion language_service/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ fn get_indent(compilation: &Compilation, package_offset: u32) -> String {
.try_into()
.expect("offset can't be converted to uszie");
let before_offset = &source.contents[..source_offset];
let mut indent = match before_offset.rfind(|c| c == '{' || c == '\n') {
let mut indent = match before_offset.rfind(&['{', '\n'][..]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to change this? IMO this PR should focus only on implementing DH library.

I saw your commit message saying that build.py doesnt work for you without this change. I will leave this decision to @sezna.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a way to skip over the error when I run the build.py I am happy to change it. I agree it is out of place, and will be hard to log trace down the line while it is included in this PR. maybe we can create a ticket to address this and I can push a PR for that?

Some(begin) => {
let indent = &before_offset[begin..];
indent.strip_prefix('{').unwrap_or(indent)
Expand Down
4 changes: 4 additions & 0 deletions library/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
),
];
1 change: 1 addition & 0 deletions library/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod canon;
mod convert;
mod core;
mod diagnostics;
mod durrhoyerlibrary;
mod intrinsic;
mod logical;
mod math;
Expand Down
37 changes: 37 additions & 0 deletions library/src/tests/durrhoyerlibrary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use super::test_expression_with_lib;
use qsc::interpret::Value;

const DURR_HOYER_LIB: &str = include_str!("resources/src/durrhoyerlibrary.qs");

#[test]
fn check_durr_hoyer_minimum_test_case() {
// Call the test expression for running the Durr-Hoyer Minimum Unit Test
test_expression_with_lib(
"Test.RunDurrHoyerMinimumUnitTestWithShots(1000)",
DURR_HOYER_LIB,
&Value::unit(),
);
}

#[test]
fn check_durr_hoyer_maximum_test_case() {
// Call the test expression for running the Durr-Hoyer Maximum Unit Test
test_expression_with_lib(
"Test.RunDurrHoyerMaximumUnitTestWithShots(1000)",
DURR_HOYER_LIB,
&Value::unit(),
);
}

#[test]
fn check_durr_hoyer_zero_test_case() {
// Call the test expression for running the Durr-Hoyer Maximum Unit Test
test_expression_with_lib(
"Test.RunDurrHoyerZeroValuesUnitTestWithShots(1000)",
DURR_HOYER_LIB,
&Value::unit(),
);
}
98 changes: 98 additions & 0 deletions library/src/tests/resources/src/durrhoyerlibrary.qs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Some of the comments from previous file regarding imports apply here.
  • Also, could you please extract common logic from RunDHMinUT and RunDHMaxUT?

Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Test {
import Std.Math.*;
import Std.Arrays.*;
import Std.Convert.*;
import Std.Random.*;
import Std.Diagnostics.*;
import Std.DurrHoyerLibrary.*;

function MaxIntArray(arr : Int[]) : Int {
mutable max = arr[0];
for i in 1..(Length(arr) - 1) {
if (arr[i] > max) {
set max = arr[i];
}
}
return max;
}

/// Shared logic for running the Durr-Hoyer test cases
operation RunDurrHoyerTestWithShots(testLists : Int[][], expectedIndices : Int[], shots : Int, testType : String) : Unit {
// Iterate over test cases
for (list, expectedIndex) in Zipped(testLists, expectedIndices) {
let maxValue = MaxIntArray(list);
let double : Double = IntAsDouble(maxValue + 1);
let log : Double = Log(double) / Log(2.0);
let nQubits = Ceiling(log);
let listSize = Length(list);


// Variable to track how many times we find the correct index
mutable correctCount = 0;

// Run the Durr-Hoyer algorithm multiple times (shots)
for _ in 1..shots {
let candidate = DrawRandomInt(0, Length(list) - 1);
let foundIndex : Int = DurrHoyerAlgorithm(list, nQubits, testType, candidate, listSize);

// Check if the found index matches the expected index
if (foundIndex == expectedIndex) {
set correctCount += 1;
}
}

// Calculate the probability of finding the correct index
let probability = IntAsDouble(correctCount) / IntAsDouble(shots);

// Assert that the probability is above 50%
Fact(probability >= 0.5, $"Probability of finding the {testType} for list {list} is less than 50%. Found: {probability * 100.0}%");

// Optionally print debugging info
Message($"List: {list}");
Message($"Probability of finding the {testType} is {probability * 100.0}%");
}
}

// Minimum test case using the shared logic
operation RunDurrHoyerMinimumUnitTestWithShots(shots : Int) : Unit {
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];

// Use the shared logic to run the test with "min" type
RunDurrHoyerTestWithShots(testLists, expectedMinIndices, shots, "min");
}
operation RunDurrHoyerMaximumUnitTestWithShots(shots : Int) : Unit {
let testLists : Int[][] = [
[2, 3, 1, 5, 4],
[1, 5, 4, 3, 6],
[7, 5, 6, 1, 2]
];

// Expected results (maximum element index for each list)
let expectedMaxIndices : Int[] = [3, 4, 0];

// Use the shared logic to run the test with "max" type
RunDurrHoyerTestWithShots(testLists, expectedMaxIndices, shots, "max");
}
operation RunDurrHoyerZeroValuesUnitTestWithShots(shots : Int) : Unit {
let testLists = [
[0, 3, 1, 2, 4],
[6, 0, 4, 3, 1],
[7, 5, 6, 0, 2]
];

// Expected results (minimum element index for each list)
let expectedMinIndices = [0, 1, 3];

// Use the shared logic to run the test with "min" type
RunDurrHoyerTestWithShots(testLists, expectedMinIndices, shots, "min");
}
}
6 changes: 4 additions & 2 deletions library/std/qsharp.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"src/Std/Math.qs",
"src/Std/Measurement.qs",
"src/Std/Random.qs",
"src/Std/ResourceEstimation.qs"
"src/Std/ResourceEstimation.qs",
"src/Std/DurrHoyerLibrary.qs"

]
}
}
213 changes: 213 additions & 0 deletions library/std/src/Std/DurrHoyerLibrary.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import Std.Math.*;
import Std.Arrays.*;
import Std.Convert.*;

/// # Summary
/// Counts the number of marked states to be used to optimize Grover iterations to do.
///
function CountElements(list : Int[], comparison : Int -> Bool) : Int {
mutable count = 0;

for element in list {
if comparison(element) {
set count += 1;
}
}

return count;
}

/// # Summary
/// 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;
}

/// # Summary
/// 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]);
}
}
}

/// # Summary
/// 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);
}
/// # Summary
/// 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);
}

/// # Summary
/// Dürr-Høyer for finding min or max algorithm
///
operation DurrHoyerAlgorithm(list : Int[], nQubits : Int, type : String, candidate : Int, listSize : Int) : Int {
mutable candidate = candidate; // Random initial candidate

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;
let threshold = list[candidate];

// Define the comparison function based on the type
let comparison = type == "min" ? (x -> x < threshold) | (x -> x > threshold);

// Calculate M: the number of elements smaller than the current candidate (for min)
let M = CountElements(list, comparison);

// 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 candidate;
}

// 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 {
let groverIteration = (type == "min") ? GroverIterationMin | GroverIterationMax;
groverIteration(list[candidate], 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 = ResultArrayAsInt(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[candidate]) {
OracleLessThan(list[candidate], inputQubits, auxQubit); // Mark the last candidate
set candidate = candidateIndex;
set betterCandidateFound = true;
} elif (type == "max" and candidateValue > list[candidate]) {
OracleMoreThan(list[candidate], inputQubits, auxQubit); // Mark the last candidate
set candidate = 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 candidate;
}
}
}
export DurrHoyerAlgorithm;