atomic_shorts_demo / manim.py
skydaive's picture
Upload manim.py
de8e0be verified
from manim import *
import numpy as np
# Render with:
# manim -ql storyboard.py StoryboardScene -o video.mp4
class StoryboardScene(MovingCameraScene):
def construct(self):
# Palette
DIAMOND_COLOR = GREY_B
N_COLOR = TEAL_C
VACANCY_COLOR = RED_B
LEVEL_COLOR = BLUE_E
SINGLET_COLOR = YELLOW_B
EMIT_COLOR = RED_B
# Helper: build a simple 2D diamond-like lattice with an NV- center (N + vacancy)
def create_lattice(rows=6, cols=8, spacing=0.6, r_dot=0.06):
dots = VGroup()
positions = {}
for i in range(rows):
for j in range(cols):
offset = (spacing / 2) if (i % 2 == 1) else 0
x = (j * spacing) + offset - (cols * spacing) / 2
y = (i * spacing * 0.85) - (rows * spacing * 0.85) / 2
d = Dot(point=np.array([x, y, 0]), radius=r_dot, color=DIAMOND_COLOR)
dots.add(d)
positions[(i, j)] = d.get_center()
# Choose central site for N and adjacent site for vacancy
ic = rows // 2
jc = cols // 2
nv_pos = positions[(ic, jc)]
# Pick vacancy to the right if possible
if (ic, jc + 1) in positions:
vac_pos = positions[(ic, jc + 1)]
else:
vac_pos = positions[(ic, jc - 1)]
# Replace central carbon with Nitrogen
N = Dot(point=nv_pos, radius=r_dot * 1.5, color=N_COLOR)
N_label = Text("N", font_size=22, color=N_COLOR).next_to(N, UP)
# Remove the dot at vacancy position
vac_circle = Circle(radius=r_dot * 1.6, color=VACANCY_COLOR, stroke_width=4).move_to(vac_pos)
V_label = MathTex(r"V^{-}", color=VACANCY_COLOR).scale(0.6).next_to(vac_circle, DOWN)
# Remove the vacancy carbon dot from dots (find by position)
to_remove = None
for d in dots:
if np.allclose(d.get_center(), vac_pos):
to_remove = d
break
if to_remove:
dots.remove(to_remove)
lattice_group = VGroup(dots, N, vac_circle, N_label, V_label)
# Find three nearest neighbors to N (excluding vacancy) for later bonds
carbon_positions = [d.get_center() for d in dots]
dists = sorted([(np.linalg.norm(p - nv_pos), p) for p in carbon_positions], key=lambda x: x[0])
neighbors = []
for _, p in dists:
if len(neighbors) >= 4:
break
if np.allclose(p, vac_pos):
continue
neighbors.append(p)
neighbors = neighbors[:3]
bonds = VGroup(*[Line(N.get_center(), p, color=DIAMOND_COLOR, stroke_width=3) for p in neighbors])
return lattice_group, dots, N, vac_circle, N_label, V_label, bonds, nv_pos
# Helper: Energy level diagram for NV-
def create_energy_diagram():
# Axis
E_axis = Arrow(start=LEFT * 5 + DOWN * 2.5, end=LEFT * 5 + UP * 2.5, buff=0, stroke_width=4, color=GREY_B)
E_label = Text("Energy", font_size=24, color=GREY_B).next_to(E_axis, RIGHT, buff=0.2)
# Ground triplet (3A2): ms=0 lower, ms=±1 slightly higher (two lines)
xL, xR = -3.0, 3.0
y_g0 = -1.6
y_gpm1a = -1.2
y_gpm1b = -1.05
g0 = Line([xL, y_g0, 0], [xR, y_g0, 0], color=LEVEL_COLOR, stroke_width=5)
gpm1a = Line([xL, y_gpm1a, 0], [xR, y_gpm1a, 0], color=LEVEL_COLOR, stroke_width=5)
gpm1b = Line([xL, y_gpm1b, 0], [xR, y_gpm1b, 0], color=LEVEL_COLOR, stroke_width=5)
g_label = MathTex(r"{}^3A_2", color=LEVEL_COLOR).scale(0.8).next_to(g0, LEFT, buff=0.5)
g_m0 = MathTex(r"m_s=0", color=LEVEL_COLOR).scale(0.6).next_to(g0, RIGHT, buff=0.3)
g_mpm1 = MathTex(r"m_s=\pm 1", color=LEVEL_COLOR).scale(0.6).next_to(gpm1b, RIGHT, buff=0.3)
# Excited triplet (3E): similar splitting
y_e0 = 1.6
y_epm1a = 2.0
y_epm1b = 2.15
e0 = Line([xL, y_e0, 0], [xR, y_e0, 0], color=LEVEL_COLOR, stroke_width=5)
epm1a = Line([xL, y_epm1a, 0], [xR, y_epm1a, 0], color=LEVEL_COLOR, stroke_width=5)
epm1b = Line([xL, y_epm1b, 0], [xR, y_epm1b, 0], color=LEVEL_COLOR, stroke_width=5)
e_label = MathTex(r"{}^3E", color=LEVEL_COLOR).scale(0.8).next_to(e0, LEFT, buff=0.5)
e_m0 = MathTex(r"m_s=0", color=LEVEL_COLOR).scale(0.6).next_to(e0, RIGHT, buff=0.3)
e_mpm1 = MathTex(r"m_s=\pm 1", color=LEVEL_COLOR).scale(0.6).next_to(epm1b, RIGHT, buff=0.3)
# Singlet shelving state (simplified as one line)
y_s = 0.3
s = Line([xL * 0.5, y_s, 0], [xR * 0.5, y_s, 0], color=SINGLET_COLOR, stroke_width=5)
s_label = Text("Singlet", font_size=24, color=SINGLET_COLOR).next_to(s, RIGHT, buff=0.3)
# Transitions
radiative = Arrow(start=[0.0, y_e0 - 0.05, 0], end=[0.0, y_g0 + 0.05, 0],
buff=0, color=EMIT_COLOR, stroke_width=5)
y_epm_avg = 0.5 * (y_epm1a + y_epm1b)
nr1 = Arrow(start=[1.5, y_epm_avg - 0.05, 0], end=[1.0, y_s + 0.05, 0],
buff=0, color=SINGLET_COLOR, stroke_width=5)
nr2 = Arrow(start=[1.0, y_s - 0.05, 0], end=[0.5, y_g0 + 0.05, 0],
buff=0, color=SINGLET_COLOR, stroke_width=5)
levels = VGroup(g0, gpm1a, gpm1b, e0, epm1a, epm1b, s)
labels = VGroup(g_label, e_label, g_m0, g_mpm1, e_m0, e_mpm1, s_label)
arrows = VGroup(radiative, nr1, nr2)
axis = VGroup(E_axis, E_label)
diagram = VGroup(axis, levels, labels, arrows)
return diagram, axis, levels, labels, arrows
# -------------------------
# Scene 1: Introduction to NV- Center (8 s)
# -------------------------
lattice_group, dots, N_dot, vac_circle, N_label, V_label, bonds, nv_pos = create_lattice()
title = Text("NV- center in diamond", font_size=30, color=GREY_B).to_edge(UP)
highlight = Circle(radius=0.6, color=N_COLOR, stroke_width=3).move_to((N_dot.get_center() + vac_circle.get_center()) / 2)
self.play(FadeIn(lattice_group), FadeIn(title), run_time=1.0)
self.play(Create(highlight), run_time=0.6)
self.play(highlight.animate.scale(1.2), run_time=0.7)
self.wait(5.7) # total 8.0 s
# -------------------------
# Scene 2: Structure of the NV- Center (8 s)
# -------------------------
self.play(self.camera.frame.animate.scale(0.6).move_to(N_dot.get_center()), run_time=1.2)
if len(bonds) > 0:
self.play(Create(bonds), run_time=0.8)
self.play(Indicate(N_label, color=N_COLOR), Indicate(V_label, color=VACANCY_COLOR), run_time=0.6)
self.wait(5.4) # total 8.0 s
# -------------------------
# Scene 3: Energy Level Diagram (12 s)
# -------------------------
# Clear lattice and show energy levels
self.play(FadeOut(VGroup(lattice_group, title, highlight, bonds)), run_time=0.6)
energy_diagram, axis, levels, labels, arrows = create_energy_diagram()
energy_diagram.shift(DOWN * 0.3) # small center tweak
self.play(FadeIn(axis), FadeIn(levels), run_time=2.0)
self.play(FadeIn(labels), run_time=0.8)
self.play(GrowArrow(arrows[0]), run_time=0.8) # radiative
self.play(GrowArrow(arrows[1]), run_time=0.8) # nonradiative e->singlet
self.play(GrowArrow(arrows[2]), run_time=0.8) # singlet->ground
self.wait(6.2) # total 12.0 s
# -------------------------
# Scene 4: Emission in the Red Region (7 s)
# -------------------------
self.play(FadeOut(energy_diagram), run_time=0.6)
# Minimal NV- motif for emission
nv_group = VGroup(
Dot(ORIGIN, radius=0.09, color=N_COLOR), # place at origin for this beat
Circle(radius=0.12, color=VACANCY_COLOR, stroke_width=4).shift(RIGHT * 0.25)
)
nv_group.move_to(ORIGIN)
red_label = Text("Red emission ~637 nm", font_size=28, color=EMIT_COLOR).to_edge(UP).shift(DOWN * 0.2)
self.play(FadeIn(nv_group), run_time=0.6)
# Emission pulses from NV center (from N position)
emitter_point = nv_group[0].get_center() # Dot at origin
pulse1 = Circle(radius=0.15, color=EMIT_COLOR, stroke_width=4).move_to(emitter_point)
pulse2 = Circle(radius=0.15, color=EMIT_COLOR, stroke_width=4).move_to(emitter_point)
self.play(pulse1.animate.scale(3.0), run_time=1.0)
self.play(FadeOut(pulse1), run_time=0.3)
self.play(pulse2.animate.scale(3.0), run_time=1.0)
self.play(FadeOut(pulse2), run_time=0.3)
self.play(FadeIn(red_label), run_time=0.6)
self.wait(2.6) # total 7.0 s
# -------------------------
# Scene 5: Real-World Applications (10 s)
# -------------------------
self.play(nv_group.animate.shift(LEFT * 3.0), run_time=0.6)
# Magnet icon (quantum sensing): simple U-shape from rectangles
bar_left = Rectangle(height=1.6, width=0.25, color=BLUE_E, fill_opacity=1).move_to(RIGHT * 2.2 + UP * 0.6)
bar_right = Rectangle(height=1.6, width=0.25, color=BLUE_E, fill_opacity=1).move_to(RIGHT * 3.0 + UP * 0.6)
bridge = Rectangle(height=0.25, width=0.8, color=BLUE_E, fill_opacity=1).move_to(RIGHT * 2.6 + UP * 1.4)
magnet = VGroup(bar_left, bar_right, bridge)
# Chip icon (quantum tech/computing)
chip_body = Rectangle(height=1.2, width=1.8, color=GREY_B, fill_opacity=0).move_to(RIGHT * 2.6 + DOWN * 1.0)
trace1 = Line(chip_body.get_left() + RIGHT * 0.15 + UP * 0.3, chip_body.get_right() - RIGHT * 0.15 + UP * 0.3,
color=TEAL_C, stroke_width=3)
trace2 = Line(chip_body.get_left() + RIGHT * 0.15, chip_body.get_right() - RIGHT * 0.15,
color=TEAL_C, stroke_width=3)
trace3 = Line(chip_body.get_left() + RIGHT * 0.15 + DOWN * 0.3, chip_body.get_right() - RIGHT * 0.15 + DOWN * 0.3,
color=TEAL_C, stroke_width=3)
chip = VGroup(chip_body, trace1, trace2, trace3)
# Arrows from NV to icons
arrow_to_magnet = Arrow(start=nv_group.get_right(), end=bar_left.get_left() + LEFT * 0.1, color=GREY_B, buff=0.1)
arrow_to_chip = Arrow(start=nv_group.get_right(), end=chip_body.get_left() + LEFT * 0.1, color=GREY_B, buff=0.1)
label_magnet = Text("Quantum sensing", font_size=26, color=BLUE_E).next_to(magnet, UP, buff=0.25)
label_chip = Text("Quantum tech & computing", font_size=26, color=TEAL_C).next_to(chip, DOWN, buff=0.25)
self.play(FadeIn(magnet), run_time=0.6)
self.play(FadeIn(chip), run_time=0.6)
self.play(GrowArrow(arrow_to_magnet), GrowArrow(arrow_to_chip), run_time=0.8)
self.play(FadeIn(label_magnet), FadeIn(label_chip), run_time=0.6)
self.wait(6.8) # total 10.0 s (Grand total 45.0 s)