diff --git a/src/Simulation/QCTraceSimulator.Tests/Circuits/RandomTests.qs b/src/Simulation/QCTraceSimulator.Tests/Circuits/RandomTests.qs new file mode 100644 index 00000000000..b37c8c0c33f --- /dev/null +++ b/src/Simulation/QCTraceSimulator.Tests/Circuits/RandomTests.qs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + + +namespace Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.Tests { + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Diagnostics; + + internal function Fact(condition : Bool, message : String) : Unit { + if (not condition) { + fail message; + } + } + + @Test("ResourcesEstimator") + /// # Summary + /// Checks for regression against microsoft/qsharp-runtime#256. + operation CheckRandomInCorrectRange() : Unit { + for (idxTrial in 0..99) { + let sample = Random([1.0, 2.0, 2.0]); + + Fact(0 <= sample and sample <= 2, $"sample was {sample}, not in range [0, 2]"); + } + } +} diff --git a/src/Simulation/Simulators/QCTraceSimulator/InterfaceUtils.cs b/src/Simulation/Simulators/QCTraceSimulator/InterfaceUtils.cs index 79395f62ec6..f3beba01330 100644 --- a/src/Simulation/Simulators/QCTraceSimulator/InterfaceUtils.cs +++ b/src/Simulation/Simulators/QCTraceSimulator/InterfaceUtils.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Diagnostics; using Microsoft.Quantum.Simulation.Core; @@ -73,5 +74,19 @@ public static partial class Extensions { return InterfaceType(t, typeof(IControllable<>)); } + + internal static IEnumerable SelectAggregates( + this IEnumerable source, + Func aggregate, + TResult initial = default + ) + { + var acc = initial; + foreach (var element in source) + { + acc = aggregate(acc, element); + yield return acc; + } + } } } \ No newline at end of file diff --git a/src/Simulation/Simulators/QCTraceSimulator/Utils.cs b/src/Simulation/Simulators/QCTraceSimulator/Utils.cs index 203173c3137..9f991e7ed0c 100644 --- a/src/Simulation/Simulators/QCTraceSimulator/Utils.cs +++ b/src/Simulation/Simulators/QCTraceSimulator/Utils.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Quantum.Simulation.Core; using Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime; @@ -214,32 +216,36 @@ static class SimulatorsUtils /// Number between Zero and one, uniformly distributed public static long SampleDistribution(IQArray unnormalizedDistribution, double uniformZeroOneSample) { - double total = 0.0; - foreach (double prob in unnormalizedDistribution) + if (unnormalizedDistribution.Any(prob => prob < 0.0)) { - if (prob < 0) - { - throw new ExecutionFailException("Random expects array of non-negative doubles."); - } - total += prob; + throw new ExecutionFailException("Random expects array of non-negative doubles."); } + var total = unnormalizedDistribution.Sum(); if (total == 0) { throw new ExecutionFailException("Random expects array of non-negative doubles with positive sum."); } - double sample = uniformZeroOneSample * total; - double sum = unnormalizedDistribution[0]; - for (int i = 0; i < unnormalizedDistribution.Length - 1; ++i) - { - if (sum >= sample) - { - return i; - } - sum += unnormalizedDistribution[i]; - } - return unnormalizedDistribution.Length; + var sample = uniformZeroOneSample * total; + + return unnormalizedDistribution + // Get the unnormalized CDF of the distribution. + .SelectAggregates((double acc, double x) => acc + x) + // Look for the first index at which the CDF is bigger + // than the random sample of 𝑈(0, 1) that we were given + // as a parameter. + .Select((cumulativeProb, idx) => (cumulativeProb, idx)) + .Where(item => item.cumulativeProb >= sample) + // Cast that index to long, and default to returning + // the last item. + .Select( + item => (long)item.idx + ) + .DefaultIfEmpty( + unnormalizedDistribution.Length - 1 + ) + .First(); } } }