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.

In bovenstaande simulatie hebben de twee zuigers een tegengestelde beweging. Maak nu een nieuwe simulatie, waarbij de twee zuigers in dezelfde richting bewegen. Dan verandert het oppervlak van het volume dus niet. (Dit kan in een nieuw werkblad, als je dat fijner vindt)

  • Meet de temperatuur van het gas als functie van de verplaatsing van de zuigers. Beweeg de zuigers minimaal over de halve lengte van het volume.

  • Varieer de snelheid van de zuigers en de starttemperatuur van het gas en onderzoek de resultaten.

  • Verklaar het gedrag. Doe dit zowel op een microscopisch niveau (met stuiterende balletjes met een impuls en een energie) als op een macroscopisch niveau (met druk, volume en temperatuur)

# ruimte voor uitwerking

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from scipy.optimize import curve_fit

BOX_SIZE_0 = 10                 # Hoogte en breedte startvolume in nm
N = 40                          # Aantal deeltjes
V_0 = 410                       # Startsnelheid van deeltjes in nm/ns
RADIUS = 0.3                    # Straal van moleculen in nm
DT = 7.5e-5                     # Tijdstap om geen botsing te missen in ns
V_PISTON_0 = V_0         # Startsnelheid van zuiger 
# (negatief betekent zowel links als rechts naar binnen gericht)

class ParticleClass:
    def __init__(self, m, v, r, R):
        """ maakt een deeltje (constructor) """
        self.m = m                         
        self.v = np.array(v, dtype=float)  
        self.r = np.array(r, dtype=float)  
        self.R = R

    def update_position(self):
        """ verandert positie voor één tijdstap """
        self.r += self.v * DT 
            
    @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)
    
def collide_detection(p1: ParticleClass, p2: ParticleClass) -> bool:
    """ Geeft TRUE als de deeltjes overlappen """
    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
box_height = BOX_SIZE_0     # hoogte van beheersvolume
box_length = BOX_SIZE_0     # breedte van beheersvolume
box_center_x = 0.0          # Midden van de box
impulse_outward = 0.0       # totale stoot van deeltjes naar buiten gericht
pressure = 0.0              # druk in beheersvolume
v_piston = V_PISTON_0       # huidige snelheid van zuiger
if 1: v_piston*=1          # If 1: Move right, if 0: Move left


work = 0.0                  # arbeid uitgevoerd door gas
def top_down_collision(particle: ParticleClass):
    """ botsingen met wanden onder en boven controleren en totale stoot bepalen """
    global impulse_outward, box_height
    if abs(particle.r[1]) + particle.R > box_height / 2:
        particle.r[1] = np.sign(particle.r[1]) * (box_height/2 - particle.R)
        impulse_outward += abs(particle.momentum[1]) * 2
        particle.v[1] *= -1
    
def left_right_collision(particle: ParticleClass):
    """ verzorgen van botsingen met wand links en rechts, die als zuiger kunnen bewegen """
    global box_length, v_piston, impulse_outward, work, box_center_x

    wall_right = box_center_x + box_length/2
    wall_left = box_center_x - box_length/2

    if particle.r[0] + particle.R > wall_right:     # Hits the RIGHT wall
        particle.r[0] = wall_right - particle.R
        piston_velocity = v_piston # If piston to the right, 
        relative_velocity = particle.v[0] - piston_velocity
        particle.v[0] = -relative_velocity + piston_velocity
        impulse_outward += 2 * particle.m * abs(relative_velocity)
        work += 2 * particle.m * relative_velocity * piston_velocity

    elif particle.r[0] - particle.R < wall_left:     # Hits the LEFT wall
        particle.r[0] = wall_left + particle.R
        piston_velocity = v_piston # If piston to the right, 
        relative_velocity = particle.v[0] - piston_velocity
        particle.v[0] = -relative_velocity + piston_velocity
        impulse_outward += 2 * particle.m * abs(relative_velocity)
        work += 2 * particle.m * relative_velocity * piston_velocity
def create_particles(particles):
    """ Leegmaken en opnieuw aanmaken van deeltjes  in lijst """
    global box_length, box_height
    particles.clear()
    for _ in range(N):
        vx = np.random.uniform(-V_0, V_0)
        vy = np.random.choice([-1, 1]) * np.sqrt(V_0**2 - vx**2)        
        x = np.random.uniform(-box_length/2 + RADIUS, box_length/2 - RADIUS)
        y = np.random.uniform(-box_height/2 + RADIUS, box_height/2 - RADIUS)
        particles.append(ParticleClass(m=1.0, v=[vx, vy], r=[x, y], R=RADIUS))
            
def handle_collisions(particles):
    """ 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])

def handle_walls(particles):
    """ botsing met wanden controleren voor alle deeltjes in lijst en gemiddeld bepaling druk """
    global pressure, impulse_outward, box_height, box_length   # om pressure buiten de functie te kunnen gebruiken
    impulse_outward = 0.0
    for p in particles:
        left_right_collision(p)
        top_down_collision(p)    
    pressure = 0.95 * pressure + 0.05 * impulse_outward / ((2 * box_length + 2 * box_height) * DT) 

def take_time_step(particles):
    """ zet tijdstap voor een lijst deeltjes en verwerk alle botsingen onderling en met wanden """
    global box_center_x, v_piston
    box_center_x += v_piston*DT

    for p in particles:
        p.update_position()
    handle_collisions(particles)
    handle_walls(particles)  

k_B = 1.38e-23      # Constante van Boltzmann J/K
f_grad = 2          # Twee dimensies
m_gas = 4.65e-26    # mass of a molecule
def temperature(particles) -> float:
    particle_velocities = np.array([p.v for p in particles])        # Make an array of all velocity values of all particles
    #particle_velocities[:, 0] -= v_piston
    velocities_squared = np.sum(particle_velocities**2, axis=1)     # Square this array
    velocities_sqared_avg = np.mean(velocities_squared)             # Calculate the average

    temp = (m_gas*velocities_sqared_avg)/(f_grad*k_B)               # Apply temperature formula

    return temp

def take_time_step(particles):
    """ zet tijdstap voor een lijst deeltjes en verwerk alle botsingen onderling en met wanden """
    global box_center_x, v_piston
    box_center_x += v_piston * DT 
    for p in particles:
        p.update_position()
    handle_walls(particles)  
    handle_collisions(particles)

def update(frame):
    global box_center_x
    take_time_step(particles)

    wall_right = box_center_x + box_length/2
    wall_left = box_center_x - box_length/2

    dot.set_data([p.r[0] for p in particles], [p.r[1] for p in particles])
    ax.set_xlim(wall_left, wall_right)
    return dot,
# Deel van de animated simulatie
from matplotlib.animation import FuncAnimation
from IPython.display import HTML

fig, ax = plt.subplots()
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_xlim(-BOX_SIZE_0/2, BOX_SIZE_0/2)
ax.set_ylim(-BOX_SIZE_0/2, BOX_SIZE_0/2)
ax.set_aspect('equal')
dot, = ax.plot([], [], 'ro', ms=14);

def init():
    dot.set_data([], [])
    return dot,


# we kiezen het aantal datapunten zodat het volume tot 1/3 van het begin volume reduceert
num_steps = 250

particles = []
box_center_x = 0

box_length = BOX_SIZE_0         # zetten zuiger terug
v_piston = V_PISTON_0
create_particles(particles)     # resetten deeltjes     

ani = FuncAnimation(fig, update, frames=int(num_steps), init_func=init, blit=True, interval=50)
HTML(ani.to_jshtml())
Loading...
<Figure size 640x480 with 1 Axes>
# Meet de temperatuur van het gas als functie van de verplaatsing van de zuigers. Beweeg de zuigers minimaal over de halve lengte van het volume. 

num_steps = 1000
BOX_SIZE_0 = 10                 # Hoogte en breedte startvolume in nm
N = 40                          # Aantal deeltjes


particles = []
temperatures = np.zeros(num_steps, dtype=float)
x_verplaatsing = np.zeros(num_steps, dtype=float)

box_length = BOX_SIZE_0         # zetten zuiger terug
box_center_x = 0
v_piston = 2*V_PISTON_0
create_particles(particles)     # resetten deeltjes 

for i in range(num_steps):
    take_time_step(particles)
    temperatures[i] = temperature(particles)
    x_verplaatsing[i] = box_center_x

plt.figure()
plt.xlabel(r'Verplaatsing (nm)')
plt.ylabel('Temperature (K)')

plt.plot(x_verplaatsing, temperatures, '-r')

plt.show()

BOX_SIZE_0 = 10                 # Hoogte en breedte startvolume in nm
N = 40                          # Aantal deeltjes
<Figure size 640x480 with 1 Axes>
# Deel van de animated simulatie
fig, ax = plt.subplots()
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_xlim(-BOX_SIZE_0/2, BOX_SIZE_0/2)
ax.set_ylim(-BOX_SIZE_0/2, BOX_SIZE_0/2)
ax.set_aspect('equal')
dot, = ax.plot([], [], 'ro', ms=14);

def init():
    dot.set_data([], [])
    return dot,


# we kiezen het aantal datapunten zodat het volume tot 1/3 van het begin volume reduceert
num_steps = 250

particles = []
box_center_x = 0

box_length = BOX_SIZE_0         # zetten zuiger terug
v_piston = 6*V_PISTON_0
create_particles(particles)     # resetten deeltjes     

ani = FuncAnimation(fig, update, frames=int(num_steps), init_func=init, blit=True, interval=50)
HTML(ani.to_jshtml())
Loading...
<Figure size 640x480 with 1 Axes>
# - Varieer de snelheid van de zuigers en de starttemperatuur van het gas en onderzoek de resultaten.
def run_simulation(steps, v0_particles, v0_piston):
    global v_piston, box_center_x, box_length, V_0
    V_0 = 410

    num_steps = steps
    v_piston = v0_piston*V_PISTON_0
    V_0*= v0_particles

    particles = []
    box_length = BOX_SIZE_0         # zetten zuiger terug
    box_center_x = 0
    create_particles(particles)     # resetten deeltjes 
    
    temperatures = np.zeros(num_steps, dtype=float)
    x_verplaatsing = np.zeros(num_steps, dtype=float)


    for i in range(num_steps):
        take_time_step(particles)
        temperatures[i] = temperature(particles)
        x_verplaatsing[i] = box_center_x

    return temperatures, x_verplaatsing

# Analyzeer temperatuur & beginsnelheid relatie.
N=40
total_runs = 25
steps_per_run = 500

start_speed = 0.1   # Multiple of V0
end_speed = 3       # Multiple of v0
speeds = np.linspace(start_speed, end_speed, total_runs)

start_particles = 1
end_particles = 100
particle_n = np.linspace(start_particles, end_particles, total_runs)
print(particle_n)

avg_temperature_bin_piston = np.zeros(total_runs, dtype=float)
avg_temperature_bin_particles = np.zeros(total_runs, dtype=float)

piston_speed_bin = np.zeros(total_runs, dtype=float)
particle_speed_bin = np.zeros(total_runs, dtype=float)

for run in range(total_runs):
    run_temperature, run_movement = run_simulation(steps_per_run, 1, speeds[run])
    avg_temperature_bin_piston[run] = np.mean(run_temperature[int(len(run_temperature)/2):])
    piston_speed_bin[run] = speeds[run]
    print(f'Speed used: {speeds[run]}')

    run_temperature, run_movement = run_simulation(steps_per_run, speeds[run], 1)
    avg_temperature_bin_particles[run] = np.mean(run_temperature[int(len(run_temperature)/2):])
    particle_speed_bin[run] = speeds[run]

    print(f'Run done {run+1}/{total_runs}')

#plt.figure()
#plt.plot(piston_speed_bin, avg_temperature_bin_speeds)
#plt.show()


fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
ax1.plot(piston_speed_bin, avg_temperature_bin_piston, label='Speed-Temperature relationship', linestyle='-')

ax1.set_xlabel(r'Speed (multiple of baseline speed)')
ax1.set_ylabel(r'Mean stable temperature')
ax1.set_title(r'Piston speed-Temperature relation')
ax1.legend()

ax2.plot(particle_speed_bin, avg_temperature_bin_particles, label='Particle-Temperature relationship', linestyle='-')

ax2.set_xlabel(r'Multiple of baseline speed')
ax2.set_ylabel(r'Mean stable temperature')
ax2.set_title(r'Particle speed-Temperature relation')
ax2.legend()

plt.show()
[  1.      5.125   9.25   13.375  17.5    21.625  25.75   29.875  34.
  38.125  42.25   46.375  50.5    54.625  58.75   62.875  67.     71.125
  75.25   79.375  83.5    87.625  91.75   95.875 100.   ]
Speed used: 0.1
Run done 1/25
Speed used: 0.22083333333333333
Run done 2/25
Speed used: 0.3416666666666667
Run done 3/25
Speed used: 0.4625
Run done 4/25
Speed used: 0.5833333333333334
Run done 5/25
Speed used: 0.7041666666666666
Run done 6/25
Speed used: 0.825
Run done 7/25
Speed used: 0.9458333333333333
Run done 8/25
Speed used: 1.0666666666666667
Run done 9/25
Speed used: 1.1875
Run done 10/25
Speed used: 1.3083333333333333
Run done 11/25
Speed used: 1.4291666666666667
Run done 12/25
Speed used: 1.55
Run done 13/25
Speed used: 1.6708333333333334
Run done 14/25
Speed used: 1.7916666666666667
Run done 15/25
Speed used: 1.9125
Run done 16/25
Speed used: 2.033333333333333
Run done 17/25
Speed used: 2.154166666666667
Run done 18/25
Speed used: 2.275
Run done 19/25
Speed used: 2.3958333333333335
Run done 20/25
Speed used: 2.5166666666666666
Run done 21/25
Speed used: 2.6375
Run done 22/25
Speed used: 2.7583333333333333
Run done 23/25
Speed used: 2.879166666666667
Run done 24/25
Speed used: 3.0
Run done 25/25
<Figure size 1000x400 with 2 Axes>
  • Verklaar het gedrag. Doe dit zowel op een microscopisch niveau (met stuiterende balletjes met een impuls en een energie) als op een macroscopisch niveau (met druk, volume en temperatuur)

Microscopish niveau: De deeltjes beginnen met een willekeurige snelheid. Wanneer een deeltje wordt aangeraakt door de bewegende wand vind er een grote snelheidsverandering plaats. Naarmate de piston-snelheid snller wordt, neemt de kinetische energie toe. Aangezien de Kinetische energie een kwadratische functie is, neemt de temperatuur kwadratisch toe.

Macroscopisch niveau: De bewegende doos verricht arbeid op het stilstaande gas. dU = Q + W. Bij het “opstarten” van de beweging geld er dan dus een toename van interne energie gelijk aan de arbeid (met Q=0, adiabatisch process). Deze interne energie is direct gekoppeld aan de temperatuur, wat daardoor dus stijgt.