Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Deeltjes model

Zoom je heel ver in, dan zie je deeltjes rond vliegen. Elk met een eigen massa, een eigen snelheid en richting. De deeltjes botsen onderling, wisselen energie uit. We zouden op basis van een botsingsmodel van deeltjes iets moeten kunnen leren over thermodynamica. In de thermodynamica gaat het dan om heel veel deeltjes. Maar laten we beginnen met twee botsende ‘deeltjes’.

Je raadt het misschien al... deze simulatie gaan we na bouwen! Daarbij maken we gebruik van Python classes uit het vorige hoofdstuk en de basics van simulaties geleerd in Q1. Zorg er dus voor dat je weet hoe dit werkt! We maken ook gebruik van plotten, en daarbovenop een animatie. Hoe de animatie precies werkt en hoe je die zelf maakt hoef je niet te weten. Je zou wel in staat moeten zijn de code te lezen.

In de onderstaande cell maken we de ParticleClass aan, en geven we enkele parameters van onze simulatie op.

# Importeren van libraries
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# Maken van de class
class ParticleClass:
    def __init__(self, m, v, r, R):
        self.m = m                         
        self.v = np.array(v, dtype=float)  
        self.r = np.array(r, dtype=float)  
        self.R = np.array(R, dtype=float)  

    def update_position(self):
        self.r += self.v * dt

# Simulation parameters
dt = 0.1                                                            # tijd stap
num_steps = 500                                                     # aantal te nemen stappen
particle = ParticleClass(m=1.0, v=[5.0, 0], r=[0.0, 0.0], R=1.0)    # het maken van ons deeltje

We hebben nu een deeltje met massa, een snelheid, een begin positie en een straal. We hebben ook al de stapgrootte bepaald!

We willen de beweging van dat deeltje straks bestuderen en moeten dus een plot maken:

# Creeer een figuur en de assen
fig, ax = plt.subplots()
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
ax.set_aspect('equal')
ax.set_title("Animatie")
ax.set_xlabel("x")
ax.set_ylabel("y")

# Toon het deeltje als een rode stip
dot, = ax.plot([], [], 'ro', markersize=10); # semicolon to suppress output
<Figure size 640x480 with 1 Axes>

Bij het aanmaken van ons deeltje hebben we het deeltje een beginpositie en snelheid mee gegeven. Als we dan per tijdstap de positie bepalen en deze laten plotten en die plots achter elkaar plakken, dan krijgen we een animatie van het deeltje. Met FuncAnimation wordt die animatie voor ons gedaan.

# Initialisieren van de functie voor de animatie
def init():
    dot.set_data([], [])
    return dot,

# Updaten van de functie voor elk frame
def update(frame):
    particle.update_position()
    dot.set_data([particle.r[0]], [particle.r[1]])
    return dot,

# Creeer de animatie
ani = FuncAnimation(fig, update, frames=range(200), init_func=init, blit=True, interval=50)

# Omdat we werken met Jup. Notebooks (en niet een .py file)
from IPython.display import HTML
HTML(ani.to_jshtml())
Loading...

Frames in FuncAnimation verwijst naar het totaal aantal frames dat gebruikt wordt. Interval naar de snelheid van de animatie, nl. 50 milliseconden ofwel 20 frames per seconde.

Merk op dat als we de laatste cel opnieuw runnen, het deeltje zich niet in de oorsprong bevindt. Dat is een ‘eigenaardigheid’ van Jupyter Notebooks. Het is nu beter om alle code in één cel te plaatsen (hieronder gedaan voor je), zodat we ervoor zorgen dat het deeltje altijd in de oorsprong begint.

# Simulation parameters
dt = 0.1         # time step
num_steps = 500  # number of time steps
particle = ParticleClass(m=1.0, v=[5.0, 0], r=[0.0, 0.0],R=1.0)  

# Create the figure and axis
fig, ax = plt.subplots()
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
ax.set_aspect('equal')
ax.set_title("Particle Animation")
ax.set_xlabel("x")
ax.set_ylabel("y")

# Create the particle as a red dot
dot, = ax.plot([], [], 'ro', markersize=10)

# Initialization function for animation
def init():
    dot.set_data([], [])
    return dot,

# Update function for each frame
def update(frame):
    particle.update_position()
    dot.set_data([particle.r[0]], [particle.r[1]])
    return dot,

# Create animation
ani = FuncAnimation(fig, update, frames=range(200), init_func=init, blit=True, interval=50)

# For Jupyter notebook:
from IPython.display import HTML
HTML(ani.to_jshtml())
Loading...
<Figure size 640x480 with 1 Axes>

Een van de dingen die je kunt opmerken, is dat het deeltje niet in zijn doos blijft.

# doorlopende doos

#your code/answer
# Simulation parameters
dt = 0.1         # time step
num_steps = 500  # number of time steps
particle = ParticleClass(m=1.0, v=[5.0, 0], r=[0.0, 0.0],R=1.0)  

# Create the figure and axis
fig, ax = plt.subplots()
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
ax.set_aspect('equal')
ax.set_title("Particle Animation")
ax.set_xlabel("x")
ax.set_ylabel("y")

# Create the particle as a red dot
dot, = ax.plot([], [], 'ro', markersize=10)

# Initialization function for animation
def init():
    dot.set_data([], [])
    return dot,

#grenzen maken van het veld waarin het deeltje zich moet bevinden
x_min, x_max = -10, 10
y_min, y_max = -10, 10

# Update function for each frame
def update(frame):
    particle.update_position()
    # Check for boundary collisions and reflect velocity if necessary
    if particle.r[0]>x_max:
        particle.r[0]=x_min
    elif particle.r[0]<x_min:
        particle.r[0]=x_max
    dot.set_data([particle.r[0]], [particle.r[1]])
    return dot,

# Create animation
ani = FuncAnimation(fig, update, frames=range(200), init_func=init, blit=True, interval=50)

# For Jupyter notebook:
from IPython.display import HTML
HTML(ani.to_jshtml())
Loading...
<Figure size 640x480 with 1 Axes>
# doos met harde wanden

#your code/answer
# Simulation parameters
dt = 0.1         # time step
num_steps = 500  # number of time steps
particle = ParticleClass(m=1.0, v=[5.0, 0], r=[0.0, 0.0],R=1.0)  

# Create the figure and axis
fig, ax = plt.subplots()
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
ax.set_aspect('equal')
ax.set_title("Particle Animation")
ax.set_xlabel("x")
ax.set_ylabel("y")

#grenzen van de doos
x_min,x_max = -10,10
y_min,y_max = -10,10

# de grenzen rekening houdend met de straal van het deeltje
R=float(particle.R)

x_min_r = x_min + R
x_max_r = x_max - R
y_min_r = y_min + R
y_max_r = y_max - R

# Create the particle as a red dot
dot, = ax.plot([], [], 'ro', markersize=10)

# Initialization function for animation
def init():
    dot.set_data([], [])
    return dot,

#deeltje mag doos niet uit dus botst tegen wanden aan 
def reflect_axis(position, velocity, min_bound, max_bound):
    if position < min_bound:
        position = min_bound
        velocity = -velocity
    elif position > max_bound:
        position = max_bound
        velocity = -velocity
    return position, velocity

# Update function for each frame
def update(frame):
    particle.update_position()
    particle.r[0], particle.v[0] = reflect_axis(particle.r[0], particle.v[0], x_min_r, x_max_r)
    particle.r[1], particle.v[1] = reflect_axis(particle.r[1], particle.v[1], y_min_r, y_max_r)
    dot.set_data([particle.r[0]], [particle.r[1]])
    return dot,

# Create animation
ani = FuncAnimation(fig, update, frames=range(200), init_func=init, blit=True, interval=50)

# For Jupyter notebook:
from IPython.display import HTML
HTML(ani.to_jshtml())
Loading...
<Figure size 640x480 with 1 Axes>
#your code/answer
# Maken van de class
class ParticleClass:
    def __init__(self, m, v, r, R):
        self.m = m                         
        self.v = np.array([5.0,3.0], dtype=float)  
        self.r = np.array(r, dtype=float)  
        self.R = np.array(R, dtype=float)  

    def update_position(self):
        self.r += self.v * dt

# Simulation parameters
dt = 0.1         # time step
num_steps = 500  # number of time steps
particle = ParticleClass(m=1.0, v=[5.0, 0], r=[0.0, 0.0],R=1.0)  

# Create the figure and axis
fig, ax = plt.subplots()
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
ax.set_aspect('equal')
ax.set_title("Particle Animation")
ax.set_xlabel("x")
ax.set_ylabel("y")

#grenzen van de doos
x_min,x_max = -10,10
y_min,y_max = -10,10

# de grenzen rekening houdend met de straal van het deeltje
R=float(particle.R)

x_min_r = x_min + R
x_max_r = x_max - R
y_min_r = y_min + R
y_max_r = y_max - R

# Create the particle as a red dot
dot, = ax.plot([], [], 'ro', markersize=10)

# Initialization function for animation
def init():
    dot.set_data([], [])
    return dot,

#deeltje mag doos niet uit dus botst tegen wanden aan 
def reflect_axis(position, velocity, min_bound, max_bound):
    if position < min_bound:
        position = min_bound
        velocity = -velocity
    elif position > max_bound:
        position = max_bound
        velocity = -velocity
    return position, velocity

# Update function for each frame
def update(frame):
    particle.update_position()
    particle.r[0], particle.v[0] = reflect_axis(particle.r[0], particle.v[0], x_min_r, x_max_r)
    particle.r[1], particle.v[1] = reflect_axis(particle.r[1], particle.v[1], y_min_r, y_max_r)
    dot.set_data([particle.r[0]], [particle.r[1]])
    return dot,

# Create animation
ani = FuncAnimation(fig, update, frames=range(200), init_func=init, blit=True, interval=50)

# For Jupyter notebook:
from IPython.display import HTML
HTML(ani.to_jshtml())
Loading...
<Figure size 640x480 with 1 Axes>

Laten we teruggaan naar ons deeltje. Er is een functie om de positie bij te werken, hoewel de snelheid hetzelfde lijkt te blijven... kunnen we de snelheid veranderen door (bijvoorbeeld) de versnelling door de zwaartekracht?

# Maken van de class met versnelling
class ParticleClass:
    def __init__(self, m, v, r, R):
        self.m = m                  # mass of the particle
        self.v = np.array([5.0,3.0], dtype=float)  # velocity vector
        self.r = np.array(r, dtype=float)  # position vector
        self.R = np.array(R, dtype=float)  # radius of the particle

    def update_position(self):
        self.r += self.v * dt
    
    def update_velocity(self, a):
        a=np.array([0, -9.81], dtype=float)
        self.v += a*dt
        
#your code/answer
# Maken van de class met versnelling
class ParticleClass:
    def __init__(self, m, v, r, R):
        self.m = m                  # mass of the particle
        self.v = np.array([5.0,3.0], dtype=float)  # velocity vector
        self.r = np.array(r, dtype=float)  # position vector
        self.R = np.array(R, dtype=float)  # radius of the particle

    def update_position(self):
        self.r += self.v * dt
    
    def update_velocity(self, a):
        a=np.array([0, -9.81], dtype=float)
        self.v += a*dt
# Simulation parameters
dt = 0.1         # time step
num_steps = 500  # number of time steps
particle = ParticleClass(m=1.0, v=[5.0, 0], r=[0.0, 0.0],R=1.0)  

# Create the figure and axis
fig, ax = plt.subplots()
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
ax.set_aspect('equal')
ax.set_title("Particle Animation")
ax.set_xlabel("x")
ax.set_ylabel("y")

#grenzen van de doos
x_min,x_max = -10,10
y_min,y_max = -10,10

# de grenzen rekening houdend met de straal van het deeltje
R=float(particle.R)

x_min_r = x_min + R
x_max_r = x_max - R
y_min_r = y_min + R
y_max_r = y_max - R

# Create the particle as a red dot
dot, = ax.plot([], [], 'ro', markersize=10)

# Initialization function for animation
def init():
    dot.set_data([], [])
    return dot,

#deeltje mag doos niet uit dus botst tegen wanden aan 
def reflect_axis(position, velocity, min_bound, max_bound):
    if position < min_bound:
        position = min_bound
        velocity = -velocity
    elif position > max_bound:
        position = max_bound
        velocity = -velocity
    return position, velocity

# Update function for each frame
def update(frame):
    particle.update_velocity(a=None)  # Update velocity with acceleration
    particle.update_position()
    particle.r[0], particle.v[0] = reflect_axis(particle.r[0], particle.v[0], x_min_r, x_max_r)
    particle.r[1], particle.v[1] = reflect_axis(particle.r[1], particle.v[1], y_min_r, y_max_r)
    dot.set_data([particle.r[0]], [particle.r[1]])
    return dot,

# Create animation
ani = FuncAnimation(fig, update, frames=range(200), init_func=init, blit=True, interval=50)

# For Jupyter notebook:
from IPython.display import HTML
HTML(ani.to_jshtml())
Loading...
<Figure size 640x480 with 1 Axes>

Een optie om de simulatie te verbeteren is de tijdstap Δt\Delta t kleiner te maken, maar dan hebben we ook meer geduld meer nodig - het aantal berekeningen schaalt met 1Δt\frac{1}{\Delta t}. Een tweede optie is een meer directe oplossing: We weten dat a=const.a = const. en daarom weten we ook de bewegingsvergelijking van het deeltje!

Daarnaast willen we graag weten waar het deeltje is geweest, dat is in onderstaande code toegevoegd.

# Maken van de class met versnelling
class ParticleClass:
    def __init__(self, m, v, r, R):
        self.m = m                  
        self.v = np.array(v, dtype=float)  
        self.r = np.array(r, dtype=float)  
        self.R = np.array(R, dtype=float)  

    def update_position(self):
        self.r += self.v * dt + 1/2 * a * dt**2  
    
    def update_velocity(self, a):
        """Update the particle's velocity."""
        self.v += a*dt

# Simulation parameters
dt = 0.1         
num_steps = 500  
particle = ParticleClass(m=1.0, v=[0, 0], r=[0.0, 0.0],R=1.0)  
a = np.array([0.0, -5.0])  

track_x = []
track_y = []

# creeeren van de plot en de assen
fig, ax = plt.subplots()
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
ax.set_aspect('equal')
ax.set_title("Particle Animation")
ax.set_xlabel("x")
ax.set_ylabel("y")
track_line, = ax.plot([], [], 'r--', linewidth=1)  

# creeeren van ons rode deeltje
dot, = ax.plot([], [], 'ro', markersize=10);

# initializeren van onze functie voor de animatie
def init():
    dot.set_data([], [])
    return dot,

# Update function for each frame
def update(frame):
    particle.update_position()
    particle.update_velocity(a)

    track_x.append(particle.r[0])
    track_y.append(particle.r[1])
    track_line.set_data(track_x, track_y)
    
    dot.set_data([particle.r[0]], [particle.r[1]])
    if particle.r[0]**2>100: # Check if particle is outside the bounds, np.abs could be used but is slower
        particle.v[0] = -particle.v[0]
    if particle.r[1]**2>100: # Check if particle is outside the bounds, np.abs could be used but is slower
        particle.v[1] = -particle.v[1]
    return dot, track_line

# Create animation
ani = FuncAnimation(fig, update, frames=range(200), init_func=init, blit=True, interval=50)

# For Jupyter notebook:
from IPython.display import HTML
HTML(ani.to_jshtml())
Loading...
<Figure size 640x480 with 1 Axes>

We hebben steeds slechts gewerkt met een enkel deeltje. Maar om de simulatie uit het filmpje te maken, hebben we twee deeltjes nodig.

#your code/answer
# Twee deeltjes in een doos met zwaartekracht
# -------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# ---------- Klasse ----------
class ParticleClass:
    def __init__(self, m, v, r, R):
        self.m = float(m)
        self.v = np.array(v, dtype=float)   # [vx, vy]
        self.r = np.array(r, dtype=float)   # [x, y]
        self.R = float(R)                   # straal (voor wandbotsingen)

    def update_position(self, a, dt):
        # r(t+dt) = r(t) + v(t)*dt + 0.5*a*dt^2
        self.r += self.v * dt + 0.5 * a * dt**2

    def update_velocity(self, a, dt):
        # v(t+dt) = v(t) + a*dt
        self.v += a * dt

    def reflect_on_walls(self, box_L):
        """
        Elastische botsing met dozenwanden.
        box_L: half-breede/hoogte van de doos (doos is [-L, L] x [-L, L]).
        We houden rekening met de straal R.
        """
        xmin, xmax = -box_L + self.R, box_L - self.R
        ymin, ymax = -box_L + self.R, box_L - self.R

        # X-rand
        if self.r[0] < xmin:
            self.r[0] = xmin
            self.v[0] *= -1
        elif self.r[0] > xmax:
            self.r[0] = xmax
            self.v[0] *= -1

        # Y-rand
        if self.r[1] < ymin:
            self.r[1] = ymin
            self.v[1] *= -1
        elif self.r[1] > ymax:
            self.r[1] = ymax
            self.v[1] *= -1


# ---------- Simulatie-instellingen ----------
dt = 0.05
num_steps = 600
g = 5.0
a = np.array([0.0, -g])       # zwaartekracht omlaag

L = 10.0                      # doos loopt van -L..L
R = 0.3                       # straal van elk deeltje

# Zelfde startpunt, geen verticale beginsnelheid
start_r = np.array([0.0, 0.0])
vA = np.array([6.0, 0.0])     # deeltje A: horizontale startsnelheid
vB = np.array([0.0, 0.0])     # deeltje B: geen horizontale startsnelheid

particleA = ParticleClass(m=1.0, v=vA, r=start_r.copy(), R=R)
particleB = ParticleClass(m=1.0, v=vB, r=start_r.copy(), R=R)

# Trajecten bijhouden
trackA_x, trackA_y = [], []
trackB_x, trackB_y = [], []

# ---------- Plot & animatie ----------
fig, ax = plt.subplots(figsize=(6, 6))
ax.set_xlim(-L, L)
ax.set_ylim(-L, L)
ax.set_aspect('equal')
ax.set_title("Twee deeltjes in een doos met zwaartekracht")
ax.set_xlabel("x")
ax.set_ylabel("y")

# Randen visueel (optioneel)
ax.plot([-L, L, L, -L, -L], [-L, -L, L, L, -L], 'k-', linewidth=1)

# Trajectlijnen
trackA_line, = ax.plot([], [], 'r--', linewidth=1, label="A - met vx0")
trackB_line, = ax.plot([], [], 'b--', linewidth=1, label="B - zonder vx0")

# Deeltjes (verschillende kleuren)
dotA, = ax.plot([], [], 'ro', markersize=10)
dotB, = ax.plot([], [], 'bo', markersize=10)
ax.legend(loc="upper right")

def init():
    dotA.set_data([], [])
    dotB.set_data([], [])
    trackA_line.set_data([], [])
    trackB_line.set_data([], [])
    return dotA, dotB, trackA_line, trackB_line

def step_particle(p):
    # 1) positie updaten met huidige v en a
    p.update_position(a, dt)
    # 2) snelheid updaten met a
    p.update_velocity(a, dt)
    # 3) check botsing met wanden (elastisch)
    p.reflect_on_walls(L)

def update(frame):
    step_particle(particleA)
    step_particle(particleB)

    # Traject opslaan
    trackA_x.append(particleA.r[0]); trackA_y.append(particleA.r[1])
    trackB_x.append(particleB.r[0]); trackB_y.append(particleB.r[1])

    # Plot bijwerken
    trackA_line.set_data(trackA_x, trackA_y)
    trackB_line.set_data(trackB_x, trackB_y)
    dotA.set_data([particleA.r[0]], [particleA.r[1]])
    dotB.set_data([particleB.r[0]], [particleB.r[1]])

    return dotA, dotB, trackA_line, trackB_line

ani = FuncAnimation(fig, update, frames=range(num_steps),
                    init_func=init, blit=True, interval=20)

# In Jupyter:
from IPython.display import HTML
HTML(ani.to_jshtml())
# In een .py-bestand kun je i.p.v. bovenstaand HTML() ook plt.show() gebruiken:
# plt.show()
Animation size has reached 20996737 bytes, exceeding the limit of 20971520.0. If you're sure you want a larger animation embedded, set the animation.embed_limit rc parameter to a larger value (in MB). This and further frames will be dropped.
Loading...
<Figure size 600x600 with 1 Axes>

We waren gebleven bij het maken van een botsingsmodel, waarbij we nu twee deeltjes hebben die onderhevig zijn aan zwaartekracht.

Laten we de zwaartekracht even vergeten en alleen 1D kijken.

# Define a class for a particle

# Maken van de class met botsing
class ParticleClass:
    def __init__(self, m, v, r, R):
        self.m = m                  
        self.v = np.array(v, dtype=float)  
        self.r = np.array(r, dtype=float)  
        self.R = np.array(R, dtype=float)  

    def update_position(self):
        self.r += self.v * dt  

    def collide_detection(self, other):
        dx = self.r[0] - other.r[0]
        dy = self.r[1] - other.r[1]
        rr = self.R + other.R
        return  dx**2+dy**2 < rr**2 

# Simulation parameters
dt = 0.1         # time step
num_steps = 200  # number of time steps

particleA = ParticleClass(m=1.0, v=[2.5, 0], r=[-2.0, 0.0],R=0.45)  
particleB = ParticleClass(m=1.0, v=[-1, 0], r=[0.0, 0.0],R=0.45)  

track_x = []
track_y = []


# Creer de plot

fig, ax = plt.subplots()

ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
ax.set_aspect('equal')
ax.set_title("Particle Animation")
ax.set_xlabel("x")
ax.set_ylabel("y")
track_line, = ax.plot([], [], 'r--', linewidth=1)  

# Toon het deeltje als een rode stip
dot, = ax.plot([], [], 'ro', markersize=10); # semicolon to suppress output
dotA, = ax.plot([], [], 'ro', markersize=10)
dotB, = ax.plot([], [], 'bo', markersize=10)

# Initaliseren voor de animatie
def init():
    dot.set_data([], [])
    return dot,

# Updaten van de functie per frame
def update(frame):
    particleA.update_position()
    particleB.update_position()

    track_x.append(particleA.r[0])
    track_y.append(particleA.r[1])
    track_line.set_data(track_x, track_y)
    
    dotA.set_data([particleA.r[0]], [particleA.r[1]])
    dotB.set_data([particleB.r[0]], [particleB.r[1]])

    # botsing tussen de deeltjes onderling
    if particleA.collide_detection(particleB):
            
        # simpele elastische botsing voor gelijke massa's

        # botsingsrichting
        n = particleA.r - particleB.r
        n = n / np.linalg.norm(n)

        # projecties van de snelheden op n
        vA_n = np.dot(particleA.v, n)
        vB_n = np.dot(particleB.v, n)

        # wissel de snelheidscomponenten langs n om
        particleA.v += (vB_n - vA_n) * n
        particleB.v += (vA_n - vB_n) * n



    # botsing met de wand
    if particleA.r[0]**2>100: 
        particleA.v[0] = -particleA.v[0]
    if particleA.r[1]**2>100: 
        particleA.v[1] = -particleA.v[1]

    dot.set_data([particleB.r[0]], [particleB.r[1]])
    if particleB.r[0]**2>100: 
        particleB.v[0] = -particleB.v[0]
    if particleB.r[1]**2>100: 
        particleB.v[1] = -particleB.v[1]

    return dot, track_line

# Creeer animatie
ani = FuncAnimation(fig, update, frames=range(num_steps), init_func=init, blit=True, interval=50)

# Voor Jupyter notebook:
from IPython.display import HTML
HTML(ani.to_jshtml())
Loading...
<Figure size 640x480 with 1 Axes>

Terug naar ons vraagstuk... we willen een simulatie waarbij een deeltje met een massa m1m_1 en snelheid v1v_1 op een andere stilstaand deeltje met massa m2m_2 botst. Deeltje twee beweegt naar een muur, botst tegen de muur en beweegt richting deeltje 1 en botst tegen dit deeltje. Hoe vaak vindt deze botsing plaats als functie van de massa verhouding m1m2\frac{m_1}{m_2}?

Daarvoor moeten we even terug naar het botsingsmodel zoals geleerd in Klassieke Mechanica. Bij elastische botsingen is zowel het impulsmoment als de kinetische energie behouden.

ipibefore=ipiafter\sum_i \vec{p}_i^{before} = \sum_i \vec{p}_i^{after}
Ekin,before=Ekin,afterE_{kin, before} = E_{kin, after}

Voor een botsing met twee deeltjes levert dit een analytische oplossing:

v1=v12m2m1+m2 v1v2,x1x2x1x22 (x1x2),v2=v22m1m1+m2 v2v1,x2x1x2x12 (x2x1)\begin{align} \mathbf{v}'_1 &= \mathbf{v}_1-\frac{2 m_2}{m_1+m_2} \ \frac{\langle \mathbf{v}_1-\mathbf{v}_2,\,\mathbf{x}_1-\mathbf{x}_2\rangle}{\|\mathbf{x}_1-\mathbf{x}_2\|^2} \ (\mathbf{x}_1-\mathbf{x}_2), \\ \mathbf{v}'_2 &= \mathbf{v}_2-\frac{2 m_1}{m_1+m_2} \ \frac{\langle \mathbf{v}_2-\mathbf{v}_1,\,\mathbf{x}_2-\mathbf{x}_1\rangle}{\|\mathbf{x}_2-\mathbf{x}_1\|^2} \ (\mathbf{x}_2-\mathbf{x}_1) \end{align}

we vragen je natuurlijk niet om deze vergelijking zelf te schrijven in Python, maar die vergelijking operationaliseren we hieronder wel.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

class ParticleClass:
    def __init__(self, m, v, r, R):
        self.m = m                  # mass of the particle
        self.v = np.array(v, dtype=float)  # velocity vector
        self.r = np.array(r, dtype=float)  # position vector
        self.R = np.array(R, dtype=float)  # radius of the particle

    def update_position(self):
        self.r += self.v * dt 

    def collide_detection(self, other):
        return np.linalg.norm(self.r - other.r) <= (self.R + other.R)

# Simulation parameters
dt = 0.1         # time step
num_steps = 530  # number of time steps
m1 = 1.0
m2 = 1.0

particleA = ParticleClass(m=m1, v=[0, 0], r=[-4.0, 0.0],R=0.45)  
particleB = ParticleClass(m=m2, v=[-1, 0], r=[-2.0, 0.0],R=0.45)  


# Create the figure and axis

fig, ax = plt.subplots()

ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
ax.set_aspect('equal')
ax.set_title("Particle Animation")
ax.set_xlabel("x")
ax.set_ylabel("y")
dot, = ax.plot([], [], 'ro', markersize=10); # semicolon to suppress output

counter = 0

# Create the particle as a red dot
dotA, = ax.plot([], [], 'ro', markersize=10)
dotB, = ax.plot([], [], 'bo', markersize=10)

counter_text = ax.text(-9.5, 9, "")

# Initialization function for animation
def init():
    dot.set_data([], [])
    return dot,

# Update function for each frame
def update(frame):
    global counter
    particleA.update_position()
    particleB.update_position()

    dotA.set_data([particleA.r[0]], [particleA.r[1]])
    dotB.set_data([particleB.r[0]], [particleB.r[1]])

    counter_text.set_text(f"Collisions: {counter}")

    #collision detection and response
    if particleA.collide_detection(particleB):
        vA, vB, mA, mB, rA, rB = particleA.v, particleB.v, particleA.m, particleB.m, particleA.r, particleB.r
        vA_new = vA - 2 * mB / (mA + mB) * np.dot(vA - vB, rA - rB) / (1e-12+np.linalg.norm(rA - rB))**2 * (rA - rB)
        vB_new = vB - 2 * mA / (mA + mB) * np.dot(vB - vA, rB - rA) / (1e-12+np.linalg.norm(rB - rA))**2 * (rB - rA)
        particleA.v = vA_new
        particleB.v = vB_new
        counter=counter+1


    # wall collision detection and response
    if particleA.r[0]**2>100: # Check if particle is outside the bounds, np.abs could be used but is slower
        particleA.v[0] = -particleA.v[0]
        counter=counter+1
    if particleA.r[1]**2>100: 
        particleA.v[1] = -particleA.v[1]
        counter=counter+1

    dot.set_data([particleB.r[0]], [particleB.r[1]])
    if particleB.r[0]**2>100: 
        particleB.v[0] = -particleB.v[0]
        counter=counter + 1
    if particleB.r[1]**2>100: 
        particleB.v[1] = -particleB.v[1]
        counter=counter+1

    return dot, counter_text

# Create animation
ani = FuncAnimation(fig, update, frames=range(200), init_func=init, blit=True, interval=50)

# For Jupyter notebook:
from IPython.display import HTML
HTML(ani.to_jshtml())
Loading...
<Figure size 640x480 with 1 Axes>