[RandomNumberGeneration] Refactor tests (#602)

This closes #557 and #600.
This commit is contained in:
Manvi-Agrawal 2021-04-28 07:02:24 +05:30 коммит произвёл GitHub
Родитель 330c2dbaaa
Коммит 51e8ace939
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 135 добавлений и 99 удалений

Просмотреть файл

@ -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 {