January 18, 2025

About

The Taichi physics library is a tool designed to efficiently perform high-performance numerical simulations, enabling concise descriptions and fast execution of physics-based simulations.
Taichi leverages Python’s simple syntax while utilizing JIT (Just-In-Time) compilation to harness the power of GPUs and CPUs, achieving high-speed simulations. It is capable of efficiently implementing physics simulations such as fluids, rigid bodies, cloth, and particle systems.
As far as I know, it was originally developed by a Chinese researcher at MIT. It seems that Taichi is used behind Genesis simulator.
For learning purposes, I tested it with a simple sample.

Code

import os
import glob
import argparse

import matplotlib.pyplot as plt
import imageio
import taichi as ti


def main(args: argparse.Namespace):
    
    ti.init(arch=ti.gpu) 

    # parameters
    particles_n = args.particles_n 
    dt = args.time_diff
    gravity = ti.Vector([0, -9.8])
    iterations_total = args.iterations_total
    output_dir = args.output_dir
    video_name = args.video_name
    
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)


    # The state of particles
    positions = ti.Vector.field(2, dtype=ti.f32, shape=particles_n)  # position
    velocities = ti.Vector.field(2, dtype=ti.f32, shape=particles_n)  # velocity

    # The size of boundary
    boundary = ti.Vector([1.0, 1.0])

    # Initilize Kernel
    @ti.kernel
    def initialize():
        for i in range(particles_n):
            # Random Position
            positions[i] = ti.Vector([ti.random(), ti.random()])
            # Random Velocity
            velocities[i] = ti.Vector([ti.random() - 0.5, ti.random() - 0.5]) * 2

    # Kernel : update particle
    @ti.kernel
    def update():
        for i in range(particles_n):
            # update velocity with gravity
            velocities[i] += dt * gravity
            # update positions based on velocity
            positions[i] += dt * velocities[i]

            # Bound on boundary
            for d in ti.static(range(2)):  # on X, Y Axis
                if positions[i][d] < 0 or positions[i][d] > boundary[d]:
                    velocities[i][d] *= -0.8  # Reverse velocity and attenuate
                    positions[i][d] = max(0, min(positions[i][d], boundary[d]))

    # 
    # 
    # 
    initialize()

    # Main Loop
    for step in range(iterations_total):
        # Update Particle
        update()

        # Convert position info with numpy
        positions_np = positions.to_numpy()

        # Plot images with matplot
        plt.figure(figsize=(5, 5))
        plt.scatter(positions_np[:, 0], positions_np[:, 1], s=2, c='blue')
        plt.xlim(0, 1)
        plt.ylim(0, 1)
        plt.title(f"Step: {step}")
        plt.xlabel("x")
        plt.ylabel("y")
        plt.savefig(f"{output_dir}/frame_{step:04d}.png")
        plt.close()

    # Create Video file
    with imageio.get_writer(
        f"{output_dir}/{video_name}", fps=30
    ) as writer:
        for step in range(iterations_total):
            image_path = f"{output_dir}/frame_{step:04d}.png"
            writer.append_data(imageio.imread(image_path))

    png_files = glob.glob(os.path.join(output_dir, "*.png"))
    for file_path in png_files:
        try:
            os.remove(file_path)
        except Exception as e:
            print(f"Failed Removing: {file_path}, Error: {e}")
    print(f"Simulation video saved as {video_name}")


if __name__ == '__main__':
    
    parser = argparse.ArgumentParser(
        description='Particle Motion'
    )
    parser.add_argument(
        '--particles_n', '-PN', type=int,
        default=1000, help=''
    )
    parser.add_argument(
        '--time_diff', '-TD', type=float,
        default=0.01, help=''
    )
    parser.add_argument(
        '--iterations_total', '-IT', type=int,
        default=300, help=''
    )
    parser.add_argument(
        '--output_dir', '-OD', type=str,
        default='./frames', help=''
    )
    parser.add_argument(
        '--video_name', '-VN', type=str,
        default='particle_simulation.mp4', help=''
    )
    args = parser.parse_args()
    main(args)

Result