Manim-powered animations for visualizing quantum circuits and neutral atom moves in QuEra's hardware
Project description
Quantum Animation Toolbox
Disclaimer this library was initially developed as an internship project at QuEra. It is not an official QuEra producted and is not maintained by QuEra.
This library was made to create quantum circuits and qubit visualization of QuEra processors in ManimGL, as well as providing QuEra styling for animations.
QuEra logos, images and stylings can be found on our website.
Thank you to Grant Sanderson for creating the Manim framework on which this package is based. Note that this package runs on ManimGL (also known as 3b1b Manim) as opposed to ManimCE. Ensure you are using the correct version on Manim to run this package, the ManimGL installation guide can be found here. Documentation for ManimGL can be found here.
To see this package documentation, testing, and installation guides, check out our wiki page.
Installation
To use, make sure that your files have this structure:
manim_stuff/
├── venv/ # virtual environment
├── project_folder/ # contains your Manim script
│ └── your_script.py
├── quera-animation-toolbox/
│ └── src/
│ └── quantum_animation_toolbox/
│ ├── quera_colors.py
│ ├── quera_circuit_lib.py
│ └── quera_qubit_lib.py
Then once you're in your script file add to the header:
from manimlib import *
import numpy as np
import sys
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
lib_path = os.path.normpath(
os.path.join(current_dir, "..", "quera-animation-toolbox", "src", "quantum_animation_toolbox")
)
sys.path.append(lib_path)
from quera_colors import *
from quera_qubit_lib import *
Testing
This library contains tests to make sure that the classes work properly. In order to run these tests after installaction, enter a terminal window in your quera-animation-toolbox folder and run:
python generate_control_frames.py
followed by:
pytest -q
If you get a message saying "6 passed", you're all good to go!
Qubit Visualization Toolkit: quera_qubit_lib.py
A compact Manim GL helper-library that lets you draw, animate, and interact with arrays of qubits—plus the laser tweezers and glow effects you need to represent qubit motions within a quantum processor.
Classes
Qubit(self, position = ORIGIN, radius = 0.15, glow_radius = 0.4, color)
A drawable qubit: a colored Dot with a faint GlowDot centred behind it.
You can recolor it, make the glow breathe continuously, or trigger individual glow pulses.
Methods
set_color(new_color, animate=False) – change colour (returns a list of animations when animate=True)
reset_color(animate=False) – restore the original colour
turn_glow(on=True, animate=False) – show / hide the glow ring
pulse_once(run_time=1.0, scale_factor=1.5) – returns a single in-and-out glow pulse you can pass to self.play
start_pulsing(pulse_period=1.0, scale_factor=1.5) – begin a continuous “breathing” glow
stop_pulsing() – halt the breathing effect and hide the glow
class QubitDemo(Scene):
def construct(self):
q = Qubit(ORIGIN, color=QUERA_PURPLE)
self.add(q)
# breathe for three seconds
q.start_pulsing(scale_factor=1.5)
self.wait(3)
q.stop_pulsing()
self.wait(0.3)
# change color to red
self.play(*q.set_color(QUERA_RED, animate=True))
self.wait(0.2)
# one stronger pulse
q.start_pulsing(scale_factor=2)
self.wait(1)
q.stop_pulsing()
self.wait(0.5)
# return to original purple
self.play(*q.reset_color(animate=True))
self.wait()
Vacancy(self, position, radius, color, stroke_width, num_dashes)
Dashed-circle placeholder indicating where a Qubit could sit.
Methods
set_color(new_color, new_opacity=None) – change stroke (and optional opacity)
class VacancyQubitScene(Scene):
def construct(self):
# two side-by-side vacancies
left = Vacancy(position=LEFT * 0.5, color=QUERA_MGRAY)
right = Vacancy(position=RIGHT * 0.5, color=QUERA_MGRAY)
# qubit starts in the left vacancy
q = Qubit(position=left.get_center(), color=QUERA_PURPLE)
self.add(left, right, q)
self.wait(0.5)
# move qubit to the right vacancy …
self.play(q.animate.move_to(right.get_center()), run_time=1)
# … then give it a single pulse
q.pulse_once(self, run_time=1, scale_factor=1.5)
self.wait()
QubitArray(self, layout, …)
Versatile container that arranges Qubit and Vacancy objects in a linear row, rectangular grid, or two-row bilinear pattern.
It also provides convenience methods to move / place qubits or vacancies after creation.
Constructor highlights
| Arg | Meaning | Default |
|---|---|---|
layout |
"linear", "grid", or "bilinear" |
"linear" |
num_qubits |
how many qubits (linear / bilinear) | 6 |
rows, cols |
grid dimensions | 2, 3 |
qubit_spacing |
horizontal spacing | 1.0 |
line_spacing |
vertical gap (bilinear) | 2 |
use_vacancies |
draw dashed placeholders too? | True |
fill_pattern |
"all", "none", "alternate" or set[int] |
"all" |
Selected methods
move_qubits(scene, [(idx, dx, dy), …])– shift chosen qubitsmove_vacancies(scene, …)– same for vacanciesplace_qubits(scene, [(idx, x, y), …])– absolute positioningget_pairs([(i,j), …])– return list of (Qubit, Qubit) pairsremove_vacancies()– delete all vacancy markers
class QubitArrayScene(Scene):
def construct(self):
# 4×4 grid with a “column-alternate” fill
array = QubitArray(
layout="grid",
rows=4, cols=4,
qubit_spacing=1.0,
use_vacancies=True,
fill_pattern="checkerboard" # whole columns 0 & 2 filled
)
glow_group = Group(*(q.glow for q, _ in array.qubits))
self.add(glow_group, array) # glows first → higher z-index
self.wait(0.5)
# --- build shifts: odd ROW → right, even ROW → left -------------
shifts = []
qubits_per_row = 2
for idx, (q, pos) in enumerate(array.qubits):
row = idx // qubits_per_row # row number 0-based
dx = -1 if row % 2 else +1 # odd row → +1, even row → –1
shifts.append((idx, dx, 0))
# animate the slide
array.move_qubits(self, shifts, run_time=1.2, animate=True)
# pulse each qubit once after arriving
self.play(*[q.pulse_once(scale_factor=2.5)
for q, _ in array.qubits])
array.move_qubits(self, [(indiv[0], -indiv[1], indiv[2]) for indiv in shifts], run_time=1.2, animate=True)
self.wait()
DotLaserTweezer(self, position, radius, color, opacity)
Glowing “tweezer-dot” that can grab a Qubit, drag it around, and release it anywhere in the scene.
Useful for interactive demos where atoms are rearranged by optical tweezers.
Key methods
move_to(target)– jump/animate to a new location (another tweezer or point)pick_up(qubit)– attach a qubit; it will follow the tweezerrelease()– drop the qubit (stops following)- (auto-called) tweezer keeps the qubit centred via an updater while attached
class DotLaserTweezerScene(Scene):
def construct(self):
# --- build a 2×2 array: vacancies everywhere,
# qubits only in the two left-hand sites ---------
array = QubitArray(layout="grid",
rows=2, cols=2,
use_vacancies=True,
fill_pattern={0, 2}) # indices: 0 upper-left, 2 lower-left
self.add(array)
self.wait(0.5)
# Tweezer starts over the upper-left qubit
tweezer = DotLaserTweezer()
tweezer.move_to(array.get_qubit(0))
tweezer.set_opacity(0)
self.add(tweezer)
# --- move first qubit from left to right -----------------------
self.play(*tweezer.pick_up(array.get_qubit(0), show=True), run_time=0.3)
self.play(tweezer.animate.move_to(array.get_vacancy(1).get_center()),
run_time=1.0)
self.play(*tweezer.release(hide=True), run_time=0.3)
self.wait(0.5)
# --- move second qubit (lower-left) to lower-right -------------
tweezer.move_to(array.get_qubit(1)) # jump down
self.play(*tweezer.pick_up(array.get_qubit(1), show=True), run_time=0.3)
self.play(tweezer.animate.move_to(array.get_vacancy(3).get_center()),
run_time=1.0)
self.play(*tweezer.release(hide=True), run_time=0.3)
# Pulse both relocated qubits
self.play(*[array.get_qubit(idx).pulse_once() for idx in (0,1)])
self.wait()
LineLaserTweezer(self, point1, point2, qubits, catch_radius, …)
Glowing line tweezer that “grabs” every Qubit lying within catch_radius of its path and drags them together when it moves. Perfect for demonstrating bulk transport of atoms with an optical conveyor belt.
Key methods
move_by(scene, delta, run_time=1.0)– shift laser and all collected qubits by a vectorturn_on(animate=False)/turn_off()– fade the beam in / outpulse(scene, run_time=1.0)– quick brightening flash
(collection list auto-updates after every move)
class LineLaserTweezerScene(Scene):
def construct(self):
array = QubitArray(
layout="grid",
rows=2, cols=2,
use_vacancies=True,
fill_pattern={0, 2} # Left side qubits start filled
)
self.add(array)
left_vac = array.get_vacancy(0)
p1 = left_vac.get_center() + UP * 0.2
p2 = left_vac.get_center() + DOWN * 1.2
laser = LineLaserTweezer(
p1, p2,
qubit_array=array,
catch_radius=0.2,
opacity=0,
infinite=True
)
self.wait(0.5)
self.add(laser)
laser.turn_on(scene=self, animate=True, run_time=0.5)
self.wait(0.3)
shift_vec = RIGHT * array.qubit_spacing
laser.move_by(
scene=self,
delta=shift_vec,
run_time=1.2,
animate=True # show the motion
)
laser.turn_off(scene=self, animate=True, run_time=0.5)
self.wait(0.5)
laser.turn_on(scene=self, animate=True, run_time=0.5)
laser.move_by(
scene=self,
delta=-shift_vec,
run_time=1.2,
animate=True # show the motion
)
laser.turn_off(scene=self, animate=True, run_time=0.5)
self.wait(0.5)
entanglements(scene, pairs, color, width, height, run_time)
Animates all supplied qubit–qubit pairs in parallel:
- A glowing
GlowEllipsefades-in to connect each pair. - Both qubits smoothly blend from their own colour to
color. - After a short pause everything plays backward, restoring the original state and hiding the ellipses.
Parameters
| name | purpose | default |
|---|---|---|
scene |
current Scene instance |
– |
pairs |
iterable of (q1, q2) Qubit tuples |
– |
color |
entanglement highlight colour | QUERA_RED |
width / height |
ellipse size | 0.6 / 0.15 |
run_time |
fade-in (and fade-out) duration | 0.4 |
class EntanglementsScene(Scene):
def construct(self):
array = QubitArray(
layout="grid",
rows=2, cols=4,
use_vacancies=True,
fill_pattern="all"
)
self.add(array)
self.wait(0.5)
dx = 0.5 * array.qubit_spacing
moves = [
(0, dx, 0), # qubit-0 → right
(4, dx, 0), # qubit-4 → right
(3, -dx, 0), # qubit-3 ← left
(7, -dx, 0) # qubit-7 ← left
]
# use DotLaserTweezer internally
array.move_qubits(
scene=self,
shift_list=moves,
run_time=0.5,
animate=True,
use_lasers=True
)
self.wait(0.3)
pairs = array.get_pairs([(0, 1), (2, 3), (4, 5), (6, 7)])
entanglements(
self, pairs,
color=QUERA_RED,
width=0.8, height=0.2,
run_time=0.5,
animate=True
)
self.wait(0.3)
# Qubits moving in reverse
array.move_qubits(
scene=self,
shift_list=[(indiv[0], -indiv[1], -indiv[2]) for indiv in moves],
run_time=0.5,
animate=True,
use_lasers=True
)
self.wait(0.5)
Quantum Circuit Maker: quera_circuit_lib.py
HadamardGate(slot, start, pitch, size=0.5, color=WHITE, bg_color=BLACK, shift=None)
A drawable 1-qubit H gate that snaps to a specified slot on a horizontal quantum wire.
Gate placement is determined by the wire’s start point, the slot number, and the uniform pitch between slots.
Use shift="left" or "right" to nudge the gate sideways by ±(pitch / 3) for better layout in multi-gate circuits.
No animations or interactive methods are defined (use Manim’s standard transforms to move or fade gates).
class HadamardGateScene(Scene):
def construct(self):
bg = BackgroundColor(BLACK)
h = HadamardGate(start=ORIGIN, slot=0, size=2, pitch =2.4, color=WHITE)
self.add(bg, h)
Classes
QuEra Styling Toolkit: quera_colors.py
A drop-in helper-package that skins Manim GL with QuEra’s look-and-feel and a handful of higher-level animations.
Classes:
QuEraLogo(color, height, include_text)
A lightweight SVG wrapper for the QuEra company logo. Accepts any color and can render either the simple “QuEra” mark or the full “QuEra Computing Inc” version.
| Parameter | Type | Default | Description |
|---|---|---|---|
color |
str or ManimColor |
QUERA_PURPLE |
Hex string ("#FF9900") or a Manim Color object. |
height |
float |
QUERA_LOGO_HEIGHT |
Desired logo height in scene units. |
include_text |
bool |
False |
True → full-text SVG • False → icon + “QuEra”. |
Methods (inherited from SVGMobject)
.set_color(new_color)– change fill/stroke colour..set_height(h)– resize while preserving aspect ratio..scale(factor)– uniformly scale about the logo’s centre..shift(vector)/.move_to(point)– translate.- Any standard SVGMobject transform (rotate, flip, fade, etc.).
File expectation The class looks for
quera_logo.svg(icon + wordmark) andquera_logo_text.svg
(long “QuEra Computing Inc”) in yourvector_images/folder.
BackgroundColor(color, z_index)
A full-screen rectangle that tints the entire scene background.
| Parameter | Type | Default | Description |
|---|---|---|---|
color |
ManimColor |
BLACK |
Solid fill colour behind every other mobject. |
z_index |
int |
-100 |
Negative index places the panel under normal content. |
QuEraSlideBG(self, key)
This is how you set the background of your Manim scene to match the QuEra Slide templates, key argument goes from 1 to 6 for the following backgrounds:
- slide_bg_00: Purple Title and Textbox
- slide_bg_01: White Logo Blank
- slide_bg_02: Dark Purple Title and space
- slide_bg_03: Light Purple Blank
- slide_bg_04: White Title and space
- slide_bg_05: Dark Purple Blank
QueraBanner(self, color, direction) Creates the QuEra logo packed into $\ket{Q}$ form, which can be expanded and contracted using methods
Parameters: color - logo color, defaults to QUERA_PURPLE direction - direction to which the banner opens
Methods:
create(run_time=1) - draws folded $\ket{Q}$ logo dot by dot
expand(run_time=0.75) - expands logo to $\ket{QuEra}$ in the direction specified by initialization
contract(run_time=0.75) - undoes expand() and returns to $\ket{Q}$ form
class QuEraRevealScene(Scene):
def construct(self):
banner = QuEraBanner(color=QUERA_PURPLE, direction="center")
self.add(banner)
self.play(banner.create())
self.wait(0.3)
self.play(banner.expand())
self.wait()
class ExpandDirections(Scene):
def construct(self):
banner1 = QuEraBanner(color=QUERA_PURPLE, direction="left")
banner2 = QuEraBanner(color=QUERA_PURPLE, direction="center")
banner3 = QuEraBanner(color=QUERA_PURPLE, direction="right")
banner1.scale(0.5).shift(2*UP)
banner2.scale(0.5)
banner3.scale(0.5).shift(2*DOWN)
self.add(banner1, banner2, banner3)
self.play(banner1.appear(),banner2.appear(),banner3.appear())
self.wait(0.5)
self.play(banner1.expand(),banner2.expand(),banner3.expand())
self.wait(1)
self.play(banner1.contract(),banner2.contract(),banner3.contract())
self.wait(0.5)
QueraScatter(self, color, height)
Creates a random arrangement of dots that when form_logo() is called transform into the QuEra logo.
Methods:
form_logo(duration=2.0) - Makes dots transform themselves into QuEra logo
form_logo(duration=2.0) - Makes dots transform themselves into QuEra logo
class QuEraScatterScene(Scene):
def construct(self):
scatter = QuEraScatter(color=QUERA_PURPLE)
self.add(scatter)
self.wait(0.5)
self.play(scatter.form_logo())
self.wait(0.5)
self.play(scatter.unform_logo())
self.wait()
BloqadeScatter(self, color, height)
Creates a random arrangement of dots that when form_logo() is called transform into the QuEra logo.
Methods:
form_logo(duration=2.0) - Makes dots transform themselves into QuEra logo
form_logo(duration=2.0) - Makes dots transform themselves into QuEra logo
class BloqadeScatterScene(Scene):
def construct(self):
scatter = BloqadeScatter(color=QUERA_PURPLE)
self.add(scatter)
self.wait(0.5)
self.play(scatter.form_logo())
self.wait(0.5)
self.play(scatter.unform_logo())
self.wait()
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file quantum_animation_toolbox-0.1.1.tar.gz.
File metadata
- Download URL: quantum_animation_toolbox-0.1.1.tar.gz
- Upload date:
- Size: 43.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f49ced58151247a938087f191bb3f01c082eca6b11fa6a1021031a155942ec91
|
|
| MD5 |
b84590eda084d849b5c67cee9cdd7dbd
|
|
| BLAKE2b-256 |
a77f4e1f42181a4d4617ee2f4828205056dd1db89bdf3c2ec39004f2bb4b0064
|
File details
Details for the file quantum_animation_toolbox-0.1.1-py3-none-any.whl.
File metadata
- Download URL: quantum_animation_toolbox-0.1.1-py3-none-any.whl
- Upload date:
- Size: 76.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3af03bf6af8eccd97941c82bfe0b7d5c14f39bfce3a420cac82670d5f83f9edb
|
|
| MD5 |
29cd8b5e1a2e850cc1ca7e1bbed725f2
|
|
| BLAKE2b-256 |
1dd9f5cf28d5d41fe91992f6a09226fb5f25b00cbd1751d5af57b0946a124ad9
|