Spaces:
Sleeping
Sleeping
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) |