The runtime environment
How Qollab runs your project in the browser: Python or JS project, the pre-created backend, libraries, imports, data files, and limits.
Your project's code runs in your browser. Python runs on Pyodide (Python compiled to WebAssembly), and the JavaScript of a JS project runs in the page itself. Nothing is installed on your machine, and no code runs on a server except the quantum job itself, which runs on IonQ's servers when you pick a remote backend.
Python or JS project
Qollab decides how to run a project from its top-level files every time you press Run:
index.htmlandmain.jsat the top level make a JS project (the editor labels it HTML/JS). Your page renders above the console, and its JavaScript can call Python.- Otherwise, a
main.pyat the top level makes a Python project. Its output goes to the console. - With neither, the project cannot run, and Run stays disabled.
Files in folders do not count, so app/main.py does not make a Python project. If all three files are there, the project runs as a JS project. To switch, add or remove files; there is no separate setting.
The backend variable
Your code does not choose a quantum device. Whoever presses Run picks one in the Select QPU dialog, and the runtime hands it to your code as backend:
- In Python it is a global in
main.py. Pass it as an argument to any of your modules that needs it. - In JavaScript it is available in
main.jsand in every JS file it imports.await backend.run(circuit, { shots })returns a job, andawait job.result()gives the result.
Do not create or hardcode a backend yourself, or the project ignores the choice made in the dialog.
One job per run on IonQ
On the local simulators (the built-in one, the AWS Braket local simulator and the IBM noise models), your code can call backend.run as many times as it needs in one run. The IonQ backends, both the remote simulators and the quantum computers, accept one job per run. The limit protects whoever runs your project from spending credits or rate limit on jobs they did not expect.
We currently only handle single-circuit IonQ jobs, so pass backend.run a single circuit. Passing a list of multiple circuits stops the run with an error saying backend.run() was given a list. A list holding one circuit runs as a single-circuit job, with a warning to pass the circuit itself. On the local simulators a list still works, and the console warns that the same call will fail on IonQ. Qiskit's Sampler and Estimator wrappers always send a list, so don't use them on IonQ; Primitives shows what to use there.
A second backend.run call stops the run with an error saying only one job is allowed per code run. In a JS project the circuit running job also has to complete before the run ends: once main.js returns, IonQ is out of reach, so an app that waits for a click keeps main.js waiting; see Waiting for user input. Work that needs several circuits, such as one per measurement basis, runs on a local simulator. On IonQ, each circuit takes its own run, and every press of Run starts the script from the top. In a Python project, pick the circuit with a main() parameter, and set it under Run settings before each run.
Python projects
main.py runs from top to bottom, and the console shows everything written to stdout and stderr. input() does not work, because there is no keyboard input.
If main.py defines a main() function that follows the rules below, the runtime calls it after the script finishes, so do not call it yourself or it runs twice. When main() takes parameters, pressing Run shows a form for them under Run settings, at the top of the Select QPU dialog:
def main(shots: int = 100, label: str = "run 1", threshold: float = 0.05, verbose: bool = False):
...
- The
def main(line must start at the beginning of the line, with the whole signature on that one line. - Every parameter needs one of the annotations
int,float,strorbool. - Defaults fill the form, and parameter names become labels:
lowProbabilityThresholdshows as "Low Probability Threshold".
If the signature breaks one of these rules, for example with Optional[int] or a parameter without an annotation, no form appears and the runtime does not call main(). Nothing warns you, so fix the signature, or call main() yourself at the end of main.py.
Figures appear in the console. plt.show() shows the figure as an SVG. plt.savefig("result.png") shows it and offers the PNG as a download; nothing is written to disk. Qiskit's circuit.draw("mpl", filename="circuit.png") works the same way.
To put any other file in the console, call sendFile with a name, a content type and the data:
import json
from js import sendFile
sendFile("summary.json", "application/json", json.dumps({"shots": 1000}))
Images get a preview in the console, and text, JSON and LaTeX files get a copy button.
Libraries
List Python packages in requirements.txt, one per line: name==1.2.3, or a range such as name>=1.0,<2. Comments (# ...) and blank lines are fine. The runtime refuses pip options (-r, -e, --index-url), environment markers (; python_version ...) and URL requirements, and names the line it refused in the console.
Qiskit is always installed. When requirements.txt does not name Qiskit, the runtime adds qiskit>=2.5.2,<3.0.0; when the file does, your Qiskit line is used as written. The Libraries list in the editor adds the lines in the table below. In the file tree view on a wide screen the list is the Libraries section; otherwise it sits behind the Extra runtime features / libraries button.
| Library | What it adds to requirements.txt |
|---|---|
| Qiskit Visualizations | qiskit[visualization]>=2.5.2,<3.0.0, in place of the plain Qiskit line |
| Qiskit Algorithms | qiskit-algorithms==0.4.0 |
| Qiskit Optimization | qiskit-optimization==0.7.0 |
| Qiskit Nature (+ PySCF) | qiskit-nature==0.8.0 and pyscf>=2.14.0,<3.0.0 |
| Qiskit Finance | qiskit-finance==0.4.1 |
| Qiskit Machine Learning | qiskit-machine-learning==0.9.1 |
| QAVE | qave==0.1.0, and qiskit[visualization]>=2.5.2,<3.0.0 in place of the plain Qiskit line |
| PQCA | pqca==2.1.0a1, qiskit_aer==0.17.2 and qiskit_ibm_runtime==0.50.0 |
The qollab account's projects show most of these in use.
Packages that come with Pyodide, such as NumPy, SciPy and Matplotlib, load when your Python code imports them, so you do not need to list them. Beyond that, any PyPI package with a pure-Python wheel installs, and so does any package Pyodide has built for WebAssembly. If a listed package needs compiled code that has no Pyodide build, the run stops: the console shows Could not install every package listed in requirements.txt and names the line with no installable distribution for '<name>'. If you need a package that is missing, tell us on the forum.
Network access
Your code can read from the web with GET requests, such as pyodide.http.pyfetch(url) in Python or fetch(url) in JavaScript. POST, PUT, DELETE and every other method except OPTIONS are blocked and fail with an error saying network access is restricted. XMLHttpRequest, WebSocket and web workers are not available either.
The server you read from has to allow cross-origin requests, and the safest way is Access-Control-Allow-Origin: *. A JS project runs in a sandboxed frame with no origin of its own. Its requests, including those from the Python it imports, go out with the origin null, which a server that allows only named origins refuses. No cookies or other credentials are sent to other sites, so a request that needs you to be signed in there fails.
Your project's code is public once you publish it, so never put an API key, token or password in it. Keep the core of the project working without outside services too: a server that is down, slow or rate limited should not stop the project from running for someone else.
Importing your own files
Python imports work the way they do on your machine. The project root is the working directory and the first entry on sys.path, so main.py can import extra.helpers.common, and extra/helpers/common.py can reach extra/data.py with from ..data import VALUES. As in any Python program, relative imports do not work inside main.py itself. A file of yours with the same name as an installed package wins over the package.
JavaScript files import other JavaScript files with a path that starts with ./ or ../. The runtime tries the exact path, then adds .js or .mjs, then looks for index.js or index.mjs in a folder of that name. Other file types cannot be imported.
main.js can also import Python, both your own files and installed packages. Use the dotted module name, not a file path:
import { renderCounts } from './ui/chart.js'; // your ui/chart.js
import * as bell from 'quantum.bell'; // your quantum/bell.py
import { QuantumCircuit } from 'qiskit'; // an installed Python package
const counts = (await bell.run_bell.callPromising(backend, 1000)).toJs();
renderCounts(document.getElementById('chart'), counts);
Call the functions in your own Python files through .callPromising, as above, or .callPromisingKwargs when you pass keyword arguments. On IonQ backends, a plain call such as bell.run_bell(backend, 1000) stops the run with a ConnectionError that says XHR is not supported.
Use named imports or import * as. A default import such as import bell from 'quantum.bell' gives undefined. A Python package that only your JavaScript imports is not installed automatically, so add it to requirements.txt.
Python values reach JavaScript as proxies. JS / Qiskit projects covers the conventions: constructors without new, keyword arguments through callKwargs, and .toJs() to turn a dict or list into plain JavaScript data.
For a complete example, open QCFlows: Multi-file Example, a fork of QCFlows rebuilt as a multi-file project. Its D3 interface is split into JavaScript modules, the correlation engine is a Python package, and the example circuits are JSON data files. Fork it to start from the same layout.
JavaScript libraries
three and three/addons/… (three.js 0.186.1) and postprocessing (6.39.5) can be imported by name, and @tweenjs/tween.js and meshline are bundled in. Import anything else from a full URL, such as https://cdn.jsdelivr.net/npm/<package>/+esm.
Data files
Python can read files from the project with the usual APIs: open, pathlib, numpy.loadtxt, pandas.read_csv. The path has to appear as a plain string in your Python code, relative to the project root. The runtime loads only the files your code names that way.
DATA = "data/measurements.csv" # loaded: the path is written out in the code
rows = open(DATA).read()
name = "measurements"
open(f"data/{name}.csv") # not loaded: the path is only built while the code runs
The string can be anywhere in main.py or in a module it imports; it does not have to sit inside open(). Paths built with f-strings, + or os.path.join are not picked up. Reading one of those files fails with an OSError, and the console explains which file was not loaded. Binary files work. Files your code writes last until the run ends.
JavaScript cannot read project files: fetch("data.csv") does not reach them. To get data into the page, read it in a Python module and return it to main.js.
Saving data between runs
Qollab gives a project no storage that lasts from one run to the next. Files your Python code writes last only until the run ends. A JS project's page runs in a sandboxed frame, so localStorage, sessionStorage, IndexedDB and cookies are not available there. Everything the page builds up while it runs is gone after Stop or a page reload.
To give people a saved state to start from, ship it as a project file, such as states/demo.json. Load it when the project starts, and add a selector if there is more than one. Quantum Hackathon Soundtrack works this way. It stores recorded IonQ Forte shots in data/forte_takes.json, and a Python module reads them and hands them to main.js. A selector then switches between the recorded take, an ideal simulation and the circuit measured in the current run.
HTML and CSS
index.html is placed inside the page body, so write body content only. The runtime drops <html>, <head> and <body> tags. <script> tags do not run, so put your code in main.js. main.css is optional and is the only stylesheet the runtime loads. Other .html and .css files are not loaded, so build any extra markup from JavaScript.
In a JS project, console.log writes to the console below the page. Each argument prints on its own line as text, so an object shows as [object Object]; wrap it in JSON.stringify to see its contents.
Limits
A project holds up to 100 files of up to 2 MB each, and all your projects together can use 200 MB. A run has no time limit. Stop ends it, giving Python code five seconds to finish first.
Larger datasets
Every project file is capped at 2 MB, so plan around that before you build code on a larger file. The cap comes from how the runtime works: each data file your code names is loaded into memory before the run starts. The file system Python sees is held in memory as well, so saving a download to a file does not keep it out of memory either.
For a larger dataset, host it somewhere else and read it with a GET request, as described in Network access. Read it in pieces and handle each piece as it arrives, rather than downloading the whole file first:
from pyodide.http import pyfetch
URL = "https://example.org/measurements.csv"
response = await pyfetch(URL)
reader = response.js_response.body.getReader()
rows, rest = 0, b""
while True:
chunk = await reader.read()
if chunk.done:
break
lines = (rest + chunk.value.to_bytes()).split(b"\n")
rest = lines.pop() # the last line may stop partway through a row
for line in lines:
rows += 1 # handle one row here
if rest:
rows += 1 # the last row, when the file does not end in a newline
print(rows)
Only the current piece and one unfinished line are held at a time. await response.text() and await response.bytes() would hold the whole file at once. main.py can use await at the top level, as above.
Browser support
The runtime needs a browser that supports WebAssembly JSPI, such as a recent Chrome, Edge or Opera, or Firefox 153 or later. In a browser without JSPI, Qollab shows a compatibility warning and keeps Run disabled.
Related
Stay in the loop.
Get the latest tutorials, demos, and project showcases straight to your inbox. No noise, just the good stuff.
Enable JavaScript to sign up.
Qollab updates only. Unsubscribe any time. Privacy Policy.
On this page
Running code