diff --git a/src/Jupyter/Magic/AbstractNativeSimulateMagic.cs b/src/Jupyter/Magic/AbstractNativeSimulateMagic.cs new file mode 100644 index 0000000000..cee7d6c8d0 --- /dev/null +++ b/src/Jupyter/Magic/AbstractNativeSimulateMagic.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Jupyter.Core; +using Microsoft.Quantum.IQSharp.Common; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace Microsoft.Quantum.IQSharp.Jupyter +{ + /// + /// Abstract class for magic commands that can be used to simulate + /// operations and functions on a full-state quantum simulator, using + /// a common C API. + /// + public abstract class AbstractNativeSimulateMagic : AbstractMagic + { + private const string ParameterNameOperationName = "__operationName__"; + private readonly IPerformanceMonitor Monitor; + + /// + /// Constructs a new magic command given a resolver used to find + /// operations and functions, and a configuration source used to set + /// configuration options. + /// + public AbstractNativeSimulateMagic(string keyword, Documentation docs, ISymbolResolver resolver, + IConfigurationSource configurationSource, IPerformanceMonitor monitor, + ILogger logger) + : base(keyword, docs, logger) + { + this.SymbolResolver = resolver; + this.ConfigurationSource = configurationSource; + this.Monitor = monitor; + } + + /// + /// 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; + + internal abstract CommonNativeSimulator CreateNativeSimulator(); + + /// + /// 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 IQSharpSymbol; + if (symbol == null) throw new InvalidOperationException($"Invalid operation name: {name}"); + + var maxNQubits = 0L; + + using var qsim = CreateNativeSimulator() + .WithStackTraceDisplay(channel); + + qsim.DisableLogToConsole(); + qsim.OnLog += channel.Stdout; + + qsim.OnDisplayableDiagnostic += (displayable) => + { + if (displayable is CommonNativeSimulator.DisplayableState state && ConfigurationSource.MeasurementDisplayHistogram) + { + // Make sure to display the state first so that it's there for the client-side + // JavaScript to pick up. + var id = $"{System.Guid.NewGuid()}"; + channel.Display(new DisplayableStateWithId + { + Amplitudes = state.Amplitudes, + NQubits = state.NQubits, + QubitIds = state.QubitIds, + Id = id + }); + + // Tell the client to add a histogram using chart.js. + var commsRouter = channel.GetCommsRouter(); + Debug.Assert(commsRouter != null, "Histogram display requires comms router."); + commsRouter.OpenSession( + "iqsharp_state_dump", + new MeasurementHistogramContent() + { + State = state, + Id = id + } + ).Wait(); + } + else + { + channel.Display(displayable); + } + }; + + qsim.AfterAllocateQubits += (args) => + { + maxNQubits = System.Math.Max(qsim.QubitManager?.AllocatedQubitsCount ?? 0, maxNQubits); + }; + var stopwatch = Stopwatch.StartNew(); + var value = await symbol.Operation.RunAsync(qsim, inputParameters); + stopwatch.Stop(); + var result = value.ToExecutionResult(); + (Monitor as PerformanceMonitor)?.ReportSimulatorPerformance(new SimulatorPerformanceArgs( + simulatorName: qsim.GetType().FullName, + nQubits: (int)maxNQubits, + duration: stopwatch.Elapsed + )); + return result; + } + } +} diff --git a/src/Jupyter/Magic/Simulate.cs b/src/Jupyter/Magic/Simulate.cs index 3e9f9e5bf6..b43fef5ce8 100644 --- a/src/Jupyter/Magic/Simulate.cs +++ b/src/Jupyter/Magic/Simulate.cs @@ -16,11 +16,8 @@ namespace Microsoft.Quantum.IQSharp.Jupyter /// A magic command that can be used to simulate operations and functions /// on a full-state quantum simulator. /// - public class SimulateMagic : AbstractMagic + public class SimulateMagic : AbstractNativeSimulateMagic { - private const string ParameterNameOperationName = "__operationName__"; - private readonly IPerformanceMonitor Monitor; - /// /// Constructs a new magic command given a resolver used to find /// operations and functions, and a configuration source used to set @@ -61,96 +58,10 @@ or function name that has been defined either in the notebook or in a Q# file in ``` ".Dedent(), } - }, logger) + }, resolver, configurationSource, monitor, logger) { - this.SymbolResolver = resolver; - this.ConfigurationSource = configurationSource; - this.Monitor = monitor; } - /// - /// 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 IQSharpSymbol; - if (symbol == null) throw new InvalidOperationException($"Invalid operation name: {name}"); - - var maxNQubits = 0L; - - using var qsim = new QuantumSimulator() - .WithStackTraceDisplay(channel); - - qsim.DisableLogToConsole(); - qsim.OnLog += channel.Stdout; - - qsim.OnDisplayableDiagnostic += (displayable) => - { - if (displayable is CommonNativeSimulator.DisplayableState state && ConfigurationSource.MeasurementDisplayHistogram) - { - // Make sure to display the state first so that it's there for the client-side - // JavaScript to pick up. - var id = $"{System.Guid.NewGuid()}"; - channel.Display(new DisplayableStateWithId - { - Amplitudes = state.Amplitudes, - NQubits = state.NQubits, - QubitIds = state.QubitIds, - Id = id - }); - - // Tell the client to add a histogram using chart.js. - var commsRouter = channel.GetCommsRouter(); - Debug.Assert(commsRouter != null, "Histogram display requires comms router."); - commsRouter.OpenSession( - "iqsharp_state_dump", - new MeasurementHistogramContent() - { - State = state, - Id = id - } - ).Wait(); - } - else - { - channel.Display(displayable); - } - }; - - qsim.AfterAllocateQubits += (args) => - { - maxNQubits = System.Math.Max(qsim.QubitManager?.AllocatedQubitsCount ?? 0, maxNQubits); - }; - var stopwatch = Stopwatch.StartNew(); - var value = await symbol.Operation.RunAsync(qsim, inputParameters); - stopwatch.Stop(); - var result = value.ToExecutionResult(); - (Monitor as PerformanceMonitor)?.ReportSimulatorPerformance(new SimulatorPerformanceArgs( - simulatorName: qsim.GetType().FullName, - nQubits: (int)maxNQubits, - duration: stopwatch.Elapsed - )); - return result; - } + internal override CommonNativeSimulator CreateNativeSimulator() => new QuantumSimulator(); } } diff --git a/src/Jupyter/Magic/SimulateSparse.cs b/src/Jupyter/Magic/SimulateSparse.cs new file mode 100644 index 0000000000..572510753a --- /dev/null +++ b/src/Jupyter/Magic/SimulateSparse.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Jupyter.Core; +using Microsoft.Quantum.IQSharp.Common; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace Microsoft.Quantum.IQSharp.Jupyter +{ + /// + /// A magic command that can be used to simulate operations and functions + /// on a sparse simulator. + /// + public class SimulateSparseMagic : AbstractNativeSimulateMagic + { + /// + /// Constructs a new magic command given a resolver used to find + /// operations and functions, and a configuration source used to set + /// configuration options. + /// + public SimulateSparseMagic(ISymbolResolver resolver, IConfigurationSource configurationSource, IPerformanceMonitor monitor, ILogger logger) : base( + "simulate_sparse", + new Microsoft.Jupyter.Core.Documentation + { + Summary = "Runs a given function or operation on the sparse simulator.", + Description = @" + This magic command allows executing a given function or operation on the sparse simulator, + which performs a sparse simulation of the given function or operation + and prints the resulting return value. + + #### 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. + ".Dedent(), + Examples = new [] + { + @" + Simulate a Q# operation defined as `operation MyOperation() : Result`: + ``` + In []: %simulate_sparse MyOperation + Out[]: + ``` + ".Dedent(), + @" + Simulate a Q# operation defined as `operation MyOperation(a : Int, b : Int) : Result`: + ``` + In []: %simulate_sparse MyOperation a=5 b=10 + Out[]: + ``` + ".Dedent(), + } + }, resolver, configurationSource, monitor, logger) + { + } + + internal override CommonNativeSimulator CreateNativeSimulator() => new SparseSimulator(); + } +} diff --git a/src/Python/qsharp-core/qsharp/clients/iqsharp.py b/src/Python/qsharp-core/qsharp/clients/iqsharp.py index 173b2f1b39..465b8718a9 100644 --- a/src/Python/qsharp-core/qsharp/clients/iqsharp.py +++ b/src/Python/qsharp-core/qsharp/clients/iqsharp.py @@ -173,6 +173,10 @@ def simulate(self, op, **kwargs) -> Any: kwargs.setdefault('_timeout_', None) return self._execute_callable_magic('simulate', op, **kwargs) + def simulate_sparse(self, op, **kwargs) -> Any: + kwargs.setdefault('_timeout_', None) + return self._execute_callable_magic('simulate_sparse', op, **kwargs) + def toffoli_simulate(self, op, **kwargs) -> Any: kwargs.setdefault('_timeout_', None) return self._execute_callable_magic('toffoli', op, **kwargs) diff --git a/src/Python/qsharp-core/qsharp/loader.py b/src/Python/qsharp-core/qsharp/loader.py index 13a71d4713..f136cbbdd2 100644 --- a/src/Python/qsharp-core/qsharp/loader.py +++ b/src/Python/qsharp-core/qsharp/loader.py @@ -90,6 +90,13 @@ def simulate(self, **kwargs) -> Any: """ return qsharp.client.simulate(self, **kwargs) + def simulate_sparse(self, **kwargs) -> Any: + """ + Executes this function or operation on the sparse simulator, returning + its output as a Python object. + """ + return qsharp.client.simulate_sparse(self, **kwargs) + def toffoli_simulate(self, **kwargs) -> Any: """ Executes this function or operation on the ToffoliSimulator target diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index dc8843a809..fac83224ab 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -183,7 +183,10 @@ public async Task CompleteMagic() => await Assert.That .UsingEngine() .Input("%sim", 3) - .CompletesTo("%simulate") + .CompletesTo( + "%simulate", + "%simulate_sparse" + ) .Input("%experimental.", 14) .CompletesTo( "%experimental.build_info",