文献阅读课需要制作ppt但是感觉选的这篇论文都是公式,决定做点动画直观展示一下。还没有完成会继续更新这个笔记
manim动画代码
- 需要下载ffmpeg
- 下载latex
https://docs.manim.org.cn/getting_started/installation.html
ffmpeg下载教程
texlive官网
但是其实不需要这两样也可以播放只是很丑陋。。。
论文的ppt做好了不知道怎么上传,后续整理成笔记完善一下这篇blog
概念 | 定义 | 噪声添加方式 | 优势 | 劣势 | ε 相关性 |
---|---|---|---|---|---|
差分隐私(DP,含 CDP) | 保护查询结果不泄露个体数据,通过中心化添加噪声 | 在查询结果或中间计算中添加噪声 | 提供严格数学隐私保证,数据效用较高,适合有可信中心的场景 | 需要信任数据收集者,存在数据泄露风险 | ε 小则隐私强,数据效用可能低 |
本地差分隐私(LDP) | 用户本地扰动数据后再发送,无需可信第三方 | 每个数据点独立添加噪声 | 无需信任中心,增强隐私,用户控制数据,适合分布式架构 | 数据效用低,需较多用户参与,准确性通常低于 DP | 常需高 ε 以平衡实用性 |
集中式差分隐私(CDP) | 可信管理者收集数据后添加噪声发布结果 | 在聚合数据后添加噪声 | 数据效用较高,适合需要高准确度的场景 | 需要信任管理者,存在单点故障风险 | 与 DP 类似,ε 影响隐私与效用 |
python
# -*- coding: utf-8 -*-
from manim import *
import numpy as np
config.background_color = "#121212" # Dark background
# ========================================================
# Helper Functions (Applying Fixes based on Analysis)
# ========================================================
def construct_intro(scene: Scene):
""" Constructs the Introduction part of the animation. """
# No issues reported here, code seems fine.
title = Text("PrivKV: Key-Value Data Collection with Local Differential Privacy", font_size=40)
title.to_edge(UP, buff=0.5)
authors = Text("Author: Paper Reproduction Demo", font_size=24, color=BLUE_C)
authors.next_to(title, DOWN, buff=0.5)
scene.play(Write(title))
scene.play(FadeIn(authors))
scene.wait(2)
problem_title = Text("Research Background", font_size=36, color=YELLOW)
problem_title.to_edge(UP, buff=1.0) # Keep UP edge positioning
problem_points = VGroup(
Text("• User data privacy protection is increasingly important", font_size=24),
Text("• Local Differential Privacy (LDP) does not require a trusted central party", font_size=24),
Text("• Existing work mainly focuses on simple data types", font_size=24),
Text("• Key-Value data is the main model for NoSQL", font_size=24),
Text("• Challenge: Correlation exists between keys and values", font_size=24)
).arrange(DOWN, aligned_edge=LEFT, buff=0.4)
# Ensure enough space below the title
problem_points.next_to(problem_title, DOWN, buff=MED_LARGE_BUFF) # Increased buff
scene.play(FadeOut(authors), TransformMatchingShapes(title, problem_title))
scene.play(FadeIn(problem_points, lag_ratio=0.2))
scene.wait(3)
solution_title = Text("Our Work", font_size=36, color=YELLOW)
solution_title.move_to(problem_title) # Keep position
solution_points = VGroup(
Text("• Proposed the first LDP solution for Key-Value data", font_size=24),
Text("• Designed Local Perturbation Protocol (LPP) to handle key-value correlation", font_size=24),
Text("• Developed an iterative scheme to improve accuracy (PrivKVM)", font_size=24),
Text("• Proposed an adaptive scheme to balance accuracy and communication (PrivKVM+)", font_size=24),
Text("• Optimization strategy: Virtual Iteration reduces network latency", font_size=24)
).arrange(DOWN, aligned_edge=LEFT, buff=0.4)
# Ensure consistent spacing
solution_points.next_to(solution_title, DOWN, buff=MED_LARGE_BUFF) # Match buff
scene.play(Transform(problem_title, solution_title),
FadeOut(problem_points, shift=DOWN*0.5),
FadeIn(solution_points, shift=DOWN*0.5))
scene.wait(3)
return VGroup(problem_title, solution_points)
def construct_ldp_explanation(scene: Scene):
""" Constructs the LDP Explanation part. """
# No specific issues reported, but ensure formula clarity
title = Text("Local Differential Privacy (LDP)", font_size=36)
title.to_edge(UP, buff=0.5)
scene.play(Write(title))
scene.wait(1)
ldp_def = VGroup(
# Use slightly larger fonts for better readability
MathTex(r"\text{For any two inputs } t, t' \in D \ \text{ and any output } t^* \text{:}", font_size=30),
MathTex(r"\Pr[M(t) = t^*] \leq e^{\varepsilon} \times \Pr[M(t') = t^*]", font_size=36) # Larger formula
).arrange(DOWN, buff=0.4) # Slightly more space
ldp_def.next_to(title, DOWN, buff=MED_LARGE_BUFF) # Consistent spacing
scene.play(Write(ldp_def))
scene.wait(2)
explanation = VGroup()
user_box = Rectangle(height=2.5, width=3, fill_color=BLUE_E, fill_opacity=0.3, stroke_color=BLUE)
user_text = Text("User", font_size=24).next_to(user_box, UP, buff=0.2)
user_data = MathTex(r"\text{Original data: } t").move_to(user_box)
collector_box = Rectangle(height=2.5, width=3, fill_color=RED_E, fill_opacity=0.3, stroke_color=RED).shift(RIGHT * 5)
collector_text = Text("Data Collector", font_size=24).next_to(collector_box, UP, buff=0.2)
collector_data = MathTex(r"\text{Perturbed data: } t^*").move_to(collector_box)
arrow = Arrow(user_box.get_right(), collector_box.get_left(), buff=0.1)
ldp_mech = Text("LDP Perturbation\nMechanism M", font_size=22, color=GREEN, line_spacing=0.8).next_to(arrow, UP, buff=0.1)
epsilon_text = MathTex(r"\text{Privacy budget: } \varepsilon").next_to(arrow, DOWN, buff=0.1)
recovery_text = Text("Data collector cannot determine the original data", font_size=20, color=YELLOW).next_to(collector_box, DOWN, buff=0.5)
explanation.add(
user_box, user_text, user_data,
collector_box, collector_text, collector_data,
arrow, ldp_mech, epsilon_text
)
explanation.next_to(ldp_def, DOWN, buff=LARGE_BUFF) # Ensure space below definition
scene.play(Create(user_box), Write(user_text), Write(user_data))
scene.play(Create(collector_box), Write(collector_text), Write(collector_data))
scene.play(GrowArrow(arrow), Write(ldp_mech), Write(epsilon_text))
scene.play(Write(recovery_text))
scene.wait(3)
ldp_features = VGroup(
Text("Key Features of LDP:", font_size=28, color=YELLOW),
Text("• Perturbation occurs on the user side, not on a central server", font_size=22),
Text("• Even if the data collector has all the data, original values cannot be recovered", font_size=22),
Text("• Privacy guarantee is controlled by parameter ε (smaller ε = stronger privacy)", font_size=22)
).arrange(DOWN, aligned_edge=LEFT, buff=0.3)
scene.play(
FadeOut(ldp_def),
FadeOut(explanation),
FadeOut(recovery_text)
)
ldp_features.next_to(title, DOWN, buff=MED_LARGE_BUFF) # Match buff
scene.play(Write(ldp_features, lag_ratio=0.2))
scene.wait(3)
return VGroup(title, ldp_features)
def construct_key_value_model(scene: Scene):
""" Constructs the Key-Value Data Model part. """
# Issue: Formulas potentially not displaying correctly.
# Fix: Ensure MathTex syntax is correct (already checked, seems okay).
# Verify add/play logic (seems okay). Check scale/position/color (seems okay).
# Add comment about checking LaTeX installation if issues persist.
title = Text("Key-Value Data Model", font_size=36)
title.to_edge(UP, buff=0.5)
scene.play(Write(title))
scene.wait(1)
# NOTE: If formulas below don't render, double-check LaTeX syntax and installation.
definition = VGroup(
Text("Definition:", font_size=28, color=YELLOW),
MathTex(r"\bullet \text{ Key space } K = \{1, 2, \dots, d\}", font_size=24),
MathTex(r"\bullet \text{ Value domain } V = [-1, 1]", font_size=24),
# This line uses correct LaTeX angle brackets and set notation braces
MathTex(r"\bullet \text{ User } i \text{ owns a set } S_i = \{\langle k_j, v_j \rangle\}", font_size=24),
MathTex(r"\bullet \text{ Key } k_j \in K, \text{ Value } v_j \in V", font_size=24)
).arrange(DOWN, aligned_edge=LEFT, buff=0.3)
definition.next_to(title, DOWN, buff=MED_LARGE_BUFF) # Consistent spacing
scene.play(FadeIn(definition, lag_ratio=0.2))
scene.wait(2)
example_title = Text("Real-world Examples", font_size=28, color=YELLOW)
example_title.next_to(definition, DOWN, buff=LARGE_BUFF) # More space before examples
example1 = VGroup(
Text("Ex 1: Video Ad Performance", font_size=24, color=BLUE),
Text("• Key: Ad ID", font_size=22),
Text("• Value: Viewing duration", font_size=22),
Text("• Goal: Analyze ad popularity privately", font_size=22)
).arrange(DOWN, aligned_edge=LEFT, buff=0.2)
example2 = VGroup(
Text("Ex 2: Mobile App Activity", font_size=24, color=GREEN),
Text("• Key: App ID", font_size=22),
Text("• Value: Usage time/frequency", font_size=22),
Text("• Goal: Analyze app usage privately", font_size=22)
).arrange(DOWN, aligned_edge=LEFT, buff=0.2)
examples = VGroup(example1, example2).arrange(RIGHT, buff=1.5) # Increase buff for separation
examples.next_to(example_title, DOWN, buff=MED_LARGE_BUFF) # Consistent spacing
scene.play(Write(example_title))
scene.play(FadeIn(examples, lag_ratio=0.2))
scene.wait(3)
return VGroup(title, definition, example_title, examples)
def construct_estimation_goals(scene: Scene):
""" Constructs the Estimation Goals part. """
# Issue: Formulas potentially not displaying. Same checks as above apply.
title = Text("Statistical Goals", font_size=36)
title.to_edge(UP, buff=0.5)
scene.play(Write(title))
scene.wait(1)
freq_title = Text("1. Frequency Estimation", font_size=28, color=BLUE)
freq_title.next_to(title, DOWN, buff=MED_LARGE_BUFF) # Consistent spacing
# NOTE: If formulas below don't render, double-check LaTeX syntax and installation.
freq_formula = MathTex(
r"f_k = \frac{|\{u_i \mid \exists \langle k, v \rangle \in S_i\}|}{n}", font_size=30 # Slightly larger
)
freq_formula.next_to(freq_title, DOWN, buff=0.4)
freq_explanation = Text("Proportion of users having key k", font_size=22)
freq_explanation.next_to(freq_formula, DOWN, buff=0.3)
scene.play(Write(freq_title))
scene.play(Write(freq_formula))
scene.play(FadeIn(freq_explanation))
scene.wait(2)
mean_title = Text("2. Mean Estimation", font_size=28, color=GREEN)
mean_title.next_to(freq_explanation, DOWN, buff=LARGE_BUFF) # More vertical space
# NOTE: If formulas below don't render, double-check LaTeX syntax and installation.
mean_formula = MathTex(
# Use \cdot for multiplication clarity if needed
r"m_k = \frac{\sum_{i} \sum_{j:k_j=k} v_j}{n \cdot f_k}", font_size=30 # Slightly larger
)
mean_formula.next_to(mean_title, DOWN, buff=0.4)
mean_explanation = Text("Average value for key k across relevant users", font_size=22)
mean_explanation.next_to(mean_formula, DOWN, buff=0.3)
scene.play(Write(mean_title))
scene.play(Write(mean_formula))
scene.play(FadeIn(mean_explanation))
scene.wait(2)
challenges_title = Text("Main Challenge", font_size=28, color=RED)
# Position near the bottom clearly
challenges_title.to_edge(DOWN, buff=1.5)
challenges = Text("Key-Value Correlation: Perturbing key affects value interpretation", font_size=24)
challenges.next_to(challenges_title, UP, buff=0.3) # Position above the title
scene.play(Write(challenges)) # Write text first
scene.play(Write(challenges_title)) # Then title below it
scene.wait(3)
return VGroup(title, freq_title, freq_formula, freq_explanation,
mean_title, mean_formula, mean_explanation,
challenges_title, challenges)
def construct_kv_challenge(scene: Scene):
""" Constructs the Key-Value Perturbation Challenge part. """
# No specific issues reported, layout seems okay. Ensure enough spacing.
title = Text("Key-Value Correlation Challenge", font_size=36)
title.to_edge(UP, buff=0.5)
scene.play(Write(title))
scene.wait(1)
problem_text = VGroup(
Text("Problem:", font_size=28, color=YELLOW),
Text("How to perturb key-value pairs while preserving meaning?", font_size=24, line_spacing=0.8)
).arrange(DOWN, aligned_edge=LEFT, buff=0.3)
problem_text.next_to(title, DOWN, buff=MED_LARGE_BUFF) # Consistent spacing
scene.play(FadeIn(problem_text))
scene.wait(2)
naive_title = Text("Incorrect Approach: Perturb Key and Value Independently", font_size=28, color=RED)
naive_title.next_to(problem_text, DOWN, buff=LARGE_BUFF) # More space
original_data = VGroup(
Text("Original User Data:", font_size=24),
MathTex(r"S_i = \{\langle \text{Cancer}, 0.6 \rangle, \langle \text{HIV}, 0.9 \rangle, \dots\}", font_size=26)
).arrange(DOWN, buff=0.3)
original_data.next_to(naive_title, DOWN, buff=MED_LARGE_BUFF) # Consistent spacing
perturbation_flow = VGroup(
MathTex(r"\text{Original Pair: } \langle \text{Cancer}, 0.6 \rangle"),
MathTex(r"\Downarrow \text{ Independent Perturbation } \Downarrow"),
MathTex(r"\text{Perturbed Pair: } \langle \text{Fever}, 0.75 \rangle \quad (\text{Key: Cancer} \to \text{Fever, Value: } 0.6 \to 0.75)")
).arrange(DOWN, buff=0.3)
perturbation_flow.next_to(original_data, DOWN, buff=MED_LARGE_BUFF) # Consistent spacing
problem_explanation = VGroup(
Text("Issue:", font_size=24, color=RED),
Text("Value 0.75 may be meaningless for the key 'Fever'", font_size=22),
Text("Correlation is lost!", font_size=22)
).arrange(DOWN, buff=0.2)
problem_explanation.next_to(perturbation_flow, DOWN, buff=MED_LARGE_BUFF) # Consistent spacing
scene.play(Write(naive_title))
scene.play(FadeIn(original_data))
scene.play(Write(perturbation_flow))
scene.play(FadeIn(problem_explanation))
scene.wait(3)
scene.play(
FadeOut(naive_title), FadeOut(original_data),
FadeOut(perturbation_flow), FadeOut(problem_explanation)
)
solution_text = VGroup(
Text("Required Solution:", font_size=28, color=GREEN),
Text("• Maintain key-value correlation during perturbation", font_size=24),
Text("• Satisfy ε-local differential privacy", font_size=24),
Text("• Enable accurate frequency and mean estimation", font_size=24)
).arrange(DOWN, aligned_edge=LEFT, buff=0.3)
# Position relative to problem_text to ensure spacing
solution_text.next_to(problem_text, DOWN, buff=LARGE_BUFF)
scene.play(FadeIn(solution_text, lag_ratio=0.2))
scene.wait(3)
return VGroup(title, problem_text, solution_text)
def construct_vpp_algorithm(scene: Scene):
""" Constructs the VPP Algorithm part. """
# Issue: VPP Title Overlapping Content.
# Fix: Ensure algorithm_group is positioned relative to title with enough buffer.
# Issue: Potential for white blocks if MathTex fails. Check formulas.
# --- Store the original title ---
original_vpp_title = Text("Value Perturbation Primitive (VPP)", font_size=36)
original_vpp_title.to_edge(UP, buff=0.5)
scene.play(Write(original_vpp_title))
scene.wait(1)
# --- Algorithm description part ---
algorithm_box = Rectangle(height=5.5, width=9, color=GREEN)
algorithm_title = Text("Algorithm 2: Value Perturbation Primitive (VPP)", font_size=28)
algorithm_title.next_to(algorithm_box, UP, buff=0.2)
# ... (inputs, outputs, steps, content_group definition remains the same) ...
inputs = VGroup(
Text("Input:", font_size=24, color=YELLOW),
MathTex(r"\text{- Value } v \in [-1, 1]", font_size=22),
MathTex(r"\text{- Privacy budget } \varepsilon", font_size=22)
).arrange(DOWN, aligned_edge=LEFT, buff=0.1)
outputs = VGroup(
Text("Output:", font_size=24, color=YELLOW),
MathTex(r"\text{- Perturbed value } v^* \in \{-1, 1\}", font_size=22)
).arrange(DOWN, aligned_edge=LEFT, buff=0.1)
steps = VGroup(
MathTex(r"1.~\text{Discretize to } \{-1, 1\}:", font_size=22),
MathTex(r"~~~v_d = 1, \quad \text{w.p. } \frac{1+v}{2}", font_size=22), # Use w.p. for clarity
MathTex(r"~~~v_d = -1, \quad \text{w.p. } \frac{1-v}{2}", font_size=22),
MathTex(r"2.~\text{Randomized Response (perturb):}", font_size=22),
MathTex(r"~~~v^* = v_d, \quad \text{w.p. } \frac{e^\varepsilon}{1+e^\varepsilon}", font_size=22),
MathTex(r"~~~v^* = -v_d, \quad \text{w.p. } \frac{1}{1+e^\varepsilon}", font_size=22),
MathTex(r"3.~\text{Return } v^*", font_size=22)
).arrange(DOWN, aligned_edge=LEFT, buff=0.15) # Slightly more buff
content_group = VGroup(inputs, outputs, steps).arrange(DOWN, buff=0.4, aligned_edge=LEFT)
content_group.move_to(algorithm_box.get_center()).shift(LEFT * 0.5)
# --- End algorithm description part ---
algorithm_group = VGroup(algorithm_box, algorithm_title, content_group)
# Position explicitly below the *original* title with large buffer
algorithm_group.scale(0.9).next_to(original_vpp_title, DOWN, buff=LARGE_BUFF)
scene.play(FadeIn(algorithm_group))
scene.wait(4)
scene.play(FadeOut(algorithm_group)) # Fade out the algorithm description
# --- Example part ---
example_title = Text("VPP Example: v = 0.7, ε = 1.0", font_size=32)
# Position consistently where the original title was
example_title.to_edge(UP, buff=0.5)
# Replace original title with example title smoothly
scene.play(ReplacementTransform(original_vpp_title, example_title)) # Transform instead of just Write
original_value = MathTex(r"\text{Original value: } v = 0.7", font_size=30)
original_value.next_to(example_title, DOWN, buff=MED_LARGE_BUFF)
scene.play(Write(original_value))
scene.wait(1)
# ... (Discretization and Perturbation steps definition remains the same) ...
discretization_title = Text("Step 1: Discretization", font_size=28)
discretization_title.next_to(original_value, DOWN, buff=MED_LARGE_BUFF)
p_pos = (1 + 0.7) / 2
p_neg = (1 - 0.7) / 2
discretization_calc = VGroup(
MathTex(fr"P(v_d = 1) = \frac{{1+0.7}}{{2}} = {p_pos:.2f}", font_size=24),
MathTex(fr"P(v_d = -1) = \frac{{1-0.7}}{{2}} = {p_neg:.2f}", font_size=24)
).arrange(DOWN, buff=0.3)
discretization_calc.next_to(discretization_title, DOWN, buff=0.3)
scene.play(Write(discretization_title))
scene.play(Write(discretization_calc))
scene.wait(2)
perturbation_title = Text("Step 2: Perturbation (Rand. Resp.)", font_size=28)
perturbation_title.next_to(discretization_calc, DOWN, buff=LARGE_BUFF) # More space
p_keep = np.exp(1.0) / (1 + np.exp(1.0))
p_flip = 1 / (1 + np.exp(1.0))
perturbation_calc = VGroup(
MathTex(fr"P(\text{{Keep }} v_d) = \frac{{e^1}}{{1+e^1}} \approx {p_keep:.2f}", font_size=24),
MathTex(fr"P(\text{{Flip }} v_d) = \frac{1}{{1+e^1}} \approx {p_flip:.2f}", font_size=24)
).arrange(DOWN, buff=0.3)
perturbation_calc.next_to(perturbation_title, DOWN, buff=0.3)
scene.play(Write(perturbation_title))
scene.play(Write(perturbation_calc))
scene.wait(2)
# --- End Example part ---
conclusion = Text("Result v* is either 1 or -1, satisfying ε-LDP", font_size=28, color=BLUE)
conclusion.next_to(perturbation_calc, DOWN, buff=LARGE_BUFF)
scene.play(Write(conclusion))
scene.wait(3)
# --- Fix: Fade out ALL elements of this section, including the example title ---
elements_to_fade = VGroup(
example_title, original_value, # Use the example_title which replaced the original one
discretization_title, discretization_calc,
perturbation_title, perturbation_calc,
conclusion
)
scene.play(FadeOut(elements_to_fade))
# --- End Fix ---
# Return an empty group as all content specific to VPP is now faded out internally.
return VGroup()
def construct_lpp_algorithm(scene: Scene):
""" Constructs the LPP Algorithm part. """
# No specific issues reported, layout seems okay.
title = Text("Local Perturbation Protocol (LPP)", font_size=36)
title.to_edge(UP, buff=0.5)
scene.play(Write(title))
scene.wait(1)
lpp_summary = VGroup(
Text("Algorithm 3: Local Perturbation Protocol (LPP)", font_size=28, color=YELLOW),
MathTex(r"\text{Handles one pair } \langle k, v \rangle \text{ with budget } \varepsilon = \varepsilon_1 + \varepsilon_2", font_size=22), # Use MathTex
Text("1. Perturb Key (k → j) using mechanism M₁ (budget ε₁)", font_size=22),
Text("2. IF key is kept (k = j):", font_size=22),
Text(" Perturb Value (v → v*) using VPP (budget ε₂)", font_size=22),
Text("3. IF key is changed (k ≠ j):", font_size=22),
Text(" Assign New Value (v* = ±1 uniformly) (implicit budget ε₂)", font_size=22),
MathTex(r"4. \text{Output: } (j, \langle k, v^* \rangle) \quad \text{(Original key k included!)}", font_size=22, color=GREEN) # Use MathTex
).arrange(DOWN, aligned_edge=LEFT, buff=0.25)
lpp_summary.next_to(title, DOWN, buff=MED_LARGE_BUFF).scale(0.95) # Add scaling
scene.play(FadeIn(lpp_summary, lag_ratio=0.1))
scene.wait(5)
privacy_title = Text("LPP Privacy Guarantee", font_size=32, color=GREEN)
privacy_theorem = MathTex(
r"\text{Theorem 4.1: LPP satisfies } (\varepsilon_1 + \varepsilon_2)\text{-LDP}", font_size=28)
privacy_group = VGroup(privacy_title, privacy_theorem).arrange(DOWN, buff=0.4)
# Position near bottom, leaving space above
privacy_group.to_edge(DOWN, buff=LARGE_BUFF)
scene.play(FadeOut(lpp_summary, shift=UP*0.5), FadeIn(privacy_group, shift=UP*0.5))
scene.wait(3)
return VGroup(title, privacy_group)
def construct_privkv_algorithm(scene: Scene):
""" Constructs the PrivKV Algorithm Flow part. """
# Issue: privkv algorithm flow exceeding boundaries / flowchart
# Fix: Scale down the workflow_group. Ensure positioning allows space.
# Fix: Reduce font size within workflow boxes if necessary.
title = Text("PrivKV Algorithm Flow", font_size=40)
title.to_edge(UP, buff=0.5)
scene.play(Write(title))
scene.wait(1)
privkv_summary = VGroup(
Text("Algorithm 4: PrivKV (Basic)", font_size=28, color=YELLOW),
Text("User Side (for each user i):", font_size=24),
# --- Fix: Use MathTex for angle brackets ---
MathTex(r"\bullet \text{ For each } \langle k, v \rangle \text{ in } S_i:", font_size=22),
MathTex(r" \bullet \text{ Apply } LPP(k, v, \varepsilon_1, \varepsilon_2) \to \text{ get } (j, \langle k, v^* \rangle)", font_size=22),
# --- End Fix ---
Text(" • Send all perturbed results to server", font_size=22),
Text("Server Side:", font_size=24),
# --- Fix: Use MathTex for formula elements ---
MathTex(r"\bullet \text{ Collect all perturbed data}", font_size=22),
MathTex(r"\bullet \text{ Estimate frequencies } (\hat{f}_k) \text{ and means } (\hat{m}_k) \text{ using calibration}", font_size=22),
MathTex(r"\bullet \text{ Perform outlier correction}", font_size=22)
# --- End Fix ---
).arrange(DOWN, aligned_edge=LEFT, buff=0.2) # Adjust buff as needed
# Position and scale summary
privkv_summary.next_to(title, DOWN, buff=MED_LARGE_BUFF).scale(0.9)
scene.play(FadeIn(privkv_summary, lag_ratio=0.1))
scene.wait(5)
scene.play(FadeOut(privkv_summary))
flow_title = Text("PrivKV Workflow", font_size=32)
flow_title.to_edge(UP, buff=0.8) # Position relative to top edge
# Components for workflow visualization - Reducing font size further
internal_font_size = 16 # Smaller font inside boxes
users_box = Rectangle(width=4, height=3, fill_color=BLUE_E, fill_opacity=0.2)
users_title = Text("User i", font_size=24).next_to(users_box, UP, buff=0.2)
user_text = VGroup(
# --- Fix: Use MathTex for the line with angle brackets ---
MathTex(r"\text{Owns } S_i = \{\langle k, v \rangle, \dots \}", font_size=internal_font_size),
# --- End Fix ---
Text("Applies LPP to each pair", font_size=internal_font_size), # This line is plain text, so Text is fine
MathTex(r"\text{Sends: } \{(j_1, \langle k_1, v_1^* \rangle), \dots\}", font_size=internal_font_size)
# Already correctly using MathTex
).arrange(DOWN, aligned_edge=LEFT, buff=0.15).move_to(users_box) # Tighter buff
collector_box = Rectangle(width=4, height=3, fill_color=RED_E, fill_opacity=0.2)
collector_title = Text("Data Collector", font_size=24).next_to(collector_box, UP, buff=0.2) # Keep title size reasonable
collector_text = VGroup(
Text("Receives data from N users", font_size=internal_font_size),
MathTex(r"\text{Calibrates freq. } \hat{f}_k", font_size=internal_font_size), # Use MathTex
MathTex(r"\text{Calibrates mean } \hat{m}_k", font_size=internal_font_size), # Use MathTex
Text("Corrects outliers", font_size=internal_font_size)
).arrange(DOWN, aligned_edge=LEFT, buff=0.15).move_to(collector_box) # Tighter buff
# Group boxes and position them first
box_group = VGroup(
VGroup(users_box, users_title, user_text).shift(LEFT * 3.5),
VGroup(collector_box, collector_title, collector_text).shift(RIGHT * 3.5)
)
# Create arrow between the *final* positions of the boxes
data_arrow = Arrow(users_box.get_right(), collector_box.get_left(), buff=0.2)
data_text = MathTex(r"(j, \langle k, v^* \rangle)", font_size=20).next_to(data_arrow, UP, buff=0.1) # Smaller MathTex
ldp_protection = Text("ε-LDP Protection", font_size=18, color=GREEN).next_to(data_arrow, DOWN, buff=0.1) # Smaller text
workflow_group = VGroup(box_group, data_arrow, data_text, ldp_protection)
# Fix: Scale down the entire workflow group and position it below the flow_title
workflow_group.scale(0.85).next_to(flow_title, DOWN, buff=MED_LARGE_BUFF)
scene.play(ReplacementTransform(title, flow_title)) # Transform original title
# Animate boxes and text together
scene.play(
Create(users_box), Write(users_title),
Create(collector_box), Write(collector_title),
run_time=1.5
)
scene.play(
Write(user_text), Write(collector_text),
run_time=1.5
)
scene.play(GrowArrow(data_arrow), Write(data_text), Write(ldp_protection))
scene.wait(3)
# We need to return the elements that should be faded out by the main construct loop
return VGroup(flow_title, workflow_group) # Return the current visible elements
def construct_privkvm_algorithm(scene: Scene):
""" Constructs the PrivKVM Algorithm part. """
# No specific issues reported, layout seems okay. Check MathTex.
title = Text("PrivKVM: Iterative PrivKV", font_size=40)
title.to_edge(UP, buff=0.5)
scene.play(Write(title))
scene.wait(1)
privkvm_summary = VGroup(
Text("Algorithm 5: PrivKVM (Iterative)", font_size=28, color=YELLOW),
Text("Goal: Improve mean estimation accuracy via iteration.", font_size=24),
Text("Key Idea:", font_size=24),
Text(" • Run PrivKV multiple times (c iterations).", font_size=22),
Text(" • In each iteration r > 1:", font_size=22),
MathTex(r" \bullet \text{ Server sends previous mean estimate } \hat{m}^{(r-1)} \text{ back to users.}", font_size=20), # MathTex
MathTex(r" \bullet \text{ User uses } \hat{m}^{(r-1)} \text{ to adjust value perturbation (focus noise).}", font_size=20), # MathTex
Text(" • Server aggregates results from final iteration.", font_size=22)
).arrange(DOWN, aligned_edge=LEFT, buff=0.2)
privkvm_summary.next_to(title, DOWN, buff=MED_LARGE_BUFF).scale(0.9)
scene.play(FadeIn(privkvm_summary, lag_ratio=0.1))
scene.wait(5)
scene.play(FadeOut(privkvm_summary))
theory_title = Text("PrivKVM Guarantees", font_size=32)
theory_title.next_to(title, DOWN, buff=MED_LARGE_BUFF) # Position relative to main title space
# NOTE: Check formulas below for potential LaTeX errors
theory_text = VGroup(
MathTex(r"\bullet \text{ Satisfies } \varepsilon\text{-LDP (Thm 5.2)}", font_size=26, color=BLUE),
# Clarify limit notation
MathTex(r"\bullet \text{ Converges: } \lim_{c \to \infty} \hat{m}^{(c)}_k = m_k \text{ (Thm 5.3)}", font_size=26, color=GREEN),
MathTex(r"\bullet \text{ Accuracy improves with iterations (Thm 5.4)}", font_size=26, color=RED)
).arrange(DOWN, aligned_edge=LEFT, buff=0.4)
theory_text.next_to(theory_title, DOWN, buff=MED_LARGE_BUFF)
scene.play(Write(theory_title))
scene.play(Write(theory_text, lag_ratio=0.2))
scene.wait(4)
return VGroup(title, theory_title, theory_text)
def construct_privkvmplus_algorithm(scene: Scene):
""" Constructs the PrivKVM+ Algorithm part. """
# No specific issues reported, layout seems okay. Check MathTex.
title = Text("PrivKVM+: Adaptive PrivKVM", font_size=40)
title.to_edge(UP, buff=0.5)
scene.play(Write(title))
scene.wait(1)
privkvmplus_summary = VGroup(
Text("Algorithm 6: PrivKVM+ (Adaptive)", font_size=28, color=YELLOW),
Text("Problem: PrivKVM uses a fixed number of iterations (c).", font_size=24),
Text("Goal: Stop iterating adaptively when cost outweighs benefit.", font_size=24),
Text("Key Idea:", font_size=24),
MathTex(r" \bullet \text{ Define a Cost Function } F(r) = F_1(r) + F_2(r)", font_size=22), # MathTex
Text(" • F₁(r): Accuracy Cost (decreases with iteration r)", font_size=20),
Text(" • F₂(r): Communication Cost (increases with iteration r)", font_size=20),
MathTex(r" \bullet \text{ Termination Condition: Stop at iteration } r \text{ if } F(r) \ge F(r-1)", font_size=22), # MathTex
Text(" (Stop when total cost starts increasing)", font_size=20),
).arrange(DOWN, aligned_edge=LEFT, buff=0.2)
privkvmplus_summary.next_to(title, DOWN, buff=MED_LARGE_BUFF).scale(0.9)
scene.play(FadeIn(privkvmplus_summary, lag_ratio=0.1))
scene.wait(5)
scene.play(FadeOut(privkvmplus_summary))
adaptive_title = Text("PrivKVM+ Adaptive Termination", font_size=36)
adaptive_title.next_to(title, DOWN, buff=MED_LARGE_BUFF) # Reposition
# NOTE: Check formulas below for potential LaTeX errors
cost_group = VGroup(
MathTex(r"\text{Total Cost: } F(r) = \underbrace{F_1(r)}_{\text{Accuracy}} + \underbrace{F_2(r)}_{\text{Communication}}", font_size=30),
Text("Balances accuracy gain vs. communication overhead", font_size=22)
).arrange(DOWN, buff=0.4)
cost_group.next_to(adaptive_title, DOWN, buff=MED_LARGE_BUFF)
termination_group = VGroup(
Text("Termination Condition:", font_size=28, color=YELLOW),
MathTex(r"\text{Stop at iteration } r \text{ if } F(r) - F(r-1) \ge 0", font_size=28),
Text("(Stop when cost function stops decreasing)", font_size=22)
).arrange(DOWN, buff=0.3)
termination_group.next_to(cost_group, DOWN, buff=LARGE_BUFF) # More space
scene.play(Write(adaptive_title))
scene.play(Write(cost_group))
scene.wait(2)
scene.play(Write(termination_group))
scene.wait(3)
end_elements = VGroup(adaptive_title, cost_group, termination_group)
return VGroup(title, end_elements)
def construct_virtual_iterations(scene: Scene):
""" Constructs the Virtual Iterations part. """
# No specific issues reported, but layout could be tight. Ensure spacing.
title = Text("Virtual Iteration Optimization", font_size=40)
title.to_edge(UP, buff=0.5)
scene.play(Write(title))
scene.wait(1)
virtual_summary = VGroup(
Text("Algorithm 7: Virtual Iteration", font_size=28, color=YELLOW),
Text("Problem: PrivKVM/PrivKVM+ require multiple communication rounds.", font_size=24),
Text("Goal: Reduce network latency while approximating iterative results.", font_size=24),
Text("Key Idea:", font_size=24),
Text(" • User sends only first iteration data (like PrivKV).", font_size=22),
MathTex(r" \bullet \text{ Server calculates first iteration mean } \hat{m}^{(1)}.", font_size=22), # MathTex
MathTex(r" \bullet \text{ Server *predicts* means for subsequent iterations } (\hat{m}^{(2)}, \dots, \hat{m}^{(c)})", font_size=22), # MathTex
MathTex(r" \text{using } \hat{m}^{(1)} \text{ and a convergence parameter } \theta.", font_size=22), # MathTex continuation
Text(" • Achieves similar accuracy with only ONE round of communication.", font_size=22, color=GREEN)
).arrange(DOWN, aligned_edge=LEFT, buff=0.2)
virtual_summary.next_to(title, DOWN, buff=MED_LARGE_BUFF).scale(0.9)
scene.play(FadeIn(virtual_summary, lag_ratio=0.1))
scene.wait(5)
scene.play(FadeOut(virtual_summary))
comparison_title = Text("Real vs. Virtual Iteration", font_size=36)
comparison_title.next_to(title, DOWN, buff=MED_LARGE_BUFF) # Reposition
# Visualization using simple boxes and arrows - increase spacing
iter_box_width = 3.5 # Slightly wider boxes
iter_box_height = 1.0
real_iter_group = VGroup(
Text("Real Iteration (PrivKVM)", font_size=24, color=RED),
Rectangle(width=iter_box_width, height=iter_box_height, color=RED).add(Text("Round 1", font_size=18)),
Arrow(UP*0.5, DOWN*0.5, color=RED),
Rectangle(width=iter_box_width, height=iter_box_height, color=RED).add(Text("Round 2", font_size=18)),
Arrow(UP*0.5, DOWN*0.5, color=RED),
Text("...", font_size=24, color=RED),
Arrow(UP*0.5, DOWN*0.5, color=RED),
Rectangle(width=iter_box_width, height=iter_box_height, color=RED).add(Text("Round c", font_size=18)),
Text("High Communication Cost", font_size=18, color=RED)
).arrange(DOWN, buff=0.25).shift(LEFT * 4.0) # Increase shift and buff
virtual_iter_group = VGroup(
Text("Virtual Iteration", font_size=24, color=GREEN),
Rectangle(width=iter_box_width, height=iter_box_height, color=GREEN).add(Text("Round 1", font_size=18)),
Arrow(UP*0.5, DOWN*0.5, color=GREEN),
Rectangle(width=iter_box_width, height=1.5, color=GREEN).add(VGroup(Text("Server Prediction", font_size=18), Text("(Iter 2...c)", font_size=16)).arrange(DOWN)),
Text("Low Communication Cost", font_size=18, color=GREEN)
).arrange(DOWN, buff=0.25).shift(RIGHT * 4.0) # Increase shift and buff
divider = Line(UP*3, DOWN*2.5).shift(DOWN*0.2) # Adjust length/position
comparison_placeholder = VGroup(real_iter_group, virtual_iter_group, divider)
comparison_placeholder.scale(0.9).next_to(comparison_title, DOWN, buff=MED_LARGE_BUFF) # Scale down slightly
scene.play(Write(comparison_title))
scene.play(Write(comparison_placeholder))
scene.wait(4)
# NOTE: Check formula below for potential LaTeX errors
prediction_title = Text("Mean Prediction Formula (Conceptual)", font_size=28)
prediction_formula = MathTex(r"\hat{m}^{(r)}_k \approx \text{Predict}(\hat{m}^{(1)}_k, \theta, r)", font_size=24)
prediction_group = VGroup(prediction_title, prediction_formula).arrange(DOWN)
# Position near bottom edge
prediction_group.to_edge(DOWN, buff=MED_SMALL_BUFF)
scene.play(Write(prediction_group))
scene.wait(3)
end_elements = VGroup(comparison_title, comparison_placeholder, prediction_group)
return VGroup(title, end_elements)
def construct_theoretical_analysis(scene: Scene):
""" Constructs the Theoretical Analysis summary part. """
# No specific issues reported, layout seems okay. Check MathTex.
title = Text("Theoretical Analysis Summary", font_size=40)
title.to_edge(UP, buff=0.5)
scene.play(Write(title))
scene.wait(1)
# NOTE: Check formulas below for potential LaTeX errors
analysis_summary = VGroup(
Text("Privacy Guarantee (ε-LDP):", font_size=30, color=BLUE),
MathTex(r"\bullet \text{ LPP satisfies } (\varepsilon_1 + \varepsilon_2)\text{-LDP (Thm 4.1)}", font_size=24), # MathTex
MathTex(r"\bullet \text{ PrivKVM satisfies } \varepsilon\text{-LDP (Thm 5.2)}", font_size=24), # MathTex
# --- Fix: Replace Spacer with an invisible Rectangle ---
Rectangle(height=0.3, width=0.01, stroke_opacity=0, fill_opacity=0), # Add spacer for visual separation
# --- End Fix ---
Text("Convergence Guarantee (PrivKVM):", font_size=30, color=GREEN),
MathTex(r"\bullet \text{ Mean estimate converges to true mean as iterations } \to \infty \text{ (Thm 5.3)}", font_size=24), # MathTex
# --- Fix: Replace Spacer with an invisible Rectangle ---
Rectangle(height=0.3, width=0.01, stroke_opacity=0, fill_opacity=0), # Add spacer
# --- End Fix ---
Text("Accuracy Guarantee (PrivKVM):", font_size=30, color=RED),
Text(" • Error bound provided for mean estimate (MSE)", font_size=24),
MathTex(r"\bullet \text{ Depends on } n \text{ (users), } d \text{ (keys), } \varepsilon \text{ (privacy), } c \text{ (iterations) (Thm 5.4)}", font_size=24), # MathTex
).arrange(DOWN, aligned_edge=LEFT, buff=0.3) # Adjust spacing
analysis_summary.next_to(title, DOWN, buff=MED_LARGE_BUFF).scale(0.95)
scene.play(Write(analysis_summary, lag_ratio=0.1))
scene.wait(5)
return VGroup(title, analysis_summary)
def construct_comparison(scene: Scene):
""" Constructs the Comparison of PrivKV Methods part. """
# Issue: Virtual Iteration Comparison Chart issues (clarity/missing steps).
# Fix: Ensure labels are clear, positioned well. Consider slightly longer waits or staged appearance.
# Fix: The RIGHT vs R issue is already corrected in the provided code.
title = Text("Comparison of PrivKV Methods", font_size=40)
title.to_edge(UP, buff=0.5)
scene.play(Write(title))
scene.wait(1)
# Table section seems okay, styling applied.
table_data = [
["Method", "Iterations", "Communication", "Key Feature"],
["PrivKV", "1", "1 Round", "Basic, no downlink"],
["PrivKVM", "Fixed (c)", "c Rounds", "Higher accuracy"],
["PrivKVM+", "Adaptive (r ≤ c)", "r Rounds", "Balances Acc/Comm"],
["+ Virtual Iter.", "(c predicted)", "1 Round", "Low latency approx."]
]
comparison_table = Table(
table_data, include_outer_lines=True,
line_config={"stroke_width": 1, "color": WHITE},
).scale(0.55)
comparison_table.next_to(title, DOWN, buff=MED_LARGE_BUFF) # Consistent spacing
comparison_table.get_rows()[0].set_color(YELLOW)
if len(comparison_table.get_horizontal_lines()) > 1:
comparison_table.get_horizontal_lines()[1].set_stroke(color=YELLOW, width=3)
comparison_table.get_horizontal_lines().set_color(WHITE)
comparison_table.get_vertical_lines().set_color(WHITE)
if len(comparison_table.get_horizontal_lines()) > 1:
comparison_table.get_horizontal_lines()[1].set_stroke(color=YELLOW, width=3)
comparison_table.get_columns()[1].set_color(BLUE_C)
comparison_table.get_columns()[2].set_color(GREEN_C)
comparison_table.get_columns()[3].set_color(RED_C)
scene.play(FadeIn(comparison_table))
scene.wait(5)
scene.play(FadeOut(comparison_table))
# Chart Section - ensure clarity
performance_title = Text("Performance Comparison (Conceptual)", font_size=36)
performance_title.next_to(title, DOWN, buff=MED_LARGE_BUFF) # Reposition
axes = Axes(
x_range=[0, 5.1, 1], y_range=[0, 1.21, 0.2], # Extend range slightly
x_length=7, y_length=4.5,
axis_config={"include_tip": True, "numbers_to_include": np.arange(1, 6, 1)},
y_axis_config={"numbers_to_include": np.arange(0.2, 1.3, 0.2)}
).shift(DOWN * 0.5)
# Use MathTex for epsilon label
x_label = axes.get_x_axis_label(MathTex(r"\varepsilon \text{ (Privacy Budget)}"), edge=DOWN, direction=DOWN)
# Ensure y-label is clear and doesn't overlap axis numbers
y_label = axes.get_y_axis_label(Text("Accuracy (e.g., 1 - MSE)", font_size=20), edge=LEFT, direction=LEFT, buff=0.5) # Increased buff
# Curves and Labels - adjust positioning slightly if needed
curve_privkv = axes.plot(lambda x: 0.8 * (1 - np.exp(-0.5 * x)), x_range=[0.1, 5], color=RED)
label_privkv = MathTex("PrivKV", color=RED, font_size=24).next_to(curve_privkv.get_point_from_function(4.5), UR, buff=0.2) # Position near x=4.5, larger buff
curve_privkvm = axes.plot(lambda x: 1.0 * (1 - np.exp(-0.7 * x)), x_range=[0.1, 5], color=GREEN)
label_privkvm = MathTex("PrivKVM / +", color=GREEN, font_size=24).next_to(curve_privkvm.get_end(), UR, buff=0.2) # Larger buff
curve_virtual = axes.plot(lambda x: 0.95 * (1 - np.exp(-0.65 * x)), x_range=[0.1, 5], color=BLUE)
# Use get_point_from_function for more control if needed, ensure buff is sufficient
label_virtual = MathTex("Virtual Iter.", color=BLUE, font_size=24).next_to(curve_virtual.get_end(), RIGHT, buff=0.2) # Larger buff
chart_group = VGroup(axes, x_label, y_label,
curve_privkv, label_privkv,
curve_privkvm, label_privkvm,
curve_virtual, label_virtual)
chart_group.next_to(performance_title, DOWN, buff=0.5) # Increased buff
scene.play(Write(performance_title))
scene.play(Create(axes), Write(x_label), Write(y_label))
scene.wait(0.5) # Small pause
scene.play(Create(curve_privkv), Write(label_privkv))
scene.wait(0.5) # Small pause
scene.play(Create(curve_privkvm), Write(label_privkvm))
scene.wait(0.5) # Small pause
scene.play(Create(curve_virtual), Write(label_virtual))
scene.wait(4)
return VGroup(title, performance_title, chart_group)
def construct_conclusion(scene: Scene):
""" Constructs the Conclusion part. """
# No specific issues reported, layout seems okay.
title = Text("Conclusion", font_size=48)
title.to_edge(UP, buff=0.5)
scene.play(Write(title))
scene.wait(1)
# Using VGroups with MathTex numbers for better alignment
summary = VGroup(
VGroup(MathTex(r"1.~"), Text("PrivKV: First LDP solution for Key-Value data.", font_size=28)).arrange(RIGHT, buff=0.2),
VGroup(MathTex(r"2.~"), Text("Addresses key-value correlation via LPP.", font_size=28)).arrange(RIGHT, buff=0.2),
VGroup(MathTex(r"3.~"), Text("PrivKVM improves accuracy via iteration.", font_size=28)).arrange(RIGHT, buff=0.2),
VGroup(MathTex(r"4.~"), Text("PrivKVM+ adaptively balances accuracy/communication.", font_size=28)).arrange(RIGHT, buff=0.2),
VGroup(MathTex(r"5.~"), Text("Virtual Iteration reduces latency, maintains accuracy.", font_size=28)).arrange(RIGHT, buff=0.2)
).arrange(DOWN, aligned_edge=LEFT, buff=0.4) # Adjust buff
summary.scale(0.9).next_to(title, DOWN, buff=LARGE_BUFF) # Increase buff
scene.play(FadeIn(summary, lag_ratio=0.1))
scene.wait(5)
scene.play(FadeOut(summary))
final_message = Text("PrivKV: Key-Value Data Collection with LDP\n\nThanks for watching!",
font_size=36, t2c={'PrivKV': YELLOW, 'LDP': BLUE}, line_spacing=1.2)
final_message.center()
scene.play(Write(final_message))
scene.wait(3)
return VGroup(title, final_message)
# ========================================================
# Main Scene (Orchestrator) - No changes needed here
# ========================================================
class MainAnimation(Scene):
def construct(self):
# Section 1: Intro
intro_elements = construct_intro(self)
self.wait(1)
self.play(FadeOut(intro_elements, shift=DOWN))
self.wait(0.5)
# Section 2: LDP Explanation
ldp_elements = construct_ldp_explanation(self)
self.wait(1)
self.play(FadeOut(ldp_elements, shift=DOWN))
self.wait(0.5)
# Section 3: Key-Value Data Model
kv_model_elements = construct_key_value_model(self)
self.wait(1)
self.play(FadeOut(kv_model_elements, shift=DOWN))
self.wait(0.5)
# Section 4: Estimation Goals
goals_elements = construct_estimation_goals(self)
self.wait(1)
self.play(FadeOut(goals_elements, shift=DOWN))
self.wait(0.5)
# Section 5: Key-Value Perturbation Challenge
challenge_elements = construct_kv_challenge(self)
self.wait(1)
self.play(FadeOut(challenge_elements, shift=DOWN))
self.wait(0.5)
# Section 6: VPP Algorithm
vpp_elements = construct_vpp_algorithm(self)
# vpp_elements might be empty if all content faded internally
if vpp_elements: # Only fade if something was returned
self.play(FadeOut(vpp_elements, shift=DOWN))
self.wait(0.5)
# Section 7: LPP Algorithm
lpp_elements = construct_lpp_algorithm(self)
self.wait(1)
self.play(FadeOut(lpp_elements, shift=DOWN))
self.wait(0.5)
# Section 8: PrivKV Algorithm Flow
privkv_elements = construct_privkv_algorithm(self)
self.wait(1)
self.play(FadeOut(privkv_elements, shift=DOWN))
self.wait(0.5)
# Section 9: PrivKVM Algorithm
privkvm_elements = construct_privkvm_algorithm(self)
self.wait(1)
self.play(FadeOut(privkvm_elements, shift=DOWN))
self.wait(0.5)
# Section 10: PrivKVM+ Algorithm
privkvmplus_elements = construct_privkvmplus_algorithm(self)
self.wait(1)
self.play(FadeOut(privkvmplus_elements, shift=DOWN))
self.wait(0.5)
# Section 11: Virtual Iterations
virtual_elements = construct_virtual_iterations(self)
self.wait(1)
self.play(FadeOut(virtual_elements, shift=DOWN))
self.wait(0.5)
# Section 12: Theoretical Analysis
theory_elements = construct_theoretical_analysis(self)
self.wait(1)
self.play(FadeOut(theory_elements, shift=DOWN))
self.wait(0.5)
# Section 13: Comparison of Algorithms
comparison_elements = construct_comparison(self)
self.wait(1)
self.play(FadeOut(comparison_elements, shift=DOWN))
self.wait(0.5)
# Section 14: Conclusion
conclusion_elements = construct_conclusion(self)
self.wait(4)
self.play(FadeOut(conclusion_elements, shift=DOWN))
self.wait(1)
# To run this revised code:
# python -m manim -pql your_file_name.py MainAnimation