[RandomNumberGeneration] Refactor tests (#602)
This closes #557 and #600.
This commit is contained in:
Родитель
330c2dbaaa
Коммит
51e8ace939
|
@ -126,13 +126,11 @@ function Validate {
|
|||
# List of Notebooks that can't be validated for various reasons:
|
||||
# * Check.ipynb is a validation artifact and not an actual kata notebook.
|
||||
# * ComplexArithmetic and LinearAlgebra have tasks with deliberately invalid Python code.
|
||||
# * RandomNumberGenerationTutorial have tasks to generate random numbers but sometimes these numbers will look insufficiently random and fail validation.
|
||||
$not_ready =
|
||||
@(
|
||||
'Check.ipynb',
|
||||
'ComplexArithmetic.ipynb',
|
||||
'LinearAlgebra.ipynb',
|
||||
'RandomNumberGenerationTutorial.ipynb'
|
||||
'LinearAlgebra.ipynb'
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// This file is a back end for the tasks in Deutsch-Jozsa algorithm tutorial.
|
||||
// This file is a back end for the tasks in random number generation tutorial.
|
||||
// We strongly recommend to use the Notebook version of the tutorial
|
||||
// to enjoy the full experience.
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
@ -27,7 +27,7 @@ namespace Quantum.Kata.RandomNumberGeneration {
|
|||
}
|
||||
|
||||
// Exercise 3.
|
||||
operation RandomNBits (N: Int) : Int {
|
||||
operation RandomNBits (N : Int) : Int {
|
||||
// ...
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -16,66 +16,90 @@ namespace Quantum.Kata.RandomNumberGeneration {
|
|||
open Quantum.Kata.Utils;
|
||||
open Microsoft.Quantum.Random;
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Exercise 1.
|
||||
@Test("QuantumSimulator")
|
||||
@Test("Microsoft.Quantum.Katas.CounterSimulator")
|
||||
operation T1_RandomBit () : Unit {
|
||||
Message("Testing...");
|
||||
CheckFlatDistribution(RandomBit_Wrapper, 1, 0.4, 0.6, 1000, 450);
|
||||
Message("Testing one random bit generation...");
|
||||
RetryCheckUniformDistribution(RandomBit_Wrapper, 0, 1, 1000);
|
||||
Message("Test passed");
|
||||
}
|
||||
|
||||
operation RandomBit_Wrapper (throwaway: Int) : Int {
|
||||
operation RandomBit_Wrapper (throwawayMin : Int, throwawayMax : Int) : Int {
|
||||
return RandomBit();
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Exercise 2.
|
||||
@Test("QuantumSimulator")
|
||||
@Test("Microsoft.Quantum.Katas.CounterSimulator")
|
||||
operation T2_RandomTwoBits () : Unit {
|
||||
Message("Testing...");
|
||||
CheckFlatDistribution(RandomTwoBits_Wrapper, 2, 1.4, 1.6, 1000, 200);
|
||||
Message("Testing two random bits generation...");
|
||||
RetryCheckUniformDistribution(RandomTwoBits_Wrapper, 0, 3, 1000);
|
||||
Message("Test passed");
|
||||
}
|
||||
|
||||
operation RandomTwoBits_Wrapper (throwaway: Int) : Int {
|
||||
operation RandomTwoBits_Wrapper (throwawayMin : Int, throwawayMax : Int) : Int {
|
||||
return RandomTwoBits();
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Exercise 3.
|
||||
@Test("QuantumSimulator")
|
||||
@Test("Microsoft.Quantum.Katas.CounterSimulator")
|
||||
operation T3_RandomNBits () : Unit {
|
||||
Message("Testing N = 1...");
|
||||
CheckFlatDistribution(RandomNBits, 1, 0.4, 0.6, 1000, 450);
|
||||
Message("Testing N = 2...");
|
||||
CheckFlatDistribution(RandomNBits, 2, 1.4, 1.6, 1000, 200);
|
||||
Message("Testing N = 3...");
|
||||
CheckFlatDistribution(RandomNBits, 3, 3.3, 3.7, 1000, 90);
|
||||
Message("Testing N = 10...");
|
||||
CheckFlatDistribution(RandomNBits, 10, 461.0, 563.0, 1000, 0);
|
||||
// Test random number generation for 1, 2, 3, 10 bits
|
||||
for (min, max) in [(0, 1), (0, 3), (0, 7), (0, 1023)] {
|
||||
let N = Ceiling( Lg(IntAsDouble(max + 1)) );
|
||||
Message($"Testing N = {N}...");
|
||||
RetryCheckUniformDistribution(RandomNBits_Wrapper, min, max, 1000);
|
||||
Message($"Test passed for N = {N}");
|
||||
}
|
||||
}
|
||||
|
||||
operation RandomNBits_Wrapper (min : Int, max : Int) : Int {
|
||||
// For N bit random number : min = 0, max = 2^N - 1
|
||||
let N = Ceiling( Lg(IntAsDouble(max+1)) );
|
||||
return RandomNBits(N);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
/// # Summary
|
||||
/// Helper operation that checks that the given RNG operation generates a uniform distribution.
|
||||
/// # Input
|
||||
/// ## f
|
||||
/// Random number generation operation to be tested
|
||||
/// ## numBits
|
||||
/// Number of bits in the generated result
|
||||
/// ## lowRange
|
||||
/// The lower bound of the median and average for generated dataset
|
||||
/// ## highRange
|
||||
/// The upper bound of the median and average for generated dataset
|
||||
/// Random number generation operation to be tested.
|
||||
/// ## min, max
|
||||
/// Minimal and maximal numbers in the range to be generated, inclusive.
|
||||
/// ## nRuns
|
||||
/// The number of random numbers to generate for test
|
||||
/// ## minimumCopiesGenerated
|
||||
/// The minimum number of times each possible number should be generated
|
||||
operation CheckFlatDistribution (f : (Int => Int), numBits : Int, lowRange : Double, highRange : Double, nRuns : Int, minimumCopiesGenerated : Int) : Unit {
|
||||
let max = PowI(2, numBits);
|
||||
mutable counts = ConstantArray(max, 0);
|
||||
/// The number of random numbers to generate for test.
|
||||
operation CheckUniformDistribution (f : ((Int, Int) => Int), min : Int, max : Int, nRuns : Int) : Bool {
|
||||
let idealMean = 0.5 * IntAsDouble(max + min) ;
|
||||
let rangeDividedByTwo = 0.5 * IntAsDouble(max - min);
|
||||
// Variance = a*(a+1)/3, where a = (max-min)/2
|
||||
// For sample population : divide it by nRuns
|
||||
let varianceInSamplePopulation = (rangeDividedByTwo * (rangeDividedByTwo + 1.0)) / IntAsDouble(3 * nRuns);
|
||||
let standardDeviation = Sqrt(varianceInSamplePopulation);
|
||||
|
||||
// lowRange : The lower bound of the median and average for generated dataset
|
||||
// highRange : The upper bound of the median and average for generated dataset
|
||||
// Set them with 3 units of std deviation for 99% accuracy.
|
||||
let lowRange = idealMean - 3.0 * standardDeviation;
|
||||
let highRange = idealMean + 3.0 * standardDeviation;
|
||||
|
||||
let idealCopiesGenerated = IntAsDouble(nRuns) / IntAsDouble(max-min+1);
|
||||
let minimumCopiesGenerated = ( 0.8 * idealCopiesGenerated > 40.0 ) ? 0.8 * idealCopiesGenerated | 0.0;
|
||||
|
||||
mutable counts = ConstantArray(max + 1, 0);
|
||||
mutable average = 0.0;
|
||||
|
||||
ResetOracleCallsCount();
|
||||
for i in 1..nRuns {
|
||||
let val = f(numBits);
|
||||
if (val < 0 or val >= max) {
|
||||
fail $"Unexpected number generated. Expected values from 0 to {max - 1}, generated {val}";
|
||||
let val = f(min, max);
|
||||
if (val < min or val > max) {
|
||||
Message($"Unexpected number generated. Expected values from {min} to {max}, generated {val}");
|
||||
return false;
|
||||
}
|
||||
set average += IntAsDouble(val);
|
||||
set counts w/= val <- counts[val] + 1;
|
||||
|
@ -84,25 +108,47 @@ namespace Quantum.Kata.RandomNumberGeneration {
|
|||
|
||||
set average = average / IntAsDouble(nRuns);
|
||||
if (average < lowRange or average > highRange) {
|
||||
fail $"Unexpected average of generated numbers. Expected between {lowRange} and {highRange}, got {average}";
|
||||
Message($"Unexpected average of generated numbers. Expected between {lowRange} and {highRange}, got {average}");
|
||||
return false;
|
||||
}
|
||||
|
||||
let median = FindMedian (counts, max, nRuns);
|
||||
let median = FindMedian (counts, max+1, nRuns);
|
||||
if (median < Floor(lowRange) or median > Ceiling(highRange)) {
|
||||
fail $"Unexpected median of generated numbers. Expected between {Floor(lowRange)} and {Ceiling(highRange)}, got {median}.";
|
||||
|
||||
Message($"Unexpected median of generated numbers. Expected between {Floor(lowRange)} and {Ceiling(highRange)}, got {median}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for i in 0..max - 1 {
|
||||
if (counts[i] < minimumCopiesGenerated) {
|
||||
fail $"Unexpectedly low number of {i}'s generated. Only {counts[i]} out of {nRuns} were {i}";
|
||||
for i in min..max {
|
||||
if (counts[i] < Floor(minimumCopiesGenerated)) {
|
||||
Message($"Unexpectedly low number of {i}'s generated. Only {counts[i]} out of {nRuns} were {i}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// # Summary
|
||||
/// Helper operation to rerun check for uniform distribution several times
|
||||
/// (a single run can fail with non-negligible probability even for a correct solution).
|
||||
operation RetryCheckUniformDistribution (f : ((Int, Int) => Int), min : Int, max : Int, nRuns : Int) : Unit {
|
||||
let numRetries = 3;
|
||||
mutable sufficientlyRandom = false;
|
||||
mutable attemptNum = 1;
|
||||
repeat {
|
||||
set sufficientlyRandom = CheckUniformDistribution(f, min, max, nRuns);
|
||||
set attemptNum += 1;
|
||||
} until (sufficientlyRandom or attemptNum >= numRetries);
|
||||
|
||||
if not sufficientlyRandom {
|
||||
fail $"Failed to generate sufficiently random integers";
|
||||
}
|
||||
}
|
||||
|
||||
operation FindMedian (counts : Int [], arrSize : Int, sampleSize : Int) : Int {
|
||||
|
||||
operation FindMedian (counts : Int[], arrSize : Int, sampleSize : Int) : Int {
|
||||
mutable totalCount = 0;
|
||||
for i in 0..arrSize - 1 {
|
||||
for i in 0 .. arrSize - 1 {
|
||||
set totalCount = totalCount + counts[i];
|
||||
if (totalCount >= sampleSize / 2) {
|
||||
return i;
|
||||
|
@ -112,27 +158,29 @@ namespace Quantum.Kata.RandomNumberGeneration {
|
|||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Exercise 4.
|
||||
@Test("QuantumSimulator")
|
||||
@Test("Microsoft.Quantum.Katas.CounterSimulator")
|
||||
operation T4_WeightedRandomBit () : Unit {
|
||||
ResetOracleCallsCount();
|
||||
CheckXPercentZero(0.0);
|
||||
CheckXPercentZero(0.25);
|
||||
CheckXPercentZero(0.5);
|
||||
CheckXPercentZero(0.75);
|
||||
CheckXPercentZero(1.0);
|
||||
RetryCheckXPercentZero(0.0);
|
||||
RetryCheckXPercentZero(0.25);
|
||||
RetryCheckXPercentZero(0.5);
|
||||
RetryCheckXPercentZero(0.75);
|
||||
RetryCheckXPercentZero(1.0);
|
||||
CheckRandomCalls();
|
||||
}
|
||||
|
||||
operation CheckXPercentZero (x : Double) : Unit {
|
||||
Message($"Testing x = {x}...");
|
||||
|
||||
operation CheckXPercentZero (x : Double) : Bool {
|
||||
mutable oneCount = 0;
|
||||
let nRuns = 1000;
|
||||
ResetOracleCallsCount();
|
||||
for N in 1..nRuns {
|
||||
let val = WeightedRandomBit(x);
|
||||
if (val < 0 or val > 1) {
|
||||
fail $"Unexpected number generated. Expected 0 or 1, instead generated {val}";
|
||||
Message($"Unexpected number generated. Expected 0 or 1, instead generated {val}");
|
||||
return false;
|
||||
}
|
||||
set oneCount += val;
|
||||
}
|
||||
|
@ -143,59 +191,49 @@ namespace Quantum.Kata.RandomNumberGeneration {
|
|||
// We don't have tests with probabilities near 0.0 or 1.0, so for those the matching has to be exact
|
||||
if (goalZeroCount == 0 or goalZeroCount == nRuns) {
|
||||
if (zeroCount != goalZeroCount) {
|
||||
fail $"Expected {x * 100.0}% 0's, instead got {zeroCount} 0's out of {nRuns}";
|
||||
Message($"Expected {x * 100.0}% 0's, instead got {zeroCount} 0's out of {nRuns}");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (zeroCount < goalZeroCount - 4 * nRuns / 100) {
|
||||
fail $"Unexpectedly low number of 0's generated: expected around {x * IntAsDouble(nRuns)} 0's, got {zeroCount} out of {nRuns}";
|
||||
Message($"Unexpectedly low number of 0's generated: expected around {x * IntAsDouble(nRuns)} 0's, got {zeroCount} out of {nRuns}");
|
||||
return false;
|
||||
} elif (zeroCount > goalZeroCount + 4 * nRuns / 100) {
|
||||
fail $"Unexpectedly high number of 0's generated: expected around {x * IntAsDouble(nRuns)} 0's, got {zeroCount} out of {nRuns}";
|
||||
Message($"Unexpectedly high number of 0's generated: expected around {x * IntAsDouble(nRuns)} 0's, got {zeroCount} out of {nRuns}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
operation RetryCheckXPercentZero (x : Double) : Unit {
|
||||
let numRetries = 3;
|
||||
Message($"Testing probability of zero = {x}...");
|
||||
mutable sufficientlyRandom = false;
|
||||
mutable attemptNum = 1;
|
||||
repeat {
|
||||
set sufficientlyRandom = CheckXPercentZero(x);
|
||||
set attemptNum += 1;
|
||||
} until sufficientlyRandom or attemptNum >= numRetries;
|
||||
|
||||
if not sufficientlyRandom {
|
||||
fail $"Failed to generate 0 with {100.0*x}% probability";
|
||||
} else {
|
||||
Message($"Test passed for probability of zero {x}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Exercise 5.
|
||||
@Test("QuantumSimulator")
|
||||
@Test("Microsoft.Quantum.Katas.CounterSimulator")
|
||||
operation T5_RandomNumberInRange () : Unit {
|
||||
Message("Testing...");
|
||||
CheckFlatDistributionRange(RandomNumberInRange, 1, 3, 1.8, 2.2, 1000, 200);
|
||||
CheckFlatDistributionRange(RandomNumberInRange, 27, 312, 160.0, 180.0, 1000, 0);
|
||||
CheckFlatDistributionRange(RandomNumberInRange, 0, 3, 1.4, 1.6, 1000, 200);
|
||||
CheckFlatDistributionRange(RandomNumberInRange, 0, 1023, 461.0, 563.0, 1000, 0);
|
||||
}
|
||||
|
||||
operation CheckFlatDistributionRange (f : ((Int, Int) => Int), min : Int, max : Int, lowRange : Double, highRange : Double, nRuns : Int, minimumCopiesGenerated : Int) : Unit {
|
||||
mutable counts = ConstantArray(max+1, 0);
|
||||
mutable average = 0.0;
|
||||
|
||||
ResetOracleCallsCount();
|
||||
for i in 1..nRuns {
|
||||
let val = f(min, max);
|
||||
if (val < min or val > max) {
|
||||
fail $"Unexpected number generated. Expected values from {min} to {max}, generated {val}";
|
||||
}
|
||||
set average += IntAsDouble(val);
|
||||
set counts w/= val <- counts[val] + 1;
|
||||
}
|
||||
CheckRandomCalls();
|
||||
|
||||
set average = average / IntAsDouble(nRuns);
|
||||
if (average < lowRange or average > highRange) {
|
||||
fail $"Unexpected average of generated numbers. Expected between {lowRange} and {highRange}, got {average}";
|
||||
}
|
||||
|
||||
let median = FindMedian (counts, max, nRuns);
|
||||
if (median < Floor(lowRange) or median > Ceiling(highRange)) {
|
||||
fail $"Unexpected median of generated numbers. Expected between {Floor(lowRange)} and {Ceiling(highRange)}, got {median}.";
|
||||
|
||||
}
|
||||
|
||||
for i in min..max {
|
||||
if (counts[i] < minimumCopiesGenerated) {
|
||||
fail $"Unexpectedly low number of {i}'s generated. Only {counts[i]} out of {nRuns} were {i}";
|
||||
}
|
||||
}
|
||||
for (min, max) in [(1, 3), (27, 312), (0, 3), (0, 1023)] {
|
||||
Message($"Testing for min = {min} and max = {max}...");
|
||||
RetryCheckUniformDistribution(RandomNumberInRange, min, max, 1000);
|
||||
Message($"Test passed for min = {min} and max = {max}");
|
||||
}
|
||||
}
|
||||
|
||||
operation CheckRandomCalls () : Unit {
|
||||
|
|
Загрузка…
Ссылка в новой задаче