Doelen¶
In hoofdstukken 5 en 6 van het boek wordt er verder ingegaan op de reversibiliteit van processen. De klassieke mechanica eist alleen behoud van energie en impuls, maar er is geen richtingsvoorkeur in processen. Dat is in strijd met je dagelijkse ervaring: van een video-opname kun je vrijwel altijd zeggen of deze vooruit of achteruit wordt gespeeld.
Deze richtingsvoorkeur wordt het best gemodelleerd door de entropie. Dit is een kwantitatieve grootheid die vaak een beetje mysterieus wordt gevonden. In dit werkblad zullen we kijken of we wat meer over deze entropie te weten kunnen komen.
In dit werkblad komen de volgende onderdelen langs:
We herhalen de belangrijke stukken code met gekozen constanten en het gedrag van deeltjes.
We introduceren een nieuwe klasse voor het beheersvolume die de reeds ontwikkelde functies bevat.
We controleren of deze code gelijkwaardige resultaten geeft als de code in voorgaande werkbladen.
We bekijken een ingewikkelder modelsysteem van gekoppelde zuigers en kijken hier naar verschillende processen.
We nemen een reversibele en een niet-reversibele route en kijken naar het verschil in entropie.
Laden van eerdere code¶
Net als bij de eerdere simulaties, gaan we uit van dezelfde set van constanten.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
BOX_SIZE_0 = 10 # Hoogte en lengte startvolume
N = 40 # Aantal deeltjes
V_0 = 1 # Startsnelheid van deeltjes
RADIUS = 0.3 # Straal van moleculen
DT = 0.1*RADIUS/V_0 # Tijdstap om geen botsing te missen
V_PISTON_0 = -0.02 * V_0 # Startsnelheid van zuiger
k_B = 1.38E-23
# (negatief betekent zowel links als rechts naar binnen gericht)
#your code/answer
En maken we daarnaast ook gebruik van een klasse die het gedrag van deeltjes omschrijft:
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_rHet beheersvolume¶
In het boek wordt er vaak gesproken over een ‘control volume’. Dit is een reëel of denkbeeldig volume waaraan een aantal macroscopische eigenschappen worden toegekend. Het Nederlandse woord hiervoor is ‘beheersvolume’. Veel van de functies die we tot nu toe hebben ontwikkeld gaan over eigenschappen en gedrag van een dergelijk beheersvolume - deze hebben we slechts impliciet gebruikt. Om de code beter te structureren, maken we hier een klasse voor. Dit maakt het vooral makkelijk om bijvoorbeeld met twee beheersvolumes te rekenen (later in dit werkblad is dat ons doel).
Als je de code hieronder vergelijkt met die van de vorige werkbladen, zal je een aantal zaken opvallen:
Elke functie heeft een parameter
selfgekregen. Daarmee begrijpt Python dat het om een functie van de klasse gaat.De variabelen die als
globalstonden vermeld zijn nu een variabele van de klasse en hoeven dus niet meer gedeclareerd te worden in elke functie. Ze moeten in de code wel telkens worden voorafgegaan door het woordself, om dit duidelijk te maken.Om de eigenschappen
heat,workenpressurealleen te laten lezen en niet te laten schrijven, maken we gebruik van een Python conventie waarbij we een ‘geheime’ variabele gebruiken die wordt voorafgegaan door een laag liggend streepje ‘_’ (Engels: underscore).
class ControlVolume:
def __init__(self, length, height, N, v_piston, set_temp):
""" maakt een beheersvolume (constructor) """
self.length = length
self.height = height
self.v_piston = v_piston
self.set_temp = set_temp
self.particles = []
self._work = 0.0
self._heat = 0.0
self._impulse_out = 0.0
self._pressure = 0.0
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(-self.length/2 + RADIUS, self.length/2 - RADIUS)
y = np.random.uniform(-self.height/2 + RADIUS, self.height/2 - RADIUS)
self.particles.append(ParticleClass(m=1.0, v=[vx, vy], r=[x, y], R=RADIUS))
def handle_collisions(self) -> None:
""" alle onderlinge botsingen afhandelen voor deeltjes in lijst """
num_particles = len(self.particles)
for i in range(num_particles):
for j in range(i+1, num_particles):
if collide_detection(self.particles[i], self.particles[j]):
particle_collision(self.particles[i], self.particles[j])
def piston_collision(self, particle: ParticleClass) -> None:
""" verzorgen van botsingen met wand links en rechts, die als zuiger kunnen bewegen """
if abs(particle.r[0]) + particle.R > self.length / 2:
particle.r[0] = np.sign(particle.r[0]) * (self.length/2 - particle.R)
piston_velocity = np.sign(particle.r[0]) * self.v_piston
relative_velocity = particle.v[0] - piston_velocity # stelsel zuiger
particle.v[0] = -relative_velocity + piston_velocity # stelsel waarnemer
self._impulse_out += 2 * particle.m * abs(relative_velocity)
self._work += 2 * particle.m * relative_velocity * piston_velocity
def thermostat_collision(self, particle: ParticleClass) -> None:
""" verzorgen van botsingen met wand boven en onder, die als thermostaat kunnen werken """
if abs(particle.r[1]) + particle.R > self.height / 2:
temp_factor = (self.set_temp/self.temperature) if self.set_temp > 0 else 1.0
particle.r[1] = np.sign(particle.r[1]) * (self.height/2 - particle.R)
self._impulse_out += abs(particle.momentum[1]) * (1 + temp_factor**0.5)
self._heat += particle.kin_energy * (temp_factor - 1)
particle.v *= temp_factor**0.5
particle.v[1] *= -1
def handle_walls(self) -> None:
""" verzorgen van alle botsingen met wanden en aanpassen waarde voor druk """
self._impulse_out = 0.0
for p in self.particles:
self.piston_collision(p)
self.thermostat_collision(p)
self._pressure = 0.95 * self._pressure + 0.05 * self._impulse_out / (self.circumference * DT)
def take_time_step(self) -> None:
self.length += 2 * self.v_piston * DT # zowel links als rechts zuiger
for p in self.particles:
p.update_position()
self.handle_collisions()
self.handle_walls()
@property
def temperature(self) -> float:
temp = 0.0
k = 1.380649e-23
for p in self.particles:
temp += p.kin_energy/k
temp = temp*30*1.66e-27 * 2.5 * 10**3
return temp
@property
def area(self) -> float:
return self.length * self.height
@property
def circumference(self) -> float:
return 2 * (self.length + self.height)
@property
def heat(self) -> float:
return self._heat
@property
def work(self) -> float:
return self._work
@property
def pressure(self) -> float:
return self._pressureHerhaling oude test¶
Om zeker te zijn dat de code functioneert herhalen we eerst een berekening, waarvan we het antwoord al kennen. Dit is de simulatie van de druk en de temperatuur als functie van het volume voor een isotherm proces (waarbij de temperatuur dus constant wordt gehouden met behulp van een thermostaat).
num_steps = round(2/3 * BOX_SIZE_0 / (2 * -V_PISTON_0 * DT))
volumes = np.zeros(num_steps, dtype=float)
pressures = np.zeros(num_steps, dtype=float)
temperatures = np.zeros(num_steps, dtype=float)
cv = ControlVolume(BOX_SIZE_0, BOX_SIZE_0, 40, V_PISTON_0, 300)
for i in range(num_steps):
cv.take_time_step()
volumes[i] = cv.area
pressures[i] = cv.pressure
temperatures[i] = cv.temperature
pressures = (pressures * 30*1.66e-27) / (1e-12)**2
volumes = volumes * (1e-9)**2
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 3))
ax1.set_xlabel('Volume')
ax1.set_ylabel('Pressure')
ax2.set_xlabel('Volume')
ax2.set_ylabel('Temperature')
fig.tight_layout
ax1.plot(volumes, pressures, '-r')
ax2.plot(volumes, temperatures, '-b')
plt.show()
Systeem van twee gekoppelde zuigers¶
Om nu een studie te doen naar de entropie van een systeem maken we gebruik van twee gekoppelde zuigers, zie Figure 1. Dit zijn twee zuigers genummerd ‘1’ en ‘2’ die samen een constant volume hebben, maar waarbij de beweging van de zuiger(s) het ene volume verkleint en het andere volume evenveel vergroot. Dit systeem houden we voor de rest van dit weerkblad thermisch geïsoleerd ten opzichte van de omgeving, zodat we een goede boekhouding kunnen doen van de totale hoeveelheid energie.

Figure 1:De gekoppelde zuigers, waarbij het totaal volume constant is.
Isotherme verplaatsing¶
Als eerste kijken we naar het reversibele proces. We herhalen dat er geen thermisch contact is met de omgeving, maar er is wel thermisch contact tussen de twee volumes zodat ze constant dezelfde temperatuur hebben.
In de onderstaande code beginnen we met twee beheersvolumes cv1 en cv2, die aan het begin hetzelfde volume hebben.
Dan verplaatsen we de zuiger(s) met een constante snelheid naar het punt waar en (wat verwacht je, conceptueel, wat er gebeurt met de verschillende grootheden?)
Hieronder vind je het verloop van de druk en de temperatuur van beide volumes tijdens dit proces.
# runnen van de simulatie
num_data = round(abs(BOX_SIZE_0 * 0.4 / (DT * V_PISTON_0)))
volumes1c = np.zeros(num_data, dtype=float)
pressures1c = np.zeros(num_data, dtype=float)
temperatures1c = np.zeros(num_data, dtype=float)
volumes2c = np.zeros(num_data, dtype=float)
pressures2c = np.zeros(num_data, dtype=float)
temperatures2c = np.zeros(num_data, dtype=float)
works1c = np.zeros(num_data, dtype=float)
works2c = np.zeros(num_data, dtype=float)
cv1c = ControlVolume(BOX_SIZE_0, BOX_SIZE_0, 50, +V_PISTON_0, 0.5)
cv2c = ControlVolume(BOX_SIZE_0, BOX_SIZE_0, 50, -V_PISTON_0, 0.5)
for i in range(num_data):
cv1c.take_time_step()
cv2c.take_time_step()
average_temp = (cv1c.temperature + cv2c.temperature) / 2
cv1c.set_temp = average_temp
cv2c.set_temp = average_temp
volumes1c[i] = cv1c.area
pressures1c[i] = cv1c.pressure
temperatures1c[i] = cv1c.temperature
works1c[i] = cv1c.work
volumes2c[i] = cv2c.area
pressures2c[i] = cv2c.pressure
temperatures2c[i] = cv2c.temperature
works2c[i] = cv2c.work# plotten van de druk en temperatuur van beide volumes
pressures1real = (pressures1c * 30*1.66e-27) / (1e-12)**2
volumes1real = volumes1c * (1e-9)**2
pressures2real = (pressures2c * 30*1.66e-27) / (1e-12)**2
volumes2real = volumes2c * (1e-9)**2
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 3))
ax1.set_xlabel('Volume')
ax1.set_ylabel('Pressure')
ax2.set_xlabel('Volume')
ax2.set_ylabel('Temperature')
fig.tight_layout
#druk als functie van volume
ax1.plot(volumes1real, pressures1real, '-r', label='cv1')
ax1.plot(volumes2real, pressures2real, '-m',label='cv2')
ax1.legend()
#temperatuur als functie van volume
ax2.plot(volumes1real, temperatures1c, '-b',label='cv1')
ax2.plot(volumes2real, temperatures2c, '-g',label='cv2')
ax2.legend()
plt.show()

De snelheid van de deeltjes en van de zuiger heeft de grootste invloed op dit verschil, want na het proces heeft cv2 een veel grotere volume dan cv1 met hetzelfde aantal deeltjes dus zullen er veel meer botsingen met de wand plaatsvinden in de kleinere volume cv1 en minder in cv2. Deze botsingen zorgen voor grotere snelheid van de deeltjes dus meer kinetische energie waardoor de temperatuur ook hoger wordt.```
Dit systeem is zo gekozen dat het mogelijk is om de temperatuur analytisch te bepalen met behulp van een wiskundige afleiding.
We gaan ervan uit dat het systeem bij de start helemaal symmetrisch is (zoals in de simulatie) en definiëren hierbij als de positie van de zuiger.
Bij staat de zuiger helemaal aan de kant van cv1, zodat en .
Bij staat de zuiger helemaal aan de andere kant, zodat en .
Dan moet dus gelden dat:
Omdat ons gas twee vrijheidsgraden heeft, wordt de interne energie van elk van de volumes () gegeven door:
Met het aantal deeltjes aan beide kanten gelijk betekent dit voor de interne energie van het totale systeem
Voor het verplaatsen van de zuiger geldt voor beide kanten:
Als we deze twee formules samennemen dan levert dit:
Vullen we de startformules voor en in, dan wordt dit:
In de bovenstaande afleiding is gebruik gemaakt van de relatie . Waarom kan je dat in dit geval stellen?
Volgt uit Tds = dU + pdV, Tds is 0 want geen entropie uitwisseling want geen warmte uitwisseling met omgeving dus dU = -pdV = -dW. Omdat de eerste hoofdwet is , en in dit geval is 0 omdat er geen thermisch contact is met de omgeving (we kijken alleen naar het controle volume van de zuigers)```
Los de differentiaalvergelijking op en plot deze theoretische voorspelling voor de temperatuur samen met het resultaat van de simulatie van hierboven. (LET OP: Dit gaat dus niet om een fit, maar om een theoretische voorspelling die volledig gegeven wordt door de begincondities)
#your code/answer
def temp(x):
return (np.exp(- 0.5 * (np.log(1-(x)**2))))
x = np.linspace(-0.8, 0.8, 1001)
xnew = (x * 1E-16 ) + 1E-16
temperatuur = (temp(x)/(k_B*N)) *28*1.66e-27 * (2.5e3)**2
plt.figure()
plt.xlabel('volume')
plt.ylabel('temperature')
plt.plot(xnew, temperatuur)
plt.plot(volumes1real, temperatures1c, '-b',label='cv1')
plt.plot(volumes2real, temperatures2c, '-g',label='cv2')
plt.show()

Zie je afwijkingen tussen de theorie en de simulatie? Bespreek deze en leg uit waardoor ze worden veroorzaakt.
Beide grafieken hebben een vorm van een dalparabool. De verschillen zijn:
De theoretische grafiek is geheel hoger dan de simulatie grafiek, maar ik denk dat dit vooral een fout is in mijn programma.
Bij de theoretische grafiek hebben beide volumes dezelfde maximum temperatuur, maar in de simulatie niet. Dit komt door de eerder genoemde reden over de veranderende volumes en de snelheid, die in deze vergelijking niet worden meegenomen in de berekening.
De theoretische grafiek is boller/steiler dan de simulatie grafiek, dit komt doordat een deel van de volume wordt meegenomen die in de simulatie niet deel is van het proces.
Om te verifiëren dat dit proces reversibel is moeten we het proces ook terug kunnen uitvoeren.
Beweeg de zuiger terug en controleer de reversibiliteit van het proces
# neem de vorige code tussen opgave 3 en 4 over en breidt deze uit.
# runnen van de simulatie
num_data = round(abs(BOX_SIZE_0 * 0.4 / (DT * V_PISTON_0)))
volumes1a = np.zeros(num_data, dtype=float)
pressures1a = np.zeros(num_data, dtype=float)
temperatures1a = np.zeros(num_data, dtype=float)
volumes2a = np.zeros(num_data, dtype=float)
pressures2a = np.zeros(num_data, dtype=float)
temperatures2a = np.zeros(num_data, dtype=float)
works1a = np.zeros(num_data, dtype=float)
works2a = np.zeros(num_data, dtype=float)
cv1a = ControlVolume(BOX_SIZE_0, BOX_SIZE_0, 50, +V_PISTON_0, 0.5)
cv2a = ControlVolume(BOX_SIZE_0, BOX_SIZE_0, 50, -V_PISTON_0, 0.5)
for i in range(num_data):
cv1a.take_time_step()
cv2a.take_time_step()
average_temp = - (cv1a.temperature + cv2a.temperature) / 2
cv1a.set_temp = average_temp
cv2a.set_temp = average_temp
volumes1a[i] = cv1a.area
pressures1a[i] = cv1a.pressure
temperatures1a[i] = cv1a.temperature
works1a[i] = cv1a.work
volumes2a[i] = cv2a.area
pressures2a[i] = cv2a.pressure
temperatures2a[i] = cv2a.temperature
works2a[i] = cv2a.work
#your code/answer
volumes1b = np.zeros(num_data, dtype=float)
pressures1b = np.zeros(num_data, dtype=float)
temperatures1b = np.zeros(num_data, dtype=float)
volumes2b = np.zeros(num_data, dtype=float)
pressures2b = np.zeros(num_data, dtype=float)
temperatures2b = np.zeros(num_data, dtype=float)
works1b = np.zeros(num_data, dtype=float)
works2b = np.zeros(num_data, dtype=float)
cv1b = ControlVolume(BOX_SIZE_0, BOX_SIZE_0, 50, -V_PISTON_0, 0.5)
cv2b = ControlVolume(BOX_SIZE_0, BOX_SIZE_0, 50, +V_PISTON_0, 0.5)
for i in range(num_data):
cv1b.take_time_step()
cv2b.take_time_step()
average_temp = - (cv1b.temperature + cv2b.temperature) / 2
cv1b.set_temp = average_temp
cv2b.set_temp = average_temp
volumes1b[i] = cv1b.area
pressures1b[i] = cv1b.pressure
temperatures1b[i] = cv1b.temperature
works1b[i] = cv1b.work
volumes2b[i] = cv2b.area
pressures2b[i] = cv2b.pressure
temperatures2b[i] = cv2b.temperature
works2b[i] = cv2b.work
# plotten van de druk en temperatuur van beide volumes
# fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(9, 3))
# ax1.set_xlabel('Volume')
# ax1.set_ylabel('Pressure')
# ax2.set_xlabel('Volume')
# ax2.set_ylabel('Temperature')
# ax3.set_xlabel('Temperature')
# ax3.set_ylabel('Work')
# fig.tight_layout
# #druk als functie van volume
# ax1.plot(volumes1a, pressures1a, '-r', label='cv1')
# ax1.plot(volumes2a, pressures2a, '-m',label='cv2')
# ax1.legend()
# #temperatuur als functie van volume
# ax2.plot(volumes1a, temperatures1a, '-b',label='cv1')
# ax2.plot(volumes2a, temperatures2a, '-g',label='cv2')
# ax2.legend()
plt.figure()
plt.plot(temperatures1a, works1a, '-b')
plt.plot(temperatures1b, works1b, '-g')
plt.plot(temperatures2a, works2a, '-r')
plt.plot(temperatures2b, works2b, '-k')
plt.xlabel('temperature')
plt.ylabel('arbeid')
plt.show()
# zoals te zien is het proces inderdaad reversibel, maar het keert niet volledig terug naar de begin toestand.
Je ziet dat het proces terug niet helemaal overeenkomt met dat op de heenweg. Beschrijf de verschillen en leg uit waar deze vandaan komen. Welke parameters kan je veranderen aan de simulatie om de reversibiliteit te verhogen? Zal het je lukken dit helemaal perfect te krijgen?
Hier jouw antwoord... Voor een volledig reversibel proces moet het proces quasistatisch verlopen, dit betekent dat het oneindig langzaam verloopt. Je kan dus de snelheid van de zuiger aanpassen naar een heel laag getal maar het zal nooit perfect worden omdat oneindig langzaam niet haalbaar is.
Adiabatische verplaatsing zuiger¶
We kunnen in een vergelijkbare eindsituatie komen door de zuiger eerst adiabatisch te verplaatsen (dat wil zeggen: zonder onderling thermisch contact tussen de volumes) en pas daarna het thermische contact toe te staan. Om dat in onze simulatie te modelleren concentreren we ons eerst op de adiabatische verplaatsing van de zuigerwand en nemen we het thermische contact nog niet mee.
Welke eindtemperatuur verwacht je na dit adiabatische proces voor de twee volumes als het om een ideaal gas gaat en we de zuiger net zoveel verplaatsen als in het vorige experiment?
De temperatuur verandert met dezelfde verhouding als de volumeverandering van de volumes, cv1 verandert naar 1/5 V0 en cv2 verandert naar 9/5 V0. Hoe kleiner het volume, hoe hoger de temperatuur, dus in dit geval zal de eindtemperatuur van cv1 worden 5*T0 en de eindtemperatuur van cv2 5/9 * T0 (met T0 de starttemperatuur)```
Maak een code voor een adiabatische verplaatsing voor de zuigerwand van de positie naar de positie . Je kan hiervoor het best starten vanuit de code voor het isotherme proces en deze aanpassen.
class ControlVolume:
def __init__(self, length, height, N, v_piston, set_temp):
""" maakt een beheersvolume (constructor) """
self.length = length
self.height = height
self.v_piston = v_piston
self.set_temp = set_temp
self.particles = []
self._work = 0.0
self._heat = 0.0
self._impulse_out = 0.0
self._pressure = 0.0
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(-self.length/2 + RADIUS, self.length/2 - RADIUS)
y = np.random.uniform(-self.height/2 + RADIUS, self.height/2 - RADIUS)
self.particles.append(ParticleClass(m=1.0, v=[vx, vy], r=[x, y], R=RADIUS))
def handle_collisions(self) -> None:
""" alle onderlinge botsingen afhandelen voor deeltjes in lijst """
num_particles = len(self.particles)
for i in range(num_particles):
for j in range(i+1, num_particles):
if collide_detection(self.particles[i], self.particles[j]):
particle_collision(self.particles[i], self.particles[j])
def piston_collision(self, particle: ParticleClass) -> None:
""" verzorgen van botsingen met wand links en rechts, die als zuiger kunnen bewegen """
if abs(particle.r[0]) + particle.R > self.length / 2:
particle.r[0] = np.sign(particle.r[0]) * (self.length/2 - particle.R)
piston_velocity = np.sign(particle.r[0]) * self.v_piston
relative_velocity = particle.v[0] - piston_velocity # stelsel zuiger
particle.v[0] = -relative_velocity + piston_velocity # stelsel waarnemer
self._impulse_out += 2 * particle.m * abs(relative_velocity)
self._work += 2 * particle.m * relative_velocity * piston_velocity
def thermostat_collision(self, particle: ParticleClass) -> None:
""" verzorgen van botsingen met wand boven en onder, die als thermostaat kunnen werken """
if abs(particle.r[1]) + particle.R > self.height / 2:
temp_factor = (self.set_temp/self.temperature) if self.set_temp > 0 else 1.0
particle.r[1] = np.sign(particle.r[1]) * (self.height/2 - particle.R)
self._impulse_out += abs(particle.momentum[1]) * (1 + temp_factor**0.5)
particle.v *= temp_factor**0.5
particle.v[1] *= -1
def handle_walls(self) -> None:
""" verzorgen van alle botsingen met wanden en aanpassen waarde voor druk """
self._impulse_out = 0.0
for p in self.particles:
self.piston_collision(p)
self.thermostat_collision(p)
self._pressure = 0.95 * self._pressure + 0.05 * self._impulse_out / (self.circumference * DT)
def take_time_step(self) -> None:
self.length += 2 * self.v_piston * DT # zowel links als rechts zuiger
for p in self.particles:
p.update_position()
self.handle_collisions()
self.handle_walls()
@property
def temperature(self) -> float:
temp = 0.0
k = 1.380649e-23
for p in self.particles:
temp += p.kin_energy/k
temp = temp*30*1.66e-27 * 2.5 * 10**3
return temp
@property
def area(self) -> float:
return self.length * self.height
@property
def circumference(self) -> float:
return 2 * (self.length + self.height)
@property
def heat(self) -> float:
return self._heat
@property
def work(self) -> float:
return self._work
@property
def pressure(self) -> float:
return self._pressure
# runnen van de simulatie
num_data = round(abs(BOX_SIZE_0 * 0.4 / (DT * V_PISTON_0)))
temperatures1 = np.zeros(num_data, dtype=float)
temperatures2 = np.zeros(num_data, dtype=float)
works1 = np.zeros(num_data, dtype=float)
works2 = np.zeros(num_data, dtype=float)
heats1 = np.zeros(num_data, dtype=float)
heats2 = np.zeros(num_data, dtype=float)
pressures1 = np.zeros(num_data, dtype=float)
pressures2 = np.zeros(num_data, dtype=float)
volumes1 = np.zeros(num_data, dtype=float)
volumes2 = np.zeros(num_data, dtype=float)
cv1 = ControlVolume(BOX_SIZE_0, BOX_SIZE_0, 50, +V_PISTON_0, 0.5)
cv2 = ControlVolume(BOX_SIZE_0, BOX_SIZE_0, 50, -V_PISTON_0, 0.5)
for i in range(num_data):
cv1.take_time_step()
cv2.take_time_step()
average_temp = (cv1.temperature + cv2.temperature) / 2
cv1.set_temp = average_temp
cv2.set_temp = average_temp
temperatures1[i] = cv1.temperature
works1[i] = cv1.work
heats1[i] = cv1.heat
pressures1[i] = cv1.pressure
volumes1[i] = cv1.area
temperatures2[i] = cv2.temperature
works2[i] = cv2.work
heats2[i] = cv2.heat
pressures2[i] = cv2.pressure
volumes2[i] = cv2.area
pressures1 = (pressures1 * 30*1.66e-27) / (1e-12)**2
volumes1 = volumes1 * (1e-9)**2
pressures2 = (pressures2 * 30*1.66e-27) / (1e-12)**2
volumes2 = volumes2 * (1e-9)**2
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(9, 3))
ax1.set_xlabel('Volume')
ax1.set_ylabel('Pressure')
ax2.set_xlabel('Volume')
ax2.set_ylabel('Temperature')
ax3.set_xlabel('energy')
ax3.set_ylabel('Temperature')
fig.tight_layout
#druk als functie van volume
ax1.plot(volumes1, pressures1, '-r', label='cv1')
ax1.plot(volumes2, pressures2, '-m',label='cv2')
ax1.legend()
#temperatuur als functie van volume
ax2.plot(volumes1, temperatures1, '-b',label='cv1')
ax2.plot(volumes2, temperatures2, '-g',label='cv2')
ax2.legend()
ax3.plot(temperatures1, heats1, '-b', label='cv1')
ax3.plot(temperatures2, heats2, '-g', label='cv2')
plt.show()
energies = np.zeros(1000, dtype=float)
energies2 = np.zeros(1000, dtype=float)
temperatures = np.zeros(1000, dtype=float)
temperatures2b = np.zeros(1000, dtype=float)

Na de adiabatische verplaatsing moeten de twee volumes alsnog in thermisch evenwicht gebracht worden. Dat doen we met de code hieronder.
Wat is de theoretische eindtemperatuur als we deze twee volumes vervolgens in thermisch evenwicht laten komen?
In beide controle volumes zit hetzelfde soort gas. Aangezien de warmteoverdracht tussen de volumes wordt bepaald door de soortelijke warmte van het gas, en deze voor beide hetzelfde is, zal de eindtemperatuur het gemiddelde zijn van de temperaturen van beide volumes.
#your code/answer
times = []
temp1 = []
temp2 = []
average_temp = (cv1.temperature + cv2.temperature) / 2
cv1.set_temp = average_temp
cv2.set_temp = average_temp
cv1.v_piston = 0.0
cv2.v_piston = 0.0
time = 0.0
while cv2.temperature < 0.99 * cv2.set_temp:
time += DT
cv1.take_time_step()
cv2.take_time_step()
temp1.append(cv1.temperature)
temp2.append(cv2.temperature)
times.append(time)
plt.figure()
plt.xlabel('$t$')
plt.ylabel('$T$')
plt.plot(times, temp1, '-r', label='cv1')
plt.plot(times, temp2, 'b-', label='cv2')
plt.legend()
plt.show()

Als je kritisch naar de code kijkt staan de twee volumes helemaal niet in thermisch contact met elkaar, maar gebeurt er iets anders. Omschrijf dat proces en leg uit waarom je hierbij toch hetzelfde antwoord verwacht.
Aangezien we verwachten dat de eindtemperatuur de gemiddelde temperatuur tussen beide volumes zal zijn (zie vorige vraag), wordt eerst de gemiddelde temperatuur berekend en ingevuld voor set_temp. Vervolgens wordt een while loop gemaakt die door blijft gaan met tijdstappen nemen (waardoor de temperatuur verandert) totdat de temperatuur van cv2 het gemiddelde heeft bereikt (en cv1 dus ook). Er wordt dus nooit thermisch contact gemaakt, maar het heeft wel hetzelfde effect omdat de temperaturen even snel als bij thermisch contact veranderen naar de verwachtte gemiddelde waarde.
Het antwoord laat zien dat de reactie van de twee volumes een verschillende karakteristieke tijd hebben. Waardoor wordt dit veroorzaakt? Hint: stel je hiervoor de stuiterende gasmoleculen voor.
De deeltjes in de kleinere volume cv1 hebben een hoge kinetische energie wanneer de volumes in thermisch contact worden gebracht. Hierdoor zullen ze veel meer bewegen aan het begin dan deeltjes uit cv2, waardoor ze meer botsingen hebben aan het begin en dus meer energie verliezen aan de andere deeltjes. Hierdoor zal de temperatuur aan de kant van cv1 sneller dalen en is de karakteristieke tijd voor het dalen van de temperatuur van cv1 sneller dan cv2. ```
Vergelijk van de twee experimenten¶
We hebben de zuiger in twee experimenten verschoven:
We begonnen in toestand 0, waarbij de zuiger in het midden stond en naar toestand 1 bewoog, met thermisch contact tussen de twee volumes.
We begonnen in toestand 0 en verplaatsten de zuiger zonder thermisch contact (adiabatisch) naar toestand 2. Daarna brachten we de twee volumes in thermisch contact en zonder volumeverandering naar evenwicht kwamen in toestand 3.
Deze processen zijn schematisch weergegeven in Figure 2.

Figure 2:Schematische weergave van de twee processen in een -diagram met de verschillende toestanden genummerd weergegeven.
Uit de thermodynamische formules en uit de simulatie kun je concluderen dat de eindtemperatuur na deze twee experimenten verschillend is. Omdat we het systeem nooit in thermisch contact met de omgeving hebben gebracht, moet de energie hiervoor uit de arbeid komen die de zuigerwand heeft verricht. Dit laat zien dat de arbeid die nodig is van de ene in de andere toestand te komen, afhankelijk is van het gekozen proces.
Bereken het verschil in de verrichte arbeid voor beide processen. Hint: kies je systeem voor elk proces zo dat er geen warmtecontact is naar de omgeving en bereken de arbeid via de interne energie.
De arbeid van de processen wordt volgens energiebalansen gegeven door:
Proces 1: isotherm W1 = m(h0 - h1) (met h0 de begin enthalpie en h1 de eind enthalpie, en m de massastroom maar ik kan er geen punt op zetten)
Proces 2: adiabatisch W2 = m(h0 - h2)
De massastroom neem ik als: (N * m_deeltje * snelheid_deeltje) / afstand = (40 * 28*1.7E-27 * 1 * (1E-9/2.5E-12)) / norm(r[-1] - r[0]) (berekend in programma helemaal onderaan)
Deze wordt berekend als 7.616x10^-22 kg/s. Dit lijkt me niet goed maar ik ga door.
Met python wordt het verschil in arbeid berekend op dezelfde plek als de massastroom. Hier komt een verschrikkelijk foute waarde uit. Ik heb geen tijd om dit te veranderen helaas.
Een reversibele route is dan het ‘goedkoopste’ proces om in een toestand te komen. Voor het experiment zou het perfect uitgevoerde isotherme proces reversibel zijn en daarom de laagste temperatuur opleveren bij .
Bereken uit de theoretische, thermodynamische formules hoeveel entropie er vrijkomt bij het tweede proces.
De entropie productie wordt berekend met de ongelijkheid van Clausius, ik dacht eerst dat dit met Python moest maar het moest blijkbaar wiskundig alleen heb ik hier geen tijd meer voor. Ik heb een for loop gemaakt die als een kringintegraal werkt en de totale heats/temperatuur berekent, en hieruit volgt een entropie productie van 0.305 J/kgK. Dit klopt niet, want het zou een reversibel proces moeten zijn wat betekent dat de entropieproductie 0 is.
#your code/answer
Willen we maximale arbeid halen in de stap van toestand 2 naar toestand 3, dan kunnen we dat doen met twee reversibele warmtepompen die de twee beheersvolumes koppelen aan een denkbeeldig bad. Entropie is een toestandsgrootheid, zodat de entropieproductie in dit proces net zo groot moet zijn als in onze zuiger van daarnet. Voor een reversibel proces zoals we met deze warmtepompen uitvoeren is er geen entropieproductie. Dan moet er dus gelden dat:
Als we de omgevingstemperatuur op een waarde stellen van dan moet er dus gelden:
Dit betekent dat het systeem tijdens dit reversibele proces deze hoeveelheid moet accepteren van de omgeving om de eindtoestand te bereiken.
Omdat we daarnaast van de eerste hoofdwet weten dat:
en er moet gelden dat de interne energie van het systeem niet verandert tijdens dit temperatuurverloop kunnen we concluderen dat:
In het proces dat bij onze simulatie heeft plaatsgevonden is er geen enkele arbeid door het systeem uitgevoerd. We zien dus dat de entropie die we in dit geval hebben berekend een maat is voor de arbeid die we hadden kunnen winnen door op dezelfde eindtoestand te komen via een reversibel proces.
We kunnen dit ook andersom zeggen: de hoeveelheid entropie die tijdens het proces gecreëerd wordt is een maat voor de hoeveelheid beschikbare arbeid die we tijdens het proces hebben verloren door het niet perfect reversibel uit te voeren.
Dit concept van verloren arbeid wordt verder uitgewerkt met de grootheid ‘exergie’ die wordt behandeld in hoofdstuk 7 van het boek. Deze is geen onderdeel van de stof van het vak thermodynamica van dit kwartaal.
Push je werk naar GitHub.
energies = np.zeros(1000, dtype=float)
energies2 = np.zeros(1000, dtype=float)
temperatures = np.zeros(1000, dtype=float)
temperatures2 = np.zeros(1000, dtype=float)
r1 = np.zeros(1000, dtype=float)
r2 = np.zeros(1000, dtype=float)
for i in range(500):
time += DT
cv1.take_time_step
cv2.take_time_step
energy = 0.0
energy2 = 0.0
for p in cv1.particles:
energy += p.kin_energy
r1[i] = np.linalg.norm(p.r)
for p in cv2.particles:
energy2 += p.kin_energy
energy = energy *30*1.66e-27 * 2.5 * 10**3
energy2 = energy2 *30*1.66e-27 * 2.5 * 10**3
temperatures[i] = cv1.temperature
temperatures2[i] = cv2.temperature
energies[i] = energy
energies2[i] = energy2
energies1c = np.zeros(1000, dtype=float)
energies2c = np.zeros(1000, dtype=float)
temperatures1c = np.zeros(1000, dtype=float)
temperatures2c = np.zeros(1000, dtype=float)
heats1c = np.zeros(1000, dtype=float)
heats2c = np.zeros(1000, dtype=float)
for i in range(500):
time += DT
cv1c.take_time_step
cv2c.take_time_step
energyc = 0.0
energy2c = 0.0
for p in cv1c.particles:
energyc += p.kin_energy
for p in cv2c.particles:
energy2c += p.kin_energy
energyc = energyc *30*1.66e-27 * (2.5 * 10**3)**2
energy2c = energy2c *30*1.66e-27 * (2.5 * 10**3)**2
temperatures1c[i] = cv1c.temperature
temperatures1c[i] = cv2c.temperature
energies1c[i] = energyc
energies2c[i] = energy2c
heats1c[i] = cv1c.heat
heats2c[i] = cv2c.heat
# plt.figure()
# plt.plot(temperatures, energies)
# plt.plot(temperatures2, energies2)
# plt.xlabel('Temperature')
# plt.ylabel('Energy')
# plt.figure()
# plt.plot(temperatures1c, energies1c)
# plt.plot(temperatures2c, energies2c)
# plt.xlabel('Temperature')
# plt.ylabel('Energy')
work_isothermal = energies[0] - energies[-1]
work_adiabatic = energies1c[0] - energies1c[-1]
m = (40 * 28*1.7E-27 * 1 * (1E-9/2.5E-12)) / np.linalg.norm(r1[-1] - r1[0])
print('Verschil in arbeid is', m*(work_adiabatic - work_isothermal))
Verschil in arbeid is 4.4443582964552606e-39
entropie_productie = 0.0
# entropie productie via ongelijkheid van clausius
for i in range(500):
entropie_productie += ((heats1c[i] - heats1c[i-1]) / temperatures1c[i])
print('De entropie productie is', - entropie_productie)De entropie productie is 0.3050553034469342