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.

Brownian motion

Een van de bekendste voorbeelden van botsende deeltjes in de natuur is Brownian motion. Fijn gemalen pollen in water lijken te dansen in willekeurige richting. Dit komt doordat de pollen worden geraakt door watermoleculen die in alle richtingen bewegen. Omdat de pollen veel zwaarder zijn dan watermoleculen, dus de beweging van de pollen is veel langzamer en minder “intens” dan die van de watermoleculen. Dit proces van willekeurige beweging door botsingen met kleinere deeltjes wordt Brownian motion genoemd en kunnen we simuleren op basis van ons (premature) botsingsmodel. Daarbij kunnen we ook gebruik maken van de zojuist geleerde manier van tracking van deeltjes, waarbij we een zowel het zware bolletjes als een enkel deeltje kunnen volgen.

Let op! We bestuderen hier nog geen thermische effecten, deze opdrachten zijn met name bedoeld om beter te begrijpen hoe het botsingsmodel in elkaar zit.

import numpy as np
import matplotlib.pyplot as plt
# Maken van de ParticleClass

class ParticleClass:
    # Het maken van het deeltje
    def __init__(self, m, v, r, R, c):
        self.m = m                         
        self.v = np.array(v, dtype=float)  
        self.r = np.array(r, dtype=float)  
        self.R = np.array(R, dtype=float)  
        self.c = c

    # Het updaten van de positie, eventueel met zwaartekracht
    def update_position(self):
        self.r += self.v * dt #+ 1/2 * a * dt**2  
              
    # Harde wand
    def boxcollision(self):
        if abs(self.r[0]) + self.R > Box_length: 
            self.v[0] = -self.v[0]                                  # Omdraaien van de snelheid
            self.r[0] = np.sign(self.r[0]) * (Box_length - self.R)  # Zet terug net binnen box                 
        if abs(self.r[1]) + self.R > Box_length: 
            self.v[1] = -self.v[1]     
            self.r[1] = np.sign(self.r[1]) * (Box_length - self.R) 
            
    @property
    def momentum(self):
        return self.m * self.v
    
    @property
    def kin_energy(self):
        return 1/2 * self.m * np.dot(self.v, self.v)
# Aanmaken van de randvoorwaarden en initiele condities
Box_size_0 = 10
Box_length_0 = Box_size_0/2
Box_length = Box_length_0     # De grootte van de box kan wijzigen!

# Particles
dt = 0.1
particles = []
N = 40
v_0 = 1

dt = 0.04

# Aanmaken van deeltjes
for i in range(N-1):
    vx = np.random.uniform(-v_0,v_0)
    vy = np.random.choice([-1, 1])*np.sqrt(v_0**2-vx**2)        
    pos = Box_length_0*np.random.uniform(-1,1,2)
    particles.append(ParticleClass(m=1.0, v=[vx, vy], r = pos, R=.5,c='blue')) 

particles.append(ParticleClass(m=20.0, v=[0, 0], r = [0, 0], R=.5,c='red')) 
#een extra deeltje wordt hier toegevoegt die veel zwaarder is dan de rest met beginsnelheid nul (de pollen), in het midden van de doos met kleur rood

er wordt een extra deeltje toegevoegt die veel zwaarder is dan de rest met snelheid nul in het midden van de doos. dit is het deeltje (pollen) waar de andere deeltjes (waterdeeltjes) mee gaan botsen

Er is een doos vol met deeltjes op willekeurige positie aangemaakt. We willen kijken waar de deeltjes zijn terechtgekomen. Hieronder staat dit weergegeven.

# Inspecteren van beginsituatie
plt.figure()

plt.xlabel('x')
plt.ylabel('y')

plt.xlim(-Box_length_0,Box_length_0)
plt.ylim(-Box_length_0,Box_length_0)


for particle, particle_object in enumerate(particles):
    plt.plot(particle_object.r[0],particle_object.r[1],color=particle_object.c,marker='.')
    plt.arrow(particle_object.r[0],particle_object.r[1], 
               particle_object.v[0],particle_object.v[1], 
               head_width=0.05, head_length=0.1, color='red')
plt.show()
<Figure size 640x480 with 1 Axes>

de code laat de richting van de snelheid zien per deeltje (snelheidsvector)

door vx overal hetzelfde te kiezen (np.random.uniform) en vy als np.sqrt(v0^2 - vx^2) hebben alle deeltjes |v| = v_0 --> alle snelheden hebben dezelfde grootte.

We gaan nu de functies van de simulatie weer aanroepen:

# Het bepalen of er een botsing plaats vindt
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 
        
def particle_collision(p1: ParticleClass, p2: ParticleClass):
    """ past snelheden aan uitgaande van overlap """
    m1, m2 = p1.m, p2.m
    delta_r = p1.r - p2.r
    delta_v = p1.v - p2.v
    dot_product = np.dot(delta_r, delta_v)
    
    # Als deeltjes van elkaar weg bewegen dan geen botsing
    if dot_product >= 0: # '='-teken voorkomt ook problemen als delta_r == \vec{0}
        return
    
    distance_squared = np.dot(delta_r, delta_r) 
    # Botsing oplossen volgens elastische botsing in 2D
    p1.v -= 2 * m2 / (m1 + m2) * dot_product / distance_squared * delta_r
    p2.v += 2 * m1 / (m1 + m2) * dot_product / distance_squared * delta_r

def handle_collisions(particles):
#your code/answer
    """ alle onderlinge botsingen afhandelen voor deeltjes in lijst """
    num_particles = len(particles)
    for i in range(num_particles):
        for j in range(i+1, num_particles):
            if collide_detection(particles[i], particles[j]):
                particle_collision(particles[i], particles[j])
#your code/answer

In onderstaande code geven we de code voor de simulatie en volgen we de positie van het zware deeltje.

# tracken van het zware deeltje
track_x = []
track_y = []

#your code/answer
track_x_light = []
track_y_light = []
#your code/answer

class ParticleClass:
    # Het maken van het deeltje
    def __init__(self, m, v, r, R, c):
        self.m = m                         
        self.v = np.array(v, dtype=float)  
        self.r = np.array(r, dtype=float)  
        self.R = np.array(R, dtype=float)  
        self.c = c

    # Het updaten van de positie, eventueel met zwaartekracht
    def update_position(self):
       self.r += self.v * dt #+ 1/2 * a * dt**2  
              
    # Harde wand
    def boxcollision(self):
        if abs(self.r[0]) + self.R > Box_length: 
           self.v[0] = -self.v[0]                                  # Omdraaien van de snelheid
           self.r[0] = np.sign(self.r[0]) * (Box_length - self.R)  # Zet terug net binnen box                 
        if abs(self.r[1]) + self.R > Box_length: 
            self.v[1] = -self.v[1]     
            self.r[1] = np.sign(self.r[1]) * (Box_length - self.R) 
#your code/answer
particles = []

#lichte deeltje

for i in range(N-1):
    vx = np.random.uniform(-v_0,v_0)
    vy = np.random.choice([-1, 1])*np.sqrt(v_0**2-vx**2)        
    pos = Box_length_0*np.random.uniform(-1,1,2)
    particles.append(ParticleClass(m=1.0, v=[vx, vy], r = pos, R=.5,c='blue')) 
#your code/answer
#zwaar deeltje
particles.append(ParticleClass(m=20.0, v=[0, 0], r = [0, 0], R=.5,c='red'))

collision_counts = []

def handle_collisions(particles):
    num_particles = len(particles)
    count = 0   # teller voor botsingen in deze tijdstap

    for i in range(num_particles):
        for j in range(i + 1, num_particles):
            if collide_detection(particles[i], particles[j]):
                particle_collision(particles[i], particles[j])
                count += 1   # tel botsing

    return count


for i in range(400):

    for p in particles:
        p.update_position()    # Update positie        
        p.boxcollision()         # Wandbotsing werkt per deeltje
        
        
    collision_count = handle_collisions(particles)
    collision_counts.append(collision_count)



#your code/answer
    
    track_x.append(particles[-1].r[0])
    track_y.append(particles[-1].r[1])
    track_x_light.append(particles[0].r[0])  # licht deeltje
    track_y_light.append(particles[0].r[1])
#your code/answer

plt.figure()
plt.plot(track_x,track_y,'r', label="zwaar deeltje")
plt.plot(track_x_light,track_y_light, 'b', label="licht deeltje")
plt.xlabel("x positie")
plt.ylabel("y positie")
plt.title("Trajecten van een zwaar en een licht deeltje")
plt.axis("equal")
#your code/answer
plt.show()
<Figure size 640x480 with 1 Axes>

overeenkomsten: ze bewegen allebei random

verschillen: -het massa effect--> het zware deeltje beweegt langzamer dan het lichte deeltje, dat kan je heel goed zien omdat er een groot massaverschil is (de lijn van het zware deeltje is korter => komt minder ver in dezelfde tijd, dus langzamer) -het lichte deeltje heeft veel scherpere hoeken als die tegen een ander deeltje aanbotst, die kan makkelijk van richting veranderen. het zware deeltje verandert minder makkelijk compleet van richting.

wat opvalt als je een aantal keer runt: de beweging is elke keer anders, maar het zware deeltje legt altijd minder afstand af dan licht.

We zouden gevoel willen krijgen voor het aantal botsingen dat per tijdseenheid plaatsvindt. Elke keer dat er een botsing plaatsvindt, zou de counter met 1 omhoog moeten gaan. Idealiter wordt het aantal botsingen opgeslagen in een array zodat je het aantal botsingen als functie van de tijd kunt weergeven.


#your code/answer
plt.figure()
plt.plot(collision_counts)
plt.xlabel("Tijdstap")
plt.ylabel("Aantal botsingen")
plt.title("Botsingen per tijdstap")
plt.show()

<Figure size 640x480 with 1 Axes>

In zulke fysica modellen is de afgelegde weg (afstand tussen begin en eindpunt) van belang. Deze afgelegde weg zegt iets over de snelheid van difussie. Idealiter bekijken we een histogram. Maar voor een histogram hebben we veel deeltjes nodig.

#your code/answer
import numpy as np
import matplotlib.pyplot as plt
 #Het bepalen of er een botsing plaats vindt
def collide_detection(p1, p2):
    dx = p1.r[0] - p2.r[0]
    dy = p1.r[1] - p2.r[1]
    rr = p1.R + p2.R
    return  dx**2+dy**2 < rr**2 
        
def particle_collision(p1: ParticleClass, p2: ParticleClass):
    """ past snelheden aan uitgaande van overlap """
    m1, m2 = p1.m, p2.m
    delta_r = p1.r - p2.r
    delta_v = p1.v - p2.v
    dot_product = np.dot(delta_r, delta_v)
    
    # Als deeltjes van elkaar weg bewegen dan geen botsing
    if dot_product >= 0: # '='-teken voorkomt ook problemen als delta_r == \vec{0}
        return
    
    distance_squared = np.dot(delta_r, delta_r) 
    # Botsing oplossen volgens elastische botsing in 2D
    p1.v -= 2 * m2 / (m1 + m2) * dot_product / distance_squared * delta_r
    p2.v += 2 * m1 / (m1 + m2) * dot_product / distance_squared * delta_r

def handle_collisions(particles):
#your code/answer
    num_particles = len(particles)
    for i in range(num_particles):
        for j in range(i+1, num_particles):
            if collide_detection(particles[i], particles[j]):
                particle_collision(particles[i], particles[j])
#your code/answer

class ParticleClass:
    # Het maken van het deeltje
    def __init__(self, m, v, r, R, c):
        self.m = m                         
        self.v = np.array(v, dtype=float)  
        self.r = np.array(r, dtype=float)  
        self.R = np.array(R, dtype=float)  
        self.c = c

    # Het updaten van de positie, eventueel met zwaartekracht
    def update_position(self):
       self.r += self.v * dt #+ 1/2 * a * dt**2

    def boxcollision(self):
        if abs(self.r[0]) + self.R > Box_length: 
           self.v[0] = -self.v[0]                                  # Omdraaien van de snelheid
           self.r[0] = np.sign(self.r[0]) * (Box_length - self.R)  # Zet terug net binnen box                 
        if abs(self.r[1]) + self.R > Box_length: 
            self.v[1] = -self.v[1]     
            self.r[1] = np.sign(self.r[1]) * (Box_length - self.R) 
#your code/answer


N = 361                     # totaal aantal deeltjes
m_heavy = 40                # massa zwaar deeltje
m_light = 1                 # massa lichte deeltjes
R = 0.5                     # straal
dt = 0.01                   # tijdstap
steps = 800          
v_0 = 4                     #startsnelheid

# Box schaalt mee met N
Box_length = 10 * np.sqrt(N / 20)
Box_length_0 = Box_length * 0.9

particles = []

# zwaar deeltje in het midden
particles.append(ParticleClass( m=m_heavy,v=[0, 0],r=[0, 0],R=R,c='red'))

# 360 lichte deeltjes
for i in range(N - 1):
    vx = np.random.uniform(-v_0, v_0)
    vy = np.random.choice([-1, 1]) * np.sqrt(v_0**2 - vx**2)
    pos = Box_length_0 * np.random.uniform(-1, 1, 2)
    particles.append(ParticleClass(m=m_light, v=[vx, vy], r=pos, R=R, c='blue'))

start_positions = [p.r.copy() for p in particles]

for t in range(steps):
    for p in particles:
        p.update_position()
        p.boxcollision()
    handle_collisions(particles)

displacements = np.array([
    np.linalg.norm(particles[i].r - start_positions[i])
    for i in range(N)])

heavy_displacement = displacements[0]   # het zware deeltje

# 
plt.figure(figsize=(8,5))
plt.hist(displacements, bins=15)
plt.axvline(heavy_displacement, color='red', linewidth=3,
            label=f'Zwaar deeltje: {heavy_displacement:.2f}')

plt.xlabel("Afgelegde weg")
plt.ylabel("Aantal deeltjes")
plt.title("Histogram van afgelegde weg na simulatie")
plt.legend()
plt.show()
<Figure size 800x500 with 1 Axes>

En nu we toch bezig zijn met twee verschillende deeltjes....

We kunnen twee “groepen” van deeltjes aanmaken, elk met een andere massa. Als we dan de zwaartekracht aan zetten, dan zouden we verwachten dat de lichtere deeltjes boven komen “drijven”.

#your code/answer
Box_width  = 10 * np.sqrt(N / 20)
Box_height = 2 * Box_width

def boxcollision(self):
    if abs(self.r[0]) + self.R > Box_width:
        self.v[0] = -self.v[0]
        self.r[0] = np.sign(self.r[0]) * (Box_width - self.R)

    if abs(self.r[1]) + self.R > Box_height:
        self.v[1] = -self.v[1]
        self.r[1] = np.sign(self.r[1]) * (Box_height - self.R)


N = 720                 # verdubbeld
N_light = N // 2
N_heavy = N // 2

m_light = 1
m_heavy = 5

particles = []

# lichte deeltjes
for i in range(N_light):
    v = np.random.uniform(-v_0, v_0, 2)
    pos = [np.random.uniform(-Box_width, Box_width),
           np.random.uniform(-Box_height, Box_height)]
    particles.append(ParticleClass(m_light, v, pos, R, 'blue'))

# zware deeltjes
for i in range(N_heavy):
    v = np.random.uniform(-v_0, v_0, 2)
    pos = [np.random.uniform(-Box_width, Box_width),
           np.random.uniform(-Box_height, Box_height)]
    particles.append(ParticleClass(m_heavy, v, pos, R, 'red'))

g = 20      # artificieel groot
def update_position(self):
    self.v[1] -= g * dt        # zwaartekracht
    self.r += self.v * dt

light_y = [p.r[1] for p in particles if p.m == m_light]
heavy_y = [p.r[1] for p in particles if p.m == m_heavy]

print("y licht :", np.mean(light_y))
print("y zwaar :", np.mean(heavy_y))


y licht : -0.6092730494025389
y zwaar : 2.013887670398977