diff --git a/BoundedKnapsack/BoundedKnapsack.csproj b/BoundedKnapsack/BoundedKnapsack.csproj new file mode 100644 index 00000000000..14ae3eec6d9 --- /dev/null +++ b/BoundedKnapsack/BoundedKnapsack.csproj @@ -0,0 +1,22 @@ + + + netcoreapp3.1 + x64 + Quantum.Kata.BoundedKnapsack + true + + + + + + + + + + + + + + + + diff --git a/BoundedKnapsack/BoundedKnapsack.sln b/BoundedKnapsack/BoundedKnapsack.sln new file mode 100644 index 00000000000..b164b3ffa32 --- /dev/null +++ b/BoundedKnapsack/BoundedKnapsack.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.705 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BoundedKnapsack", "BoundedKnapsack.csproj", "{A924553C-0AF5-4802-8A3A-E5E074DC34AB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A924553C-0AF5-4802-8A3A-E5E074DC34AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A924553C-0AF5-4802-8A3A-E5E074DC34AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A924553C-0AF5-4802-8A3A-E5E074DC34AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A924553C-0AF5-4802-8A3A-E5E074DC34AB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DCD43C51-6BFB-465B-808F-52F685E3257E} + EndGlobalSection +EndGlobal diff --git a/BoundedKnapsack/Hints.md b/BoundedKnapsack/Hints.md new file mode 100644 index 00000000000..954c79eefa4 --- /dev/null +++ b/BoundedKnapsack/Hints.md @@ -0,0 +1,92 @@ +``` +////////////////////////////////////////////////////////////////////// +// This file contains hints to all tasks. +// The tasks themselves can be found in Tasks.qs file. +// We recommend that you try to solve the tasks yourself first, +// but feel free to look up these hints, and ultimately +// the solutions if you get stuck. +////////////////////////////////////////////////////////////////////// +``` + +Hints have been provided to clarify and guide your work through some of +the more complicated tasks. The hints within each task gradually reveal +more and more of the solution. You might consider reading hints for a task +up to a point of understanding, and then try finishing the task yourself. + +``` +// Task 1.3. Calculate total value of selected items +``` +1) The IncrementByInteger operation may be of use: https://docs.microsoft.com/en-us/qsharp/api/qsharp/microsoft.quantum.arithmetic.incrementbyinteger. + +2) How does the state of a qubit in the `selectedItems` register affect whether its corresponding value should be added into the total? How can you express this in quantum computing? + + +``` +// Task 1.4. Compare an integer stored in a qubit array with an integer (>) +``` +1) Given two classical bit strings, what would be the most straightforward way to compare them? + +2) Are the more significant bits or the less significant bits more important in the comparison of two integers? + +3) If we begin at the most significant bit and iterate towards the least significant bit, at what point can we conclude whether a or b is larger? + +4) We can conclude which number is larger upon arriving at the highest (first encountered) bit that has different value + in a and b. Example: If a = 10101101₂ (173) and b = 10100011₂ (163), the highest 4 bits are the same in a and b. The 5th bit from the left (4th least significant bit) is the highest bit that has different values in a and b. Since it is 1 in a and 0 in b, + we can conclude that a > b. + +5) After passing through one of a's qubits in iteration, how can we temporarily change its state to mark whether a and b had different bits in this position or the same ones? Then it can be used to determine whether + a later, less significant bit is the first bit with different values in a and b? (Remember that you have to uncompute any changes you've done to the input register!) + + + +``` +// Task 1.5. Compare an integer stored in a qubit array with an integer (≤) +``` + +1) You can use bitwise logic similar to the previous task. Is it necessary to go through all these steps, though, or is there an easier way to solve this task? Remember that you can reuse the code of previous tasks. + + +``` +// Task 2.2. Convert an array into a jagged array +``` + +1) For each i, xᵢ can have values from 0 to bᵢ, inclusive. How many distinct values are thus possible for xᵢ? What's the minimum number of (qu)bits required to hold these values? +2) Consider using library operations from [Microsoft.Quantum.Arrays namespace](https://docs.microsoft.com/en-us/qsharp/api/qsharp/microsoft.quantum.arrays) instead of manipulating array elements manually. + + +``` +// Task 2.4. Increment a quantum integer by a product of classical and quantum integers +``` + +1) Given two classical unsigned integers and their bit string representations, how would you calculate their product? Could you split this product into partial products? +2) Additional information on multiplication: https://en.wikipedia.org/wiki/Binary_multiplier##Unsigned_integers +3) Consider using library operations from [Microsoft.Quantum.Arithmetic namespace](https://docs.microsoft.com/en-us/qsharp/api/qsharp/microsoft.quantum.arithmetic) to simplify your implementation. + + + +``` +// Task 2.6. Calculate total value of selected items +``` + +1) How do the weight of an item and the selected number of copies of that item affect how much weight that item type contributes to the total? + + + +``` +// Task 3.3. Using Grover search to solve (a slightly modified) knapsack decision problem +``` + +1) How many total qubits are necessary for the register? You may want to revisit Hint #1 for Task 2.2. +2) If you know how to implement the quantum counting algorithm in Q#, feel free to do so. Otherwise, consider + using the iteration method in the GraphColoring kata. + + + +``` +// Task 3.4. Solving the bounded knapsack optimization search +``` +1) The key in this task is choosing an efficient method to adjust the value P over several calls to Grover's algorithm, to ultimately + identify the maximum achievable profit value. The method should minimize the number of calls to Grover's algorithm and the total number of oracle + oracle calls. +2) There are numerous methods of performing the task as previously described. One such method is exponential search. See + https://en.wikipedia.org/wiki/Exponential_search for more information. diff --git a/BoundedKnapsack/README.md b/BoundedKnapsack/README.md new file mode 100644 index 00000000000..bba4eb74ca7 --- /dev/null +++ b/BoundedKnapsack/README.md @@ -0,0 +1,9 @@ +# Welcome! + +The "BoundedKnapsack" quantum kata is a series of exercises designed to teach you to use Grover's search algorithm to solve the knapsack problem - a prominent computational problem that is very applicable in industries like e-commerce. +The overall goal in this kata is to solve the knapsack optimization problem by running Grover's algorithm. You will implement oracles that implement various parts of the knapsack problem, and use these oracles with Grover's algorithm to solve the problem. + +* More information on the knapsack problem can be found [on Wikipedia](https://en.wikipedia.org/wiki/Knapsack_problem). +* It is strongly recommended to complete the [Grover's Algorithm kata](./../GroversAlgorithm/) before proceeding to this one. You can also refer to its [README](./../GroversAlgorithm/README.md) for the list of resources on Grover's algorithm. +* You may find this kata to be more challenging than other Grover search katas, so you might want to complete [SolveSATWithGrover](./../SolveSATWithGrover/) or [GraphColoring](./../GraphColoring/) first. +* Much of the reference implementation provided in this kata is based on the circuits described in the paper "Quantum-based algorithm and circuit design for bounded knapsack optimization problem" by Wenjun Hou and Marek Perkowski in the journal *Quantum Information and Computation*. diff --git a/BoundedKnapsack/ReferenceImplementation.qs b/BoundedKnapsack/ReferenceImplementation.qs new file mode 100644 index 00000000000..fd863c8bab6 --- /dev/null +++ b/BoundedKnapsack/ReferenceImplementation.qs @@ -0,0 +1,275 @@ +// Copyright (c) Wenjun Hou. +// Licensed under the MIT license. + +////////////////////////////////////////////////////////////////////// +// This file contains reference solutions to all tasks. +// The tasks themselves can be found in Tasks.qs file. +// We recommend that you try to solve the tasks yourself first, +// but feel free to look up the solution if you get stuck. +////////////////////////////////////////////////////////////////////// + +namespace Quantum.Kata.BoundedKnapsack { + + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Arithmetic; + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Measurement; + + + ////////////////////////////////////////////////////////////////// + // Part I. 0-1 Knapsack Problem + ////////////////////////////////////////////////////////////////// + + + // Task 1.1. Read combination from a register + operation MeasureCombination01_Reference (register : Qubit[]) : Bool[] { + return ResultArrayAsBoolArray(MultiM(register)); + } + + + // Task 1.2. Calculate the number of (qu)bits necessary to hold the maximum total value + function NumBitsTotalValue01_Reference (itemValues : Int[]) : Int { + mutable maxValue = 0; + for itemValue in itemValues { + set maxValue += itemValue; + } + return BitSizeI(maxValue); + } + + + // Task 1.3. Calculate total value of selected items + operation CalculateTotalValueOfSelectedItems01_Reference (itemValues : Int[], selectedItems : Qubit[], total : Qubit[]) : Unit is Adj+Ctl { + // Each qubit in selectedItems determines whether the corresponding value is added. + // Adding the selected items is implemented using a library operation with a control from each qubit of the selectedItems. + let totalLE = LittleEndian(total); + for (control, value) in Zipped(selectedItems, itemValues) { + Controlled IncrementByInteger([control], (value, totalLE)); + } + } + + + // Task 1.4. Compare an integer stored in a qubit array with an integer (>) + operation CompareQubitArrayGreaterThanInt_Reference (a : Qubit[], b : Int, target : Qubit) : Unit is Adj+Ctl { + let D = Length(a); + + // Convert b into array of bits in little endian format + let binaryB = IntAsBoolArray(b, D); + + // Iterate descending from the most significant digit (stored last), flipping the target qubit + // upon finding i such that a[i] > binaryB[i], AND a[j] = binaryB[j] for all j > i. + // The X gate flips a[i] to represent whether a[i] and binaryB[i] are equal, to + // be used as controls for the Toffoli. + // Thus, the Toffoli will only flip the target when a[i] = 1, binaryB[i] = 0, and + // a[j] = 1 for all j > i (meaning a and binaryB have the same digits above i). + + for i in D - 1 .. -1 .. 0 { + if (not binaryB[i]) { + // Checks if a has a greater bit than b at index i AND all bits above index i have equal values in a and b. + Controlled X(a[i ...], target); + // Flips the qubit if b's corresponding bit is 0. + // This temporarily sets the qubit to 1 if the corresponding bits are equal. + X(a[i]); + } + } + + // Uncompute + ApplyPauliFromBitString(PauliX, false, binaryB, a); + } + + + // Task 1.5. Compare an integer stored in a qubit array with an integer (≤) + operation CompareQubitArrayLeqThanInt_Reference (a : Qubit[], b : Int, target : Qubit) : Unit is Adj+Ctl { + // This operation calculates the opposite of the greater-than comparator from the previous task, + // so we can just call CompareQubitArrayGreaterThanInt, and then an X gate. + CompareQubitArrayGreaterThanInt_Reference(a, b, target); + X(target); + } + + + // Task 1.6. Verify that the total weight doesn't exceed the limit W + operation VerifyTotalWeight01_Reference (W : Int, itemWeights : Int[], selectedItems : Qubit[], target : Qubit) : Unit is Adj+Ctl { + let numQubitsTotalWeight = NumBitsTotalValue01_Reference(itemWeights); + use totalWeight = Qubit[numQubitsTotalWeight]; + within { + CalculateTotalValueOfSelectedItems01_Reference(itemWeights, selectedItems, totalWeight); + } apply { + CompareQubitArrayLeqThanInt_Reference(totalWeight, W, target); + } + } + + + // Task 1.7. Verify that the total profit exceeds threshold P + operation VerifyTotalProfit01_Reference (P : Int, itemProfits : Int[], selectedItems : Qubit[], target : Qubit) : Unit is Adj+Ctl { + let numQubitsTotalProfit = NumBitsTotalValue01_Reference(itemProfits); + use totalProfit = Qubit[numQubitsTotalProfit]; + within { + CalculateTotalValueOfSelectedItems01_Reference(itemProfits, selectedItems, totalProfit); + } apply { + CompareQubitArrayGreaterThanInt_Reference(totalProfit, P, target); + } + } + + + // Task 1.8. Verify the solution to the 0-1 knapsack problem + operation VerifyKnapsackProblemSolution01_Reference ( + W : Int, P : Int, itemWeights : Int[], itemProfits : Int[], selectedItems : Qubit[], target : Qubit + ) : Unit is Adj+Ctl { + use (outputW, outputP) = (Qubit(), Qubit()); + within { + VerifyTotalWeight01_Reference(W, itemWeights, selectedItems, outputW); + VerifyTotalProfit01_Reference(P, itemProfits, selectedItems, outputP); + } apply { + CCNOT(outputW, outputP, target); + } + } + + + ////////////////////////////////////////////////////////////////// + // Part II. Bounded Knapsack Problem + ////////////////////////////////////////////////////////////////// + + + // Task 2.1. Read combination from a jagged array of qubits + operation MeasureCombination_Reference (selectedItemCounts : Qubit[][]) : Int[] { + // Measure each array and convert the result to int. + return Mapped(ResultArrayAsInt, ForEach(MultiM, selectedItemCounts)); + } + + + // Task 2.2. Convert an array into a jagged array + function RegisterAsJaggedArray_Reference<'T> (array : 'T[], b : Int[]) : 'T[][] { + // Identify bit lengths of integers bᵢ. + let bitLengths = Mapped(BitSizeI, b); + // Partition the array in accordance to these lengths. + // Remember to discard the last element in the return of Partitioned, + // which will be empty, since our partitioning is precise. + return Most(Partitioned(bitLengths, array)); + } + + + // Task 2.3. Verification of limits satisfaction + operation VerifyLimits_Reference (itemCountLimits : Int[], selectedItemCounts : Qubit[][], target : Qubit) : Unit is Adj+Ctl { + use satisfy = Qubit[Length(itemCountLimits)]; + within { + for (x, b, satisfyBit) in Zipped3(selectedItemCounts, itemCountLimits, satisfy) { + // Check that each individual xᵢ satisfies the limit itemCountLimits[i]. + // If the number represented by x is ≤ bᵢ, then the result will be 1, indicating satisfaction. + CompareQubitArrayLeqThanInt_Reference(x, b, satisfyBit); + } + } apply { + // If all constraints are satisfied, then the combination passes limits verification. + Controlled X(satisfy, target); + } + } + + + // Task 2.4. Increment a quantum integer by a product of classical and quantum integers + operation IncrementByProduct_Reference (x : Int, y : Qubit[], z : Qubit[]) : Unit is Adj+Ctl { + for (i, control) in Enumerated(y) { + // Calculate each partial product, y[i] · x · 2ⁱ, + // and add each partial product to z, if the corresponding qubit in y is 1. + // For more information, see https://en.wikipedia.org/wiki/Binary_multiplier#Unsigned_numbers. + // IncrementByInteger performs addition modulo 2ᵐ, where m is the length of register z. + Controlled IncrementByInteger([control], (x <<< i, LittleEndian(z))); + } + } + + + // Task 2.5. Calculate the number of qubits necessary to hold the maximum total value + function NumBitsTotalValue_Reference (itemValues : Int[], itemCountLimits : Int[]) : Int { + mutable maxValue = 0; + for (v, lim) in Zipped(itemValues, itemCountLimits) { + set maxValue += v * lim; + } + return BitSizeI(maxValue); + } + + + // Task 2.6. Calculate total value of selected items + operation CalculateTotalValueOfSelectedItems_Reference (itemValues : Int[], selectedCounts : Qubit[][], total : Qubit[]) : Unit is Adj+Ctl { + // The item type with index i contributes xᵢ instances to the knapsack, adding itemValues[i] per instance to the total. + // Thus, for each item type, we increment the total by their product. + for (value, count) in Zipped(itemValues, selectedCounts) { + IncrementByProduct_Reference(value, count, total); + } + } + + + // Task 2.7. Verify that the total weight doesn't exceed the limit W + operation VerifyTotalWeight_Reference (W : Int, itemWeights : Int[], itemCountLimits : Int[], selectedCounts : Qubit[][], target : Qubit) : Unit is Adj+Ctl { + let numQubitsTotalWeight = NumBitsTotalValue_Reference(itemWeights, itemCountLimits); + use totalWeight = Qubit[numQubitsTotalWeight]; + within { + // Calculate the total weight + CalculateTotalValueOfSelectedItems_Reference(itemWeights, selectedCounts, totalWeight); + } apply { + CompareQubitArrayLeqThanInt_Reference(totalWeight, W, target); + } + } + + + // Task 2.8. Verify that the total profit exceeds threshold P + operation VerifyTotalProfit_Reference (P : Int, itemProfits : Int[], itemCountLimits : Int[], selectedCounts : Qubit[][], target : Qubit) : Unit is Adj+Ctl { + let numQubitsTotalProfit = NumBitsTotalValue_Reference(itemProfits, itemCountLimits); + use totalProfit = Qubit[numQubitsTotalProfit]; + within { + // Calculate the total profit + CalculateTotalValueOfSelectedItems_Reference(itemProfits, selectedCounts, totalProfit); + } apply { + CompareQubitArrayGreaterThanInt_Reference(totalProfit, P, target); + } + } + + + // Task 2.9. Bounded knapsack problem validation oracle + operation VerifyKnapsackProblemSolution_Reference (W : Int, P : Int, itemWeights : Int[], itemProfits : Int[], itemCountLimits : Int[], selectedCountsRegister : Qubit[], target : Qubit) : Unit is Adj+Ctl { + let selectedItems = RegisterAsJaggedArray_Reference(selectedCountsRegister, itemCountLimits); + use (outputB, outputW, outputP) = (Qubit(), Qubit(), Qubit()); + within { + // Compute the result of each constraint check. + VerifyLimits_Reference(itemCountLimits, selectedItems, outputB); + VerifyTotalWeight_Reference(W, itemWeights, itemCountLimits, selectedItems, outputW); + VerifyTotalProfit_Reference(P, itemProfits, itemCountLimits, selectedItems, outputP); + } apply { + // The final result is the AND operation of the three separate results (all constraints must be satisfied). + Controlled X([outputB, outputW, outputP], target); + } + } + + ////////////////////////////////////////////////////////////////// + // Part III. Using Grover's algorithm for knapsack optimization problems + ////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////////////////// + // Appendix. Service functions used both in reference implementations and in tests + ////////////////////////////////////////////////////////////////// + + /// # Summary + /// Calculate the number of qubits necessary to store a concatenation of the given integers. + /// # Remarks + /// Storing each integer bᵢ requires log₂(bᵢ+1) qubits (rounded up), computes using BitSizeI. + /// Storing all integers requires a sum of registers required to store each one. + internal function RegisterSize (arrayElementLimits : Int[]) : Int { + mutable Q = 0; + for b in arrayElementLimits { + set Q += BitSizeI(b); + } + return Q; + } + + + /// # Summary + /// Convert a single array of bits which stores binary notations of n integers into an array of integers written in it. + /// Each integer can be between 0 and bᵢ, inclusive, which defines the number of bits its notation takes. + internal function BoolArrayConcatenationAsIntArray (arrayElementLimits : Int[], binaryCombo : Bool[]) : Int[] { + // Split the bool array into a jagged bool array. + let binaryNotations = RegisterAsJaggedArray_Reference(binaryCombo, arrayElementLimits); + // Convert each element of the jagged array into an integer. + return Mapped(BoolArrayAsInt, binaryNotations); + } +} diff --git a/BoundedKnapsack/Tasks.qs b/BoundedKnapsack/Tasks.qs new file mode 100644 index 00000000000..35322aab408 --- /dev/null +++ b/BoundedKnapsack/Tasks.qs @@ -0,0 +1,341 @@ +// Copyright (c) Wenjun Hou. +// Licensed under the MIT license. + +namespace Quantum.Kata.BoundedKnapsack +{ + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Arithmetic; + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Measurement; + + + ////////////////////////////////////////////////////////////////// + // Welcome! + ////////////////////////////////////////////////////////////////// + + // The Bounded Knapsack quantum kata is a series of exercises designed + // to teach you to use Grover search to solve the knapsack problem. + // The knapsack problem is a prominent computational problem; + // you can find its description at https://en.wikipedia.org/wiki/Knapsack_problem. + + // The overall goal in this kata is to solve the knapsack optimization problem + // by running Grover's algorithm. + + // The kata covers the following topics: + // - writing operations to perform arithmetic tasks. + // - writing oracles to implement constraints based on: + // - the 0-1 knapsack problem, a simple version of the knapsack problem; + // - the bounded knapsack problem. + // - using the oracle with Grover's algorithm to solve the knapsack decision problem. + // - using Grover's algorithm repeatedly to solve the knapsack optimization problem. + + // You may find this kata to be more challenging than other Grover's search + // katas, so you might want to try SolveSATWithGrover or GraphColoring first. + // Hints for the more complicated tasks can be found in the Hints.qs file. + + // Each task is wrapped in one operation preceded by the description of the task. + // Each task (except tasks in which you have to write a test) has a unit test associated with it, + // which initially fails. Your goal is to fill in the blank (marked with // ... comment) + // with some Q# code to make the failing test pass. + + + ////////////////////////////////////////////////////////////////// + // Part I. 0-1 Knapsack Problem + ////////////////////////////////////////////////////////////////// + + // Introduction: + // There exist n items, indexed 0 to n-1. + // The item with index i has a weight of wᵢ and yields a profit of pᵢ. + // In the original 0-1 knapsack decision problem, we wish to determine + // whether we can put items in a knapsack to get a total profit + // of at least P, while not exceeding a maximum weight W. + + // However, we will slightly modify the problem for this part of the kata: + // the total profit must be strictly greater than P, rather than at least P. + + // In the following tasks you will implement an oracle that evaluates whether + // a particular register of n qubits, representing an item combination, + // satisfies the described conditions. + + + // Task 1.1. Read combination from a register + // Input: An array of n qubits, which are guaranteed to be in one of the 2ⁿ basis states. + // Output: The item combination that this state represents, expressed as a boolean array of length n. + // The i-th element of the array should be true (indicating that i-th item is selected) + // if and only if the i-th qubit of the register is in the |1⟩ state. + // The operation should not change the state of the qubits. + // Example: For n = 3 and the qubits state |101⟩, return [true, false, true]. + operation MeasureCombination01 (selectedItems : Qubit[]) : Bool[] { + // ... + return new Bool[0]; + } + + + // Task 1.2. Calculate the number of (qu)bits necessary to hold the maximum total value + // Input: An array of n positive integers, describing the value (the weight or the profit) of each item. + // Output: The minimum number of (qu)bits needed to store the maximum total weight/profit of the items. + // Example: For n = 4 and itemValues = [1, 2, 3, 4], the maximum possible total value is 10, + // which requires at least 4 qubits to store it, so 4 is returned. + function NumBitsTotalValue01 (itemValues : Int[]) : Int { + // ... + return 0; + } + + + // Task 1.3. Calculate total value of selected items + // Inputs: + // 1) An array of n integers, describing the values (the weights or the profits) of the items. + // 2) An array of n qubits, describing whether each item is put into the knapsack. + // 3) An array of qubits "total" in the |0...0⟩ state to store the total value of the selected items. + // It is guaranteed that there are enough qubits to store the total value. + // Goal: Transform the "total" array to represent, in little-endian format, the sum of the values of the items that are put in the knapsack. + // The input qubits can be in a superposition state. Leave the qubits in selectedItems in the same state they started in. + // Example: For n = 4 and itemValues = [1, 2, 3, 4], the input state |1001⟩|0000⟩ should be transformed to |1001⟩|1010⟩, + // since items 0 and 3 are put in the knapsack, and itemValues[0] + itemValues[3] = 1 + 4 = 5 = 1010₂. + operation CalculateTotalValueOfSelectedItems01 (itemValues : Int[], selectedItems : Qubit[], total : Qubit[]) : Unit is Adj+Ctl { + // ... + } + + + // Task 1.4. Compare an integer stored in a qubit array with an integer (>) + // Inputs: + // 1) An array of D qubits representing an integer in little-endian format. + // 2) An integer b (0 ≤ b ≤ 2ᴰ - 1). + // 3) A qubit in an arbitrary state (target qubit). + // Goal: Flip the state of the target qubit if the integer represented by the qubit array is greater than b. + // The input qubits can be in superposition. Leave the qubits in the qubit array in the same state they started in. + // Example: For b = 11, the input state |1011⟩|0⟩ should be transformed to |1011⟩|1⟩, since 1101₂ = 13₁₀ > 11₁₀. + operation CompareQubitArrayGreaterThanInt (qs : Qubit[], b : Int, target : Qubit) : Unit is Adj+Ctl { + // ... + } + + + // Task 1.5. Compare an integer stored in a qubit array with an integer (≤) + // Inputs: + // 1) An array of D qubits representing an integer in little-endian format. + // 2) An integer b (0 ≤ b ≤ 2ᴰ - 1). + // 3) A qubit in an arbitrary state (target qubit). + // Goal: Flip the state of the target qubit if the integer represented by the qubit array is less than or equal to b. + // The input qubits can be in superposition. Leave the qubits in the qubit array in the same state they started in. + // Example: For b = 7, the input state |1010⟩|0⟩ should be transformed to |1010⟩|1⟩, since 0101₂ = 5₁₀ ≤ 7₁₀. + operation CompareQubitArrayLeqThanInt (qs : Qubit[], b : Int, target : Qubit) : Unit is Adj+Ctl { + // ... + } + + + // Task 1.6. Verify that the total weight doesn't exceed the limit W + // Inputs: + // 1) An integer W, the maximum weight the knapsack can hold. + // 2) An array of n integers, describing the weights of the items: itemWeights[i] = wᵢ. + // 3) An array of n qubits, describing whether each item is put into the knapsack. + // 4) A qubit in an arbitrary state (target qubit). + // Goal: Flip the state of the target qubit if the total weight is less than or equal to W. + // The input qubits can be in superposition. Leave the qubits in the qubit array in the same state they started in. + operation VerifyTotalWeight01 (W : Int, itemWeights : Int[], selectedItems : Qubit[], target : Qubit) : Unit is Adj+Ctl { + // ... + } + + + // Task 1.7. Verify that the total profit exceeds the threshold P + // Inputs: + // 1) An integer P, the threshold which the total profit must exceed. + // 2) An array of n integers, describing the profits of the items: itemProfits[i] = pᵢ. + // 3) An array of n qubits, describing whether each item is put into the knapsack. + // 4) A qubit in an arbitrary state (target qubit). + // Goal: Flip the state of the target qubit if the total profit is greater than P. + // The input qubits can be in superposition. Leave the qubits in the qubit array in the same state they started in. + operation VerifyTotalProfit01 (P : Int, itemProfits : Int[], selectedItems : Qubit[], target : Qubit) : Unit is Adj+Ctl { + // ... + } + + + // Task 1.8. Verify the solution to the 0-1 knapsack problem + // Inputs: + // 1) An integer W, the maximum weight the knapsack can hold. + // 2) An integer P, the threshold which the total profit must exceed. + // 3) An array of n integers, describing the weights of the items: itemWeights[i] = wᵢ. + // 4) An array of n integers, describing the profits of the items: itemProfits[i] = pᵢ. + // 5) An array of n qubits, describing whether each item is put into the knapsack. + // 6) A qubit in an arbitrary state (target qubit). + // Goal: Flip the state of the target qubit if the selection of the items in selectedItems satisfies both constraints: + // * the total weight of all items is less than or equal to W, and + // * the total profit of all items is greater than P. + // The input qubits can be in superposition. Leave the qubits in the qubit array in the same state they started in. + operation VerifyKnapsackProblemSolution01 ( + W : Int, P : Int, itemWeights : Int[], itemProfits : Int[], selectedItems : Qubit[], target : Qubit + ) : Unit is Adj+Ctl { + // ... + } + + + + ////////////////////////////////////////////////////////////////// + // Part II. Bounded Knapsack Problem + ////////////////////////////////////////////////////////////////// + + // Introduction: + // In this version of the problem we still consider n items, indexed 0 to n-1, + // the item with index i has a weight of wᵢ and yields a profit of pᵢ. + // But in the bounded knapsack version of the problem, unlike in the 0-1 knapsack problem, + // each type of item can have more than one copy, and can be selected multiple times. + // Specifically, there are bᵢ copies of item type i available, + // so this item type can be selected between 0 and bᵢ times, inclusive. + // Let xᵢ represent the number of copies of index i that are put into the knapsack; 0 ≤ xᵢ ≤ bᵢ for all i. + // Thus, we seek a combination xs = [x₀, x₁, ..., xₙ₋₁] which + // has total weight at most W, and has total profit that is greater than P. + + // Again, we slightly modify the problem, such that the total profit must + // be strictly greater than P, rather than at least P. + + // The comparators from Part I can be reused, but the operations for + // calculating total weight and profit will need to be rewritten. + + + // Task 2.1. Read combination from a jagged array of qubits + // Input: A jagged array of qubits of length n. + // Array selectedItemCounts[i] represents the binary notation of xᵢ in little-endian format. + // Each qubit is guaranteed to be in one of the basis states (|0⟩ or |1⟩). + // Output: An integer array of length n, containing the combination that this jagged array represents. + // The integer at index i in the output array should have value xᵢ. + // The operation should not change the state of the qubits. + // Example: For state selectedItemCounts = [|101⟩, |1110⟩, |0100⟩], return [5, 7, 2]. + operation MeasureCombination (selectedItemCounts : Qubit[][]) : Int[] { + // ... + return new Int[0]; + } + + + // Task 2.2. Convert an array into a jagged array + // To simplify access to the register as an array of integers within the oracle, + // we reorganize the register into several qubit arrays that represent, in little-endian format, + // the number of copies of each item type. + // We can do the same transformation with arrays of other types, for example, + // arrays of classical bits (boolean or integer) that store binary notations of several integers. + // To make our code reusable, we can make it type-parameterized. + // + // Inputs: + // 1) An array of type T (T could be qubits or classical bits). + // 2) An array of n integers bᵢ. + // Output: A jagged array of n arrays of type T. + // i-th element of the return value should have enough bits to represent the integer bᵢ in binary notation. + // The length of the input array of T is defined to be able to store all integers bᵢ. + // Example: For b = [6, 15, 13] and a register of qubits in state |10111100100⟩, + // you need to use 3, 4, and 4 bits to represent the integers bᵢ, + // so you'll return an array of qubit arrays [|101⟩, |1110⟩, |0100⟩]. + function RegisterAsJaggedArray<'T> (array : 'T[], b : Int[]) : 'T[][] { + // ... + return new 'T[][0]; + } + + + // Task 2.3. Verification of limits satisfaction + // Inputs: + // 1) An array of n integers itemCountLimits[i] = bᵢ. + // 2) A jagged array of n qubits. Each array selectedItemCounts[i] represents the number of items xᵢ in little-endian format. + // 3) A qubit in an arbitrary state (target qubit). + // Goal: Flip the state of the target qubit if xᵢ ≤ bᵢ for all i. + // The input qubits can be in superposition. Leave the qubits in qubit array in the same state they started in. + operation VerifyLimits (itemCountLimits : Int[], selectedItemCounts : Qubit[][], target : Qubit) : Unit is Adj+Ctl { + // ... + } + + + // Task 2.4. Increment a quantum integer by a product of classical and quantum integers + // Inputs: + // 1) An integer x. + // 2) An array of n qubits, representing an arbitrary integer y. + // 3) An array of m qubits, representing an arbitrary integer z. + // Goal: Increment register z by the product of x and y. + // Perform the increment modulo 2ᵐ (you don't need to track the carry bit). + // The input qubits can be in superposition. Leave the qubits in register y in the same state they started in. + operation IncrementByProduct (x : Int, y : Qubit[], z : Qubit[]) : Unit is Adj+Ctl { + // ... + } + + + // Task 2.5. Calculate the number of (qu)bits necessary to hold the maximum total value + // Inputs: + // 1) An array of n positive integers, describing the value (the weight or the profit) of each item. + // 2) An array of n positive integers, describing the bᵢ - the limits on the maximum number of items of type i that can be selected. + // Output: The minimum number of (qu)bits needed to store the maximum total weight/profit of the items. + // Example: For n = 4, itemValues = [1, 2, 3, 4], and itemCountLimits = [2, 5, 4, 3], + // the maximum possible total weight is 1*2 + 2*5 + 3*4 + 4*3 = 36, which requires at least 6 qubits, so 6 is returned. + function NumBitsTotalValue (itemValues : Int[], itemCountLimits : Int[]) : Int { + // ... + return 0; + } + + + // Task 2.6. Calculate total value of selected items + // Inputs: + // 1) An array of n integers, describing the values (the weights or the profits) of the items. + // 2) A jagged array of qubits of length n. Array selectedItems[i] represents the number of items of type i xᵢ in little-endian format. + // 3) An array of qubits "total" in the |0...0⟩ state to store the total value of the selected items. + // It is guaranteed that there are enough qubits to store the total value. + // Goal: Transform the "total" array to represent, in little-endian format, the sum of the values of the items that are put in the knapsack. + // The input qubits can be in a superposition state. Leave the qubits in selectedCounts in the same state they started in. + operation CalculateTotalValueOfSelectedItems (itemValues : Int[], selectedCounts : Qubit[][], total : Qubit[]) : Unit is Adj+Ctl { + // ... + } + + + // Task 2.7. Verify that the total weight doesn't exceed the limit W + // Inputs: + // 1) An integer W, the maximum weight the knapsack can hold. + // 2) An array of n integers, describing the weights of the items: itemWeights[i] = wᵢ. + // 3) An array of n positive integers, describing the bᵢ - the limits on the maximum number of items of type i that can be selected. + // 4) A jagged array of qubits, describing the number of each item type put into the knapsack. + // Array selectedCounts[i] represents xᵢ in little-endian format. + // 5) A qubit in an arbitrary state (target qubit). + // Goal: Flip the state of the target qubit if the total weight is less than or equal to W. + // The input qubits can be in superposition. Leave the qubits in the selectedCounts array in the same state they started in. + operation VerifyTotalWeight (W : Int, itemWeights : Int[], itemCountLimits : Int[], selectedCounts : Qubit[][], target : Qubit) : Unit is Adj+Ctl { + // ... + } + + + // Task 2.8. Verify that the total profit exceeds threshold P + // Inputs: + // 1) An integer P, the threshold which the total profit must exceed. + // 2) An array of n integers, describing the profits of the items: itemProfits[i] = pᵢ + // 3) An array of n positive integers, describing the bᵢ - the limits on the maximum number of items of type i that can be selected. + // 4) A jagged array of qubits, describing the number of each item type put into the knapsack. + // Array selectedCounts[i] represents xᵢ in little-endian format. + // 5) A qubit in an arbitrary state (target qubit). + // Goal: Flip the state of the target qubit if the total profit is greater than P. + // The input qubits can be in superposition. Leave the qubits in the selectedCounts array in the same state they started in. + operation VerifyTotalProfit (P : Int, itemProfits : Int[], itemCountLimits : Int[], selectedCounts : Qubit[][], target : Qubit) : Unit is Adj+Ctl { + // ... + } + + + // Task 2.9. Verify the solution to the bounded knapsack problem + // Inputs: + // 1) An integer W, the maximum weight the knapsack can hold. + // 2) An integer P, the threshold which the total profit must exceed. + // 3) An array of n integers, describing the weights of the items: itemWeights[i] = wᵢ. + // 4) An array of n integers, describing the profits of the items: itemProfits[i] = pᵢ. + // 5) An array of n integers, describing the maximum numbers of each type of item that can be selected: itemCountLimits[i] = bᵢ. + // 6) An array of Q qubits, describing the numbers of each type of item selected for the knapsack. + // (Q is the minimum number of qubits necessary to store a concatenation of the numbers up to bᵢ.) + // 7) A qubit in an arbitrary state (target qubit) + // Goal: Flip the state of the target qubit if the selection of the items in selectedCountsRegister satisfies both constraints: + // * the total weight of all items is less than or equal to W, + // * the total profit of all items is greater than P, and + // * the number of i-th type of item is less than or equal to bᵢ. + // The input qubits can be in superposition. Leave the qubits in register in the same state they started in. + operation VerifyKnapsackProblemSolution (W : Int, P : Int, itemWeights : Int[], itemProfits : Int[], itemCountLimits : Int[], selectedCountsRegister : Qubit[], target : Qubit) : Unit is Adj+Ctl { + // ... + } + + + ////////////////////////////////////////////////////////////////// + // Part III. Using Grover's algorithm for knapsack optimization problems + ////////////////////////////////////////////////////////////////// + + // Coming soon... +} diff --git a/BoundedKnapsack/Tests.qs b/BoundedKnapsack/Tests.qs new file mode 100644 index 00000000000..e290e6e26ab --- /dev/null +++ b/BoundedKnapsack/Tests.qs @@ -0,0 +1,630 @@ +// Copyright (c) Wenjun Hou. +// Licensed under the MIT license. + +////////////////////////////////////////////////////////////////////// +// This file contains testing harness for all tasks. +// You should not modify anything in this file. +// The tasks themselves can be found in Tasks.qs file. +////////////////////////////////////////////////////////////////////// + +namespace Quantum.Kata.BoundedKnapsack +{ + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Arithmetic; + open Microsoft.Quantum.Logical; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Measurement; + open Microsoft.Quantum.Random; + + open Quantum.Kata.Utils; + + + // Hardcoded sets of 0-1 knapsack problem parameters, for testing the operations. + // The function returns an array of tuples, each representing a set of parameters. + // The contents of each tuple include: n, W, P, itemWeights, itemProfits (in that order). + function ExampleSets01 () : (Int, Int, Int, Int[], Int[])[] { + return [(2, 6, 3, [2, 5], [1, 3]), + (3, 12, 15, [2, 3, 10], [2, 3, 15]), + (3, 9, 5, [6, 3, 1], [5, 2, 1]), + (4, 4, 9, [1, 2, 3, 1], [2, 4, 9, 2]), + (5, 16, 16, [7, 7, 2, 3, 3], [3, 2, 9, 6, 5])]; + } + + + @Test("QuantumSimulator") + operation T11_MeasureCombination01 () : Unit { + for n in 1 .. 4 { + use selectedItems = Qubit[n]; + // Iterate through all possible combinations. + for combo in 0 .. (1 <<< n) - 1 { + // Prepare the register so that it contains the integer a in little-endian format. + let comboBitmask = IntAsBoolArray(combo, n); + within { + ApplyPauliFromBitString(PauliX, true, comboBitmask, selectedItems); + } apply { + let measuredCombo = MeasureCombination01(selectedItems); + Fact(Length(measuredCombo) == n, $"Unexpected length of the result: expected {n}, got {Length(measuredCombo)}"); + Fact(BoolArrayAsInt(measuredCombo) == combo, $"Unexpected result for bitmask {comboBitmask} : {measuredCombo}"); + } + // Check that the measurement didn't impact the state of the qubits + AssertAllZero(selectedItems); + } + } + } + + + // ------------------------------------------------------ + @Test("QuantumSimulator") + operation T12_NumBitsTotalValue01 () : Unit { + for (_, _, _, itemWeights, itemProfits) in ExampleSets01() { + for values in [itemWeights, itemProfits] { + let res = NumBitsTotalValue01(values); + let exp = NumBitsTotalValue01_Reference(values); + Fact(res == exp, $"Unexpected result for values = {values} : {res} (expected {exp})"); + } + } + } + + + // ------------------------------------------------------ + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T13_CalculateTotalValueOfSelectedItems01 () : Unit { + for (n, _, _, itemWeights, itemProfits) in ExampleSets01() { + for values in [itemWeights, itemProfits] { + let numQubitsTotalValue = NumBitsTotalValue01_Reference(values); + use (selectedItems, totalValue) = (Qubit[n], Qubit[numQubitsTotalValue]); + // Iterate through all possible combinations of items. + for combo in 0 .. (1 <<< n) - 1 { + // Prepare the register so that it represents the combination. + let selectedItemsBitmask = IntAsBoolArray(combo, n); + ApplyPauliFromBitString(PauliX, true, selectedItemsBitmask, selectedItems); + + // Reset the counter of measurements done + ResetOracleCallsCount(); + + // Calculate and measure the weight with qubits + CalculateTotalValueOfSelectedItems01(values, selectedItems, totalValue); + + // Make sure the solution didn't use any measurements + Fact(GetOracleCallsCount(Measure) == 0, "You are not allowed to use measurements in this task"); + + mutable measuredValue = MeasureInteger(LittleEndian(totalValue)); + + // Calculate the weight classically + mutable actualValue = 0; + for i in 0 .. n - 1 { + if selectedItemsBitmask[i] { + set actualValue += values[i]; + } + } + + // Assert that both methods yield the same result + Fact(actualValue == measuredValue, $"Unexpected result for selected items = {selectedItemsBitmask}, item values = {values} : {measuredValue}"); + + // Check that the operation didn't modify the input state + ApplyPauliFromBitString(PauliX, true, selectedItemsBitmask, selectedItems); + AssertAllZero(selectedItems); + } + } + } + } + + + // ------------------------------------------------------ + // "Framework" operation to test a comparator of qubit array and an integer + operation ValidateComparator (testOp : (Qubit[], Int, Qubit) => Unit is Adj+Ctl, comparator : (Int, Int) -> Bool) : Unit { + for D in 1 .. 4 { + // Iterate through all possible left operands a. + for a in 0 .. (1 <<< D) - 1 { + use (selectedItems, target) = (Qubit[D], Qubit()); + let binaryA = IntAsBoolArray(a, D); + + // Iterate through all possible right operands b. + for b in 0 .. (1 <<< D) - 1 { + // Prepare the register so that it contains the integer a in little-endian format. + ApplyPauliFromBitString(PauliX, true, binaryA, selectedItems); + + // Reset the counter of measurements done + ResetOracleCallsCount(); + + testOp(selectedItems, b, target); + + // Make sure the solution didn't use any measurements + Fact(GetOracleCallsCount(Measure) == 0, "You are not allowed to use measurements in this task"); + + let output = MResetZ(target) == One; + Fact(comparator(a, b) == output, $"Unexpected result for a = {a}, b = {b} : {output}"); + + // Check that the operation didn't modify the input state + ApplyPauliFromBitString(PauliX, true, binaryA, selectedItems); + AssertAllZero(selectedItems); + } + } + } + } + + + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T14_CompareQubitArrayGreaterThanInt () : Unit { + ValidateComparator(CompareQubitArrayGreaterThanInt, GreaterThanI); + } + + + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T15_CompareQubitArrayLeqThanInt () : Unit { + ValidateComparator(CompareQubitArrayLeqThanInt, LessThanOrEqualI); + } + + + // ------------------------------------------------------ + // "Framework" operation to test verification of a constraint on total value of selected items + operation ValidateTotalValueVerification01 ( + testOp : (Int, Int[], Qubit[], Qubit) => Unit is Adj+Ctl, + comparator : (Int, Int) -> Bool + ) : Unit { + for (n, W, P, itemWeights, itemProfits) in ExampleSets01() { + for (limit, values) in [(W, itemWeights), (P, itemProfits)] { + use (selectedItems, target) = (Qubit[n], Qubit()); + // Iterate through all possible combinations of items. + for combo in 0 .. (1 <<< n) - 1 { + // Prepare the register so that it represents the combination. + let selectedItemsBitmask = IntAsBoolArray(combo, n); + ApplyPauliFromBitString(PauliX, true, selectedItemsBitmask, selectedItems); + + // Reset the counter of measurements done + ResetOracleCallsCount(); + + // Verify the total value + testOp(limit, values, selectedItems, target); + + // Make sure the solution didn't use any measurements + Fact(GetOracleCallsCount(Measure) == 0, "You are not allowed to use measurements in this task"); + + let output = MResetZ(target) == One; + + // Calculate the weight classically + mutable totalValue = 0; + for i in 0 .. n-1 { + if selectedItemsBitmask[i] { + set totalValue += values[i]; + } + } + + // Assert that both methods yield the same result + Fact(comparator(totalValue, limit) == output, + $"Unexpected result for selectedItems = {selectedItemsBitmask}, itemValues = {values}, limit = {limit} : {output}"); + + // Check that the operation didn't modify the input state + ApplyPauliFromBitString(PauliX, true, selectedItemsBitmask, selectedItems); + AssertAllZero(selectedItems); + } + } + } + } + + + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T16_VerifyTotalWeight01 () : Unit { + ValidateTotalValueVerification01(VerifyTotalWeight01, LessThanOrEqualI); + } + + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T17_VerifyTotalProfit01 () : Unit { + ValidateTotalValueVerification01(VerifyTotalProfit01, GreaterThanI); + } + + + // ------------------------------------------------------ + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T18_KnapsackValidationOracle_01 () : Unit { + for (n, W, P, itemWeights, itemProfits) in ExampleSets01() { + use (selectedItems, target) = (Qubit[n], Qubit()); + // Iterate through all possible combinations of items. + for combo in 0..(1 <<< n) - 1 { + // Prepare the register so that it represents the combination. + let selectedItemsBitmask = IntAsBoolArray(combo, n); + ApplyPauliFromBitString(PauliX, true, selectedItemsBitmask, selectedItems); + + // Reset the counter of measurements done + ResetOracleCallsCount(); + + // Verify the combination with qubits + VerifyKnapsackProblemSolution01(W, P, itemWeights, itemProfits, selectedItems, target); + + // Make sure the solution didn't use any measurements + Fact(GetOracleCallsCount(Measure) == 0, "You are not allowed to use measurements in this task"); + + let output = MResetZ(target) == One; + + // Verify the combination classically + mutable totalWeight = 0; + mutable totalProfit = 0; + for i in 0 .. n - 1 { + if selectedItemsBitmask[i] { + set totalWeight += itemWeights[i]; + set totalProfit += itemProfits[i]; + } + } + + // Assert that both methods yield the same result + Fact((totalWeight <= W and totalProfit > P) == output, + $"Unexpected result for selected items = {selectedItemsBitmask}, itemWeights = {itemWeights}, itemProfits = {itemProfits}, P = {P} : {output}"); + + // Check that the operation didn't modify the input state + ApplyPauliFromBitString(PauliX, true, selectedItemsBitmask, selectedItems); + AssertAllZero(selectedItems); + } + } + } + + + ////////////////////////////////////////////////////////////////// + // Part II. Bounded Knapsack Problem + ////////////////////////////////////////////////////////////////// + + // Hardcoded sets of knapsack problem parameters for testing the operations. + // The function returns an array of tuples, each representing a set of parameters. + // The contents of each tuple include: n, W, P, itemWeights, itemProfits, P_max (in that order). + // For each set, + // * P is a sample profit threshold for testing that set in the knapsack decision problem. + // * P_max is the maximum profit achievable within the given constraints of that set + // (i.e., the solution to the knapsack optimization problem). + // * P and P_max will never be used in the same test. + function ExampleSets () : (Int, Int, Int, Int[], Int[], Int[], Int)[] { + return [(2, 30, 10, [2, 5], [1, 3], [7, 5], 17), + (3, 24, 16, [2, 3, 10], [2, 3, 15], [6, 5, 2], 24), + (3, 16, 5, [6, 3, 1], [5, 2, 1], [4, 7, 2], 13), + (4, 14, 24, [1, 2, 3, 1], [2, 4, 9, 2], [4, 3, 2, 3], 34)]; + } + + + @Test("QuantumSimulator") + operation T21_MeasureCombination () : Unit { + for (_, _, _, _, _, itemCountLimits, _) in ExampleSets() { + // Calculate the total number of qubits necessary to store the integers. + let Q = RegisterSize(itemCountLimits); + use selectedItemCountsRegister = Qubit[Q]; + + // It will be too time-intensive to iterate through all possible combinations of items, + // so random combinations will be used for testing. + for _ in 1 .. 4 * Q { + let combo = DrawRandomInt(0, 2^Q - 1); + // Prepare the register so that it represents the combination. + let binaryCombo = IntAsBoolArray(combo, Q); + ApplyPauliFromBitString(PauliX, true, binaryCombo, selectedItemCountsRegister); + + // Convert the quantum register into a jagged array. + let jaggedRegister = RegisterAsJaggedArray_Reference(selectedItemCountsRegister, itemCountLimits); + + // Measure the combination written in it as an Int[]. + let measuredCombo = MeasureCombination(jaggedRegister); + + // Check that the measured result matches the expected one. + let expectedCombo = BoolArrayConcatenationAsIntArray(itemCountLimits, binaryCombo); + AllEqualityFactI(measuredCombo, expectedCombo, "The result doesn't match the expected combination"); + + ResetAll(selectedItemCountsRegister); + } + } + } + + + // ------------------------------------------------------ + @Test("QuantumSimulator") + operation T22_RegisterAsJaggedArray () : Unit { + // Run the test on integers, since qubits are not possible to compare to each other directly. + for (_, _, _, _, _, integerLimits, _) in ExampleSets() { + // Generate random integers between 0 and integerLimits[i] to fill the array. + let integers = ForEach(DrawRandomInt(0, _), integerLimits); + // Convert those integers into bit strings. + let integersBitstrings = Mapped(IntAsBoolArray, Zipped(integers, Mapped(BitSizeI, integerLimits))); + // Concatenate bit strings to get the input array. + let inputRegister = Flattened(integersBitstrings); + + // Call the solution to get bit strings back. + Message($"Testing {inputRegister}, {integerLimits}..."); + let actualBitstrings = RegisterAsJaggedArray(inputRegister, integerLimits); + + // Compare the lengths of the bit strings to the expected ones. + let actualLengths = Mapped(Length, actualBitstrings); + AllEqualityFactI(actualLengths, Mapped(BitSizeI, integerLimits), + "The lengths of the elements of your return should match the numbers of bits necessary to store bᵢ."); + + // Compare the concatenation of the bit strings to the expected one. + AllEqualityFactB(Flattened(actualBitstrings), inputRegister, + "The concatenation of all elements of your return should match the input register."); + Message(" Success!"); + } + } + + + // ------------------------------------------------------ + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T23_VerifyLimits () : Unit { + for (_, _, _, _, _, itemCountLimits, _) in ExampleSets() { + // Calculate the total number of qubits necessary to store the integers. + let Q = RegisterSize(itemCountLimits); + + use (selectedItemCountsRegister, target) = (Qubit[Q], Qubit()); + // It will be too time-intensive to iterate through all possible combinations of items, + // so random combinations will be used for testing. + for _ in 1 .. 4 * Q { + let combo = DrawRandomInt(0, 2^Q - 1); + + // Prepare the register so that it represents the combination. + let binaryCombo = IntAsBoolArray(combo, Q); + ApplyPauliFromBitString(PauliX, true, binaryCombo, selectedItemCountsRegister); + + // Reset the counter of measurements done. + ResetOracleCallsCount(); + + // Verify the limits. + let selectedItemCounts = RegisterAsJaggedArray_Reference(selectedItemCountsRegister, itemCountLimits); + VerifyLimits(itemCountLimits, selectedItemCounts, target); + + // Make sure the solution didn't use any measurements. + Fact(GetOracleCallsCount(Measure) == 0, "You are not allowed to use measurements in this task"); + + let actualOutput = MResetZ(target) == One; + + // Verify the limits classically. + let selectedItemCountsIntegers = BoolArrayConcatenationAsIntArray(itemCountLimits, binaryCombo); + mutable expectedOutput = true; + for i in 0 .. Length(selectedItemCountsIntegers) - 1 { + // If any limit isn't satisfied, the operation should return false. + if selectedItemCountsIntegers[i] > itemCountLimits[i] { + set expectedOutput = false; + } + } + + // Assert that both methods yield the same result + Fact(expectedOutput == actualOutput, $"Unexpected result for selectedItemCounts = {binaryCombo}, itemCountLimits = {itemCountLimits} : {actualOutput}"); + + // Check that the operation didn't modify the input state + ApplyPauliFromBitString(PauliX, true, binaryCombo, selectedItemCountsRegister); + AssertAllZero(selectedItemCountsRegister); + } + } + } + + + // ------------------------------------------------------ + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T24_IncrementByProduct () : Unit { + for (n, m) in [(1, 2), (2, 2), (2, 3)] { + use (qy, qz) = (Qubit[n], Qubit[m]); + + // Iterate through all possible left operands x (integers). + for x in 0 .. (1 <<< m) - 1 { + + // Iterate through all possible right operands y. + for y in 0 .. (1 <<< n) - 1 { + let binaryY = IntAsBoolArray(y, n); + + // Iterate through all initial values of z. + for z in 0 .. (1 <<< m) - 1 { + // Prepare the registers so that they contain the integers y and z in little-endian format. + let binaryZ = IntAsBoolArray(z, m); + ApplyPauliFromBitString(PauliX, true, binaryY, qy); + ApplyPauliFromBitString(PauliX, true, binaryZ, qz); + + // Reset the counter of measurements done. + ResetOracleCallsCount(); + + IncrementByProduct(x, qy, qz); + + // Make sure the solution didn't use any measurements. + Fact(GetOracleCallsCount(Measure) == 0, "You are not allowed to use measurements in this task"); + + // Check that the computation result is correct. + let res = MeasureInteger(LittleEndian(qz)); + let exp = (z + x*y) % (1 <<< m); + Fact(res == exp, $"Unexpected result for x = {x}, y = {y}, z = {z} : {res} (expected {exp})"); + + // Check that the value of y has not changed. + ApplyPauliFromBitString(PauliX, true, binaryY, qy); + AssertAllZero(qy); + } + } + } + } + } + + + // ------------------------------------------------------ + @Test("QuantumSimulator") + operation T25_NumBitsTotalValue_Reference () : Unit { + for (_, _, _, itemWeights, itemProfits, itemCountLimits, _) in ExampleSets() { + for values in [itemWeights, itemProfits] { + let res = NumBitsTotalValue(values, itemCountLimits); + let exp = NumBitsTotalValue_Reference(values, itemCountLimits); + Fact(res == exp, $"Unexpected result for values = {itemWeights}, limits = {itemCountLimits} : {res} (expected {exp})"); + } + } + } + + + // ------------------------------------------------------ + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T26_CalculateTotalValueOfSelectedItems () : Unit { + for (_, _, _, itemWeights, itemProfits, itemCountLimits, _) in ExampleSets() { + for values in [itemWeights, itemProfits] { + // Calculate the total number of qubits necessary to store the integers. + let Q = RegisterSize(itemCountLimits); + + // Calculate the number of bits necessary to store the maximal total value. + let numQubitsTotal = NumBitsTotalValue_Reference(values, itemCountLimits); + + use (selectedItemCountsRegister, totalValue) = (Qubit[Q], Qubit[numQubitsTotal]); + // It will be too time-intensive to iterate through all possible combinations of items, + // so random combinations will be used for testing. + for _ in 1 .. 4 * Q { + // Generate random integers between 0 and integerLimits[i] to fill the array. + let itemCounts = ForEach(DrawRandomInt(0, _), itemCountLimits); + // Convert those integers into bit strings and concatenate them. + let binaryCombo = Flattened(Mapped(IntAsBoolArray, Zipped(itemCounts, Mapped(BitSizeI, itemCountLimits)))); + // Prepare the register so that it represents the combination. + ApplyPauliFromBitString(PauliX, true, binaryCombo, selectedItemCountsRegister); + + // Reset the counter of measurements done + ResetOracleCallsCount(); + + // Calculate and measure the total weight and profit with qubits + let selectedItemCounts = RegisterAsJaggedArray_Reference(selectedItemCountsRegister, itemCountLimits); + CalculateTotalValueOfSelectedItems(values, selectedItemCounts, totalValue); + + // Make sure the solution didn't use any measurements + Fact(GetOracleCallsCount(Measure) == 0, "You are not allowed to use measurements in this task"); + + mutable actualValue = MeasureInteger(LittleEndian(totalValue)); + + // Calculate the total value classically + mutable expectedValue = 0; + for (val, num) in Zipped(values, itemCounts) { + // Add the weight of all instances of this item type. + set expectedValue += val * num; + } + + // Assert that both methods yield the same result + Fact(actualValue == expectedValue, $"Unexpected result for item counts = {itemCounts}, item values = {values}: total value {actualValue}, expected {expectedValue}"); + + // Check that the operation didn't modify the input state + ApplyPauliFromBitString(PauliX, true, binaryCombo, selectedItemCountsRegister); + AssertAllZero(selectedItemCountsRegister); + } + } + } + } + + + // ------------------------------------------------------ + // "Framework" operation to test verification of a constraint on total value of selected items + operation ValidateTotalValueVerification ( + testOp : (Int, Int[], Int[], Qubit[][], Qubit) => Unit is Adj+Ctl, + comparator : (Int, Int) -> Bool + ) : Unit { + for (_, W, P, itemWeights, itemProfits, itemCountLimits, _) in ExampleSets() { + for (limit, values) in [(W, itemWeights), (P, itemProfits)] { + // Calculate the total number of qubits necessary to store the integers. + let Q = RegisterSize(itemCountLimits); + + use (selectedItemCountsRegister, target) = (Qubit[Q], Qubit()); + // It will be too time-intensive to iterate through all possible combinations of items, + // so random combinations will be used for testing. + for _ in 1 .. 4 * Q { + // Generate random integers between 0 and integerLimits[i] to fill the array. + let itemCounts = ForEach(DrawRandomInt(0, _), itemCountLimits); + // Convert those integers into bit strings and concatenate them. + let binaryCombo = Flattened(Mapped(IntAsBoolArray, Zipped(itemCounts, Mapped(BitSizeI, itemCountLimits)))); + // Prepare the register so that it represents the combination. + ApplyPauliFromBitString(PauliX, true, binaryCombo, selectedItemCountsRegister); + + // Reset the counter of measurements done + ResetOracleCallsCount(); + + // Verify the weight with qubits + let selectedItemCounts = RegisterAsJaggedArray_Reference(selectedItemCountsRegister, itemCountLimits); + testOp(limit, values, itemCountLimits, selectedItemCounts, target); + + // Make sure the solution didn't use any measurements + Fact(GetOracleCallsCount(Measure) == 0, "You are not allowed to use measurements in this task"); + + let result = MResetZ(target) == One; + + // Verify the weight classically + mutable totalValue = 0; + for (val, num) in Zipped(values, itemCounts) { + // Add the weight of all instances of this item type. + set totalValue += val * num; + } + + // Assert that both methods yield the same result + Fact(comparator(totalValue, limit) == result, $"Unexpected result for item counts = {itemCounts}, item values = {values}, limit = {limit} : {result} (expected {comparator(totalValue, limit)})"); + + // Check that the operation didn't modify the input state + ApplyPauliFromBitString(PauliX, true, binaryCombo, selectedItemCountsRegister); + AssertAllZero(selectedItemCountsRegister); + } + } + } + } + + + // ------------------------------------------------------ + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T27_VerifyTotalWeight () : Unit { + ValidateTotalValueVerification(VerifyTotalWeight, LessThanOrEqualI); + } + + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T28_VerifyTotalProfit () : Unit { + ValidateTotalValueVerification(VerifyTotalProfit, GreaterThanI); + } + + + // ------------------------------------------------------ + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T29_KnapsackValidationOracle () : Unit { + // Skip the last test case to speed up the test. + for (n, W, P, itemWeights, itemProfits, itemCountLimits, _) in Most(ExampleSets()) { + + // Calculate the total number of qubits necessary to store the integers. + let Q = RegisterSize(itemCountLimits); + + use (selectedItemCountsRegister, target) = (Qubit[Q], Qubit()); + // It will be too time-intensive to iterate through all possible combinations of items, + // so random combinations will be used for testing. + for _ in 1 .. 4 * Q { + let combo = DrawRandomInt(0, 2^Q - 1); + + // Prepare the register so that it represents the combination. + let binaryCombo = IntAsBoolArray(combo, Q); + ApplyPauliFromBitString(PauliX, true, binaryCombo, selectedItemCountsRegister); + + // Reset the counter of measurements done + ResetOracleCallsCount(); + + // Verify the knapsack packing with qubits + VerifyKnapsackProblemSolution(W, P, itemWeights, itemProfits, itemCountLimits, selectedItemCountsRegister, target); + + // Make sure the solution didn't use any measurements + Fact(GetOracleCallsCount(Measure) == 0, "You are not allowed to use measurements in this task"); + + let result = MResetZ(target) == One; + + // Verify the packing classically + let selectedItemCountsIntegers = BoolArrayConcatenationAsIntArray(itemCountLimits, binaryCombo); + mutable limitsSatisfied = true; + mutable totalWeight = 0; + mutable totalProfit = 0; + for i in 0 .. n - 1 { + // Add the weight of all instances of this item type. + set totalProfit += itemProfits[i] * selectedItemCountsIntegers[i]; + set totalWeight += itemWeights[i] * selectedItemCountsIntegers[i]; + if (selectedItemCountsIntegers[i] > itemCountLimits[i]) { + set limitsSatisfied = false; + } + } + + // Assert that both methods yield the same result + Fact(result == (limitsSatisfied and totalWeight <= W and totalProfit > P), + $"Unexpected result for selectedItemCounts = {selectedItemCountsIntegers}, itemWeights = {itemWeights}, itemProfits = {itemProfits}, itemCountLimits = {itemCountLimits}, W = {W}, P = {P} : {result}"); + + // Check that the operation didn't modify the input state + ApplyPauliFromBitString(PauliX, true, binaryCombo, selectedItemCountsRegister); + AssertAllZero(selectedItemCountsRegister); + } + } + } + + + ////////////////////////////////////////////////////////////////// + // Part III. Using Grover's algorithm for knapsack optimization problems + ////////////////////////////////////////////////////////////////// + + +}