Skip to content

Commit

Permalink
3D circuit visualization using Typescript dev environment (#4334)
Browse files Browse the repository at this point in the history
### 3D circuit visualization
This PR implements the features discussed in the [3D Quantum Circuits RFC](https://docs.google.com/document/d/1dLKj8oAF_JhDWU4zLU7XMz3aBXymd0hk_9n6U0mEtms/edit?usp=sharing).

#### Relevant Issues and PRs
#1075 - 3D diagrams for circuits
#4171 - Typescript development in Cirq and prototype bloch sphere implementation

#### Features implemented
 - All features of a [Cirq Typescript widget](#4171)
 - End to end display of circuits
   - Serialization of cirq.Circuit objects, re-representation in Typescript
 - Support for arbitrary length gates
 - Testing for the different components
 - A short jupyter notebook example


https://user-images.githubusercontent.com/17647506/126213608-0c2d09d1-d5c2-4fa0-a8ae-3f0780f09912.mp4
  • Loading branch information
seunomonije authored Aug 11, 2021
1 parent 10c78c9 commit 0d4a31b
Show file tree
Hide file tree
Showing 28 changed files with 1,905 additions and 32 deletions.
23 changes: 1 addition & 22 deletions cirq-web/example.ipynb → cirq-web/bloch_sphere_example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,7 @@
"id": "fbf7b67d",
"metadata": {},
"source": [
"### Cirq-web visualization"
]
},
{
"cell_type": "markdown",
"id": "e4bf9c7d",
"metadata": {},
"source": [
"<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
" <td>\n",
" <a target=\"_blank\" href=\"https://quantumai.google/cirq/circuits\"><img src=\"https://quantumai.google/site-assets/images/buttons/quantumai_logo_1x.png\" />View on QuantumAI</a>\n",
" </td>\n",
" <td>\n",
" <a target=\"_blank\" href=\"https://colab.research.google.com/github/quantumlib/Cirq/blob/master/docs/circuits.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/colab_logo_1x.png\" />Run in Google Colab</a>\n",
" </td>\n",
" <td>\n",
" <a target=\"_blank\" href=\"https://github.com/quantumlib/Cirq/blob/master/docs/circuits.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/github_logo_1x.png\" />View source on GitHub</a>\n",
" </td>\n",
" <td>\n",
" <a href=\"https://storage.googleapis.com/tensorflow_docs/Cirq/docs/circuits.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/download_icon_1x.png\" />Download notebook</a>\n",
" </td>\n",
"</table>"
"### Cirq-web Bloch sphere visualization"
]
},
{
Expand Down
162 changes: 162 additions & 0 deletions cirq-web/circuit_example.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "737bce11",
"metadata": {},
"source": [
"#### Copyright 2021 The Cirq Developers"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6db8c987",
"metadata": {},
"outputs": [],
"source": [
"#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# https://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License."
]
},
{
"cell_type": "markdown",
"id": "88f3b242",
"metadata": {},
"source": [
"### Cirq-web 3D circuit visualization"
]
},
{
"cell_type": "markdown",
"id": "9f4089af",
"metadata": {},
"source": [
"\n",
"<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
" <td>\n",
" <a target=\"_blank\" href=\"https://colab.research.google.com/github/quantumlib/Cirq/blob/master/master/cirq-web/circuit-example.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/colab_logo_1x.png\" />Run in Google Colab</a>\n",
" </td>\n",
" <td>\n",
" <a target=\"_blank\" href=\"https://github.com/quantumlib/Cirq/blob/master/cirq-web/circuit-example.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/github_logo_1x.png\" />View source on GitHub</a>\n",
" </td>\n",
"</table>"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "99172e40",
"metadata": {},
"outputs": [],
"source": [
"!pip install --quiet cirq --pre"
]
},
{
"cell_type": "markdown",
"id": "ebc22e4d",
"metadata": {},
"source": [
"Note: this notebook relies on unreleased Cirq features. If you want to try these features, make sure you install cirq via `pip install cirq --pre`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e4bd9946",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"!pip install cirq-web"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe0d0e69",
"metadata": {},
"outputs": [],
"source": [
"import cirq\n",
"qubits = [cirq.GridQubit(x, y) for x in range(10) for y in range(10)]\n",
"\n",
"import cirq.testing\n",
"from cirq.protocols import circuit_diagram_info\n",
"circuit = cirq.testing.random_circuit(cirq.GridQubit.square(10), n_moments=5, op_density=.7)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "81689b3f",
"metadata": {
"scrolled": false
},
"outputs": [],
"source": [
"\"\"\"\n",
"This cell builds a 3D circuit diagram using a big list of operations provided TS Circuit class.\n",
"\"\"\"\n",
"import cirq_web\n",
"from typing import Optional\n",
"from cirq_web.circuits.symbols import SymbolInfo\n",
"\n",
"class FunkyHadamard(cirq_web.circuits.symbols.SymbolResolver):\n",
" def resolve(self, operation: cirq.Operation) -> Optional[SymbolInfo]:\n",
" if isinstance(operation.gate, cirq.HPowGate):\n",
" return SymbolInfo(['Hello!'], ['yellow'])\n",
" else:\n",
" return None\n",
" \n",
"resolvers = list(cirq_web.circuits.symbols.DEFAULT_SYMBOL_RESOLVERS) + [\n",
" FunkyHadamard()\n",
"]\n",
"\n",
"from cirq_web import Circuit3D\n",
"c3d = Circuit3D(circuit, resolvers, 2.5)\n",
"display(c3d)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "139acc3d",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
2 changes: 1 addition & 1 deletion cirq-web/cirq_ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,6 @@ We take the following steps for visualization testing in our development environ
3. We take a screenshot of the HTML output in the "browser" (Using Puppeteer)
4. We compare the result of the screenshot with a pre-generated PNG file.

The screenshot of the HTML browser output must live in a temporary directory; we use the [temp](https://github.com/bruce/node-temp) package to handle that for us. Reference the test at `e2e/bloch_sphere/test_bloch_sphere.ts` to see how to easily generate the screenshot in a temporary directory.
The screenshot of the HTML browser output must live in a temporary directory; we use the [temp](https://github.com/bruce/node-temp) package to handle that for us. Reference the test at `e2e/bloch_sphere/bloch_sphere_e2e_test.ts` to see how to easily generate the screenshot in a temporary directory.

The pre-generated PNG file is a screenshot of the developer's choice that represents what the visualization should look like. Each visualization is required to have at least one expected PNG screenshot. For more complex visualizations, multiple screenshots may be needed.
2 changes: 2 additions & 0 deletions cirq-web/cirq_ts/dist/circuit.bundle.js

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions cirq-web/cirq_ts/dist/circuit.bundle.js.LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* @license
* Copyright 2010-2021 Three.js Authors
* SPDX-License-Identifier: MIT
*/
14 changes: 14 additions & 0 deletions cirq-web/cirq_ts/dist/html/bloch_sphere.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<title>Cirq Web Development page</title>
</head>
<body>
<div id="container"></div>
<script src="/bloch_sphere.bundle.js"></script>
<script>
renderBlochSphere('container', 5)
.addVector(1, 0, 0);
</script>
</body>
</html>
29 changes: 29 additions & 0 deletions cirq-web/cirq_ts/dist/html/circuit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<html lang="en">
<head>
<title>Cirq Web Development Page -- 3D circuits</title>
</head>
<body>
<button id="camera-reset">Reset Camera</button>
<button id="camera-toggle">Toggle Camera Type</button>
<div id="mycircuitdiv"></div>
<script src="/circuit.bundle.js"></script>
<script>
const circuit = createGridCircuit(
[
{'wire_symbols': ['Z'], 'location_info': [{'row': 2, 'col': 3}], 'color_info': ['cyan'], 'moment': 0},
{'wire_symbols': ['X'], 'location_info': [{'row': 2, 'col': 3}], 'color_info': ['black'], 'moment': 1},
{'wire_symbols': ['@', 'X'], 'location_info': [{'row': 3, 'col': 0}, {'row': 0, 'col': 0}], 'color_info': ['black', 'black'], 'moment': 0},
], 5, 'mycircuitdiv', 1);

// Event listener for reset press
document.getElementById("camera-reset").addEventListener('click', () => {
circuit.scene.setCameraAndControls(circuit.circuit);
});

// Event listener for camera change
document.getElementById("camera-toggle").addEventListener('click', () => {
circuit.scene.toggleCamera(circuit.circuit);
});
</script>
</body>
</html>
12 changes: 6 additions & 6 deletions cirq-web/cirq_ts/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
<title>Cirq Web Development page</title>
</head>
<body>
<div id="container"></div>
<script src="/bloch_sphere.bundle.js"></script>
<script>
renderBlochSphere('container', 5)
.addVector(1, 0, 0);
</script>
<div id="bloch-sphere">
<a href="/html/bloch_sphere.html">Bloch sphere dev environment</a>
</div>
<div id="circuit">
<a href="/html/circuit.html">Circuit dev environment</a>
</div>
</body>
</html>
110 changes: 110 additions & 0 deletions cirq-web/cirq_ts/e2e/circuit/circuit_e2e_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2021 The Cirq Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import puppeteer from 'puppeteer';
import {expect} from 'chai';
import {readFileSync} from 'fs';
import pixelmatch from 'pixelmatch';
import * as PNG from 'pngjs';
import * as temp from 'temp';
import * as path from 'path';

/**
* Generates an HTML script with the current repository bundle
* that we will use to compare.
*/

// Due to the path, reading the file will only work by running this file in the same directory
// as the package.json file.
const bundleString = readFileSync('dist/circuit.bundle.js');
function htmlContent(clientCode: string) {
return `
<!doctype html>
<meta charset="UTF-8">
<html lang="en">
<head>
<title>Cirq Web Development page</title>
</head>
<body>
<div id="mycircuitdiv"></div>
<script>${bundleString}</script>
<script>${clientCode}</script>
</body>
</html>
`;
}

// Automatically track and cleanup files on exit
temp.track();

describe('Circuit', () => {
temp.mkdir('tmp', (err, dirPath) => {
const outputPath = path.join(dirPath, 'circuit.png');

before(async () => {
const browser = await puppeteer.launch({args: ['--app']});
const page = await browser.newPage();

// Take a screenshot of the first image
await page.setContent(
htmlContent(`
const circuit = createGridCircuit(
[
{
'wire_symbols': ['Z'],
'location_info': [{'row': 2, 'col': 3}],
'color_info': ['cyan'],
'moment': 0
},
{
'wire_symbols': ['X'],
'location_info': [{'row': 2, 'col': 3}],
'color_info': ['black'],
'moment': 1
},
{
'wire_symbols': ['@', 'X'],
'location_info': [{'row': 3, 'col': 0}, {'row': 0, 'col': 0}],
'color_info': ['black', 'black'],
'moment': 0
},
], 5, 'mycircuitdiv'
);
`)
);
await page.screenshot({path: outputPath});
await browser.close();
});

it('with limited gates matches the gold copy', () => {
const expected = PNG.PNG.sync.read(
readFileSync('e2e/circuit/circuit_expected.png')
);
const actual = PNG.PNG.sync.read(readFileSync(outputPath));
const {width, height} = expected;
const diff = new PNG.PNG({width, height});

const pixels = pixelmatch(
expected.data,
actual.data,
diff.data,
width,
height,
{threshold: 0.1}
);

expect(pixels).to.equal(0);
});
});
});
Binary file added cirq-web/cirq_ts/e2e/circuit/circuit_expected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 0d4a31b

Please sign in to comment.