Skip to content

Commit

Permalink
Add a Q# quantum state visualizer (microsoft#221)
Browse files Browse the repository at this point in the history
* Import visual debugger sample

Co-authored-by: Chris Granade <[email protected]>

* Fix colors in Edge

* Use OnOperationStart and OnOperationEnd

* Show operation functor names

* Wait for input before starting first operation

* Pass the simulator to the debugger constructor

* Show operations as a nested call stack

* Fix state chart labels

* Always show y-axis between -1 and 1

* Re-send history when new client connects

* Make operation call stack look a little nicer

* Don't show unit return values

* Update operation stack font and indent guides

* Use 2-column layout for operations and chart

* Remove flexbox to fix chart resizing issues

* Show arrow pointing to the current operation

* Move VisualDebugger.cs to top folder

* Allow going backward in time

* Show different icons for next op/op just finished

* Fix padding at bottom of operation list

* Add Grover's search example

* Clean up onOperationStarted handler

* Use SignalR hub method for advancing simulator

* Rename VisualizationHub to match VisualDebugger

* Move state dumper to VisualDebugger.cs

* Decrease class/method visibility when possible

* Fix emoji arrow for next operation

* Make operation list wider, allow horizontal scroll

* Simplify functor to string conversion

* Click an operation to jump to that state

* Skip operation end events

* Add step in and step over buttons

* Fix right padding in operations list

* Don't wait for advance before operation starts

* Don't wait for next event if all ops have finished

* Auto-scroll to the current operation

* Fix scrolling when the last operation ends

* Simplify AllOnesPhaseOracle

* Try rebuild

* Update visual debugger namespace

* Add copyright notices

* Code style changes for VisualDebugger.cs

* Replace tabs with spaces

* Update RootNamespace in csproj

* Create a watch script for npm

* Update README

* Add note to restart after changing the Q# program

* Listen for qubit allocation/release events

* Don't create new StateDumper every time

* Add descriptions for classes

* Add note to install Node.js and .NET Core SDK

* Rename VisualDebugger to StateVisualizer

* Update state before each allocation/release

* Fix "step over" for operations containing allocate/release

* Stop simulator when Ctrl+C is pressed

* Finish renaming to StateVisualizer

* Hide scrollbar for html/body elements

Co-Authored-By: Andres Paz <[email protected]>

* Update package name in package-lock.json

Co-Authored-By: Andres Paz <[email protected]>

* Rename more references to the visual debugger

Co-Authored-By: Andres Paz <[email protected]>

* More renaming

* rebuild?
  • Loading branch information
bamarsha authored and anpaz committed Sep 13, 2019
1 parent feed9bf commit f0bb12f
Show file tree
Hide file tree
Showing 17 changed files with 6,284 additions and 0 deletions.
1 change: 1 addition & 0 deletions Samples/src/StateVisualizer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
wwwroot
30 changes: 30 additions & 0 deletions Samples/src/StateVisualizer/Hubs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

namespace Microsoft.Quantum.Samples.StateVisualizer
{
/// <summary>
/// The hub recives receives events and commands from the web browser client and sends them to the state visualizer
/// server.
/// </summary>
internal class StateVisualizerHub : Hub
{
private readonly StateVisualizer visualizer;

public StateVisualizerHub(StateVisualizer visualizer)
{
this.visualizer = visualizer;
}

public override async Task OnConnectedAsync()
{
await visualizer.ReplayHistory(Clients.Caller);
await base.OnConnectedAsync();
}

public bool Advance() => visualizer.Advance();
}
}
29 changes: 29 additions & 0 deletions Samples/src/StateVisualizer/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading.Tasks;
using Microsoft.Quantum.Simulation.Simulators;

namespace Microsoft.Quantum.Samples.StateVisualizer
{
/// <summary>
/// Runs the Q# state visualizer. See the README for more information, including instructions on how to build and
/// run this program.
/// </summary>
internal class Program
{
private static void Main(string[] args)
{
var visualizer = new StateVisualizer(new QuantumSimulator());
try
{
visualizer.Run(QsMain.Run).Wait();
}
catch (AggregateException aggregate)
{
aggregate.Flatten().Handle(ex => ex is TaskCanceledException);
}
}
}
}
63 changes: 63 additions & 0 deletions Samples/src/StateVisualizer/Program.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Quantum.Samples.StateVisualizer {
open Microsoft.Quantum.Canon;
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Intrinsic;
open Microsoft.Quantum.Math;
open Microsoft.Quantum.Measurement;

operation QsMain () : Unit {
Teleport();
GroverSearch();
}

// Teleportation

operation Teleport () : Unit {
using ((msg, here, there) = (Qubit(), Qubit(), Qubit())) {
H(msg);

H(here);
CNOT(here, there);

CNOT(msg, here);
H(msg);

if (MResetZ(msg) == One) { Z(there); }
if (MResetZ(here) == One) { X(there); }
H(there);
}
}

// Grover's search
// Based on the Grover's algorithm kata
// https://github.com/microsoft/QuantumKatas/tree/master/GroversAlgorithm

operation AllOnesPhaseOracle (register : Qubit[]) : Unit {
Controlled Z(register[1...], register[0]);
}

operation AllZeroesPhaseOracle (register : Qubit[]) : Unit {
ApplyWith(ApplyToEachA(X, _), AllOnesPhaseOracle, register);
}

operation GroverIteration (register : Qubit[], oracle : (Qubit[] => Unit)) : Unit {
oracle(register);
ApplyToEach(H, register);
AllZeroesPhaseOracle(register);
ApplyToEach(H, register);
}

operation GroverSearch () : Unit {
let n = 3;
using (register = Qubit[n]) {
ApplyToEach(H, register);
for (i in 1 .. Floor(Sqrt(PowD(2.0, IntAsDouble(n))))) {
GroverIteration(register, AllOnesPhaseOracle);
}
ResetAll(register);
}
}
}
41 changes: 41 additions & 0 deletions Samples/src/StateVisualizer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Q# State Visualizer #

This sample lets you interactively step through the execution of a Q# program.
It shows the tree of operation calls and a visualization of the quantum state
after each operation. You can also go back to previous states by clicking the
"Previous" button or by clicking on a previous operation in the list.

Note that since this sample relies on the quantum simulator for information
about the program execution, it can only step through quantum operations, not
classical functions.

## Running the Sample ##

Install [Node.js](https://nodejs.org/en/) and the
[.NET Core SDK](https://dotnet.microsoft.com/download) if you do not already
have them installed.

Then install the dependencies and build the TypeScript component:

```
npm install
npm run release
```

Finally, start the dotnet host application:

```
dotnet run
```

This will launch a web server running the state visualizer. Open
http://localhost:5000 in a web browser to use it.

## Editing the Q# Program ##

To change the Q# program that is executed by the state visualizer, edit the
`Program.qs` file. The visualizer will start the program by running the `QsMain`
operation.

Restart the visualizer by running the `dotnet run` command again to see the new
program.
35 changes: 35 additions & 0 deletions Samples/src/StateVisualizer/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Quantum.Samples.StateVisualizer
{
/// <summary>
/// Configures the ASP.NET Core web host. This class is used when the web host is created in
/// <see cref="StateVisualizer"/>.
/// </summary>
internal class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddMvc();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IApplicationLifetime lifetime, StateVisualizer visualizer)
{
app
.UseDefaultFiles()
.UseStaticFiles()
.UseDeveloperExceptionPage()
.UseMvc()
.UseSignalR(routes => routes.MapHub<StateVisualizerHub>("/events"));
lifetime.ApplicationStopping.Register(visualizer.Stop);
}
}
}
179 changes: 179 additions & 0 deletions Samples/src/StateVisualizer/StateVisualizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Quantum.Simulation.Core;
using Microsoft.Quantum.Simulation.Simulators;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Quantum.Samples.StateVisualizer
{
internal class StateVisualizer
{
private readonly QuantumSimulator simulator;
private readonly StateDumper stateDumper;
private readonly IWebHost host;
private readonly IHubContext<StateVisualizerHub> context;
private readonly ManualResetEvent advanceEvent = new ManualResetEvent(true);
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly IList<(string method, object[] args)> history = new List<(string, object[])>();

public StateVisualizer(QuantumSimulator simulator)
{
if (simulator == null)
{
throw new ArgumentNullException(nameof(simulator));
}

this.simulator = simulator;
simulator.OnOperationStart += OnOperationStartHandler;
simulator.OnOperationEnd += OnOperationEndHandler;
simulator.OnAllocateQubits += OnAllocateQubitsHandler;
simulator.OnBorrowQubits += OnBorrowQubitsHandler;
simulator.OnReleaseQubits += OnReleaseQubitsHandler;
simulator.OnReturnQubits += OnReturnQubitsHandler;
stateDumper = new StateDumper(simulator);

host = WebHost
.CreateDefaultBuilder()
.UseStartup<Startup>()
.ConfigureServices(services =>
{
// Register ourselves as a service so that the different
// hubs and controllers can use us through DI.
services.AddSingleton(typeof(StateVisualizer), this);
})
.UseUrls("http://localhost:5000")
.UseKestrel()
.Build();
new Thread(host.Run).Start();
context = GetService<IHubContext<StateVisualizerHub>>();
}

public async Task Run(Func<IOperationFactory, Task<QVoid>> operation)
{
await operation(simulator);
}

public bool Advance() => advanceEvent.Set();

public void Stop()
{
cancellationTokenSource.Cancel();
advanceEvent.Set();
}

public async Task ReplayHistory(IClientProxy client)
{
foreach (var (method, args) in history)
{
await client.SendCoreAsync(method, args);
}
}

private T GetService<T>() =>
(T) host.Services.GetService(typeof(T));

private async Task BroadcastAsync(string method, params object[] args)
{
history.Add((method, args));
await context.Clients.All.SendCoreAsync(method, args);
}

private async Task WaitForAdvance() =>
await Task.Run(() =>
{
advanceEvent.Reset();
advanceEvent.WaitOne();
}, cancellationTokenSource.Token);

private void OnOperationStartHandler(ICallable operation, IApplyData arguments)
{
var variant = operation.Variant == OperationFunctor.Body ? "" : operation.Variant.ToString();
var qubits = arguments.Qubits?.Select(q => q.Id).ToArray() ?? Array.Empty<int>();
BroadcastAsync(
"OperationStarted",
$"{variant} {operation.Name}",
qubits,
stateDumper.DumpAndGetAmplitudes()
).Wait();
WaitForAdvance().Wait();
}

private void OnOperationEndHandler(ICallable operation, IApplyData result)
{
BroadcastAsync("OperationEnded", result?.Value, stateDumper.DumpAndGetAmplitudes()).Wait();
WaitForAdvance().Wait();
}

private void OnAllocateQubitsHandler(long count)
{
BroadcastAsync("Log", $"Allocate {count} qubit(s)", stateDumper.DumpAndGetAmplitudes()).Wait();
WaitForAdvance().Wait();
}

private void OnBorrowQubitsHandler(long count)
{
BroadcastAsync("Log", $"Borrow {count} qubit(s)", stateDumper.DumpAndGetAmplitudes()).Wait();
WaitForAdvance().Wait();
}

private void OnReleaseQubitsHandler(IQArray<Qubit> qubits)
{
BroadcastAsync(
"Log",
$"Release qubit(s) {string.Join(", ", qubits.Select(q => q.Id))}",
stateDumper.DumpAndGetAmplitudes()
).Wait();
WaitForAdvance().Wait();
}

private void OnReturnQubitsHandler(IQArray<Qubit> qubits)
{
BroadcastAsync(
"Log",
$"Return qubit(s) {string.Join(", ", qubits.Select(q => q.Id))}",
stateDumper.DumpAndGetAmplitudes()
).Wait();
WaitForAdvance().Wait();
}
}

internal class StateDumper : QuantumSimulator.StateDumper
{
private List<Complex> amplitudes = new List<Complex>();

public StateDumper(QuantumSimulator simulator) : base(simulator)
{
}

public override bool Callback(uint index, double real, double imaginary)
{
amplitudes.Add(new Complex(real, imaginary));
return true;
}

public override bool Dump(IQArray<Qubit> qubits = null)
{
amplitudes = new List<Complex>();
return base.Dump(qubits);
}

public Complex[] GetAmplitudes() => amplitudes.ToArray();

public Complex[] DumpAndGetAmplitudes(IQArray<Qubit> qubits = null)
{
Dump(qubits);
return GetAmplitudes();
}
}
}
Loading

0 comments on commit f0bb12f

Please sign in to comment.