Spaces:
Running on Zero
A newer version of the Gradio SDK is available: 6.19.0
title: SciVisual Agent
emoji: 🐨
colorFrom: yellow
colorTo: yellow
sdk: gradio
sdk_version: 6.17.3
python_version: '3.10'
app_file: app.py
pinned: true
license: apache-2.0
short_description: AI-powered Maths and Physics visualisation using ManimCE.
tags:
- build-small-hackathon
- backyard-ai
- gradio
- track:backyard
- physics
- maths
- manim
- visualization
- education
- text-to-code-generation
- text-to-video
- agentic
- sponsor:openai
- sponsor:modal
- achievement:offgrid
- achievement:welltuned
- achievement:fieldnotes
models:
- >-
vankhieu/Seed_Coder_8B_Instruct_unsloth_bnb_4bit_lora_r8_sft_grpo_rw_mean_text_visual
- unsloth/Seed-Coder-8B-Instruct-unsloth-bnb-4bit
SciVisual Agent – AI-powered Maths and Physics Visualization Agent
Built for the Hugging Face Build Small Hackathon 2026 · Backyard AI track 🏡
Try it: Live HF Space
Demo video: Video Demo
Field notes: Build blog
Social media post: LinkedIn
DEMO Video embedded (if not present, please click the Video Demo link):
SciVisual-Agent is an agent that turns math and physics prompts into animated Manim videos. The idea is simple: describe a concept in natural language, then get back a visual explanation that moves.
We built it for the Build Small hackathon as a compact virtual animation lab for scientific ideas. It combines a fine-tuned code generation model (thanks to Modal for GPU credits), ManimCE rendering, LaTeX support, and a self-correction loop that can repair generated code when rendering fails.
System dependencies
This Space uses packages.txt to install system packages required by ManimCE,
including LaTeX, dvisvgm, Ghostscript, ffmpeg, Cairo, and Pango.
Runtime dependencies
Python dependencies are installed from requirements.txt. The app is designed
for ZeroGPU: model loading is deferred until the render callback and the callback
is decorated with @spaces.GPU.
Example videos
Damped Simple Pendulum
Original Prompt:
Create a high-quality Physics simulation of a Damped Simple Pendulum using ManimCE.
Requirements:
1. Physics Setup: Define explicit variables for gravitational acceleration (g=9.81), rod length (L=3.0), initial angle (theta = pi/4), and a damping coefficient (b=0.15) to simulate real-world air resistance. Use a clear numerical integration method (like Euler-Cromer) inside an object updater (`add_updater`) to dynamically recalculate the angular velocity and displacement at every frame (`dt`).
2. Visual Elements (Strict Coordinate Updates):
- A fixed ceiling line or pivot dot at the top center.
- A colored dot (e.g., Cyber Cyan or Neon Pink) representing the heavy bob.
- A clean line representing the pendulum rod that attaches the pivot to the bob. CRITICAL: You must attach an updater to this rod using `add_updater` so that its end position dynamically calls `rod.put_start_and_end_on(pivot.get_center(), bob.get_center())` at every single frame. The rod must always move dynamically with the bob.
- Add an elegant fading trace/trail (`TracedPath`) attached to the bob to visually map its decaying sinusoidal path across the screen over time.
3. Mathematical Overlay: In the upper left corner, display the differential equation governing the motion: "d²θ/dt² + (b/m)dθ/dt + (g/L)sin(θ) = 0" rendered beautifully via LaTeX.
4. Animation flow: Let the simulation run smoothly for 12 seconds to clearly demonstrate the kinetic energy converting to thermal energy as the oscillation slowly dampens to a complete halt. Ensure the code is self-contained and wrapped inside a single executable Scene class.
Visual proof of $(a+b)^2 = a^2 + 2ab + b^2$
Original Prompt:
Create a perfect 2D geometric animation proving the identity (a+b)² = a² + 2ab + b² using ManimCE based on strict quadrant alignment. Fix the overlapping bug.
Requirements:
1. Geometry & Scale Setup:
- Define lengths: a = 2.0 and b = 1.0.
- Define a central intersection origin point: `origin = np.array([0, 0, 0])`. All blocks must be strictly positioned relative to this point by alignment.
2. Precise Corner Alignment (CRITICAL OVERLAP FIX):
- Block 1 (Square a²): Create a Square with side_length=a, color=BLUE. Use `.next_to(origin, UL, buff=0)` or explicitly align its bottom-right corner to `origin` so it sits entirely in the Upper-Left quadrant.
- Block 2 (Square b²): Create a Square with side_length=b, color=PINK. Use `.next_to(origin, DR, buff=0)` or explicitly align its top-left corner to `origin` so it sits entirely in the Lower-Right quadrant.
- Block 3 (Rectangle ab - Top Right): Create a Rectangle with width=b, height=a, color=GREEN. Use `.next_to(origin, UR, buff=0)` or align its bottom-left corner to `origin` so it sits entirely in the Upper-Right quadrant.
- Block 4 (Rectangle ab - Bottom Left): Create a Rectangle with width=a, height=b, color=GREEN. Use `.next_to(origin, DL, buff=0)` or align its top-right corner to `origin` so it sits entirely in the Lower-Left quadrant.
- Set fill_opacity=0.5 and stroke_color=WHITE for all 4 blocks.
3. Main Outer Square Framework:
- Create a large outer Square with side_length=(a+b) to represent the final boundary. Center it precisely at the combined visual center of the 4 blocks, which is `np.array([(-a+b)/2, (a-b)/2, 0])`. Color it WHITE with a thin stroke width, without fill.
4. Text Labels & Math Mapping:
- Place MathTex text labels ("a²", "b²", "ab", "ab") centered inside each corresponding block using `.move_to(block.get_center())`. Note: Labels must be created and positioned AFTER the blocks have been moved to their correct quadrants.
- Place the formula "(a + b)² = a² + 2ab + b²" at the top edge of the screen.
5. Animation Sequence:
- Step 1: Write the formula at the top and create the thin outer main square framework first (1.5s).
- Step 2: Draw Block 1 (a²) and Block 2 (b²) simultaneously using `FadeIn` (1s).
- Step 3: Animate the two Rectangle blocks (ab) creating themselves using `Create` to fill the remaining empty corners (1.5s).
- Step 4: Display the labels inside each block using `Write`.
6. Constraint: Keep the code clean, modular, and under 5 seconds total run_time. Do not use random pixel offsets; strictly use `next_to` with `buff=0` for perfect grid locking.
Spring Mass Animation
Original Prompt:
Create a professional 2D physics simulation of a horizontal spring-mass system with a real-time synchronized position graph using ManimCE. Fix the velocity easing issue.
Requirements:
1. Layout & Axes:
- Shift the entire Spring-Mass system to the upper half (around y = 1.5).
- In the lower half (centered at y = -2), create a coordinate system `Axes` using `Axes(x_range=[0, 7, 1], y_range=[-2.5, 2.5, 1], x_length=7, y_length=3)`. Color the axes WHITE and add labels "Time (t)" and "Position (x)".
2. Physical Objects:
- Vertical wall at x = -4 (from y = 0.5 to y = 2.5), color = WHITE.
- Horizontal track from x = -4 to x = 4 at y = 1.2, color = WHITE.
- Mass block using `Square(side_length=0.6, color=PURPLE, fill_opacity=0.8)`.
- Use a `ValueTracker(0)` for time `t`.
- Update the block's center position via `add_updater` to be exactly at `np.array([2 * np.cos(3 * t.get_value()), 1.5, 0])`.
- Create the spring using `always_redraw`. Calculate `L = mass.get_left()[0] - (-4)`. Use `ParametricFunction` with `t_range=[0, 1]` where:
* x_func(u) = -4 + u * L
* y_func(u) = 1.5 + 0.3 * np.sin(2 * np.pi * 8 * u)
Color the spring WHITE.
3. Graph Synchronization:
- Create an orange dynamic dot on the graph tracker using `add_updater` at `axes.c2p(t.get_value(), 2 * np.cos(3 * t.get_value()), 0)`.
- Create the graph line using `always_redraw` plotting `axes.plot(lambda time: 2 * np.cos(3 * time), x_range=[0, max(0.001, t.get_value())], color=ORANGE)`.
4. Strict Animation Speed (CRITICAL SPEED FIX):
- Animate the tracker `t` from 0 to 2*PI using `self.play(t.animate.set_value(2 * PI), run_time=6, rate_func=linear)`.
- YOU MUST EXPLICITLY INCLUDE `rate_func=linear` to eliminate the default smooth easing effect. This ensures that time 't' updates at a perfectly constant velocity, making the physical oscillation and graph rendering completely uniform from start to finish.
Model selection
By default the app loads the fine-tuned SciVisual LoRA adapter from vankhieu/Seed_Coder_8B_Instruct_unsloth_bnb_4bit_lora_r8_sft_grpo_rw_mean_text_visual and applies it to its base model unsloth/Seed-Coder-8B-Instruct-unsloth-bnb-4bit.
We also support pure base model. To test base model instead, set Space variables like:
SCIVISUAL_MODEL_KIND=base
SCIVISUAL_MODEL_PATH=unsloth/Qwen3-30B-A3B-Instruct-2507-bnb-4bit
SCIVISUAL_SELECTED_MODEL=unsloth/Qwen3-30B-A3B-Instruct-2507-bnb-4bit
SCIVISUAL_BACKEND=transformers
SCIVISUAL_LOAD_IN_4BIT=1
How The System Flows
The app is built around the feedback loop: generate code, render it, inspect the result, and repair it when needed.
The important design choice is that rendering is not treated as the end of the process. It becomes feedback. If Manim fails, the app captures the exact error and asks the model to produce a corrected version.
In the next version, we want to add a planning phase before code generation:
Acknowledgements
- Special thanks to the author of Manim Trainer for providing the fine-tuning code. The forked version of it is available at KhieuNguyen/manim-trainer which fixes some issues.
- Special thanks to Hugging Face and Gradio for providing the platform and resources to build and deploy the app, and for organizing the hackathon.
- Special thanks to Modal for providing the platform and GPU resources to fine-tune the model.
- Thanks to Codex for helping write and refine the project code and this blog post.
- Thanks for all sponsors and judges for making this event possible.