Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions src/Jupyter/Magic/AbstractNativeSimulateMagic.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
public abstract class AbstractNativeSimulateMagic : AbstractMagic
{
private const string ParameterNameOperationName = "__operationName__";
private readonly IPerformanceMonitor Monitor;

/// <summary>
/// Constructs a new magic command given a resolver used to find
/// operations and functions, and a configuration source used to set
/// configuration options.
/// </summary>
public AbstractNativeSimulateMagic(string keyword, Documentation docs, ISymbolResolver resolver,
IConfigurationSource configurationSource, IPerformanceMonitor monitor,
ILogger<AbstractNativeSimulateMagic> logger)
: base(keyword, docs, logger)
{
this.SymbolResolver = resolver;
this.ConfigurationSource = configurationSource;
this.Monitor = monitor;
}

/// <summary>
/// The symbol resolver used by this magic command to find
/// operations or functions to be simulated.
/// </summary>
public ISymbolResolver SymbolResolver { get; }

/// <summary>
/// The configuration source used by this magic command to control
/// simulation options (e.g.: dump formatting options).
/// </summary>
public IConfigurationSource ConfigurationSource { get; }

/// <inheritdoc />
public override ExecutionResult Run(string input, IChannel channel) =>
RunAsync(input, channel).Result;

internal abstract CommonNativeSimulator CreateNativeSimulator();

/// <summary>
/// Simulates an operation given a string with its name and a JSON
/// encoding of its arguments.
/// </summary>
public async Task<ExecutionResult> RunAsync(string input, IChannel channel)
{
var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName);

var name = inputParameters.DecodeParameter<string>(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;
}
}
}
95 changes: 3 additions & 92 deletions src/Jupyter/Magic/Simulate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public class SimulateMagic : AbstractMagic
public class SimulateMagic : AbstractNativeSimulateMagic
{
private const string ParameterNameOperationName = "__operationName__";
private readonly IPerformanceMonitor Monitor;

/// <summary>
/// Constructs a new magic command given a resolver used to find
/// operations and functions, and a configuration source used to set
Expand Down Expand Up @@ -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;
}

/// <summary>
/// The symbol resolver used by this magic command to find
/// operations or functions to be simulated.
/// </summary>
public ISymbolResolver SymbolResolver { get; }

/// <summary>
/// The configuration source used by this magic command to control
/// simulation options (e.g.: dump formatting options).
/// </summary>
public IConfigurationSource ConfigurationSource { get; }

/// <inheritdoc />
public override ExecutionResult Run(string input, IChannel channel) =>
RunAsync(input, channel).Result;

/// <summary>
/// Simulates an operation given a string with its name and a JSON
/// encoding of its arguments.
/// </summary>
public async Task<ExecutionResult> RunAsync(string input, IChannel channel)
{
var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName);

var name = inputParameters.DecodeParameter<string>(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();
}
}
65 changes: 65 additions & 0 deletions src/Jupyter/Magic/SimulateSparse.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// A magic command that can be used to simulate operations and functions
/// on a sparse simulator.
/// </summary>
public class SimulateSparseMagic : AbstractNativeSimulateMagic
{
/// <summary>
/// Constructs a new magic command given a resolver used to find
/// operations and functions, and a configuration source used to set
/// configuration options.
/// </summary>
public SimulateSparseMagic(ISymbolResolver resolver, IConfigurationSource configurationSource, IPerformanceMonitor monitor, ILogger<SimulateSparseMagic> 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[]: <return value of the operation>
```
".Dedent(),
@"
Simulate a Q# operation defined as `operation MyOperation(a : Int, b : Int) : Result`:
```
In []: %simulate_sparse MyOperation a=5 b=10
Out[]: <return value of the operation>
```
".Dedent(),
}
}, resolver, configurationSource, monitor, logger)
{
}

internal override CommonNativeSimulator CreateNativeSimulator() => new SparseSimulator();
}
}
4 changes: 4 additions & 0 deletions src/Python/qsharp-core/qsharp/clients/iqsharp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also add a magic command simulate_fullstate that resolves to the quantum simulator? The simulate command for now can stay as it is, but it would be good to have the option to treat it as something that resolves to a default choice later on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not, but instead would want to find the right way to add a keyword argument to .simulate itself for the simulator and remove all of the other method calls.

For using Q# notebooks directly, we can use --simulator as a parameter name to disambiguate the simulator choice from inputs to the Q# entry point itself, but for Python we need to do something different to disambiguate. For example, we could consider def simulate(self, op, __simulator=None, **kwargs) since the __ prefix is reserved in Q# and cannot be listed as an entry point input. Using that, the full-state simulator would be usable as RunFoo.simulate("full-state", x=3) and the sparse simulator would be usable as RunFoo.simulate("sparse", x=3). That would also allow custom third-party simulators to be added more easily without modifying existing APIs, reducing the challenges run into by https://github.com/qsharp-community/chp-sim. That would also make it easier to add a new config setting qsharp.config['simulator.default'] that users could use to work with default simulator preferences.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am open to how exactly the commands should look like. I believe the following points would be important:

  • We should make room for having a command where the user doesn't explicitly specify the simulator but we instead make a default choice for them (mirroring how standalone executables also work).
  • The command that is currently most widely used (%simulate) should resolve to our default choice of simulator.
  • If possible it would be great to align all commands such that we don't need to do any changes to iqsharp to enable a new simulator; ideally, that would allow also third party simulators to be used with iqsharp, but it is already and improvement if all simulator in the Microsoft.Quantum.Simulators package are automatically available via iqsharp.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • If possible it would be great to align all commands such that we don't need to do any changes to iqsharp to enable a new simulator; ideally, that would allow also third party simulators to be used with iqsharp, but it is already and improvement if all simulator in the Microsoft.Quantum.Simulators package are automatically available via iqsharp.

In earlier runtime versions, individual simulators needed much more customization than is currently needed, such that this is much more plausible than before. In particular, much of the sparse simulator work resulted in a significant reduction of code volume in IQ# by removing the override that the %simulate command used to need in order to provide visualizations for the full-state simulator.

That said, even with the previous approach of a new command for each simulator, third-party simulators such as https://github.com/qsharp-community/chp-sim were able to use IQ#'s functionality for dynamic loading of new magic commands to add new simulators. For example, using the chp-sim package, a user could run %package QSharpCommunity.Simulators.Chp, at which point IQ# automatically discovered the new simulator command, added it to documentation in the %lsmagic command, tab completion, and so forth. I want to make sure that as we consolidate simulator logic into a single command, and as we make it easier to enable third party simulators, we also maintain the current user experience, wherein dynamically loaded simulators are easily discoverable at runtime. Perhaps something like %simulate --list-available to list currently known simulators?

Copy link
Contributor

@bettinaheim bettinaheim Feb 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to make a complete list for how all simulator related magic commands could look like? Also, I didn't fully realize that the functionality to discover available simulator was added. Given that, what is the reason for wanting to revise the current API further?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I didn't fully realize that the functionality to discover available simulator was added.

It is, but only through having a proliferation of distinct magic commands. Consolidating the API into a single magic command with an argument for different simulators is much more maintainable and I'd argue usable as well. Deferring past this PR for now, in any case.

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)
Expand Down
7 changes: 7 additions & 0 deletions src/Python/qsharp-core/qsharp/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion src/Tests/IQsharpEngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down