diff --git a/build/ci.yml b/build/ci.yml index 498141e489..0f11ad451a 100644 --- a/build/ci.yml +++ b/build/ci.yml @@ -24,6 +24,8 @@ variables: jobs: - job: "iqsharp" + pool: + vmImage: 'ubuntu-latest' steps: - template: steps.yml - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 diff --git a/images/iqsharp-base/Dockerfile b/images/iqsharp-base/Dockerfile index 3eee9907b1..3466dfff6c 100644 --- a/images/iqsharp-base/Dockerfile +++ b/images/iqsharp-base/Dockerfile @@ -97,7 +97,9 @@ RUN mkdir -p ${HOME}/.nuget/NuGet && \ cat ${HOME}/.nuget/NuGet/NuGet.Config # Add Python and NuGet packages from the build context ADD nugets/*.nupkg ${LOCAL_PACKAGES}/nugets/ -ADD wheels/*.whl ${LOCAL_PACKAGES}/wheels/ +# When adding wheels, use *-any.whl to make sure that platform-specific wheels +# are not incorrectly added to the Docker image. +ADD wheels/*-any.whl ${LOCAL_PACKAGES}/wheels/ # Give the notebook user ownership over the packages and config copied from # the context. RUN chown ${USER} -R ${LOCAL_PACKAGES}/ && \ diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 6140b5b4f0..c504527d2f 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -38,10 +38,10 @@ - - - - + + + + diff --git a/src/ExecutionPathTracer/ExecutionPathTracer.csproj b/src/ExecutionPathTracer/ExecutionPathTracer.csproj index 0fc4dc0346..601a67dbf5 100644 --- a/src/ExecutionPathTracer/ExecutionPathTracer.csproj +++ b/src/ExecutionPathTracer/ExecutionPathTracer.csproj @@ -32,7 +32,7 @@ - + diff --git a/src/Jupyter/ConfigurationSource/IConfigurationSource.cs b/src/Jupyter/ConfigurationSource/IConfigurationSource.cs index 63455bb2db..4b33e1e1a9 100644 --- a/src/Jupyter/ConfigurationSource/IConfigurationSource.cs +++ b/src/Jupyter/ConfigurationSource/IConfigurationSource.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using Microsoft.Quantum.Experimental; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -129,5 +130,25 @@ public T GetOptionOrDefault(string optionName, T defaultValue) => /// public TraceVisualizationStyle TraceVisualizationStyle => GetOptionOrDefault("trace.style", TraceVisualizationStyle.Default); + + /// + /// Specifies the number of qubits that the experimental simulators + /// support for use in running Q# programs. + /// + public uint ExperimentalSimulatorCapacity => + GetOptionOrDefault("experimental.simulators.nQubits", 3); + + /// + /// Specifies the representation to use for the initial state + /// when simulating Q# programs with experimental simulators. + /// + public string ExperimentalSimulatorRepresentation => + GetOptionOrDefault("experimental.simulators.representation", "mixed"); + + /// + /// Specifies the format used in dumping stabilizer states. + /// + public StabilizerStateVisualizationStyle ExperimentalSimulatorStabilizerStateVisualizationStyle => + GetOptionOrDefault("experimental.simulators.stabilizerStateStyle", StabilizerStateVisualizationStyle.MatrixWithDestabilizers); } } diff --git a/src/Jupyter/Extensions.cs b/src/Jupyter/Extensions.cs index 1b977e1f5b..b0f2eed99d 100644 --- a/src/Jupyter/Extensions.cs +++ b/src/Jupyter/Extensions.cs @@ -7,12 +7,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Text.RegularExpressions; using Microsoft.Jupyter.Core; using Microsoft.Quantum.Simulation.Common; using Microsoft.Quantum.Simulation.Core; using Microsoft.Quantum.Simulation.Simulators; using Newtonsoft.Json; +using NumSharp; namespace Microsoft.Quantum.IQSharp.Jupyter { @@ -166,33 +168,6 @@ public static T WithStackTraceDisplay(this T simulator, IChannel channel) return simulator; } - /// - /// Removes common indents from each line in a string, - /// similarly to Python's textwrap.dedent() function. - /// - public static string Dedent(this string text) - { - // First, start by finding the length of common indents, - // disregarding lines that are only whitespace. - var leadingWhitespaceRegex = new Regex(@"^[ \t]*"); - var minWhitespace = int.MaxValue; - foreach (var line in text.Split("\n")) - { - if (!string.IsNullOrWhiteSpace(line)) - { - var match = leadingWhitespaceRegex.Match(line); - minWhitespace = match.Success - ? System.Math.Min(minWhitespace, match.Value.Length) - : minWhitespace = 0; - } - } - - // We can use that to build a new regex that strips - // out common indenting. - var leftTrimRegex = new Regex(@$"^[ \t]{{{minWhitespace}}}", RegexOptions.Multiline); - return leftTrimRegex.Replace(text, ""); - } - /// /// Retrieves and JSON-decodes the value for the given parameter name. /// @@ -275,5 +250,53 @@ public static T DecodeParameter(this Dictionary parameters, s } return JsonConvert.DeserializeObject(parameterValue, type) ?? defaultValue; } + + internal static string AsLaTeXMatrixOfComplex(this NDArray array) => + // NB: Assumes 𝑛 × 𝑛 × 2 array, where the trailing index is + // [real, imag]. + // TODO: Consolidate with logic at: + // https://github.com/microsoft/QuantumLibraries/blob/505fc27383c9914c3e1f60fb63d0acfe60b11956/Visualization/src/DisplayableUnitaryEncoders.cs#L43 + string.Join( + "\\\\\n", + Enumerable + .Range(0, array.Shape[0]) + .Select( + idxRow => string.Join(" & ", + Enumerable + .Range(0, array.Shape[1]) + .Select( + idxCol => $"{array[idxRow, idxCol, 0]} + {array[idxRow, idxCol, 1]} i" + ) + ) + ) + ); + + internal static IEnumerable IterateOverLeftmostIndex(this NDArray array) + { + foreach (var idx in Enumerable.Range(0, array.shape[0])) + { + yield return array[idx, Slice.Ellipsis]; + } + } + + internal static string AsTextMatrixOfComplex(this NDArray array, string rowSep = "\n") => + // NB: Assumes 𝑛 × 𝑛 × 2 array, where the trailing index is + // [real, imag]. + // TODO: Consolidate with logic at: + // https://github.com/microsoft/QuantumLibraries/blob/505fc27383c9914c3e1f60fb63d0acfe60b11956/Visualization/src/DisplayableUnitaryEncoders.cs#L43 + "[" + rowSep + string.Join( + rowSep, + Enumerable + .Range(0, array.Shape[0]) + .Select( + idxRow => "[" + string.Join(", ", + Enumerable + .Range(0, array.Shape[1]) + .Select( + idxCol => $"{array[idxRow, idxCol, 0]} + {array[idxRow, idxCol, 1]} i" + ) + ) + "]" + ) + ) + rowSep + "]"; } } diff --git a/src/Jupyter/NoiseModelSource/INoiseModelSource.cs b/src/Jupyter/NoiseModelSource/INoiseModelSource.cs new file mode 100644 index 0000000000..5ea1fe8c32 --- /dev/null +++ b/src/Jupyter/NoiseModelSource/INoiseModelSource.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation +// Licensed under the MIT License. + +#nullable enable + +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Quantum.Experimental +{ + + public interface INoiseModelSource + { + NoiseModel NoiseModel { get; set; } + } + +} diff --git a/src/Jupyter/NoiseModelSource/NoiseModelSource.cs b/src/Jupyter/NoiseModelSource/NoiseModelSource.cs new file mode 100644 index 0000000000..fecdc37476 --- /dev/null +++ b/src/Jupyter/NoiseModelSource/NoiseModelSource.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Quantum.Experimental +{ + + public class NoiseModelSource : INoiseModelSource + { + public NoiseModel NoiseModel { get; set; } = + NoiseModel.TryGetByName("ideal", out var ideal) + ? ideal + : throw new Exception("Could not load ideal noise model."); + } + +} diff --git a/src/Jupyter/Visualization/OpenSystemsEncoders.cs b/src/Jupyter/Visualization/OpenSystemsEncoders.cs new file mode 100644 index 0000000000..ac5ddcbf98 --- /dev/null +++ b/src/Jupyter/Visualization/OpenSystemsEncoders.cs @@ -0,0 +1,329 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using NumSharp; +using System.Linq; +using System.Text.Json; +using Microsoft.Jupyter.Core; +using Microsoft.Quantum.IQSharp.Jupyter; +using System.Collections.Generic; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json; +using System; + +namespace Microsoft.Quantum.Experimental +{ + /// + /// Represents different styles for displaying the Q# execution path + /// visualization as HTML. + /// + [JsonConverter(typeof(StringEnumConverter))] + public enum StabilizerStateVisualizationStyle + { + MatrixWithDestabilizers, + MatrixWithoutDestabilizers, + DenseGroupPresentation, + SparseGroupPresentation + } + + // TODO: add display encoders for other formats. + public class MixedStateToHtmlDisplayEncoder : IResultEncoder + { + /// + public string MimeType => MimeTypes.Html; + + /// + public EncodedData? Encode(object displayable) + { + if (displayable is MixedState state) + { + return $@" + + + + + + + + + + + +
Mixed state
# of qubits{state.NQubits}
State data + $$ + \left( + \begin{{matrix}} + {state.Data.AsLaTeXMatrixOfComplex()} + \end{{matrix}} + \right) + $$ +
+ " + .ToEncodedData(); + } + else return null; + } + } + + public class StabilizerStateToHtmlDisplayEncoder : IResultEncoder + { + private readonly IConfigurationSource config; + + /// + public string MimeType => MimeTypes.Html; + + public StabilizerStateToHtmlDisplayEncoder(IConfigurationSource config) + { + this.config = config; + } + + /// + public EncodedData? Encode(object displayable) + { + if (displayable is StabilizerState { NQubits: var nQubits, Data: {} data }) + { + var repeatedCs = new String('c', nQubits); + var colspec = $"{repeatedCs}|{repeatedCs}|c"; + return $@" + + + + + + + + + + + +
Stabilizer state
# of qubits{nQubits}
State data{ + config.ExperimentalSimulatorStabilizerStateVisualizationStyle switch + { + StabilizerStateVisualizationStyle.MatrixWithDestabilizers => + $@"$$\left(\begin{{array}}{{{colspec}}}{ + string.Join( + "\\\\\n", + Enumerable + .Range(0, data.Shape[0]) + .Select( + idxRow => + ( + idxRow == nQubits + ? "\\hline\n" + : "" + ) + string.Join(" & ", + Enumerable.Range(0, data.Shape[1]) + .Select( + idxCol => data[idxRow, idxCol] ? "1" : "0" + ) + ) + ) + ) + }\end{{array}}\right)$$", + StabilizerStateVisualizationStyle.MatrixWithoutDestabilizers => + $@"$$\left(\begin{{array}}{{{colspec}}}{ + string.Join( + "\\\\\n", + Enumerable + .Range(nQubits, data.Shape[0] / 2) + .Select( + idxRow => string.Join(" & ", + Enumerable.Range(0, data.Shape[1]) + .Select( + idxCol => data[idxRow, idxCol] ? "1" : "0" + ) + ) + ) + ) + }\end{{array}}\right)$$", + // FIXME: include phases in each! + StabilizerStateVisualizationStyle.DenseGroupPresentation => + $@"$$\left\langle { + string.Join( + ", ", + Enumerable + .Range(nQubits, data.Shape[0] / 2) + .Select( + idxRow => + (data[idxRow, 2 * nQubits] == true ? "-" : "") + + string.Join("", + Enumerable.Range(0, nQubits) + .Select(idxQubit => + { + (bool x, bool z) = (data[idxRow, idxQubit], data[idxRow, nQubits + idxQubit]); + return (x, z) switch + { + (false, false) => "𝟙", + (true, false) => "X", + (false, true) => "Z", + (true, true) => "Y" + }; + }) + ) + ) + ) + } \right\rangle$$", + StabilizerStateVisualizationStyle.SparseGroupPresentation => + $@"$$\left\langle { + string.Join( + ", ", + Enumerable + .Range(nQubits, data.Shape[0] / 2) + .Select( + idxRow => + (data[idxRow, 2 * nQubits] == true ? "-" : "") + + string.Join("", + Enumerable.Range(0, nQubits) + .Select(idxQubit => + { + (bool x, bool z) = (data[idxRow, idxQubit], data[idxRow, nQubits + idxQubit]); + return (x, z) switch + { + (false, false) => "", + (true, false) => $"X_{{{idxQubit}}}", + (false, true) => $"Z_{{{idxQubit}}}", + (true, true) => $"Y_{{{idxQubit}}}" + }; + }) + ) + ) + ) + } \right\rangle$$", + var unknown => throw new Exception($"Invalid visualization style {unknown}.") + } + }
+ " + .ToEncodedData(); + } + else return null; + } + } + + public class NoiseModelToHtmlDisplayEncoder : IResultEncoder + { + /// + public string MimeType => MimeTypes.Html; + + /// + public EncodedData? Encode(object displayable) + { + string RowForProcess(string name, Process? process) => + $@" + + {name} + + $${ + // NB: We use a switch expression here to + // pattern match on what representation + // the given process is expressed in. + // In doing so, we use the {} pattern + // to capture non-null process data, + // so that we are guaranteed that we + // only try to display processs that + // were successfully deserialized. + // TODO: Add other variants of process here. + process switch + { + UnitaryProcess { Data: {} data } => + $@" + \left( \begin{{matrix}} + {data.AsLaTeXMatrixOfComplex() ?? ""} + \end{{matrix}} \right)", + KrausDecompositionProcess { Data: {} data } => + $@" + \left\{{{ + string.Join(", ", + Enumerable + .Range(0, data.Shape[0]) + .Select(idxKrausOperator => + $@" + \left( \begin{{matrix}} + {data[idxKrausOperator].AsLaTeXMatrixOfComplex()} + \end{{matrix}} \right) + " + ) + ) + }\right\}} + ", + MixedPauliProcess { Operators: {} ops } => + $@" + \text{{(mixed Pauli process) }} + \left\{{{ + string.Join(", ", + ops.Select( + item => $@"{item.Item1} {string.Join( + "", + item.Item2.Select(pauli => pauli.ToString()) + )}" + ) + ) + }\right\}} + ", + {} unknown => unknown.ToString(), + null => "" + } + }$$ + + + "; + + if (displayable is NoiseModel noiseModel) + { + return $@" + + + + + + + {RowForProcess("CNOT", noiseModel?.Cnot)} + {RowForProcess("$I$", noiseModel?.I)} + {RowForProcess("$X$", noiseModel?.X)} + {RowForProcess("$Y$", noiseModel?.Y)} + {RowForProcess("$Z$", noiseModel?.Z)} + {RowForProcess("$H$", noiseModel?.H)} + {RowForProcess("$S$", noiseModel?.S)} + {RowForProcess("$S^{\\dagger}$", noiseModel?.SAdj)} + {RowForProcess("$T$", noiseModel?.T)} + {RowForProcess("$T^{\\dagger}$", noiseModel?.TAdj)} + + + + +
Noise model
Initial state + $$ + \left( \begin{{matrix}} + {(noiseModel.InitialState as MixedState)?.Data?.AsLaTeXMatrixOfComplex() ?? ""} + \end{{matrix}} \right) + $$ +
$Z$-measurement{ + noiseModel?.ZMeas switch + { + EffectsInstrument { Effects: var effects } => $@" + $$\left\{{{ + string.Join(", ", + (effects ?? new List()) + .Select( + process => $@" + \left( \begin{{matrix}} + {(process as ArrayProcess)?.Data?.AsLaTeXMatrixOfComplex() ?? ""} + \end{{matrix}} \right) + " + ) + ) + }\right\}}$$ + ", + ZMeasurementInstrument { PrReadoutError: var prReadoutError } => + $@"$Z$-basis measurement w/ error probability {prReadoutError}", + {} unknown => unknown.ToString(), + null => "" + } + }
+ ".ToEncodedData(); + } + else return null; + } + } +} diff --git a/src/Kernel/CustomShell/MessageExtensions.cs b/src/Kernel/CustomShell/MessageExtensions.cs index 22341f3c67..3c926bcebc 100644 --- a/src/Kernel/CustomShell/MessageExtensions.cs +++ b/src/Kernel/CustomShell/MessageExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.Jupyter.Core.Protocol; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Linq; @@ -29,9 +30,20 @@ public static T To(this Message message) { var jsonPropertyAttribute = property.GetCustomAttributes(true).OfType().FirstOrDefault(); var propertyName = jsonPropertyAttribute?.PropertyName ?? property.Name; + var propertyType = property.PropertyType; if (content.Data.TryGetValue(propertyName, out var value)) { - property.SetValue(result, content.Data[propertyName]); + var data = content.Data[propertyName]; + // If the unknown content's data is a JToken, that + // indicates that we need to further deserialize it. + if (data is JToken tokenData) + { + property.SetValue(result, tokenData.ToObject(propertyType)); + } + else + { + property.SetValue(result, data); + } } } return result; diff --git a/src/Kernel/ExperimentalFeatures.cs b/src/Kernel/ExperimentalFeatures.cs new file mode 100644 index 0000000000..b747c9e995 --- /dev/null +++ b/src/Kernel/ExperimentalFeatures.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.Quantum.IQSharp; +using Microsoft.Jupyter.Core; +using Microsoft.Jupyter.Core.Protocol; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Microsoft.Quantum.IQSharp.Kernel +{ + + /// + /// Message content to be received when a client asks for an + /// experimental feature to be turned on. + /// + public class ExperimentalFeatureContent : MessageContent + { + /// + /// The name of the experimental feature to be enabled. + /// + [JsonProperty("feature_name")] + public string? FeatureName { get; set; } + + /// + /// The names and versions of any optional packages used with the + /// requested experimental feature. + /// + [JsonProperty("optional_dependencies")] + public List? OptionalDependencies { get; set; } + } + + /// + /// Event type for when a Python client enables an experimental + /// feature. + /// + public class ExperimentalFeatureEnabledEvent : Event + { + } + + /// + /// Shell handler that allows for firing off events when a Python + /// client enables an experimental feature via + /// qsharp.experimental. + /// + internal class ExperimentalFeaturesShellHandler : IShellHandler + { + public string MessageType => "iqsharp_python_enable_experimental"; + private readonly IEventService events; + + public ExperimentalFeaturesShellHandler(IEventService events) + { + this.events = events; + } + + public Task HandleAsync(Message message) + { + var content = message.To(); + events?.Trigger(content); + return Task.CompletedTask; + } + } + +} \ No newline at end of file diff --git a/src/Kernel/Extensions.cs b/src/Kernel/Extensions.cs index 70df68a1a9..1df259c4b9 100644 --- a/src/Kernel/Extensions.cs +++ b/src/Kernel/Extensions.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Jupyter.Core; using Microsoft.Jupyter.Core.Protocol; +using Microsoft.Quantum.Experimental; using Microsoft.Quantum.IQSharp.Jupyter; using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; @@ -36,6 +37,7 @@ public static T AddIQSharpKernel(this T services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); return services; } diff --git a/src/Kernel/Magic/ExperimentalBuildInfo.cs b/src/Kernel/Magic/ExperimentalBuildInfo.cs new file mode 100644 index 0000000000..67c63e4477 --- /dev/null +++ b/src/Kernel/Magic/ExperimentalBuildInfo.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Jupyter.Core; +using Microsoft.Quantum.IQSharp; +using Microsoft.Quantum.IQSharp.Common; +using Microsoft.Quantum.IQSharp.Jupyter; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; +using System.Text.Json; +using Microsoft.Extensions.Logging; +using Microsoft.Quantum.IQSharp.Kernel; +using System.Collections.Generic; + +namespace Microsoft.Quantum.Experimental +{ + public class ExperimentalBuildInfoMagic : AbstractMagic + { + + /// + /// Allows for querying noise models and for loading new noise models. + /// + public ExperimentalBuildInfoMagic() : base( + "experimental.build_info", + new Microsoft.Jupyter.Core.Documentation + { + Summary = "Reports build info for the experimental simulators.", + Description = @" + > **⚠ WARNING:** This magic command is **experimental**, + > is not supported, and may be removed from future versions without notice. + ".Dedent(), + Examples = new string[] + { + @" + Return the build info for experimental simulators: + ``` + In []: %experimental.build_info + ``` + ".Dedent(), + } + }) + { } + + /// + public override ExecutionResult Run(string input, IChannel channel) + { + return OpenSystemsSimulator.BuildInfo.ToExecutionResult(); + } + } +} diff --git a/src/Kernel/Magic/NoiseModel.cs b/src/Kernel/Magic/NoiseModel.cs new file mode 100644 index 0000000000..8feb4f06b1 --- /dev/null +++ b/src/Kernel/Magic/NoiseModel.cs @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Jupyter.Core; +using Microsoft.Quantum.IQSharp; +using Microsoft.Quantum.IQSharp.Common; +using Microsoft.Quantum.IQSharp.Jupyter; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; +using System.Text.Json; +using Microsoft.Extensions.Logging; +using Microsoft.Quantum.IQSharp.Kernel; + +namespace Microsoft.Quantum.Experimental +{ + public class NoiseModelMagic : AbstractMagic + { + private ILogger? logger = null; + private INoiseModelSource NoiseModelSource; + + /// + /// Allows for querying noise models and for loading new noise models. + /// + public NoiseModelMagic(IExecutionEngine engine, INoiseModelSource noiseModelSource, ILogger logger) : base( + "experimental.noise_model", + new Microsoft.Jupyter.Core.Documentation + { + Summary = "Gets, sets, saves, or loads a noise model used in simulating quantum operations.", + Description = @" + > **⚠ WARNING:** This magic command is **experimental**, + > is not supported, and may be removed from future versions without notice. + + This magic command allows accessing or modifying the noise model used by + the `%experimental.simulate_noise` magic command. + ".Dedent(), + Examples = new string[] + { + @" + Return the currently set noise model: + ``` + In []: %experimental.noise_model + ``` + ".Dedent(), + @" + Return the built-in noise model with a given name: + ``` + In []: %experimental.noise_model --get-by-name ideal + ``` + ", + @" + Sets the noise model to a built-in named noise model: + ``` + In []: %experimental.noise_model --load-by-name ideal_stabilizer + ``` + ".Dedent(), + @" + Set the noise model to a noise model given as JSON: + ``` + In []: %experimental.noise_model { ... } + ``` + ".Dedent(), + @" + Save the current noise model to a JSON file named + `noise-model.json`: + ``` + In []: %experimental.noise_model --save noise-model.json + ``` + ".Dedent(), + @" + Load the noise model stored in `noise-model.json`, + making it the active noise model: + ``` + In []: %experimental.noise_model --load noise-model.json + ``` + ".Dedent() + } + }) + { + this.NoiseModelSource = noiseModelSource; + if (engine is IQSharpEngine iQSharpEngine) + { + iQSharpEngine.RegisterDisplayEncoder(new NoiseModelToHtmlDisplayEncoder()); + } + } + + /// + public override ExecutionResult Run(string input, IChannel channel) + { + var parts = input.Trim().Split(" ", 2); + var command = parts[0]; + if (command.Trim() == "--save") + { + if (NoiseModelSource.NoiseModel == null) + { + channel.Stderr("No noise model set; nothing to save."); + } + else + { + var filename = parts[1]; + File.WriteAllText(filename, JsonSerializer.Serialize(NoiseModelSource.NoiseModel)); + } + } + else if (command.Trim() == "--load") + { + var filename = parts[1]; + NoiseModelSource.NoiseModel = JsonSerializer.Deserialize(File.ReadAllText(filename)); + } + else if (command.Trim() == "--get-by-name") + { + var name = parts[1]; + if (NoiseModel.TryGetByName(name, out var noiseModel)) + { + return noiseModel.ToExecutionResult(); + } + else + { + return $"No built-in noise model with name {name}.".ToExecutionResult(ExecuteStatus.Error); + } + } + else if (command.Trim() == "--load-by-name") + { + var name = parts[1]; + if (NoiseModel.TryGetByName(name, out var noiseModel)) + { + NoiseModelSource.NoiseModel = noiseModel; + } + else + { + return $"No built-in noise model with name {name}.".ToExecutionResult(ExecuteStatus.Error); + } + } + else if (input.Trim().StartsWith("{")) + { + // Parse the input as JSON. + NoiseModelSource.NoiseModel = JsonSerializer.Deserialize(input.Trim()); + } + else + { + // Just return the existing noise model. + return NoiseModelSource.NoiseModel.ToExecutionResult(); + } + + return ExecuteStatus.Ok.ToExecutionResult(); + } + } +} diff --git a/src/Kernel/Magic/SimulateNoise.cs b/src/Kernel/Magic/SimulateNoise.cs new file mode 100644 index 0000000000..b093383939 --- /dev/null +++ b/src/Kernel/Magic/SimulateNoise.cs @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Jupyter.Core; +using Microsoft.Quantum.IQSharp; +using Microsoft.Quantum.IQSharp.Common; +using Microsoft.Quantum.IQSharp.Jupyter; +using Microsoft.Quantum.IQSharp.Kernel; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace Microsoft.Quantum.Experimental +{ + public class SimulateNoiseMagic : AbstractMagic + { + private const string ParameterNameOperationName = "__operationName__"; + private ILogger? Logger = null; + private readonly INoiseModelSource NoiseModelSource; + + /// + /// Constructs a new magic command given a resolver used to find + /// operations and functions, and a configuration source used to set + /// configuration options. + /// + public SimulateNoiseMagic(IExecutionEngine engine, ISymbolResolver resolver, IConfigurationSource configurationSource, INoiseModelSource noiseModelSource, ILogger logger) : base( + "experimental.simulate_noise", + new Microsoft.Jupyter.Core.Documentation + { + Summary = "Runs a given function or operation on the OpenSystemsSimulator target machine.", + Description = @" + > **⚠ WARNING:** This magic command is **experimental**, + > is not supported, and may be removed from future versions without notice. + + This magic command allows executing a given function or operation + on the OpenSystemsSimulator target, simulating how that function or operation + will perform when run on noisy quantum hardware. + + #### See also + + - [`%config`](https://docs.microsoft.com/qsharp/api/iqsharp-magic/config) + - [`%experimental.noise_model`](https://docs.microsoft.com/qsharp/api/iqsharp-magic/experimental.noise_model) + + #### Required parameters + + - Q# operation or function name. This must be the first parameter, and must be a valid Q# operation + or function name that has been defined either in the notebook or in a Q# file in the same folder. + - Arguments for the Q# operation or function must also be specified as `key=value` pairs. + + #### Remarks + + The behavior of this magic command can be controlled through the `%experimental.noise_model` magic command, + and the `opensim.nQubits` and `opensim.representation` configuration settings. + ".Dedent(), + Examples = new string[] + { + @" + Simulate a Q# operation defined as `operation MyOperation() : Result`: + ``` + In []: %simulate MyOperation + Out[]: + ``` + ".Dedent(), + @" + Simulate a Q# operation defined as `operation MyOperation(a : Int, b : Int) : Result`: + ``` + In []: %simulate MyOperation a=5 b=10 + Out[]: + ``` + ".Dedent(), + } + }) + { + this.SymbolResolver = resolver; + this.ConfigurationSource = configurationSource; + this.Logger = logger; + this.NoiseModelSource = noiseModelSource; + + if (engine is IQSharpEngine iQSharpEngine) + { + iQSharpEngine.RegisterDisplayEncoder(new MixedStateToHtmlDisplayEncoder()); + iQSharpEngine.RegisterDisplayEncoder(new StabilizerStateToHtmlDisplayEncoder(configurationSource)); + } + } + + /// + /// The symbol resolver used by this magic command to find + /// operations or functions to be simulated. + /// + public ISymbolResolver SymbolResolver { get; } + + /// + /// The configuration source used by this magic command to control + /// simulation options (e.g.: dump formatting options). + /// + public IConfigurationSource ConfigurationSource { get; } + + /// + public override ExecutionResult Run(string input, IChannel channel) => + RunAsync(input, channel).Result; + + /// + /// Simulates an operation given a string with its name and a JSON + /// encoding of its arguments. + /// + public async Task RunAsync(string input, IChannel channel) + { + var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName); + + var name = inputParameters.DecodeParameter(ParameterNameOperationName); + var symbol = SymbolResolver.Resolve(name) as dynamic; // FIXME: Should be IQSharpSymbol. + if (symbol == null) throw new InvalidOperationException($"Invalid operation name: {name}"); + + var qsim = new OpenSystemsSimulator( + ConfigurationSource.ExperimentalSimulatorCapacity, + ConfigurationSource.ExperimentalSimulatorRepresentation + ); + if (NoiseModelSource.NoiseModel != null) + { + var json = JsonSerializer.Serialize(NoiseModelSource.NoiseModel); + Console.WriteLine(json); + qsim.NoiseModel = NoiseModelSource.NoiseModel; + } + Logger?.LogDebug("Simulating with noise model: {NoiseModel}", JsonSerializer.Serialize(NoiseModelSource.NoiseModel)); + qsim.DisableLogToConsole(); + qsim.OnLog += channel.Stdout; + qsim.OnDisplayableDiagnostic += channel.Display; + var operation = symbol.Operation as OperationInfo; + var value = await operation.RunAsync(qsim, inputParameters); + return value.ToExecutionResult(); + } + } +} diff --git a/src/MockLibraries/Mock.Chemistry/Mock.Chemistry.csproj b/src/MockLibraries/Mock.Chemistry/Mock.Chemistry.csproj index 027424de79..d80204f186 100644 --- a/src/MockLibraries/Mock.Chemistry/Mock.Chemistry.csproj +++ b/src/MockLibraries/Mock.Chemistry/Mock.Chemistry.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 @@ -6,6 +6,6 @@ - + diff --git a/src/MockLibraries/Mock.Standard/Mock.Standard.csproj b/src/MockLibraries/Mock.Standard/Mock.Standard.csproj index 027424de79..d80204f186 100644 --- a/src/MockLibraries/Mock.Standard/Mock.Standard.csproj +++ b/src/MockLibraries/Mock.Standard/Mock.Standard.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 @@ -6,6 +6,6 @@ - + diff --git a/src/Python/qsharp-core/qsharp/__init__.py b/src/Python/qsharp-core/qsharp/__init__.py index 133f3ad376..7a19152e4d 100644 --- a/src/Python/qsharp-core/qsharp/__init__.py +++ b/src/Python/qsharp-core/qsharp/__init__.py @@ -115,6 +115,9 @@ def component_versions() -> Dict[str, LooseVersion]: versions = client.component_versions() # Add in the qsharp Python package itself. versions["qsharp"] = LooseVersion(__version__) + # If any experimental features are enabled, report them here. + if _experimental_versions is not None: + versions['experimental'] = _experimental_versions return versions @@ -124,11 +127,21 @@ def component_versions() -> Dict[str, LooseVersion]: config = Config(client) packages = Packages(client) projects = Projects(client) +_experimental_versions = None # Make sure that we're last on the meta_path so that actual modules are loaded # first. sys.meta_path.append(QSharpModuleFinder()) +# If using IPython, forward some useful IQ# magic commands as IPython magic +# commands and define a couple new magic commands for IPython. +try: + if __IPYTHON__: + import qsharp.ipython_magic + qsharp.ipython_magic.register_magics() +except NameError: + pass + # Needed to recognize PEP 420 packages as subpackages. import pkg_resources pkg_resources.declare_namespace(__name__) \ No newline at end of file diff --git a/src/Python/qsharp-core/qsharp/clients/__init__.py b/src/Python/qsharp-core/qsharp/clients/__init__.py index 16557a1439..6d3130e563 100644 --- a/src/Python/qsharp-core/qsharp/clients/__init__.py +++ b/src/Python/qsharp-core/qsharp/clients/__init__.py @@ -27,8 +27,11 @@ def _start_client(): client_name = os.getenv("QSHARP_PY_CLIENT", "iqsharp") if client_name == "iqsharp": + # Allow users to override what kernel is used, making it easier to + # test kernels side-by-side. + kernel_name = os.getenv("QSHARP_PY_IQSHARP_KERNEL_NAME", "iqsharp") import qsharp.clients.iqsharp - client = qsharp.clients.iqsharp.IQSharpClient() + client = qsharp.clients.iqsharp.IQSharpClient(kernel_name=kernel_name) elif client_name == "mock": import qsharp.clients.mock client = qsharp.clients.mock.MockClient() diff --git a/src/Python/qsharp-core/qsharp/clients/iqsharp.py b/src/Python/qsharp-core/qsharp/clients/iqsharp.py index e788b77fa5..055e5a0535 100644 --- a/src/Python/qsharp-core/qsharp/clients/iqsharp.py +++ b/src/Python/qsharp-core/qsharp/clients/iqsharp.py @@ -3,10 +3,11 @@ ## # iqsharp.py: Client for the IQ# Jupyter kernel. ## -# Copyright (c) Microsoft Corporation. All rights reserved. +# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. ## +## IMPORTS ## import subprocess import time @@ -73,8 +74,13 @@ class IQSharpClient(object): kernel_client = None _busy : bool = False - def __init__(self): - self.kernel_manager = jupyter_client.KernelManager(kernel_name='iqsharp') +class IQSharpClient(object): + kernel_manager = None + kernel_client = None + _busy : bool = False + + def __init__(self, kernel_name: str = 'iqsharp'): + self.kernel_manager = jupyter_client.KernelManager(kernel_name=kernel_name) ## Server Lifecycle ## @@ -196,6 +202,30 @@ def capture(msg): self._execute("%version", display_data_handler=capture, _quiet_=True, **kwargs) return versions + ## Experimental Methods ## + # These methods expose experimental functionality that may be removed without + # warning. To communicate to users that these are not reliable, we mark + # these methods as private, and will re-export them in the + # qsharp.experimental submodule. + + def _simulate_noise(self, op, **kwargs) -> Any: + return self._execute_callable_magic('experimental.simulate_noise', op, **kwargs) + + def _get_noise_model(self) -> str: + return self._execute(f'%experimental.noise_model') + + def _get_noise_model_by_name(self, name : str) -> None: + return self._execute(f'%experimental.noise_model --get-by-name {name}') + + def _set_noise_model(self, json_data : str) -> None: + # We assume json_data is already serialized, so that we skip the support + # provided by _execute_magic and call directly. + return self._execute(f'%experimental.noise_model {json_data}') + + def _set_noise_model_by_name(self, name : str) -> None: + return self._execute(f'%experimental.noise_model --load-by-name {name}') + + ## Internal-Use Methods ## @staticmethod diff --git a/src/Python/qsharp-core/qsharp/experimental.py b/src/Python/qsharp-core/qsharp/experimental.py new file mode 100644 index 0000000000..a8b2831b27 --- /dev/null +++ b/src/Python/qsharp-core/qsharp/experimental.py @@ -0,0 +1,410 @@ +#!/bin/env python +# -*- coding: utf-8 -*- +## +# iqsharp.py: Client for the IQ# Jupyter kernel. +## +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +## + +""" +This module allow for using experimental features of the Quantum Development Kit, +including noisy simulators for Q# programs. +""" + +## DESIGN NOTES ## + +# The functions in this module may take dependencies on QuTiP and NumPy, +# while neither of those are dependencies of the qsharp package on the whole. +# To avoid those becoming hard dependencies, we do not import either package +# at the top of this module, but do so inside each function that uses those +# dependencies. +# +# This has some performance and code maintenance implications, but allows for +# being permissive with respect to dependencies. + +## IMPORTS ## + +from typing import Any, FrozenSet, List, Tuple +import qsharp +from qsharp.loader import QSharpCallable +import json +import dataclasses + +from typing import Any, Union + +from qsharp.types import Pauli + +## EXPORTS ## + +__all__ = [ + "enable_noisy_simulation", + "get_noise_model", + "set_noise_model", + "get_noise_model_by_name", + "set_noise_model_by_name", + + # SequenceProcess process data model + "SequenceProcess", + + # Mixed pauli data model + "MixedPauli", + + # CHP decomposition data model + "ChpDecompositionProcess", + "Hadamard", + "Cnot", + "Phase", + "AdjointPhase" +] + +## PUBLIC FUNCTIONS ## + +def enable_noisy_simulation(): + """ + Enables the `.simulate_noise` method to be used on Python objects + representing Q# operations, allowing for Q# programs to be simulated using + experimental simulators. + + Noisy simulation is controlled by the :func:`~qsharp.experimental.get_noise_model`, + :func:`~qsharp.experimental.set_noise_model` and :func:`~qsharp.experimental.set_noise_model_by_name` + functions, and by the `opensim.nQubits` and `opensim.representation` keys + of the :any:`qsharp.config` object. + """ + # Prevent Python from thinking that qsharp is a local variable. + import qsharp + + # Try to import optional packages used by noise modeling. + optional_dependencies = [] + try: + import numpy as np + optional_dependencies.append(f"numpy:{np.__version__}") + except: + np = None + + try: + import qutip as qt + optional_dependencies.append(f"qutip:{qt.__version__}") + except: + qt = None + + # Tell the kernel to turn on experimental features. + try: + content = { + "feature_name": "noisy_simulation", + "optional_dependencies": optional_dependencies + } + msg = qsharp.client.kernel_client.session.msg('iqsharp_python_enable_experimental', content) + qsharp.client.kernel_client.shell_channel.send(msg) + except: + pass + + # Actually attach the new method to the type used for exposing Q# callables + # to Python. + def simulate_noise(self, **kwargs): + return qsharp.client._simulate_noise(self, **kwargs) + + QSharpCallable.simulate_noise = simulate_noise + + # Register the experimental feature with qsharp.component_versions so that + # it shows up in notebooks nicely. + version = {'simulators': qsharp.client._execute('%experimental.build_info')} + if qsharp._experimental_versions is not None: + qsharp._experimental_versions.update(version) + else: + qsharp._experimental_versions = version + + # Finally, if we're in IPython, expose experimental magic commands to + # the notebook interface. + try: + if __IPYTHON__: + import qsharp.ipython_magic + qsharp.ipython_magic.register_experimental_magics() + except NameError: + pass + +def get_noise_model(): + """ + Returns the current noise model used in simulating Q# programs with the + `.simulate_noise` method. + """ + noise_model = convert_to_arrays(qsharp.client._get_noise_model()) + # Convert {"Mixed": ...} and so forth to qobj. + return convert_to_qobjs(noise_model) + +def get_noise_model_by_name(name: str): + """ + Returns the built-in noise model with a given name. + + :param name: The name of the noise model to be returned (either `ideal` + or `ideal_stabilizer`). + """ + noise_model = convert_to_arrays(qsharp.client._get_noise_model_by_name(name)) + # Convert {"Mixed": ...} and so forth to qobj. + return convert_to_qobjs(noise_model) + +def set_noise_model(noise_model): + """ + Sets the current noise model used in simulating Q# programs with the + `.simulate_noise` method. + """ + json_data = dumps(convert_to_rust_style(noise_model)) + qsharp.client._set_noise_model(json_data) + +def set_noise_model_by_name(name): + """ + Sets the current noise model used in simulating Q# programs with the + `.simulate_noise` method to a built-in noise model, given by name. + + :param name: The name of the noise model to be returned (either `ideal` + or `ideal_stabilizer`). + """ + qsharp.client._set_noise_model_by_name(name) + +## PUBLIC DATA MODEL ## + +@dataclasses.dataclass +class SequenceProcess(): + n_qubits: int + processes: List[Any] + + def _as_jobj(self): + return { + 'n_qubits': self.n_qubits, + 'data': { + "Sequence": list(map(_as_jobj, self.processes)) + } + } + +@dataclasses.dataclass +class Hadamard(): + idx_target: int + + def _as_jobj(self): + return { + 'Hadamard': self.idx_target + } + +@dataclasses.dataclass +class Phase(): + idx_target: int + + def _as_jobj(self): + return { + 'Phase': self.idx_target + } + +@dataclasses.dataclass +class AdjointPhase(): + idx_target: int + + def _as_jobj(self): + return { + 'AdjointPhase': self.idx_target + } + +@dataclasses.dataclass +class Cnot(): + idx_control: int + idx_target: int + + def _as_jobj(self): + return { + 'Cnot': [self.idx_control, self.idx_target] + } + +@dataclasses.dataclass +class ChpDecompositionProcess(): + n_qubits: int + operations: List[Union[Hadamard, Cnot, Phase, AdjointPhase]] + + def _as_jobj(self): + return { + 'n_qubits': self.n_qubits, + 'data': { + 'ChpDecomposition': list(map(_as_jobj, self.operations)) + } + } + +@dataclasses.dataclass +class MixedPauliProcess(): + n_qubits: int + operators: List[Tuple[float, Union[List[qsharp.Pauli]], str]] + + def _as_jobj(self): + return { + 'n_qubits': self.n_qubits, + 'data': { + 'MixedPauli': [ + [ + pr, + [ + (qsharp.Pauli[p] if isinstance(p, str) else qsharp.Pauli(p)).name + for p in ops + ] + ] + for (pr, ops) in self.operators + ] + } + } + +## PRIVATE FUNCTIONS ## + +literal_keys = frozenset(['ChpDecomposition']) + +def _as_jobj(o, default=lambda x: x): + import numpy as np + if isinstance(o, np.ndarray): + # Use Rust-style arrays. + return { + 'v': 1, + 'dim': list(o.shape), + 'data': o.reshape((-1, )).tolist() + } + elif hasattr(o, '_as_jobj'): + return o._as_jobj() + + return default(o) + +class NoiseModelEncoder(json.JSONEncoder): + def default(self, o: Any) -> Any: + return _as_jobj(o, super().default) + +def dumps(obj: Any) -> str: + """ + Wraps json.dumps with a custom JSONEncoder class to cover types used in + noise model serialization. + """ + return json.dumps( + obj=obj, + cls=NoiseModelEncoder + ) + +def is_rust_style_array(json_obj): + return ( + isinstance(json_obj, dict) and + 'v' in json_obj and + 'data' in json_obj and + 'dim' in json_obj and + json_obj['v'] == 1 + ) + +def rust_style_array_as_array(json_obj, as_complex : bool = True): + import numpy as np + dims = json_obj['dim'] + [2] if as_complex else json_obj['dim'] + arr = np.array(json_obj['data']).reshape(dims) + if as_complex: + arr = arr.astype(complex) + return arr[..., 0] + 1j * arr[..., 1] + else: + return arr + +def convert_to_arrays(json_obj, as_complex : bool = True): + return ( + # Add on a trailing index of length 2. + rust_style_array_as_array(json_obj, as_complex=as_complex) + if is_rust_style_array(json_obj) else + { + key: + value + if key in literal_keys else + + convert_to_arrays(value, as_complex=key != 'table') + if isinstance(value, dict) else + + [convert_to_arrays(element) for element in value] + if isinstance(value, list) else + + value + + for key, value in json_obj.items() + } + ) + +def arr_to_qobj(arr): + import qutip as qt + import numpy as np + return qt.Qobj(arr, dims=[[2] * int(np.log2(arr.shape[1]))] * 2) + +def convert_to_qobjs(json_obj): + import qutip as qt + return ( + arr_to_qobj(json_obj['data']['Mixed']) + if 'data' in json_obj and 'Mixed' in json_obj['data'] else + + arr_to_qobj(json_obj['data']['Unitary']) + if 'data' in json_obj and 'Unitary' in json_obj['data'] else + + qt.kraus_to_super([arr_to_qobj(op) for op in json_obj['data']['KrausDecomposition']]) + if 'data' in json_obj and 'KrausDecomposition' in json_obj['data'] else + + { + key: + value + if key in literal_keys else + + # Recurse if needed... + convert_to_qobjs(value) + if isinstance(value, dict) else + + [convert_to_qobjs(element) for element in value] + if isinstance(value, list) else + + # Just return value if there's nothing else to do. + value + for key, value in json_obj.items() + } + ) + +def arr_to_rust_style(arr): + import numpy as np + return { + 'v': 1, + 'dim': list(arr.shape), + 'data': np.moveaxis(np.array([arr.real, arr.imag]), 0, -1).reshape((-1, 2)).tolist() + } + +def qobj_to_rust_style(qobj, expect_state=False): + import qutip as qt + import numpy as np + data = None + n_qubits = 1 + # Figure out what kind of qobj we have and convert accordingly. + if qobj.type == 'oper': + n_qubits = len(qobj.dims[0]) + data = { + 'Mixed' if expect_state else 'Unitary': + arr_to_rust_style(qobj.data.todense()) + } + elif qobj.type == 'super': + n_qubits = len(qobj.dims[0][0]) + data = { + 'KrausDecomposition': arr_to_rust_style( + np.array([ + op.data.todense() + for op in qt.to_kraus(qobj) + ]) + ) + } + return { + "n_qubits": n_qubits, + "data": data + } + +def convert_to_rust_style(json_obj, expect_state=False): + import qutip as qt + return ( + qobj_to_rust_style(json_obj, expect_state=expect_state) + if isinstance(json_obj, qt.Qobj) else + + list(map(convert_to_rust_style, json_obj)) + if isinstance(json_obj, list) else + + { + key: convert_to_rust_style(value, expect_state=key == 'initial_state') + for key, value in json_obj.items() + } + if isinstance(json_obj, dict) else + + json_obj + ) diff --git a/src/Python/qsharp-core/qsharp/ipython_magic.py b/src/Python/qsharp-core/qsharp/ipython_magic.py new file mode 100644 index 0000000000..7c0e9550b3 --- /dev/null +++ b/src/Python/qsharp-core/qsharp/ipython_magic.py @@ -0,0 +1,38 @@ +#!/bin/env python +# -*- coding: utf-8 -*- +## +# ipython_magic.py: Integration into the IPython notebook environment. +## +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +## + +# NB: This should ONLY be imported from an IPython session. + +import qsharp as qs +from IPython.display import display +from IPython.core.magic import (register_line_magic, register_cell_magic, + register_line_cell_magic, needs_local_scope) + + +def register_magics(): + @register_cell_magic + @needs_local_scope + def qsharp(magic_args, cell, local_ns=None): + """Compiles a Q# snippet, exposing its operations and functions to + the current local scope.""" + callables = qs.compile(cell) + if isinstance(callables, qs.QSharpCallable): + local_ns[callables._name] = callables + else: + for qs_callable in callables: + local_ns[qs_callable.name] = qs_callable + +def register_experimental_magics(): + import qsharp.experimental as exp + + @register_line_magic + def noise_model(line): + args = line.split(' ') + if args[0] == '--set-by-name': + exp.set_noise_model_by_name(args[1]) diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index b234637885..d0d4583e06 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -17,6 +17,7 @@ using Microsoft.Quantum.IQSharp.ExecutionPathTracer; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; +using Microsoft.Quantum.Experimental; #pragma warning disable VSTHRD200 // Use "Async" suffix for async methods @@ -24,6 +25,12 @@ namespace Tests.IQSharp { + internal static class TestExtensions + { + internal static void AssertIsOk(this ExecutionResult response) => + Assert.AreEqual(ExecuteStatus.Ok, response.Status, $"Response was not marked as Ok.\n\tActual status: {response.Status}\n\tResponse output: {response.Output}"); + } + [TestClass] public class IQSharpEngineTests { @@ -86,6 +93,32 @@ public static string SessionAsString(IEnumerable session) => return response.Output?.ToString(); } + public static async Task AssertNoisySimulate(IQSharpEngine engine, string snippetName, NoiseModel? noiseModel, params string[] messages) + { + await engine.Initialized; + var configSource = new ConfigurationSource(skipLoading: true); + var noiseModelSource = new NoiseModelSource(); + if (noiseModel != null) + { + noiseModelSource.NoiseModel = noiseModel; + } + + var simMagic = new SimulateNoiseMagic( + engine, + resolver: engine.SymbolsResolver!, + configurationSource: configSource, + logger: new UnitTestLogger(), + noiseModelSource: noiseModelSource + ); + var channel = new MockChannel(); + var response = await simMagic.Execute(snippetName, channel); + PrintResult(response, channel); + response.AssertIsOk(); + CollectionAssert.AreEqual(messages.Select(ChannelWithNewLines.Format).ToArray(), channel.msgs.ToArray()); + + return response.Output?.ToString(); + } + public static async Task AssertEstimate(IQSharpEngine engine, string snippetName, params string[] messages) { await engine.Initialized; @@ -96,7 +129,7 @@ public static string SessionAsString(IEnumerable session) => var response = await estimateMagic.Execute(snippetName, channel); var result = response.Output as DataTable; PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); Assert.IsNotNull(result); Assert.AreEqual(9, result?.Rows.Count); var keys = result?.Rows.Cast().Select(row => row.ItemArray[0]).ToList(); @@ -124,16 +157,16 @@ private async Task AssertTrace(string name, ExecutionPath expectedPath, int expe // Add dependencies: var response = await pkgMagic.Execute("mock.standard", channel); PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); // Reload workspace: response = await wsMagic.Execute("reload", channel); PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); response = await traceMagic.Execute(name, channel); PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); var message = channel.iopubMessages.ElementAtOrDefault(0); Assert.IsNotNull(message); @@ -245,6 +278,50 @@ public async Task Estimate() await AssertEstimate(engine, "HelloQ"); } + [TestMethod] + [TestCategory("Experimental")] + public async Task NoisySimulateWithTwoQubitOperation() + { + var engine = await Init(); + var channel = new MockChannel(); + + // Compile it: + await AssertCompile(engine, SNIPPETS.SimpleDebugOperation, "SimpleDebugOperation"); + + // Try running again: + // Note that noiseModel: null sets the noise model to be ideal. + await AssertNoisySimulate(engine, "SimpleDebugOperation", noiseModel: null); + } + + [TestMethod] + [TestCategory("Experimental")] + public async Task NoisySimulateWithFailIfOne() + { + var engine = await Init(); + var channel = new MockChannel(); + + // Compile it: + await AssertCompile(engine, SNIPPETS.FailIfOne, "FailIfOne"); + + // Try running again: + // Note that noiseModel: null sets the noise model to be ideal. + await AssertNoisySimulate(engine, "FailIfOne", noiseModel: null); + } + + [TestMethod] + [TestCategory("Experimental")] + public async Task NoisySimulateWithTrivialOperation() + { + var engine = await Init(); + var channel = new MockChannel(); + + // Compile it: + await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); + + // Try running again: + await AssertNoisySimulate(engine, "HelloQ", noiseModel: null, "Hello from quantum world!"); + } + [TestMethod] public async Task Toffoli() { @@ -260,7 +337,7 @@ public async Task Toffoli() var response = await toffoliMagic.Execute("HelloQ", channel); var result = response.Output as Dictionary; PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); Assert.AreEqual(1, channel.msgs.Count); Assert.AreEqual(ChannelWithNewLines.Format("Hello from quantum world!"), channel.msgs[0]); } @@ -351,7 +428,7 @@ public async Task ReportWarnings() var channel = new MockChannel(); var response = await engine.ExecuteMundane(SNIPPETS.ThreeWarnings, channel); PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); Assert.AreEqual(3, channel.msgs.Count); Assert.AreEqual(0, channel.errors.Count); Assert.AreEqual("ThreeWarnings", @@ -363,7 +440,7 @@ public async Task ReportWarnings() var channel = new MockChannel(); var response = await engine.ExecuteMundane(SNIPPETS.OneWarning, channel); PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); Assert.AreEqual(1, channel.msgs.Count); Assert.AreEqual(0, channel.errors.Count); Assert.AreEqual("OneWarning", @@ -399,7 +476,7 @@ public async Task TestPackages() var response = await pkgMagic.Execute("", channel); var result = response.Output as string[]; PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); Assert.AreEqual(0, channel.msgs.Count); Assert.AreEqual(packageCount, result?.Length); Assert.AreEqual("Microsoft.Quantum.Standard::0.0.0", result?[0]); @@ -414,7 +491,7 @@ public async Task TestPackages() response = await pkgMagic.Execute("mock.chemistry", channel); result = response.Output as string[]; PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); Assert.AreEqual(0, channel.msgs.Count); Assert.IsNotNull(result); Assert.AreEqual(packageCount + 1, result?.Length); @@ -451,7 +528,7 @@ public async Task TestProjectMagic() var channel = new MockChannel(); var response = await projectMagic.Execute("../Workspace.ProjectReferences/Workspace.ProjectReferences.csproj", channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); var loadedProjectFiles = response.Output as string[]; Assert.AreEqual(3, loadedProjectFiles?.Length); } @@ -470,7 +547,7 @@ public async Task TestWho() var response = await whoMagic.Execute("", channel); var result = response.Output as string[]; PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); Assert.AreEqual(6, result?.Length); Assert.AreEqual("HelloQ", result?[0]); Assert.AreEqual("Tests.qss.NoOp", result?[4]); @@ -507,20 +584,20 @@ public async Task TestWorkspace() // Add dependencies: response = await pkgMagic.Execute("mock.chemistry", channel); PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); response = await pkgMagic.Execute("mock.research", channel); PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); // Reload workspace: response = await wsMagic.Execute("reload", channel); PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); response = await wsMagic.Execute("", channel); result = response.Output as string[]; PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); Assert.AreEqual(2, result?.Length); // Compilation must work: @@ -534,7 +611,7 @@ public async Task TestWorkspace() // Check that everything still works: response = await wsMagic.Execute("", channel); PrintResult(response, channel); - Assert.AreEqual(ExecuteStatus.Ok, response.Status); + response.AssertIsOk(); } [TestMethod] diff --git a/src/Tests/SNIPPETS.cs b/src/Tests/SNIPPETS.cs index e2e09d3689..1c7254c315 100644 --- a/src/Tests/SNIPPETS.cs +++ b/src/Tests/SNIPPETS.cs @@ -158,6 +158,15 @@ operation SimpleDebugOperation() : (Result, Result) } "; + public static string FailIfOne = + @" + operation FailIfOne() : Unit { + use q = Qubit(); + if M(q) == One { + fail ""Expected measuring a freshly allocated qubit to return Zero, but returned One.""; + } + } +"; public static string ApplyWithinBlock = @" /// # Summary diff --git a/src/Tests/Workspace.ProjectReferences.ProjectA/ProjectA.csproj b/src/Tests/Workspace.ProjectReferences.ProjectA/ProjectA.csproj index 766b532a17..d992114bd1 100644 --- a/src/Tests/Workspace.ProjectReferences.ProjectA/ProjectA.csproj +++ b/src/Tests/Workspace.ProjectReferences.ProjectA/ProjectA.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 diff --git a/src/Tests/Workspace.ProjectReferences.ProjectB/ProjectB.csproj b/src/Tests/Workspace.ProjectReferences.ProjectB/ProjectB.csproj index 11d17ff86d..831bef2cd2 100644 --- a/src/Tests/Workspace.ProjectReferences.ProjectB/ProjectB.csproj +++ b/src/Tests/Workspace.ProjectReferences.ProjectB/ProjectB.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 diff --git a/src/Tests/Workspace.ProjectReferences/Workspace.ProjectReferences.csproj b/src/Tests/Workspace.ProjectReferences/Workspace.ProjectReferences.csproj index 6bfe521283..e736eff582 100644 --- a/src/Tests/Workspace.ProjectReferences/Workspace.ProjectReferences.csproj +++ b/src/Tests/Workspace.ProjectReferences/Workspace.ProjectReferences.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 @@ -7,7 +7,7 @@ - + diff --git a/src/Tool/Telemetry.cs b/src/Tool/Telemetry.cs index 6ff432eafa..30ea18dea3 100644 --- a/src/Tool/Telemetry.cs +++ b/src/Tool/Telemetry.cs @@ -15,6 +15,7 @@ using Microsoft.Quantum.Simulation.Simulators; using static Microsoft.Jupyter.Core.BaseEngine; using Microsoft.Quantum.IQSharp.Kernel; +using System.Collections.Generic; namespace Microsoft.Quantum.IQSharp { @@ -50,6 +51,20 @@ public TelemetryService( LogManager.Teardown(); }; + eventService.Events().On += (content) => + { + var evt = "ExperimentalFeatureEnabled".AsTelemetryEvent(); + evt.SetProperty( + "FeatureName".WithTelemetryNamespace(), + content.FeatureName + ); + evt.SetProperty( + "OptionalDependencies".WithTelemetryNamespace(), + string.Join(",", content.OptionalDependencies ?? new List()) + ); + TelemetryLogger.LogEvent(evt); + }; + eventService.OnServiceInitialized().On += (metadataController) => metadataController.MetadataChanged += (metadataController, propertyChanged) => SetSharedContextIfChanged(metadataController, propertyChanged, diff --git a/src/Tool/appsettings.json b/src/Tool/appsettings.json index 17643dcffc..cdbb71082f 100644 --- a/src/Tool/appsettings.json +++ b/src/Tool/appsettings.json @@ -6,26 +6,26 @@ }, "AllowedHosts": "*", "DefaultPackageVersions": [ - "Microsoft.Quantum.Compiler::0.16.2105140472", + "Microsoft.Quantum.Compiler::0.17.210627752-alpha", - "Microsoft.Quantum.CSharpGeneration::0.16.2105140472", - "Microsoft.Quantum.Development.Kit::0.16.2105140472", - "Microsoft.Quantum.Simulators::0.16.2105140472", - "Microsoft.Quantum.Xunit::0.16.2105140472", + "Microsoft.Quantum.CSharpGeneration::0.17.210627752-alpha", + "Microsoft.Quantum.Development.Kit::0.17.210627752-alpha", + "Microsoft.Quantum.Simulators::0.17.210627752-alpha", + "Microsoft.Quantum.Xunit::0.17.210627752-alpha", - "Microsoft.Quantum.Standard::0.16.2105140472", - "Microsoft.Quantum.Standard.Visualization::0.16.2105140472", - "Microsoft.Quantum.Chemistry::0.16.2105140472", - "Microsoft.Quantum.Chemistry.Jupyter::0.16.2105140472", - "Microsoft.Quantum.MachineLearning::0.16.2105140472", - "Microsoft.Quantum.Numerics::0.16.2105140472", + "Microsoft.Quantum.Standard::0.17.210627752-alpha", + "Microsoft.Quantum.Standard.Visualization::0.17.210627752-alpha", + "Microsoft.Quantum.Chemistry::0.17.210627752-alpha", + "Microsoft.Quantum.Chemistry.Jupyter::0.17.210627752-alpha", + "Microsoft.Quantum.MachineLearning::0.17.210627752-alpha", + "Microsoft.Quantum.Numerics::0.17.210627752-alpha", - "Microsoft.Quantum.Katas::0.16.2105140472", + "Microsoft.Quantum.Katas::0.17.210627752-alpha", - "Microsoft.Quantum.Research::0.16.2105140472", + "Microsoft.Quantum.Research::0.17.210627752-alpha", - "Microsoft.Quantum.Providers.IonQ::0.16.2105140472", - "Microsoft.Quantum.Providers.Honeywell::0.16.2105140472", - "Microsoft.Quantum.Providers.QCI::0.16.2105140472" + "Microsoft.Quantum.Providers.IonQ::0.17.210627752-alpha", + "Microsoft.Quantum.Providers.Honeywell::0.17.210627752-alpha", + "Microsoft.Quantum.Providers.QCI::0.17.210627752-alpha" ] }