Skip to content

Commit

Permalink
Add Stim circuit writer; update tutorial UI (#192)
Browse files Browse the repository at this point in the history
Apply the suggestions from the review of PR
#166.

Add the option to generate the circuit as an ASCII art or a STIM code
snippet.

NOTE: I do not think that the name "tutorial" is appropriate for this
alternative frontend.
Currently, the folder does not contain any explanation of the code nor
the commits provide a step-by-step guide (the commits have been squashed
in the merge).

---------

Co-authored-by: rryoung98 <ritsukiyoung98@gmail.com>
  • Loading branch information
giangiac and rryoung98 authored Apr 10, 2024
1 parent 47cb5e9 commit 4f9c663
Show file tree
Hide file tree
Showing 38 changed files with 22,959 additions and 22,270 deletions.
File renamed without changes.
14 changes: 6 additions & 8 deletions tutorial/README.md → alternative-frontend/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
# Step-by-step coding of the frontend
# Alternative Frontend

Are you new to JavaScript, React and Pixi?
Do you want to contribute or simply understand the code powering the visual frontend of `tqecjs`?
Look no further.
The alternative frontend follows a different design w.r.t. the official frontend in `<repo>/frontend`.

In this folder we will re-build the frontend adding a few lines of codes at a time.
The commit message will make clear which functionality is added (or, at least, the goal is to have informative commits).
**NOTE:** The early development of this alternative frontend is connected to the beginner's guide
of building a project in Javascript using React and Pixi.
The step-by-step guide can still be accessed in the form of commit history from the
[PR #89 page](https://github.com/QCHackers/tqec/pull/89).

**NOTE:** The step-by-step frontend does not exactly match the official one in the `<repo>/frontend` folder.
This helps to better understand certain design choices while at the same time providing a playground for alternatives.



Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes.
34 changes: 32 additions & 2 deletions tutorial/src/App.css → alternative-frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,48 @@
}

.App-link {
color: #61dafb;
color: #e42023;
margin-bottom: 0.6cm;
}

.Tabs {
margin: 0.4cm;
}

.Tab-button {
margin-right: 20px; /* Add margin to the right of each tab button */
font-size: 18px;
color: #f91a1a;
cursor: pointer;
}

.Tab-content {
display: block; /* Hide tab content by default */
display: none;
}

.Tab-content.active {
display: block;
}

.Cell-size {
font-size: 18px;
width: 40px;
height: 30px;
}

.Comment-paragraph {
margin: 20px;
font-size: 18px;
}

.Text-area {
font-size: 18px;
width: 700px;
height: 250px;
}



@keyframes App-logo-spin {
from {
transform: rotate(0deg);
Expand Down
111 changes: 111 additions & 0 deletions alternative-frontend/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import logo from './tqec_logo.svg';
import './App.css';
import {Stage} from '@pixi/react'
import TqecLibrary from './library'
import TqecCode from './code'
import formattedInfoTabContent from './formattedInfoTab';

/* The logo has been created as the SVG rendering of the ASCII:
*
* /---/ //---/ //---/ //---/
* // // / //-/ //
* // //---/== //---/ //---/
*
* with the web app: https://ivanceras.github.io/svgbob-editor/
*/

const handleTabClick = (tabId) => {
// Hide all tab contents
const tabContents = document.querySelectorAll('.Tab-content');
tabContents.forEach(content => {
content.style.display = 'none';
});

// Show the clicked tab content
const clickedTabContent = document.getElementById(`content${tabId}`);
if (clickedTabContent) {
clickedTabContent.style.display = 'block';
}

// Optionally, you can perform additional actions when a tab is clicked
console.log(`Tab ${tabId} clicked`);
};

function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>

<div className="Tabs">
<button className="Tab-button" id="tab1" onClick={() => handleTabClick(1)}>Info</button>
<button className="Tab-button" id="tab2" onClick={() => handleTabClick(2)}>Compose library</button>
<button className="Tab-button" id="tab3" onClick={() => handleTabClick(3)}>Create code</button>
{/* -- Add more tabs as needed -- */}
</div>

<div id="Tab-content">
<div id="content1" className="Tab-content active">
{/*-- Content for Tab 1 -- */}
{formattedInfoTabContent}
</div>

<div id="content2" className="Tab-content">
{/*-- Content for Tab 2 -- */}

<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
<p className="Comment-paragraph">Enter file name:</p>
<input className="Cell-size"
type="number"
id="dxCell"
placeholder="2"
/>
<p className="Comment-paragraph">x</p>
<input className="Cell-size"
type="number"
id="dyCell"
placeholder="2"
/>
</div>

<Stage width={1400} height={900} options={{backgroundColor: "rgb(154, 193, 208)", antialias: true}}>
<TqecLibrary />
</Stage>

<p className="Comment-paragraph">Circuit-editing area:</p>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<textarea
className="Text-area"
type="text"
id="editableCircuitArea"
placeholder="Edit the circuit here..."
/>
</div>

</div>

<div id="content3" className="Tab-content">
{/*-- Content for Tab 3 -- */}
<Stage width={1400} height={1100} options={{backgroundColor: 'rgb(157, 191, 145)', antialias: true}}>
<TqecCode />
</Stage>

<p className="Comment-paragraph">Compact representation of the QEC code:</p>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<textarea
className="Text-area"
type="text"
id="codeSummary"
placeholder="Code will appear here..."
/>
</div>

</div>
{/*-- Add more content areas as needed -- */}
</div>
</div>
);
}

export default App;
File renamed without changes.
200 changes: 200 additions & 0 deletions alternative-frontend/src/code/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { useApp } from '@pixi/react'
import { makeGrid } from '../library/grid'
import { Container, Graphics } from 'pixi.js'
import Position from '../library/position'
import { button } from '../library/button'
import Plaquette from '../library/plaquette'
import PlaquetteType from './plaquette-type'
import Circuit from '../library/circuit'
import { savedPlaquettes, libraryColors } from '../library'
import { Qubit } from '../library/qubit'
import { GRID_SIZE_CODE_WORKSPACE, GUIDE_MAX_BOTTOM_RIGHT_CORNER_CODE_WORKSPACE, GUIDE_TOP_LEFT_CORNER_CODE_WORKSPACE } from '../constants'

/////////////////////////////////////////////////////////////

export default function TqecCode() {
// Initialize the app
let app = useApp();

// Remove all children from the stage to avoid rendering issues
app.stage.removeChildren();
const gridSize = GRID_SIZE_CODE_WORKSPACE;
const qubitRadius = 7;
document.getElementById('dxCell').value = 2;
document.getElementById('dyCell').value = 2;
let plaquetteDx = parseInt(document.getElementById('dxCell').value);
let plaquetteDy = parseInt(document.getElementById('dyCell').value);

// Create the workspace
const workspace = new Container();
workspace.name = 'workspace-code';

// Create the grid container
const grid = makeGrid(app, gridSize);
// We want the grid to be in the lowest layer
workspace.addChildAt(grid, 0);

/////////////////////////////////////////////////////////////

// Add guide for the eyes for the plaquette boundaries.
// They are located via the position of the top, left corner.
// The first guide is where the plaquette is built, the other guides are for the library.
const guideTopLeftCorner = GUIDE_TOP_LEFT_CORNER_CODE_WORKSPACE;
let libraryTopLeftCorners = [[21, 3], [21, 7], [21, 11], [21, 15]];
const outline = new Graphics();
workspace.addChild(outline);

/////////////////////////////////////////////////////////////

// Add qubit positions to the workspace
for (let x = 0; x <= app.screen.width/gridSize; x += 1) {
for (let y = 0; y <= app.screen.height/gridSize; y += 1) {
// Skip every other qubit
if ( (x+y) % 2 === 1 )
continue;
// Create a qubit
const pos = new Position(x*gridSize, y*gridSize, qubitRadius-2);
workspace.addChild(pos);
}
}
const num_background_children = workspace.children.length;

/////////////////////////////////////////////////////////////

const infoButton = button('Library of plaquettes', libraryTopLeftCorners[0][0]*gridSize, 1*gridSize, 'orange', 'black');
workspace.addChild(infoButton);

// Select the qubits that are part of a plaquette
const importPlaquettesButton = button('Import plaquettes from composer', gridSize, 1*gridSize, 'white', 'black');
workspace.addChild(importPlaquettesButton);
let codePlaquettes = [];

importPlaquettesButton.on('click', (_e) => {
outline.clear()
plaquetteDx = parseInt(document.getElementById('dxCell').value);
plaquetteDy = parseInt(document.getElementById('dyCell').value);
libraryTopLeftCorners = [[21, 3], [21, 3+plaquetteDy+2], [21, 3+(plaquetteDy+2)*2], [21, 3+(plaquetteDy*2)*3]]
outline.lineStyle(2, 'lightcoral');
// Add workspace guidelines.
let y0 = guideTopLeftCorner[1];
let message = '';
while (y0 + plaquetteDy <= GUIDE_MAX_BOTTOM_RIGHT_CORNER_CODE_WORKSPACE[0]) {
let x0 = guideTopLeftCorner[0];
while (x0 + plaquetteDx <= GUIDE_MAX_BOTTOM_RIGHT_CORNER_CODE_WORKSPACE[1]) {
const x1 = x0 + plaquetteDx;
const y1 = y0 + plaquetteDy;
outline.moveTo(x0*gridSize, y0*gridSize);
outline.lineTo(x1*gridSize, y0*gridSize);
outline.lineTo(x1*gridSize, y1*gridSize);
outline.lineTo(x0*gridSize, y1*gridSize);
outline.lineTo(x0*gridSize, y0*gridSize);
x0 += plaquetteDx;
message += ' .';
}
y0 += plaquetteDy;
message += '\n';
}
// Add library guidelines.
for (const [x0, y0] of libraryTopLeftCorners) {
const x1 = x0 + plaquetteDx;
const y1 = y0 + plaquetteDy;
outline.moveTo(x0*gridSize, y0*gridSize);
outline.lineTo(x1*gridSize, y0*gridSize);
outline.lineTo(x1*gridSize, y1*gridSize);
outline.lineTo(x0*gridSize, y1*gridSize);
outline.lineTo(x0*gridSize, y0*gridSize);
}
// Create the compact representation of the (empty) QEC code
const codesummary = document.getElementById('codeSummary');
codesummary.value = message;
// Add library plaquettes.
//const library_workspace = document.getElementsByName('workspace-library');
let plaquetteTypes = [];
//const numPlaquettes = savedPlaquettes.length;
savedPlaquettes.forEach((plaq, index) => {
if (plaq.name !== 'WIP plaquette') {
let qubits = [];
plaq.qubits.forEach((q) => {
const qubit = new Qubit(q.globalX, q.globalY, q.Radius);
qubit.name = q.name;
qubit.updateLabel();
workspace.addChild(qubit);
qubits.push(qubit);
});
// Recall that plaquette names are like "plaquette 12", starting from "plaquette 1"
const plaquette_id = parseInt(plaq.name.match(/\d+/)[0]);
const base_translate_vector = {x: guideTopLeftCorner[0] - libraryTopLeftCorners[plaquette_id-1][0],
y: guideTopLeftCorner[1] - libraryTopLeftCorners[plaquette_id-1][1]};
const p_type = new PlaquetteType(qubits, libraryColors[index], num_background_children, base_translate_vector)
p_type.name = plaq.name;
plaquetteTypes.push(p_type);
workspace.addChildAt(p_type, num_background_children);
}
});
// FIXME: Some plaquettes in the red-delimited space may already be there.
// Compose the compact representation of the code accordingly.
});

/////////////////////////////////////////////////////////////

// Undo button, meaning that the last plaquette added is removed.
const undoButton = button('Remove last plaquette', gridSize, 2*gridSize, 'white', 'black');
workspace.addChild(undoButton);

undoButton.on('click', (_e) => {
if (workspace.children[num_background_children] instanceof Plaquette
&& !(workspace.children[num_background_children] instanceof PlaquetteType) ) {
workspace.removeChildAt(num_background_children);
// FIXME: correct the compact representation of the code!
}
});

/////////////////////////////////////////////////////////////

// Create a button to de-select all qubits
const downloadCodeButton = button('Download QEC code', gridSize, 21*gridSize, 'white', 'black');
workspace.addChild(downloadCodeButton);

downloadCodeButton.on('click', (_e) => {
if (codePlaquettes.length === 0) return;

let message = '';
// Add info on cell size
message += 'This is the complete QEC code.\n'
let counter = 0;
codePlaquettes.forEach((plaq) => {
if (plaq.name !== 'WIP plaquette') {
message += '###############\n'
message += `# plaquette ${counter} #\n`
message += '###############\n\n'
plaq.children.forEach((child) => {
if (child instanceof Circuit) {
console.log('circuit to add');
message += child.art.text;
message += '\n\n\n';
console.log(message);
}
});
counter += 1;
}
});
const blob = new Blob([message], { type: 'text/plain' });
const url = URL.createObjectURL(blob);

const link = document.createElement('a');
link.href = url;
link.download = 'qec_code.txt';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
});

/////////////////////////////////////////////////////////////

// Add workspace to the stage
workspace.visible = true;
app.stage.addChild(workspace);

return null;
}
Loading

0 comments on commit 4f9c663

Please sign in to comment.