Advanced Module Examples

15 real-world examples showcasing the MathViz module system

The Module System

MathViz modules let you organize reusable logic into separate .mviz files. Use pub fn to export functions and use module_name to import them. The compiler inlines modules as Python classes with @staticmethod methods. Each example below shows the module, the scene, and the compiled output.

0 / 15
Graph Theory

Graphs, BFS, DFS, Dijkstra

Build a reusable graph module and use it in multiple traversal algorithms.

1

Graph Basics

What you will learn

  • Create a reusable graphe module with pub fn exports
  • Import modules with use graphe
  • Call module functions: graphe.create_graph(), graphe.get_neighbors()
  • Visualize nodes, edges, and highlight neighbors
// Module graphe - structures de donnees et operations sur les graphes

/// Cree un graphe a partir d'une liste de noeuds et d'aretes
pub fn create_graph(nodes, edges) {
    return {"nodes": nodes, "edges": edges}
}

/// Retourne les voisins d'un noeud dans le graphe
pub fn get_neighbors(graph, node) {
    let neighbors = []
    for edge in graph["edges"] {
        if edge[0] == node {
            neighbors = neighbors + [edge[1]]
        }
        if edge[1] == node {
            neighbors = neighbors + [edge[0]]
        }
    }
    return neighbors
}

/// Calcule le degre d'un noeud
pub fn degree(graph, node) {
    let count = 0
    for edge in graph["edges"] {
        if edge[0] == node or edge[1] == node {
            count = count + 1
        }
    }
    return count
}
// Visualisation d'un graphe simple avec noeuds et aretes
use graphe

scene GrapheBasicsScene {
    fn construct(self) {
        let title = Text("Graphe : Structure de base", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let pos_a = LEFT * 2 + UP
        let pos_b = RIGHT * 2 + UP
        let pos_c = RIGHT * 2 + DOWN
        let pos_d = LEFT * 2 + DOWN
        let pos_e = ORIGIN
        let positions = {"A": pos_a, "B": pos_b, "C": pos_c, "D": pos_d, "E": pos_e}

        let nodes = ["A", "B", "C", "D", "E"]
        let edges = [["A", "B"], ["A", "E"], ["B", "C"], ["B", "E"], ["C", "D"], ["D", "E"]]
        let g = graphe.create_graph(nodes, edges)

        for edge in edges {
            let line = Line(positions[edge[0]], positions[edge[1]], color: GRAY, stroke_width: 2)
            self.play(Create(line), run_time: 0.2)
        }

        let node_circles = {}
        for name in nodes {
            let c = Circle(radius: 0.3, color: BLUE, fill_opacity: 0.8)
            c.move_to(positions[name])
            let label = Text(name, font_size: 24, color: WHITE)
            label.move_to(positions[name])
            node_circles[name] = c
            self.play(Create(c), Write(label), run_time: 0.3)
        }

        self.wait(0.5)

        let neighbors_e = graphe.get_neighbors(g, "E")
        let deg_e = graphe.degree(g, "E")
        let info = Text(f"Voisins de E : {neighbors_e}  Degre : {deg_e}", font_size: 24)
        info.to_edge(DOWN)
        self.play(Write(info))

        self.play(node_circles["E"].animate.set_color(YELLOW))
        for n in neighbors_e {
            self.play(node_circles[n].animate.set_color(GREEN), run_time: 0.3)
        }
        self.wait(1)
    }
}
from manim import *

# MathViz module: graphe
class graphe:
    @staticmethod
    def create_graph(nodes, edges):
        return {'nodes': nodes, 'edges': edges}
    @staticmethod
    def get_neighbors(graph, node):
        neighbors = []
        for edge in graph['edges']:
            if (edge[0] == node):
                neighbors = (neighbors + [edge[1]])
            if (edge[1] == node):
                neighbors = (neighbors + [edge[0]])
        return neighbors
    @staticmethod
    def degree(graph, node):
        count = 0
        for edge in graph['edges']:
            if ((edge[0] == node) or (edge[1] == node)):
                count = (count + 1)
        return count

class GrapheBasicsScene(Scene):
    def construct(self):
        title = Text('Graphe : Structure de base', font_size=36)
        title.to_edge(UP)
        self.play(Write(title))
        # ... scene code uses graphe.create_graph(), graphe.get_neighbors(), etc.
2

Breadth-First Search (BFS)

What you will learn

  • Build a bfs module that depends on the graphe module
  • Chain module dependencies: bfs.mviz uses graphe, main.mviz uses both
  • Implement a queue-based BFS traversal
  • Animate traversal order with step numbering
// Module BFS - Parcours en largeur d'un graphe
use graphe

/// Effectue un parcours en largeur et retourne l'ordre de visite
pub fn bfs(graph, start) {
    let visited = []
    let queue = [start]
    while len(queue) > 0 {
        let current = queue.pop(0)
        if current not in visited {
            visited = visited + [current]
            let neighbors = graphe.get_neighbors(graph, current)
            for n in neighbors {
                if n not in visited {
                    queue = queue + [n]
                }
            }
        }
    }
    return visited
}
// Animation du parcours en largeur (BFS)
use graphe
use bfs

scene BFSScene {
    fn construct(self) {
        let title = Text("Parcours en Largeur (BFS)", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let pos_a = LEFT * 3
        let pos_b = LEFT * 1.5 + UP * 1.5
        let pos_c = LEFT * 1.5 + DOWN * 1.5
        let pos_d = RIGHT * 0.5 + UP * 1.5
        let pos_e = RIGHT * 0.5 + DOWN * 1.5
        let pos_f = RIGHT * 2.5
        let positions = {"A": pos_a, "B": pos_b, "C": pos_c, "D": pos_d, "E": pos_e, "F": pos_f}

        let nodes = ["A", "B", "C", "D", "E", "F"]
        let edges = [["A", "B"], ["A", "C"], ["B", "D"], ["C", "E"], ["D", "F"], ["E", "F"]]
        let g = graphe.create_graph(nodes, edges)

        for edge in edges {
            let line = Line(positions[edge[0]], positions[edge[1]], color: GRAY, stroke_width: 2)
            self.play(Create(line), run_time: 0.15)
        }

        let circles = {}
        for name in nodes {
            let c = Circle(radius: 0.3, color: BLUE_D, fill_opacity: 0.7)
            c.move_to(positions[name])
            let label = Text(name, font_size: 22, color: WHITE)
            label.move_to(positions[name])
            circles[name] = c
            self.play(Create(c), Write(label), run_time: 0.2)
        }

        self.wait(0.5)

        let visit_order = bfs.bfs(g, "A")
        let step = 1
        for node in visit_order {
            let order_text = Text(str(step), font_size: 18, color: BLACK)
            order_text.move_to(positions[node] + UP * 0.6)
            self.play(circles[node].animate.set_color(YELLOW), Write(order_text), run_time: 0.5)
            step = step + 1
        }

        let result_text = Text(f"Ordre BFS : {visit_order}", font_size: 22)
        result_text.to_edge(DOWN)
        self.play(Write(result_text))
        self.wait(1)
    }
}
from manim import *

# MathViz module: graphe
class graphe:
    @staticmethod
    def create_graph(nodes, edges):
        return {'nodes': nodes, 'edges': edges}
    @staticmethod
    def get_neighbors(graph, node):
        neighbors = []
        for edge in graph['edges']:
            if (edge[0] == node):
                neighbors = (neighbors + [edge[1]])
            if (edge[1] == node):
                neighbors = (neighbors + [edge[0]])
        return neighbors

# MathViz module: bfs
class bfs:
    @staticmethod
    def bfs(graph, start):
        visited = []
        queue = [start]
        while len(queue) > 0:
            current = queue.pop(0)
            if current not in visited:
                visited = visited + [current]
                neighbors = graphe.get_neighbors(graph, current)
                for n in neighbors:
                    if n not in visited:
                        queue = queue + [n]
        return visited

class BFSScene(Scene):
    def construct(self):
        # ... uses graphe.create_graph() and bfs.bfs()
3

Depth-First Search (DFS)

What you will learn

  • Reuse the same graphe module with a different algorithm
  • Implement stack-based DFS traversal
  • Compare BFS (yellow) vs DFS (red) visually
// Module DFS - Parcours en profondeur
use graphe

/// Effectue un parcours en profondeur et retourne l'ordre de visite
pub fn dfs(graph, start) {
    let visited = []
    let stack = [start]
    while len(stack) > 0 {
        let current = stack.pop()
        if current not in visited {
            visited = visited + [current]
            let neighbors = graphe.get_neighbors(graph, current)
            for n in reversed(neighbors) {
                if n not in visited {
                    stack = stack + [n]
                }
            }
        }
    }
    return visited
}
// Animation du parcours en profondeur (DFS)
use graphe
use dfs

scene DFSScene {
    fn construct(self) {
        let title = Text("Parcours en Profondeur (DFS)", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let pos_1 = LEFT * 3
        let pos_2 = LEFT * 1.5 + UP * 1.5
        let pos_3 = LEFT * 1.5 + DOWN * 1.5
        let pos_4 = ORIGIN + UP * 1.5
        let pos_5 = ORIGIN + DOWN * 1.5
        let pos_6 = RIGHT * 2
        let positions = {"1": pos_1, "2": pos_2, "3": pos_3, "4": pos_4, "5": pos_5, "6": pos_6}

        let nodes = ["1", "2", "3", "4", "5", "6"]
        let edges = [["1", "2"], ["1", "3"], ["2", "4"], ["3", "5"], ["4", "6"], ["5", "6"]]
        let g = graphe.create_graph(nodes, edges)

        // ... draw edges and nodes, then animate DFS
        let visit_order = dfs.dfs(g, "1")
        let step = 1
        for node in visit_order {
            self.play(circles[node].animate.set_color(RED), run_time: 0.5)
            step = step + 1
        }
        self.wait(1)
    }
}
from manim import *

# MathViz module: graphe  (same as before)
class graphe:
    @staticmethod
    def create_graph(nodes, edges): ...
    @staticmethod
    def get_neighbors(graph, node): ...

# MathViz module: dfs
class dfs:
    @staticmethod
    def dfs(graph, start):
        visited = []
        stack = [start]
        while len(stack) > 0:
            current = stack.pop()
            if current not in visited:
                visited = visited + [current]
                neighbors = graphe.get_neighbors(graph, current)
                for n in reversed(neighbors):
                    if n not in visited:
                        stack = stack + [n]
        return visited

class DFSScene(Scene):
    def construct(self):
        # ... uses graphe and dfs modules
4

Dijkstra's Algorithm

What you will learn

  • Extend the graph module with weighted edges
  • Implement Dijkstra's shortest path algorithm
  • Animate distance labels and path highlighting
  • Return structured results with {"path": ..., "dist": ..., "steps": ...}
// Module Dijkstra - plus court chemin dans un graphe pondere
use graphe

/// Algorithme de Dijkstra retournant les distances et le chemin
pub fn dijkstra(graph, start, end) {
    let dist = {}
    let prev = {}
    let unvisited = list(graph["nodes"])
    for node in graph["nodes"] {
        dist[node] = 999999
        prev[node] = None
    }
    dist[start] = 0

    let steps = []

    while len(unvisited) > 0 {
        // Trouver le noeud non visite avec la plus petite distance
        let current = None
        let min_dist = 999999
        for node in unvisited {
            if dist[node] < min_dist {
                min_dist = dist[node]
                current = node
            }
        }

        if current == None { break }
        unvisited.remove(current)
        steps = steps + [{"visit": current, "dist": dist[current]}]

        if current == end { break }

        let neighbors = graphe.get_weighted_neighbors(graph, current)
        for neighbor in neighbors {
            let alt = dist[current] + neighbor["weight"]
            if alt < dist[neighbor["node"]] {
                dist[neighbor["node"]] = alt
                prev[neighbor["node"]] = current
            }
        }
    }

    // Reconstruire le chemin
    let path = []
    let current = end
    while current != None {
        path = [current] + path
        current = prev[current]
    }

    return {"path": path, "dist": dist, "steps": steps}
}
// Visualisation de l'algorithme de Dijkstra
use graphe
use dijkstra

scene DijkstraScene {
    fn construct(self) {
        let title = Text("Algorithme de Dijkstra", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let nodes = ["A", "B", "C", "D", "E", "F"]
        let edges = [["A", "B", 4], ["A", "C", 2], ["B", "D", 3],
                     ["C", "E", 5], ["C", "D", 1], ["D", "F", 2], ["E", "F", 3]]
        let g = graphe.create_graph(nodes, edges)

        // ... draw weighted graph
        let result = dijkstra.dijkstra(g, "A", "F")

        // Animate visited nodes with distance labels
        for step in result["steps"] {
            let node = step["visit"]
            self.play(circles[node].animate.set_color(ORANGE), run_time: 0.5)
        }

        // Highlight shortest path
        let path = result["path"]
        for i in range(len(path)) {
            self.play(circles[path[i]].animate.set_color(GREEN), run_time: 0.3)
        }
        self.wait(1)
    }
}
from manim import *

# MathViz module: graphe (with get_weighted_neighbors)
class graphe:
    @staticmethod
    def create_graph(nodes, edges): ...
    @staticmethod
    def get_weighted_neighbors(graph, node):
        neighbors = []
        for edge in graph['edges']:
            if edge[0] == node:
                neighbors.append({'node': edge[1], 'weight': edge[2]})
            elif edge[1] == node:
                neighbors.append({'node': edge[0], 'weight': edge[2]})
        return neighbors

# MathViz module: dijkstra
class dijkstra:
    @staticmethod
    def dijkstra(graph, start, end):
        # Full Dijkstra implementation with path reconstruction
        dist = {node: 999999 for node in graph['nodes']}
        prev = {node: None for node in graph['nodes']}
        dist[start] = 0
        unvisited = list(graph['nodes'])
        # ... returns {"path": [...], "dist": {...}, "steps": [...]}
Sorting

Sorting Algorithms

Visualize classic sorting algorithms step by step with animated bar charts.

5

Bubble Sort

What you will learn

  • Build a tri module that records each swap as a state snapshot
  • Visualize sorting with proportional bar heights
  • Use Transform to animate state transitions
// Module tri - Tri a bulles avec trace des echanges
/// Retourne les etapes d'echange du tri a bulles
pub fn bubble_sort(arr) {
    let a = list(arr)
    let steps = [list(a)]
    let n = len(a)
    for i in range(n) {
        for j in range(0, n - i - 1) {
            if a[j] > a[j + 1] {
                let temp = a[j]
                a[j] = a[j + 1]
                a[j + 1] = temp
                steps = steps + [list(a)]
            }
        }
    }
    return steps
}
// Animation du tri a bulles
use tri

scene TriBullesScene {
    fn construct(self) {
        let title = Text("Tri a Bulles (Bubble Sort)", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let values = [5, 3, 8, 1, 9, 2, 7, 4, 6]
        let steps = tri.bubble_sort(values)

        // Create proportional bars and animate each step
        for idx in range(1, min(total_steps, 13)) {
            let state = steps[idx]
            // ... Transform bars to new positions
        }

        let done = Text("Trie !", font_size: 28, color: GREEN)
        done.to_edge(DOWN)
        self.play(Write(done))
        self.wait(1)
    }
}
from manim import *

# MathViz module: tri
class tri:
    @staticmethod
    def bubble_sort(arr):
        a = list(arr)
        steps = [list(a)]
        n = len(a)
        for i in range(n):
            for j in range(0, n - i - 1):
                if a[j] > a[j + 1]:
                    temp = a[j]
                    a[j] = a[j + 1]
                    a[j + 1] = temp
                    steps = steps + [list(a)]
        return steps

class TriBullesScene(Scene):
    def construct(self):
        # ... visualize tri.bubble_sort() steps
6

Quicksort

What you will learn

  • Use private helper functions (fn partition) alongside pub fn quicksort
  • Record partition boundaries and pivot indices for visualization
  • Color-code: yellow = pivot, orange = active partition, green = sorted
// Module tri - Tri rapide (quicksort) avec trace des partitions

// Fonction de partition interne (pas pub = privee)
fn partition(a, low, high) {
    let pivot = a[high]
    let i = low - 1
    for j in range(low, high) {
        if a[j] <= pivot {
            i = i + 1
            let temp = a[i]
            a[i] = a[j]
            a[j] = temp
        }
    }
    let temp = a[i + 1]
    a[i + 1] = a[high]
    a[high] = temp
    return i + 1
}

fn qs_helper(a, low, high, steps) {
    if low < high {
        let pi = partition(a, low, high)
        steps.append({"state": list(a), "pivot": pi, "low": low, "high": high})
        qs_helper(a, low, pi - 1, steps)
        qs_helper(a, pi + 1, high, steps)
    }
}

/// Retourne les etapes de partition du quicksort
pub fn quicksort(arr) {
    let a = list(arr)
    let steps = []
    qs_helper(a, 0, len(a) - 1, steps)
    return steps
}
// Animation du tri rapide (quicksort)
use tri

scene TriRapideScene {
    fn construct(self) {
        let title = Text("Tri Rapide (Quicksort)", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let values = [6, 3, 8, 1, 5, 9, 2, 7, 4]
        let steps = tri.quicksort(values)

        for step in steps {
            let state = step["state"]
            let pivot_idx = step["pivot"]
            // Color: YELLOW for pivot, ORANGE for partition, GREEN for sorted
            self.play(Transform(bars, new_bars), run_time: 0.5)
        }
        self.wait(1)
    }
}
from manim import *
import numpy as np

# MathViz module: tri
class tri:
    @staticmethod
    def _partition(a, low, high):
        pivot = a[high]
        i = low - 1
        for j in range(low, high):
            if a[j] <= pivot:
                i = i + 1
                a[i], a[j] = a[j], a[i]
        a[i + 1], a[high] = a[high], a[i + 1]
        return i + 1
    @staticmethod
    def _qs_helper(a, low, high, steps):
        if low < high:
            pi = tri._partition(a, low, high)
            steps.append({'state': list(a), 'pivot': pi, 'low': low, 'high': high})
            tri._qs_helper(a, low, pi - 1, steps)
            tri._qs_helper(a, pi + 1, high, steps)
    @staticmethod
    def quicksort(arr):
        a = list(arr)
        steps = []
        tri._qs_helper(a, 0, len(a) - 1, steps)
        return steps
Linear Algebra

Matrices and Vectors

Encapsulate matrix and vector operations in reusable modules.

7

Matrix Operations

What you will learn

  • Build a matrices module with multiply, transpose, and determinant
  • Use Manim's Matrix() and MathTex() for LaTeX rendering
  • Display computation results alongside their formulas
// Module matrices - Operations sur les matrices
/// Multiplie deux matrices (listes de listes)
pub fn multiply(a, b) {
    let rows_a = len(a)
    let cols_a = len(a[0])
    let cols_b = len(b[0])
    let result = []
    for i in range(rows_a) {
        let row = []
        for j in range(cols_b) {
            let s = 0
            for k in range(cols_a) {
                s = s + a[i][k] * b[k][j]
            }
            row = row + [s]
        }
        result = result + [row]
    }
    return result
}

/// Transpose une matrice
pub fn transpose(m) {
    let rows = len(m)
    let cols = len(m[0])
    let result = []
    for j in range(cols) {
        let row = []
        for i in range(rows) {
            row = row + [m[i][j]]
        }
        result = result + [row]
    }
    return result
}

/// Calcule le determinant d'une matrice 2x2
pub fn determinant_2x2(m) {
    return m[0][0] * m[1][1] - m[0][1] * m[1][0]
}
// Visualisation de la multiplication matricielle
use matrices

scene MatricesScene {
    fn construct(self) {
        let title = Text("Multiplication Matricielle", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let a = [[1, 2], [3, 4]]
        let b = [[5, 6], [7, 8]]

        let mat_a = Matrix(a)
        mat_a.move_to(LEFT * 4)
        let times = MathTex("\\times", font_size: 36)
        let mat_b = Matrix(b)

        let result = matrices.multiply(a, b)
        let mat_r = Matrix(result)

        // Show A * B = R
        self.play(Create(mat_a), Write(times), Create(mat_b))
        self.play(Create(mat_r))

        // Show transpose and determinant
        let trans = matrices.transpose(a)
        let det = matrices.determinant_2x2(a)
        let det_text = MathTex("\\det(A) = " + str(det), font_size: 32)
        self.play(Write(det_text))
        self.wait(1)
    }
}
from manim import *

# MathViz module: matrices
class matrices:
    @staticmethod
    def multiply(a, b):
        rows_a = len(a)
        cols_a = len(a[0])
        cols_b = len(b[0])
        result = []
        for i in range(rows_a):
            row = []
            for j in range(cols_b):
                s = 0
                for k in range(cols_a):
                    s = s + a[i][k] * b[k][j]
                row = row + [s]
            result = result + [row]
        return result
    @staticmethod
    def transpose(m): ...
    @staticmethod
    def determinant_2x2(m):
        return m[0][0] * m[1][1] - m[0][1] * m[1][0]
8

Vector Operations

What you will learn

  • Build a vecteurs module with add, dot product, norm, and scale
  • Visualize vectors on a NumberPlane
  • Draw the parallelogram rule for vector addition
  • Mix LaTeX formulas with computed values
// Module vecteurs - Operations vectorielles
/// Addition de deux vecteurs 2D
pub fn add(v1, v2) {
    return [v1[0] + v2[0], v1[1] + v2[1]]
}

/// Produit scalaire de deux vecteurs
pub fn dot_product(v1, v2) {
    let s = 0
    for i in range(len(v1)) {
        s = s + v1[i] * v2[i]
    }
    return s
}

/// Norme d'un vecteur
pub fn norm(v) {
    let s = 0
    for x in v { s = s + x * x }
    return s ^ 0.5
}

/// Multiplication par un scalaire
pub fn scale(v, s) {
    let result = []
    for x in v { result = result + [x * s] }
    return result
}
// Visualisation des operations vectorielles
use vecteurs

scene VecteursScene {
    fn construct(self) {
        let title = Text("Operations Vectorielles", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let plane = NumberPlane(x_range: [-5, 5, 1], y_range: [-4, 4, 1],
            background_line_style: {"stroke_opacity": 0.3})
        self.play(Create(plane), run_time: 0.5)

        let v1 = [3, 1]
        let v2 = [1, 2]
        let v_sum = vecteurs.add(v1, v2)

        // Draw v1 (red), v2 (blue), sum (green)
        let arrow_v1 = Arrow(plane.c2p(0, 0), plane.c2p(v1[0], v1[1]), color: RED, buff: 0)
        let arrow_v2 = Arrow(plane.c2p(0, 0), plane.c2p(v2[0], v2[1]), color: BLUE, buff: 0)
        let arrow_sum = Arrow(plane.c2p(0, 0), plane.c2p(v_sum[0], v_sum[1]), color: GREEN, buff: 0)

        // Parallelogram rule with dashed lines
        let dashed_1 = DashedLine(plane.c2p(v2[0], v2[1]), plane.c2p(v_sum[0], v_sum[1]))
        let dashed_2 = DashedLine(plane.c2p(v1[0], v1[1]), plane.c2p(v_sum[0], v_sum[1]))

        // Display dot product and norms
        let dp = vecteurs.dot_product(v1, v2)
        let info = MathTex("\\vec{v_1} \\cdot \\vec{v_2} = " + str(dp), font_size: 24)
        self.wait(1)
    }
}
from manim import *

# MathViz module: vecteurs
class vecteurs:
    @staticmethod
    def add(v1, v2):
        return [v1[0] + v2[0], v1[1] + v2[1]]
    @staticmethod
    def dot_product(v1, v2):
        s = 0
        for i in range(len(v1)):
            s = s + v1[i] * v2[i]
        return s
    @staticmethod
    def norm(v):
        s = 0
        for x in v:
            s = s + x * x
        return s ** 0.5
    @staticmethod
    def scale(v, s):
        return [x * s for x in v]
Data Structures

Binary Search Tree

Implement a BST with recursive insert and inorder traversal.

9

Binary Search Tree

What you will learn

  • Build a recursive arbre module with insert, inorder, and search
  • Represent a tree as nested dictionaries: {"val": v, "left": ..., "right": ...}
  • Animate node insertion with edges connecting parent to child
  • Highlight inorder traversal sequence
// Module arbre - Arbre binaire de recherche
/// Insere une valeur dans l'arbre
pub fn insert(tree, value) {
    if tree == None {
        return {"val": value, "left": None, "right": None}
    }
    if value < tree["val"] {
        tree["left"] = arbre.insert(tree["left"], value)
    } else {
        if value > tree["val"] {
            tree["right"] = arbre.insert(tree["right"], value)
        }
    }
    return tree
}

/// Parcours infixe (inorder) retournant les valeurs triees
pub fn inorder(tree) {
    if tree == None { return [] }
    return arbre.inorder(tree["left"]) + [tree["val"]] + arbre.inorder(tree["right"])
}

/// Recherche une valeur dans l'arbre
pub fn search(tree, value) {
    if tree == None { return False }
    if tree["val"] == value { return True }
    if value < tree["val"] {
        return arbre.search(tree["left"], value)
    }
    return arbre.search(tree["right"], value)
}
// Visualisation de l'arbre binaire de recherche
use arbre

scene ArbreBinaireScene {
    fn construct(self) {
        let title = Text("Arbre Binaire de Recherche", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let values = [5, 3, 7, 1, 4, 6, 8]
        // Pre-calculated positions for tree layout
        let positions = {5: pos_5, 3: pos_3, 7: pos_7, ...}
        let parent_map = {3: 5, 7: 5, 1: 3, 4: 3, 6: 7, 8: 7}

        let tree = None
        for val in values {
            tree = arbre.insert(tree, val)
            // Draw edge from parent, then node circle
        }

        let inorder_result = arbre.inorder(tree)
        // Highlight nodes in inorder sequence
        for val in inorder_result {
            self.play(node_circles[val].animate.set_color(YELLOW), run_time: 0.3)
            self.play(node_circles[val].animate.set_color(GREEN), run_time: 0.2)
        }
        self.wait(1)
    }
}
from manim import *

# MathViz module: arbre
class arbre:
    @staticmethod
    def insert(tree, value):
        if (tree == None):
            return {'val': value, 'left': None, 'right': None}
        if (value < tree['val']):
            tree['left'] = arbre.insert(tree['left'], value)
        else:
            if (value > tree['val']):
                tree['right'] = arbre.insert(tree['right'], value)
        return tree
    @staticmethod
    def inorder(tree):
        if (tree == None):
            return []
        return (arbre.inorder(tree['left']) + [tree['val']]
                + arbre.inorder(tree['right']))
    @staticmethod
    def search(tree, value):
        if (tree == None): return False
        if (tree['val'] == value): return True
        if (value < tree['val']):
            return arbre.search(tree['left'], value)
        return arbre.search(tree['right'], value)
Mathematics

Fractals, Primes, Fibonacci, Sets, Complex, Probability

Advanced mathematical concepts with dedicated computation modules.

10

Sierpinski Triangle

What you will learn

  • Build a fractales module that generates fractal geometry
  • Recursive midpoint subdivision for Sierpinski triangle
  • Animate increasing fractal depth with Transform
// Module fractales - Generation de points pour fractales
/// Genere les sommets du triangle de Sierpinski a la profondeur donnee
pub fn sierpinski_points(depth) {
    let triangles = [[[0, 2, 0], [-2, -1, 0], [2, -1, 0]]]

    for d in range(depth) {
        let new_triangles = []
        for tri in triangles {
            let a = tri[0]
            let b = tri[1]
            let c = tri[2]
            // Points milieux
            let ab = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, 0]
            let bc = [(b[0] + c[0]) / 2, (b[1] + c[1]) / 2, 0]
            let ac = [(a[0] + c[0]) / 2, (a[1] + c[1]) / 2, 0]
            // 3 sous-triangles
            new_triangles = new_triangles + [[a, ab, ac]]
            new_triangles = new_triangles + [[ab, b, bc]]
            new_triangles = new_triangles + [[ac, bc, c]]
        }
        triangles = new_triangles
    }
    return triangles
}

/// Genere les points de la courbe de Koch
pub fn koch_points(depth) {
    // ... Koch snowflake point generation
}
// Visualisation du triangle de Sierpinski
use fractales

scene FractalesScene {
    fn construct(self) {
        let title = Text("Triangle de Sierpinski", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let prev_group = None
        for depth in range(5) {
            let triangles = fractales.sierpinski_points(depth)
            let polys = VGroup()
            for tri in triangles {
                let polygon = Polygon(tri[0], tri[1], tri[2],
                    fill_opacity: 0.6, fill_color: PURPLE,
                    stroke_color: WHITE, stroke_width: 0.5)
                polys.add(polygon)
            }
            polys.scale(1.2)
            polys.move_to(DOWN * 0.3)

            if prev_group == None {
                self.play(Create(polys), run_time: 1)
                prev_group = polys
            } else {
                self.play(Transform(prev_group, polys), run_time: 0.8)
            }
            self.wait(0.5)
        }
        self.wait(1)
    }
}
from manim import *

# MathViz module: fractales
class fractales:
    @staticmethod
    def sierpinski_points(depth):
        triangles = [[[0, 2, 0], [-2, -1, 0], [2, -1, 0]]]
        for d in range(depth):
            new_triangles = []
            for tri in triangles:
                a, b, c = tri[0], tri[1], tri[2]
                ab = [(a[0]+b[0])/2, (a[1]+b[1])/2, 0]
                bc = [(b[0]+c[0])/2, (b[1]+c[1])/2, 0]
                ac = [(a[0]+c[0])/2, (a[1]+c[1])/2, 0]
                new_triangles += [[a, ab, ac], [ab, b, bc], [ac, bc, c]]
            triangles = new_triangles
        return triangles
11

Sieve of Eratosthenes

What you will learn

  • Build a nombres module with sieve, is_prime, and factorize
  • Implement the Sieve of Eratosthenes using while loops
  • Animate a number grid: highlight primes, grey out composites
// Module nombres - Operations sur les nombres premiers
/// Crible d'Eratosthene retournant les nombres premiers <= n
pub fn sieve(n) {
    let is_prime_arr = []
    for i in range(n + 1) {
        is_prime_arr = is_prime_arr + [True]
    }
    is_prime_arr[0] = False
    is_prime_arr[1] = False

    let p = 2
    while p * p <= n {
        if is_prime_arr[p] {
            let multiple = p * p
            while multiple <= n {
                is_prime_arr[multiple] = False
                multiple = multiple + p
            }
        }
        p = p + 1
    }

    let primes = []
    for i in range(2, n + 1) {
        if is_prime_arr[i] {
            primes = primes + [i]
        }
    }
    return primes
}

/// Verifie si un nombre est premier
pub fn is_prime(n) {
    if n < 2 { return False }
    let i = 2
    while i * i <= n {
        if n % i == 0 { return False }
        i = i + 1
    }
    return True
}

/// Decomposition en facteurs premiers
pub fn factorize(n) {
    let factors = []
    let d = 2
    while d * d <= n {
        while n % d == 0 {
            factors = factors + [d]
            n = n / d
        }
        d = d + 1
    }
    if n > 1 { factors = factors + [n] }
    return factors
}
// Visualisation du crible d'Eratosthene
use nombres

scene NombresPremScene {
    fn construct(self) {
        let title = Text("Crible d'Eratosthene", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let n = 49
        let primes = nombres.sieve(n)

        // Create number grid 2..49
        let cells = {}
        for num in range(2, n + 1) {
            let cell = Square(side_length: 0.55)
            let label = Text(str(num), font_size: 14)
            cells[num] = VGroup(cell, label)
        }

        // Animate sieve: highlight primes, grey composites
        for p in [2, 3, 5, 7] {
            self.play(cells[p][0].animate.set_fill(YELLOW, opacity: 0.8))
            let multiple = p * p
            while multiple <= n {
                self.play(cells[multiple][0].animate.set_fill(RED_E, opacity: 0.5))
                multiple = multiple + p
            }
        }

        // Color all primes green
        for p in primes {
            self.play(cells[p][0].animate.set_fill(GREEN, opacity: 0.7))
        }
        self.wait(1)
    }
}
from manim import *

# MathViz module: nombres
class nombres:
    @staticmethod
    def sieve(n):
        is_prime_arr = [True] * (n + 1)
        is_prime_arr[0] = is_prime_arr[1] = False
        p = 2
        while p * p <= n:
            if is_prime_arr[p]:
                multiple = p * p
                while multiple <= n:
                    is_prime_arr[multiple] = False
                    multiple += p
            p += 1
        return [i for i in range(2, n+1) if is_prime_arr[i]]
    @staticmethod
    def is_prime(n): ...
    @staticmethod
    def factorize(n): ...
12

Fibonacci and Golden Ratio

What you will learn

  • Build a suites module with Fibonacci computation and golden ratio approximation
  • Display Fibonacci terms using MathTex and arrange_in_grid
  • Plot convergence to phi using Axes and Dot
  • Use the pipe-lambda syntax: |x| 1.618033988
// Module suites - Suite de Fibonacci et nombre d'or
/// Calcule le n-ieme nombre de Fibonacci
pub fn fibonacci(n) {
    if n <= 0 { return 0 }
    if n == 1 { return 1 }
    let a = 0
    let b = 1
    for i in range(2, n + 1) {
        let temp = a + b
        a = b
        b = temp
    }
    return b
}

/// Approximation du nombre d'or par le rapport F(n+1)/F(n)
pub fn golden_ratio(n) {
    let ratios = []
    let prev = 1
    let curr = 1
    for i in range(2, n + 1) {
        let temp = prev + curr
        prev = curr
        curr = temp
        if prev > 0 {
            ratios = ratios + [curr / prev]
        }
    }
    return ratios
}

/// Genere les tailles pour la spirale de Fibonacci
pub fn fibonacci_spiral_points(n) {
    let sizes = []
    let a = 0
    let b = 1
    for i in range(1, n + 1) {
        let temp = a + b
        a = b
        b = temp
        sizes = sizes + [b]
    }
    return sizes
}
// Visualisation de la suite de Fibonacci et du nombre d'or
use suites

scene FibonacciScene {
    fn construct(self) {
        let title = Text("Suite de Fibonacci", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let terms = VGroup()
        for i in range(10) {
            let fib_val = suites.fibonacci(i)
            let t = MathTex(f"F({i}) = {fib_val}", font_size: 24)
            terms.add(t)
        }
        terms.arrange_in_grid(rows: 2, cols: 5, buff: 0.4)
        self.play(Write(terms), run_time: 1.5)

        // Plot golden ratio convergence
        let axes = Axes(x_range: [2, 15, 1], y_range: [1, 2.5, 0.5],
            x_length: 8, y_length: 4)
        let phi_line = axes.plot(|x| 1.618033988, x_range: [2, 15], color: YELLOW)

        let ratios = suites.golden_ratio(15)
        for i in range(len(ratios)) {
            let dot = Dot(axes.c2p(i + 2, ratios[i]), color: BLUE, radius: 0.06)
            self.play(Create(dot), run_time: 0.15)
        }
        self.wait(1)
    }
}
from manim import *

# MathViz module: suites
class suites:
    @staticmethod
    def fibonacci(n):
        if n <= 0: return 0
        if n == 1: return 1
        a, b = 0, 1
        for i in range(2, n + 1):
            a, b = b, a + b
        return b
    @staticmethod
    def golden_ratio(n):
        ratios = []
        prev, curr = 1, 1
        for i in range(2, n + 1):
            prev, curr = curr, prev + curr
            if prev > 0:
                ratios.append(curr / prev)
        return ratios
    @staticmethod
    def fibonacci_spiral_points(n): ...
13

Set Operations

What you will learn

  • Build an ensembles module with union, intersection, difference, and subset
  • Visualize set operations with Venn diagrams (overlapping circles)
  • Display LaTeX set notation: A ∪ B, A ∩ B, A ∖ B
// Module ensembles - Operations sur les ensembles
/// Union de deux ensembles (listes)
pub fn union(a, b) {
    let result = list(a)
    for x in b {
        if x not in result {
            result = result + [x]
        }
    }
    return result
}

/// Intersection de deux ensembles
pub fn intersection(a, b) {
    let result = []
    for x in a {
        if x in b { result = result + [x] }
    }
    return result
}

/// Difference A \ B
pub fn difference(a, b) {
    let result = []
    for x in a {
        if x not in b { result = result + [x] }
    }
    return result
}

/// Verifie si a est un sous-ensemble de b
pub fn is_subset(a, b) {
    for x in a {
        if x not in b { return False }
    }
    return True
}
// Visualisation des operations sur les ensembles
use ensembles

scene EnsemblesScene {
    fn construct(self) {
        let title = Text("Operations sur les Ensembles", font_size: 36)
        title.to_edge(UP)
        self.play(Write(title))

        let a = [1, 2, 3, 4, 5]
        let b = [3, 4, 5, 6, 7]

        // Venn diagram circles
        let circle_a = Circle(radius: 1.8, color: BLUE, fill_opacity: 0.2)
        let circle_b = Circle(radius: 1.8, color: RED, fill_opacity: 0.2)

        // Compute operations
        let u = ensembles.union(a, b)
        let inter = ensembles.intersection(a, b)
        let diff = ensembles.difference(a, b)

        // Display results with LaTeX
        let r1 = MathTex("A \\cup B = " + str(u), font_size: 22)
        let r2 = MathTex("A \\cap B = " + str(inter), font_size: 22)
        let r3 = MathTex("A \\setminus B = " + str(diff), font_size: 22)
        self.wait(1)
    }
}
from manim import *

# MathViz module: ensembles
class ensembles:
    @staticmethod
    def union(a, b):
        result = list(a)
        for x in b:
            if x not in result:
                result = result + [x]
        return result
    @staticmethod
    def intersection(a, b):
        return [x for x in a if x in b]
    @staticmethod
    def difference(a, b):
        return [x for x in a if x not in b]
    @staticmethod
    def is_subset(a, b):
        for x in a:
            if x not in b: return False
        return True
14

Complex Numbers

What you will learn

  • Build a complexes module with add, multiply, modulus, argument, from_polar
  • Represent complex numbers as [re, im] pairs
  • Visualize on the Argand plane (ComplexPlane)
  • Show multiplication as rotation + dilation
// Module complexes - Operations sur les nombres complexes
// Un nombre complexe est represente comme [re, im]

/// Addition de deux nombres complexes
pub fn add(z1, z2) {
    return [z1[0] + z2[0], z1[1] + z2[1]]
}

/// Multiplication de deux nombres complexes
pub fn multiply(z1, z2) {
    let re = z1[0] * z2[0] - z1[1] * z2[1]
    let im = z1[0] * z2[1] + z1[1] * z2[0]
    return [re, im]
}

/// Module (valeur absolue) d'un nombre complexe
pub fn modulus(z) {
    return (z[0]^2 + z[1]^2) ^ 0.5
}

/// Argument d'un nombre complexe (en radians)
pub fn argument(z) {
    return np.arctan2(z[1], z[0])
}

/// Convertit polaire vers cartesien
pub fn from_polar(r, theta) {
    return [r * np.cos(theta), r * np.sin(theta)]
}
// Visualisation des nombres complexes sur le plan d'Argand
use complexes

scene ComplexesScene {
    fn construct(self) {
        let title = Text("Nombres Complexes - Plan d'Argand", font_size: 32)
        title.to_edge(UP)
        self.play(Write(title))

        let plane = ComplexPlane(x_range: [-4, 4, 1], y_range: [-3, 3, 1])
        plane.add_coordinates()

        let z1 = [2, 1]
        let z2 = [1, 2]
        let product = complexes.multiply(z1, z2)

        // Draw z1, z2, and z1*z2 as arrows
        let arrow_z1 = Arrow(plane.n2p(0), plane.n2p(complex(z1[0], z1[1])), color: RED)
        let arrow_prod = Arrow(plane.n2p(0), plane.n2p(complex(product[0], product[1])), color: GREEN)

        // Show |z1| and |z1*z2|
        let mod_z1 = complexes.modulus(z1)
        let mod_prod = complexes.modulus(product)
        self.wait(1)
    }
}
from manim import *
import numpy as np

# MathViz module: complexes
class complexes:
    @staticmethod
    def add(z1, z2):
        return [z1[0] + z2[0], z1[1] + z2[1]]
    @staticmethod
    def multiply(z1, z2):
        re = z1[0] * z2[0] - z1[1] * z2[1]
        im = z1[0] * z2[1] + z1[1] * z2[0]
        return [re, im]
    @staticmethod
    def modulus(z):
        return (z[0]**2 + z[1]**2) ** 0.5
    @staticmethod
    def argument(z):
        return np.arctan2(z[1], z[0])
    @staticmethod
    def from_polar(r, theta):
        return [r * np.cos(theta), r * np.sin(theta)]
15

Probability Distributions

What you will learn

  • Build a probabilites module with binomial coefficient, binomial PDF, normal PDF
  • Visualize binomial distribution as bar chart with normal curve overlay
  • Use Axes.plot() with pipe-lambda: |x| probabilites.normal_pdf(x, mu, sigma)
  • Display statistical parameters with LaTeX
// Module probabilites - Distributions de probabilites
/// Coefficient binomial C(n, k)
pub fn binomial_coeff(n, k) {
    if k > n { return 0 }
    if k == 0 or k == n { return 1 }
    let result = 1
    for i in range(min(k, n - k)) {
        result = result * (n - i) / (i + 1)
    }
    return int(result)
}

/// Probabilite binomiale P(X = k) pour X ~ B(n, p)
pub fn binomial(n, k, p) {
    return binomial_coeff(n, k) * p^k * (1 - p)^(n - k)
}

/// Densite de la loi normale
pub fn normal_pdf(x, mu, sigma) {
    return (1 / (sigma * (2 * np.pi)^0.5)) * np.exp(-0.5 * ((x - mu) / sigma)^2)
}

/// Esperance d'une variable discrete
pub fn expected_value(values, probs) {
    let s = 0
    for i in range(len(values)) {
        s = s + values[i] * probs[i]
    }
    return s
}
// Visualisation de la distribution binomiale et loi normale
use probabilites

scene ProbabilitesScene {
    fn construct(self) {
        let title = Text("Distribution Binomiale et Loi Normale", font_size: 30)
        title.to_edge(UP)
        self.play(Write(title))

        let n_val = 20
        let p_val = 0.5

        let axes = Axes(x_range: [0, 20, 2], y_range: [0, 0.25, 0.05],
            x_length: 10, y_length: 4.5)

        // Bars for binomial distribution
        let bars = VGroup()
        for k in range(n_val + 1) {
            let prob = probabilites.binomial(n_val, k, p_val)
            let bar = Rectangle(width: 0.35, height: prob * 18, color: BLUE)
            bars.add(bar)
        }

        // Normal curve overlay
        let mu = n_val * p_val
        let sigma = (n_val * p_val * (1 - p_val)) ^ 0.5
        let normal_curve = axes.plot(
            |x| probabilites.normal_pdf(x, mu, sigma),
            x_range: [0, 20], color: YELLOW, stroke_width: 3)

        let params = VGroup(
            MathTex(f"n = {n_val},\\ p = {p_val}", font_size: 20),
            MathTex(f"\\mu = {mu:.1f},\\ \\sigma = {sigma:.2f}", font_size: 20)
        ).arrange(DOWN)
        self.wait(1)
    }
}
from manim import *
import numpy as np

# MathViz module: probabilites
class probabilites:
    @staticmethod
    def binomial_coeff(n, k):
        if k > n: return 0
        if k == 0 or k == n: return 1
        result = 1
        for i in range(min(k, n - k)):
            result = result * (n - i) / (i + 1)
        return int(result)
    @staticmethod
    def binomial(n, k, p):
        return probabilites.binomial_coeff(n, k) * p**k * (1-p)**(n-k)
    @staticmethod
    def normal_pdf(x, mu, sigma):
        return (1/(sigma*(2*np.pi)**0.5)) * np.exp(-0.5*((x-mu)/sigma)**2)
    @staticmethod
    def expected_value(values, probs):
        return sum(v*p for v, p in zip(values, probs))

All 15 Module Examples Complete

You have explored the full power of the MathViz module system across these domains:

  • Graph Theory -- Reusable graph module shared across BFS, DFS, and Dijkstra
  • Sorting -- Algorithm modules that record steps for animated visualization
  • Linear Algebra -- Matrix and vector operation libraries
  • Data Structures -- Recursive BST with insert, search, and traversal
  • Number Theory -- Sieve of Eratosthenes and Fibonacci sequences
  • Set Theory -- Union, intersection, difference with Venn diagrams
  • Complex Analysis -- Complex arithmetic on the Argand plane
  • Probability -- Binomial and normal distributions with statistical parameters