Search code examples
pythonpython-3.xobjectmanim

Struggling to position objects correctly in Manim


My end goal with this program is to simulate constrained pendulums with springs but that is a goal for later, currently I have been trying to learn how object creation, positioning and interactions would work and will eventually build the way up.

Currently I have been trying to make my code produce N number of independent pendulums side by side but they all spawn at the same position.

I have tried a lot of things from trying to define a method to create multiple pendulums at once to the current iteration where I try to use a for loop for creating and shifting the position of the pendulum but to no avail.

import numpy as np
import manim as mn

class Pendulum:
    def __init__(self, mass, length, theta):
        self.mass = mass
        self.length = length
        self.g = -9.81
        self.angle = theta
        self.angular_vel = 0

    def step(self, dt):
        # Defining RK4
        def runge_kutta_4(f, t, y0, h=None):
            if h is None:
                h = t[1] - t[0]
            n = len(t)
            y = np.zeros((n, len(y0)))
            y[0] = y0
            for i in range(n - 1):
                k1 = h * f(t[i], y[i])
                k2 = h * f(t[i] + h/2, y[i] + k1/2)
                k3 = h * f(t[i] + h/2, y[i] + k2/2)
                k4 = h * f(t[i] + h, y[i] + k3)
                y[i+1] = y[i] + (k1 + 2*k2 + 2*k3 + k4) / 6
            return y

        def pendulum_equations(t, state):
            theta, omega = state
            force = self.mass * self.g * np.sin(theta)
            torque = force * self.length
            MoI = 1/3 * self.mass * self.length**2
            alpha = torque / MoI
            return np.array([omega, alpha])

        state = np.array([self.angle, self.angular_vel])
        t = np.array([0, dt])
        sol = runge_kutta_4(pendulum_equations, t, state)
        self.angle = sol[-1, 0]
        self.angular_vel = sol[-1, 1]

class PhysicalPendulum(mn.Scene):
    def construct(self):
        p = Pendulum(2, 10, np.pi/2)
        N = 3 # change number of pendulums here
        pendulums = []
        scale = 0.5
        spacing = 3 # Adjust the spacing between pendulums as needed

        def get_pendulum(i, rod_width=0.2, rod_height=1):
            rod = mn.Rectangle(width=rod_width, height=scale * p.length, color=mn.BLUE)
            rod.shift(mn.DOWN * scale * p.length / 2)
            rod.rotate(p.angle, about_point=rod.get_top())
            pendulum = mn.VGroup(rod)
            pendulum.shift(mn.UP * 3) # Adjust the vertical shift as needed
            if i % 2 == 0:
                pendulum.shift(mn.RIGHT * spacing * i)
            else:
                pendulum.shift(mn.LEFT * spacing * i)
            return pendulum

        def step(pendulum, dt, i):
            p.step(dt)
            pendulum.become(get_pendulum(i))

        for i in range(N):
            pendulum = get_pendulum(i)
            pendulum.add_updater(lambda mob, dt: step(mob, dt, i))
            pendulums.append(pendulum)

        self.add(*pendulums)
        self.wait(20)

        for pendulum in pendulums:
            pendulum.remove_updater(step)

Also this is my first time trying Object Oriented Programming so I would appreciate any tips on how to improve the coding style and any comments on things I have been doing wrong.


Solution

  • This is a classical example of a good question. You were almost complete in your code. I made a few change to it, and as I was doing this in google.colab, there might be some things that you'll need to add,

    import numpy as np
    import manim as mn
    
    class Pendulum:
        g = -9.81  
    
        def __init__(self, mass, length, theta):
            self.mass = mass
            self.length = length
            self.angle = theta
            self.angular_vel = 0
    
        def step(self, dt):
            def runge_kutta_4(f, t, y0, h=None):
                if h is None:
                    h = t[1] - t[0]
                n = len(t)
                y = np.zeros((n, len(y0)))
                y[0] = y0
                for i in range(n - 1):
                    k1 = h * f(t[i], y[i])
                    k2 = h * f(t[i] + h/2, y[i] + k1/2)
                    k3 = h * f(t[i] + h/2, y[i] + k2/2)
                    k4 = h * f(t[i] + h, y[i] + k3)
                    y[i+1] = y[i] + (k1 + 2*k2 + 2*k3 + k4) / 6
                return y
    
            def pendulum_equations(t, state):
                theta, omega = state
                force = self.mass * self.g * np.sin(theta)
                torque = force * self.length
                moment_of_inertia = 1/3 * self.mass * self.length**2
                alpha = torque / moment_of_inertia
                return np.array([omega, alpha])
    
            state = np.array([self.angle, self.angular_vel])
            t = np.array([0, dt])
            sol = runge_kutta_4(pendulum_equations, t, state)
            self.angle = sol[-1, 0]
            self.angular_vel = sol[-1, 1]
    
    class PhysicalPendulum(mn.Scene):
        def construct(self):
            N = 3  
            pendulums = [Pendulum(2, 10, np.pi/2) for _ in range(N)]
            scale = 0.5
            spacing = 3  
            def get_pendulum(pendulum, i, rod_width=0.2, rod_height=1):
                rod = mn.Rectangle(width=rod_width, height=scale * pendulum.length, color=mn.BLUE)
                rod.shift(mn.DOWN * scale * pendulum.length / 2)
                rod.rotate(pendulum.angle, about_point=rod.get_top())
                pendulum_group = mn.VGroup(rod)
                pendulum_group.shift(mn.UP * 3)  
                pendulum_group.shift(mn.RIGHT * spacing * i) 
                return pendulum_group
    
            def step(pendulum, dt, i):
                pendulums[i].step(dt)
                pendulum.become(get_pendulum(pendulums[i], i))
    
            pendulum_groups = []
            for i in range(N):
                pendulum_group = get_pendulum(pendulums[i], i)
                pendulum_group.add_updater(lambda mob, dt, i=i: step(mob, dt, i))  
                pendulum_groups.append(pendulum_group)
    
            self.add(*pendulum_groups)
            self.wait(20)
    
            for pendulum_group in pendulum_groups:
                pendulum_group.remove_updater(step)
    
    

    To run it in colab, if you do, you'll have to do a few things. It is rather tricky to install manin there. You need to do this:

    !pip install manim
    !apt-get install texlive texlive-latex-extra texlive-fonts-extra texlive-latex-recommended texlive-science dvipng
    !apt-get install ffmpeg
    !apt-get install sox
    !apt-get install libcairo2-dev libjpeg-dev libgif-dev
    
    !pip install manim
    
    
    

    To run the code:

    %load_ext manim
    

    and

    %%manim -ql -v WARNING PhysicalPendulum
    
    

    Here is a snapshot:

    enter image description here