diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fedcecb..06c0f64 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -30,6 +30,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup Pages uses: actions/configure-pages@v3 - uses: actions/setup-node@v4 diff --git a/Content/Github/1_intro.md b/Content/Github/1_intro.md index 0c4d824..f91e8fe 100644 --- a/Content/Github/1_intro.md +++ b/Content/Github/1_intro.md @@ -1,3 +1,7 @@ +--- +authors: Freek Pols +updated: december 3, 2025 +--- # Portfolio opzetten (1h) Dit is de GitHub repository voor het thermodynamica deel van IP2. diff --git a/Content/Github/2_dependencies.md b/Content/Github/2_dependencies.md index 7c846c9..4ff9555 100644 --- a/Content/Github/2_dependencies.md +++ b/Content/Github/2_dependencies.md @@ -2,6 +2,8 @@ downloads: - file: ./requirements.txt title: Software dependencies +authors: Freek Pols +updated: december 3, 2025 --- # Installeren extra software diff --git a/Content/Github/3_features.md b/Content/Github/3_features.md index c582867..80cfa9d 100644 --- a/Content/Github/3_features.md +++ b/Content/Github/3_features.md @@ -1,3 +1,7 @@ +--- +authors: Freek Pols +updated: december 3, 2025 +--- # Markdown (Cheatsheet) Dit portfolio wordt gemaakt op basis van MarkDown files en Jupyter Notebook files. Jupyter Notebooks combineren python code cellen met tekst cellen die in markdown geschreven zijn. Markdown is een eenvoudige opmaaktaal: platte tekst die *opgemaakt* wordt met kleine stukjes 'code'. Die tekst is vervolgens snel te exporteren naar allerlei andere formats zoals pdf, word, html etc. diff --git a/Content/Labs/Verdamping.ipynb b/Content/Labs/Verdamping.ipynb index 6aa394e..6f3b50d 100644 --- a/Content/Labs/Verdamping.ipynb +++ b/Content/Labs/Verdamping.ipynb @@ -5,6 +5,11 @@ "id": "4059c8b3", "metadata": {}, "source": [ + "---\n", + "authors: Freek Pols\n", + "updated: December 3, 2025\n", + "---\n", + "\n", "# Is het verdamping?\n", "\n", "In dit experiment is er een verwarmingselement in een met water gevulde maatbeker gestopt. Elke minuut is de temperatuur van het water gemeten. Deze metingen zijn opgeslagen in [tempmetingen.csv](tempmetingen.csv).\n", diff --git a/Content/Labs/Warmtecapaciteit.ipynb b/Content/Labs/Warmtecapaciteit.ipynb index 9e03a69..2bf7a90 100644 --- a/Content/Labs/Warmtecapaciteit.ipynb +++ b/Content/Labs/Warmtecapaciteit.ipynb @@ -5,6 +5,10 @@ "id": "530dbcb4", "metadata": {}, "source": [ + "---\n", + "authors: Freek Pols\n", + "updated: december 3, 2025\n", + "---\n", "# Bepaling van warmtecapaciteit van een onbekend materiaal\n" ] }, diff --git a/Content/Labs/ballonproef.ipynb b/Content/Labs/ballonproef.ipynb new file mode 100644 index 0000000..e2d7397 --- /dev/null +++ b/Content/Labs/ballonproef.ipynb @@ -0,0 +1,158 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "41da7fcc", + "metadata": {}, + "source": [ + "---\n", + "authors: Roel Smit\n", + "updated: December 3, 2025\n", + "---\n", + "# Meten bij constante druk\n", + "" + ] + }, + { + "cell_type": "markdown", + "id": "d64ce9bf", + "metadata": {}, + "source": [ + "# Introductie\n", + "\n", + "Volgens [de ideale gaswet](https://nl.wikipedia.org/wiki/Algemene_gaswet) wordt het volume $V$ van een (ideaal) gas gegeven door: \n", + "\n", + "$$\n", + " V = n R T / P\n", + "$$\n", + "\n", + "waarin \n", + "\n", + "- $n$ het aantal mol gas,\n", + "- $R$ de ideale gasconstante,\n", + "- $T$ de absolute temperatuur,\n", + "- $P$ de druk.\n", + "\n", + "In dit practicum veranderen we de temperatuur en meten we de verandering van het volume van het gas. De proef is met name kwalitatief van aard en laat zien hoe lastig het is een extensieve grootheid als volume te meten. " + ] + }, + { + "cell_type": "markdown", + "id": "693cbc3e", + "metadata": {}, + "source": [ + "# Methode en materialen\n", + "\n", + "## Ontwerp\n", + "\n", + "Om het volume van een hoeveelheid gas bij constante druk te meten is niet zo eenvoudig. Je kunt het gas vrij eenvoudig in een ballon stoppen en dan schatten hoe de diameter van de ballon verandert als functie van de temperatuur, maar dat geeft een relatief grote fout (waarom?). We maken daarom gebruik van de wet van [Archimedes](https://nl.wikipedia.org/wiki/Wet_van_Archimedes). \n", + "\n", + "## Materialen\n", + "\n", + "- eenvoudige feestballon\n", + "- bekerglas\n", + "- tweede bekerglas om mee bij te vullen\n", + "- thermometer\n", + "- verhittingsplaat\n", + "- deksel met vulcilinder met maatstrepen (met iets kleinere diameter dan interne diameter maatbeker)\n", + "- (per 5 groepjes) een $10 \\mathrm{ml}$ maatcilinder\n", + "\n", + "## Procedure\n", + "\n", + "- Blaas de ballon op, maar niet verder dan $5 \\mathrm{cm}$ in diameter. Deze moet makkelijk in de maatbeker passen. \n", + "- Knoop de ballon goed dicht zodat er geen lucht kan ontsnappen. \n", + "- Dompel de ballon onder in de maatbeker met water met behulp van het deksel.\n", + "- Pas het waterniveau aan zodat de meniscus (de bovenkant van het water) bij een van de onderste maatstrepen van de vulcilinder van het deksel zit. \n", + "- Let op dat er luchtbellen kunnen plakken aan de ballon wat leidt tot een systematische fout. Verifieer dat je zo min mogelijk systematische fouten maakt en meet op welke maatstreep de meniscus zich bevindt.\n", + "- Verhoog stapsgewijst de temperatuur van het water (en dus de ballon). *Let op dat je de temperatuur maximaal een graad of 20 kan verhogen, want als je voorbij de vulcilinder komt met de meniscus, dan kan je de volumeverandering niet meer nauwkeurig bepalen.*\n", + "- Laat het geheel na elke temperatuurtoename een minuut 'rusten' om zo de tijd te geven om in thermisch evenwicht te komen.\n", + "- Meet na elke temperatuurtoename de temperatuur en de positie van de meniscus.\n", + "\n", + "Het verschiloppervlak tussen de binnendiameter van de maatbeker en de buitendiameter van de vulcilinder is $10.0 \\mathrm{cm}^{2}$. Je kunt die handmatig kalibreren met behulp van de kleine maatcilinder die in het lokaal aanwezig is. De maatstreepjes die op de vulcilinder zijn gekerfd zitten op een onderlinge afstand van $1.0 \\mathrm{mm}$.\n", + "\n", + "```{exercise} Analyseer de data\n", + "Plot de volumeverandering van de ballon als functie van de temperatuur. Zie je hier een lineair verband? Bepaal dan ook wat het startvolume van de ballon was door te extrapoleren naar een absolute temperatuur van $0 \\mathrm{K}$.\n", + "```\n", + "\n", + "Als je kritisch nadenkt over deze grafiek en de extrapolatie, dan kun je bezwaar maken tegen de precisie van deze proef. Het water in de maatbeker zet ook uit onder de verhoging van de temperatuur en geeft een systematische fout. Daar kun je een correctie voor uitvoeren. Voer die correctie uit als je nog genoeg tijd hebt:\n", + "\n", + "```{exercise} Verbeterde meting\n", + "- Maak de maatbeker leeg en vul deze opnieuw met water tot een van de onderste maatstrepen van de vulcilinder (dus zonder ballon). \n", + "- Verhit de maatbeker weer stapsgewijs en meet na elke stap de temperatuur en de hoogte van de meniscus.\n", + "\n", + "Kun je nu met behulp van deze tweede meting een betere afschatting geven voor het startvolume van de ballon? \n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "a7227939", + "metadata": {}, + "source": [ + "# Resultaten" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b0e44339", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "temps = []\n", + "hoogtes = []\n", + "\n", + "\n", + "diam_beker = 7.66 # cm\n", + "diam_deksel = 6.77 # cm\n", + "opp_water = 1/4 * np.pi * (diam_beker**2 - diam_deksel**2) #cm2\n", + "\n", + "volumes = (np.asarray(hoogtes) - hoogtes[0]) * opp_water\n", + "temps_abs = np.asarray(temps) + 273\n", + "coef = np.polyfit(temps_abs, volumes, 1)\n", + "func = np.poly1d(coef)\n", + "\n", + "plt.plot(temps_abs, volumes, 'ob')\n", + "plt.plot(temps_abs, func(temps_abs), '--r')\n", + "plt.xlabel('Temperature [K]')\n", + "plt.ylabel('Volume [ccm]')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "38389f44", + "metadata": {}, + "source": [ + "# Discussie en conclusie\n", + "\n", + "\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Content/Labs/cappucino.ipynb b/Content/Labs/cappucino.ipynb new file mode 100644 index 0000000..8fd992d --- /dev/null +++ b/Content/Labs/cappucino.ipynb @@ -0,0 +1,121 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e971134c", + "metadata": {}, + "source": [ + "---\n", + "authors: Freek Pols\n", + "updated: December 3, 2025\n", + "---\n", + "# Cappucino\n", + "\n", + "## Introductie\n", + "Heb je al eens bedacht dat het verwarmen van melk voor een cappucino eigenlijk heel snel gaat zonder dat de melk verdunt wordt?\n", + "De melk wordt ook niet echt plaatselijk verhit door een warmte element.\n", + "In plaats daarvan wordt er stoom door de melk geblazen.\n", + "De stoom condenseert in de melk en geeft daarbij zijn latente warmte af.\n", + "Hierdoor warmt de melk snel op zonder dat er (significant veel) water aan toegevoegd wordt.\n", + "\n", + "```{warning}\n", + "Stoom is heel heet en kan voor vervelende brandplekken zorgen.\n", + "Wees dus voorzichtig bij het uitvoeren van dit practicum.\n", + "\n", + "Daarnaast, nadat de kookplaat uitgaat is er een onderdruk in de kolf.\n", + "Hang dan ook de tube uit het water wanneer je de kookplaat uitzet, anders kan de kolf imploderen.\n", + "```\n", + "\n", + "In dit practicum gaan we de verdampingswarmte van water bepalen door middel van een zelfgebouwde cappucino machine.\n", + "Het principe is als volgt: We gebruiken de opstelling weergegeven in {numref}`Figuur {number} ` waarbij we water verwarmen in een kolf met behulp van een kookplaat.\n", + "Door de warmte gaat het water koken en ontstaat er stoom.\n", + "De stoom stroomt via een tube naar een afgesloten maatbeker met water.\n", + "De stoom condenseert in het maatbeker en geeft daarbij zijn latente warmte af.\n", + "Door de temperatuurstijging van het water te meten, evenals de hoeveelheid gram water dat verdampt is, kunnen we de verdampingswarmte van water bepalen.\n", + "\n", + "```{figure} figures/verdampingswarmte_setup.png\n", + ":width: 70%\n", + ":label: fig_cappucino\n", + "\n", + "Een schematische weergave van de cappucino opstelling.\n", + "```\n", + "\n", + "## Theorie\n", + "\n", + "De latente verdampingswarmte van water bedraagt 2257 kJ/kg, dit is veel meer dan de specifieke warmtecapaciteit van water (4.18 kJ/kgK).\n", + "Wanneer we de waterdamp (g) door koud water leiden, gaan we er van uit dat de waterdamp condenseert en daarbij zijn latente warmte afgeeft aan het koude water.\n", + "Door te bepalen hoeveel gram water verdampt is en hoeveel de temperatuur van het koude water stijgt, kunnen we de latente warmte van verdamping bepalen:\n", + "\n", + "$$ Q_{condensatie} = m_{damp} L = m_{water} c \\Delta T $$\n", + "\n", + "met $m_{damp} = \\Delta m_{kolf}$.\n", + "\n", + "## Methoden en materialen\n", + "\n", + "### Materialen\n", + "- Warmteplaat\n", + "- Kolf met stop en tube\n", + "- Maatcilinder\n", + "- Thermometer\n", + "- Weegschaal\n", + "- Water\n", + "\n", + "\n", + "### Procedure\n", + "\n", + "- Vul de kolf met ongeveer 100 mL water: bepaal precies de massa water ($m_{w_1}$). \n", + "- Vul de maatcilinder met ongeveer 100 mL water: bepaal precies de massa water ($m_{bad}$).\n", + "- Bepaal de temperatuur van dit waterbad ($T_{bad_1}$).\n", + "- Zet de kolf op de warmteplaat en zet de warmteplaat aan - maximale stand 3, zorg ervoor dat de tube goed in het waterbad hangt.\n", + "- Wacht tot de temperatuur van het waterbad met ongeveer 20 K is gestegen.\n", + "- Stop de meting / zet de warmteplaat uit. Noteer meteen de temperatuur($T_{bad_2}$) en haal de tube uit het water! \n", + "\n", + "```{warning}\n", + "Pas op stoom is heet!\n", + "```\n", + "\n", + "- Bepaal precies de massa water in de kolf ($m_{w_2}$).\n", + "- Verwerk je resultaten hieronder om de latente warmte van verdamping van water te bepalen." + ] + }, + { + "cell_type": "markdown", + "id": "3fcb9409", + "metadata": {}, + "source": [ + "## Resultaten" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "589ef2a8", + "metadata": {}, + "outputs": [], + "source": [ + "m_w_1 = # kg\n", + "m_bad = # kg\n", + "T_bad_1 = # K\n", + "\n", + "\n", + "m_w_2 = # kg\n", + "T_bad_2 = # K" + ] + }, + { + "cell_type": "markdown", + "id": "025a86f1", + "metadata": {}, + "source": [ + "## Discussie en conclusie" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Content/Labs/cwater.ipynb b/Content/Labs/cwater.ipynb index 7f6fe6d..9ba7cce 100644 --- a/Content/Labs/cwater.ipynb +++ b/Content/Labs/cwater.ipynb @@ -5,6 +5,10 @@ "id": "530dbcb4", "metadata": {}, "source": [ + "--- \n", + "authors: Freek Pols\n", + "updated: december 3, 2025 \n", + "---\n", "# Bepaling van soortelijke warmte van water\n" ] }, diff --git a/Content/Labs/druksensorijken.ipynb b/Content/Labs/druksensorijken.ipynb new file mode 100644 index 0000000..e111bcf --- /dev/null +++ b/Content/Labs/druksensorijken.ipynb @@ -0,0 +1,131 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "49a18397", + "metadata": {}, + "source": [ + "--- \n", + "authors: Freek Pols\n", + "updated: december 3, 2025 \n", + "---\n", + "# Druksensor ijken en maken van een $pV$-diagram" + ] + }, + { + "cell_type": "markdown", + "id": "479359e0", + "metadata": {}, + "source": [ + "## Introductie\n", + "\n", + "In de experimentele natuurkunde was het lang geleden gelukt om de krachten tussen ladingen te bestuderen zonder dat bekend was hoe groot die ladingen nu precies waren. Men laadde een metalen bol op en hield deze tegen een andere metalen bol van hetzelfde materiaal. Men redeneerde dat de ladingen op de bollen gelijk waren, omdat ze van hetzelfde materiaal waren. Vervolgens plaatste men de bollen in een vacuüm en mat men de krachten tussen de bollen met een zeer gevoelige balans. Op deze manier kon men de krachten tussen de ladingen bestuderen zonder de absolute waarde van de ladingen te kennen. Dit trucje kon herhaald worden met andere bollen waarna een kwantiatieve beschrijving van de krachten tussen ladingen mogelijk werd.\n", + "\n", + "Een soortgelijke meettechniek gaan we gebruiken om een druksensor te ijken. Van de sensor zijn wel wat dingen bekend, maar omdat de spanning van de Arduino niet overeenkomstig is met de gewenste spanning, zouden we deze moeten ijken. We weten dat de sensor lineair is, dus als we twee punten weten, kunnen we de rest van de curve bepalen. Nog beter zou het zijn om drie punten te nemen en zo ook het lineaire karakter van de sensor te bevestigen.\n", + "\n", + "## Theorie\n", + "\n", + "Een injectiespuit met een maximaal volume van 50 mL is gevuld met lucht. De spuit kan aan een kant afgesloten worden met een tube die verbonden is met een druksensor die de gasdruk meet. Door de zuiger van de spuit in te drukken, wordt het volume verkleind en de druk verhoogd. Wanneer we de druk langzaam in drukken verwachten we dat de druk in de spuit volgens de wet van Boyle toeneemt:\n", + "\n", + "$$\n", + " P_1 V_1 = P_2 V_2 \n", + "$$ (eq:Boyle)\n", + "\n", + "Omdat de gemeten spanning van de druksensor lineair afhankelijk is van de druk, kan de druk uitgedrukt worden als:\n", + "\n", + "$$\n", + " P = a U + b\n", + "$$ (eq:lineair)\n", + "\n", + "\n", + "\n", + "## Methode en materialen\n", + "\n", + "```{note} Software\n", + "De Arduino code staat al op de Arduino's. Als je de Arduino aansluit op je computer en de Arduino IDE opent, kan je de seriële monitor openen om de gemeten spanning te zien.\n", + "```\n", + "\n", + "Je maakt gebruik van een Arduino. Daarvoor heb je de juiste IDE nodig. Het programma staat al op de Arduino's in het lokaal. Zodra je de Arduino aansluit op je computer zal de Arduino gaan meten, maar zijn de metingen nog niet zichtbaar. Je moet de Arduino op `Arduino MKR Zero` zetten. Dan wordt nog wel een driver geinstalleerd. \n", + "\n", + "Controleer of de Arduino herkend wordt door op `tools` -> `port` te klikken, daar staat de com poort van de Arduino. Open vervolgens de seriële monitor (het vergrootglas rechtsboven in de IDE) om de gemeten spanning te zien.\n", + "\n", + "```{warning}\n", + "De twee stekkertjes hoef je NIET met elkaar te verbinden. Dit is alleen voor een meting in de brandblusser.\n", + "``` \n", + "\n", + "```{code} C++\n", + "int drukpin = A1;\n", + "\n", + "void setup() {\n", + " pinMode(A1,INPUT);\n", + " Serial.begin(9600);\n", + "}\n", + "\n", + "void loop() {\n", + " Serial.println(analogRead(drukpin));\n", + " delay(100);\n", + "}\n", + "```\n", + "\n", + "### Deel 1\n", + "Stel de injectiespuit in op 40 mL en sluit de spuit aan op de druksensor door middel van een zo klein mogelijke tube. Meet de spanning van de druksensor met de Arduino en noteer deze waarde als $U_1$. Druk vervolgens de zuiger langzaam in tot 20 mL en meet opnieuw de spanning van de druksensor, noteer deze waarde als $U_2$. Herhaal dit voor volumes van 10 mL. \n", + "\n", + "1. Leg uit waarom een zo klein mogelijke tube gebruikt moet worden.\n", + "2. Welke waarde hoort bij de gasdruk bij 40 mL? Zoek deze waarde op.\n", + "3. Welke waarden horen bij de gasdruk bij 20 en 10 mL? \n", + "4. Gebruik de drie punten om de waarden van $a$ en $b$ in {numref}`vergelijking {number} ` te bepalen en controleer of de sensor inderdaad lineair is door de waarden te plotten.\n", + "\n", + "### Deel 2\n", + "Vervang daarbij de kleine tube voor een langere en bepaal het onbekende volume van de tube met een volgende meetserie waarbij je de druk en het volume bepaald. Zorg ervoor dat ook drukken onder de 1 atm gemeten worden. \n", + "```{tip}\n", + "Maak gebruik van een systematische fout in het volume om het volume van de tube te vinden.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "966115e9", + "metadata": {}, + "source": [ + "## Resultaten" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60fe29f6", + "metadata": {}, + "outputs": [], + "source": [ + "### Jouw data en code\n" + ] + }, + { + "cell_type": "markdown", + "id": "f082b7e6", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Content/Labs/figures/verdampingswarmte_setup.png b/Content/Labs/figures/verdampingswarmte_setup.png new file mode 100644 index 0000000..adaf7ac Binary files /dev/null and b/Content/Labs/figures/verdampingswarmte_setup.png differ diff --git a/Content/Labs/koelbuis.ipynb b/Content/Labs/koelbuis.ipynb index dcca9c2..a6d8b18 100644 --- a/Content/Labs/koelbuis.ipynb +++ b/Content/Labs/koelbuis.ipynb @@ -5,6 +5,11 @@ "id": "41da7fcc", "metadata": {}, "source": [ + "---\n", + "authors: Roel Smit\n", + "updated: December 3, 2025\n", + "--- \n", + "\n", "# Koelen van metalen buizen\n", "" ] diff --git a/Content/Labs/pV_diagram.ipynb b/Content/Labs/pV_diagram.ipynb new file mode 100644 index 0000000..86e760d --- /dev/null +++ b/Content/Labs/pV_diagram.ipynb @@ -0,0 +1,191 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "72309ae9", + "metadata": {}, + "source": [ + "---\n", + "authors: Freek Pols\n", + "updated: December 3, 2025\n", + "---\n", + "\n", + "# $pV-$ diagram" + ] + }, + { + "cell_type": "markdown", + "id": "5bcd8884", + "metadata": {}, + "source": [ + "## Introductie\n", + "\n", + "Wanneer je een gas snel samenperst bij gelijkblijvende volume neemt de temperatuur toe. Wanneer je het gas vervolgens laat ontsnappen, neemt de temperatuur af omdat het gas arbeid verricht. \n", + "\n", + "We gaan eigenschappen van dit proces bestuderen. In dit practicum ga je een $p-t$-diagram van een brandblusser bestuderen en gebruiken om de specifieke warmte verhouding $\\gamma$ te bepalen voor lucht." + ] + }, + { + "cell_type": "markdown", + "id": "c4d73afe", + "metadata": {}, + "source": [ + "## Experiment (60 min)\n", + "\n", + "In dit experiment vullen we een brandblusser met lucht ($P_1, T_1=T_{atm}$). We laten de lucht snel ontsnappen ($P_2=P_{atm}, T_2$), in zo'n korte tijd dat we aannemen dat dit een adiabatisch proces is. Doordat het gas arbeid verricht zal het gas afkoelen. Wanneer we dan, kort na het ontsnappen van de lucht, de kraan weer dicht doen, zal de druk weer toenemen ($P_3,T_3=T_{atm}$). \n", + "\n", + "In het eerste deel van het proces geldt: \n", + "\n", + "$$\n", + " T_1^\\gamma P_1^{1-\\gamma} = T_2^\\gamma P_2^{1-\\gamma}\n", + "$$\n", + "\n", + "ook wel bekend als ... , met $\\gamma$ de specifieke warmte verhouding: $\\gamma=\\frac{C_p}{C_V}$.\n", + "\n", + "Het tweede deel van het proces kan beschreven worden met de wet van Gay-Lussac:\n", + "\n", + "$$ \n", + " \\frac{P_2}{T_2} = \\frac{P_3}{T_3}\n", + "$$\n", + "\n", + "\n", + "Onder de aanname dat $T_1 = T_3 = T_{atm}$ volgt hieruit:\n", + "\n", + "$$\n", + " \\gamma=\\frac{\\ln{P_1}-\\ln{P_{atm}}}{\\ln{P_1}-\\ln{P_3}}\n", + "$$\n", + "\n", + "```{exercise}\n", + "1. Zet de druksensor in het brandblusapparaat, zet de kraan er op en vul de fles met lucht. \n", + "2. Wacht een tijd (~30 min). Waarom?\n", + "3. Knijp in de hendel zodat de lucht ontsnapt. Zodra het lucht niet meer ontsnapt, laat de hendel los zodat er geen uitwisseling van lucht meer is.\n", + "4. Wacht een korte tijd en haal dan je Arduino en de sd-kaart uit de blusser.\n", + "5. Lees de waarden uit in je eigen python script en bepaalde waarde van $\\gamma$. Vergelijk deze met de literatuurwaarde ($\\gamma = 1.45$)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4b250bd6", + "metadata": {}, + "outputs": [], + "source": [ + "### Jouw code\n" + ] + }, + { + "cell_type": "markdown", + "id": "05105cec", + "metadata": {}, + "source": [ + "## Arduino\n", + "\n", + "Gebruikte arduino code, heb je verder niet nodig.\n", + "\n", + "```{code} C++\n", + "#include \n", + "#include \n", + "\n", + "const int drukpin = A1;\n", + "const int CS_PIN = SDCARD_SS_PIN;\n", + "\n", + "const uint32_t SAMPLE_MS = 100; // log-interval\n", + "const char* FNAME = \"DATA.CSV\";\n", + "\n", + "void blink(uint8_t times, uint16_t onMs, uint16_t offMs, uint16_t pauseMs) {\n", + " for (uint8_t i = 0; i < times; i++) {\n", + " digitalWrite(LED_BUILTIN, HIGH);\n", + " delay(onMs);\n", + " digitalWrite(LED_BUILTIN, LOW);\n", + " delay(offMs);\n", + " }\n", + " delay(pauseMs);\n", + "}\n", + "\n", + "void fatalSd() {\n", + " while (1) blink(3, 100, 100, 600); // 3 snelle\n", + "}\n", + "\n", + "void fatalFile() {\n", + " while (1) blink(2, 250, 250, 800); // 2 langzame\n", + "}\n", + "\n", + "void setup() {\n", + " pinMode(LED_BUILTIN, OUTPUT);\n", + " digitalWrite(LED_BUILTIN, LOW);\n", + "\n", + " pinMode(drukpin, INPUT);\n", + "\n", + " // Geef voeding + SD wat tijd op batterij\n", + " delay(800);\n", + "\n", + " // SD init met retries (zonder Serial kun je anders niks zien)\n", + " bool ok = false;\n", + " for (int attempt = 0; attempt < 5; attempt++) {\n", + " if (SD.begin(CS_PIN)) { ok = true; break; }\n", + " blink(1, 50, 50, 200); // klein \"ik probeer\" knipje\n", + " delay(300);\n", + " }\n", + " if (!ok) fatalSd();\n", + "\n", + " // Bestand aanmaken + header schrijven (1x), dan sluiten\n", + " File f = SD.open(FNAME, FILE_WRITE);\n", + " if (!f) fatalFile();\n", + "\n", + " // Als bestand leeg is, header toevoegen\n", + " if (f.size() == 0) {\n", + " f.println(\"time_ms,value\");\n", + " }\n", + " f.close();\n", + "\n", + " // korte bevestiging\n", + " blink(5, 60, 60, 300);\n", + "}\n", + "\n", + "void loop() {\n", + " static uint32_t last = 0;\n", + " uint32_t now = millis();\n", + " if (now - last < SAMPLE_MS) return;\n", + " last = now;\n", + "\n", + " int value = analogRead(drukpin);\n", + "\n", + " File f = SD.open(FNAME, FILE_WRITE);\n", + " if (!f) fatalFile();\n", + "\n", + " f.print(now);\n", + " f.print(',');\n", + " f.println(value);\n", + " f.close(); // <- belangrijk: file echt wegschrijven\n", + "\n", + " // schrijf-indicatie\n", + " digitalWrite(LED_BUILTIN, HIGH);\n", + " delay(10);\n", + " digitalWrite(LED_BUILTIN, LOW);\n", + "}\n", + "```" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Content/Labs/waxine.ipynb b/Content/Labs/waxine.ipynb new file mode 100644 index 0000000..32da14f --- /dev/null +++ b/Content/Labs/waxine.ipynb @@ -0,0 +1,40 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9fa1696a", + "metadata": {}, + "source": [ + "--- \n", + "authors: Freek Pols\n", + "updated: december 3, 2025 \n", + "---\n", + "# Waxinekaarsje\n", + "\n", + "Als je een waxinekaarsje aan steekt en er een glas overheen zet, zal het kaarsje uit gaan. \n", + "Als je dat waxinekaarsje laat drijven op water terwijl je er het glas er overheen zet, zal het water in het glas stijgen.\n", + "Op het internet zwerven er verschillende verklaringen voor dit fenomeen rond.\n", + "1. Het kaarsje verbruikt zuurstof, waardoor er een vacuüm ontstaat en het water omhoog wordt gezogen.\n", + "2. Het kaarsje verwarmt de lucht in het glas, waardoor de lucht uitzet. Wanneer de kaars dooft koelt de lucht af waardoor de druk afneemt en er water omhoog wordt gezogen.\n", + "3. Bij het verbranden van de kaars ontstaan er waterdamp en koolstofdioxide. De waterdamp condenseert aan de binnenkant van het glas, waardoor er minder gas in het glas is en de druk afneemt. Hierdoor wordt het water omhoog gezogen.\n", + "\n", + "```{exercise}\n", + "Onderzoek welke verklaring het meest waarschijnlijk is en leg uit waarom de andere verklaringen minder waarschijnlijk zijn.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "88a3e651", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Content/PySim/10_voltatische_cell.ipynb b/Content/PySim/10_voltatische_cell.ipynb deleted file mode 100644 index 0337eeb..0000000 --- a/Content/PySim/10_voltatische_cell.ipynb +++ /dev/null @@ -1,219 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "d61b287a", - "metadata": {}, - "source": [ - "# Fotovoltaïsche Zonnecel\n", - "\n", - "## Inleiding\n", - "De werking van een fotovoltaïsche zonnecel is gebaseerd op de interactie tussen fotonen (met een bepaalde energie $E_p$) en de vrije elektronen in een halfgeleidermateriaal. De elektronen kunnen door interactie met een invallend foton naar een hoger energieniveau worden gebracht. Als het elektron vervolgens terugvalt naar zijn oorspronkelijke energieniveau, kan de vrijgekomen energie worden gebruikt om extern elektrische stroom te genereren. In een beschouwing van een goede keuze voor de bandgap-energie (die afhangt van het gekozen materiaal), moeten we twee 'tegenstrijdige' situaties beschouwen:\n", - "\n", - "1. $E_p > E_g$ \n", - " Wanneer een invallend foton een energie heeft die groter is dan de zogenaamde bandgap-energie $E_g$ van het halfgeleidermateriaal ($E_p > E_g$), dan zal de energie van een elektron in het halfgeleidermateriaal toenemen met een hoeveelheid energie $\\Delta E=E_g$. Het “overschot” aan foton-energie ($E_p-E_g$) wordt omgezet in warmte, en gaat \"verloren\". Het deel van de foton-energie dat in nuttige energie wordt omgezet neemt dus toe als de bandgap-energie groter wordt.\n", - "2. $E_p < E_g$ \n", - " Wanneer het invallend foton een energie heeft die kleiner is dan de bandgap-energie $E_g$ van het halfgeleidermateriaal ($E_p < E_g$), dan zal de energie van een elektron niet toenemen. De foton-energie $E_p$ wordt dan omgezet in warmte, en gaat \"verloren\". Het aantal fotonen dat leidt tot nuttige energie neemt dus af als de bandgap-energie groter wordt.\n", - " \n", - "Het resultaat van bovenstaande twee effecten is dat er een optimale bandgap-energie is, waarvoor de zonnecel een maximale hoeveelheid foton-energie omzet in nuttige energie. In deze opgave gaan we uitwerken wat de optimale bandgap-energie is van een fotovoltaïsche zonnecel, voor invallende straling die overeenkomt met het fotonen-spectrum zoals uitgestraald door de zon. We beschouwen de zon als een zwarte straler met temperatuur $T_z=5800\\,\\mathrm{K}$. Daarbij, de bandgap-energie van silicium zonnecellen is $1.10 \\; \\mathrm{eV} = 1.10 \\times 1.602 \\times 10^{-19} \\; \\mathrm{J} = 1.76 \\times 10^{-19} \\; \\mathrm{J}$.\n" - ] - }, - { - "cell_type": "markdown", - "id": "d231aecb", - "metadata": {}, - "source": [ - "## Stralingswet van Planck – analytisch en numeriek\n", - "\n", - "Volgens de [stralingswet van Planck](https://nl.wikipedia.org/wiki/Wet_van_Planck) wordt de energie (in $\\mathrm{W/m^2}$) uitgestraald door een zwart oppervlak met temperatuur $T$ binnen een golflengte-interval tussen $\\lambda$ en $\\lambda+\\Delta \\lambda$ gegeven door de Planck-kromme:\n", - "\n", - "```math\n", - "E_{\\lambda,T} = \\frac{2 \\pi h c^2}{\\lambda^5} \\cdot \n", - "\\frac{1}{e^{\\frac{hc}{\\lambda k T}} - 1} \\, \\Delta \\lambda \n", - "```\n", - "\n", - "met:\n", - "\n", - "* $h$ = constante van Planck = $6.626 \\cdot 10^{-34} \\; \\mathrm{J s}$\n", - "* $c$ = lichtsnelheid = $2.998 \\cdot 10^8 \\; \\mathrm{m/s}$\n", - "* $\\lambda$ = golflengte in $\\mathrm{m}$\n", - "* $T$ = absolute temperatuur in $\\mathrm{K}$\n", - "* $k$ = constante van Boltzmann = $1.381 \\cdot 10^{-23} \\; \\mathrm{J/K}$\n", - "\n", - "Onderstaande figuur laat zien hoe de verdeling van de stralingsenergie over de golflengtes afhangt van de temperatuur van de straler. We zien dat de zon (in geode benadering een zwarte straler met een temperatuur van $6000 \\; \\mathrm{K}$) de meeste stralingsenergie uitzendt bij zichtbaar licht golflengtes ($400-800 \\; \\mathrm{nm}$).\n", - "\n", - "```{figure} https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/BlackbodySpectrum_loglog_150dpi_en.png/1280px-BlackbodySpectrum_loglog_150dpi_en.png\n", - ":width: 50%\n", - ":label: fig_planckkromme\n", - "\n", - "De intensiteit van de uitgezonden straling als functie van de golflengte voor verschillende temperaturen van zwarte stralers. Figuur van [wiki](https://nl.wikipedia.org/wiki/Wet_van_Planck#/media/Bestand:BlackbodySpectrum_loglog_150dpi_en.png)\n", - "```\n", - "\n", - "In deze opgave gaan we analyseren welke implicaties de Planck kromme van de zon heeft voor het rendement van een fotovoltaïsche zonnecel. \n", - "\n", - "Tijdens de analyse colleges hebben jullie afgeleid dat de totale energie die door een zwart oppervlak met temperatuur T wordt gegeven door \n", - "\n", - "$$\n", - "E_T = \\int_{0}^{\\infty} E_{\\lambda,T}\\, d\\lambda \n", - " = 2\\pi h c^{2} \\int_{0}^{\\infty} \n", - " \\frac{1}{\\lambda^{5}\\left( e^{\\frac{hc}{\\lambda k T}} - 1 \\right)} \n", - " \\, d\\lambda\n", - " = \\frac{2\\pi^{5} k^{4}}{15 h^{3} c^{2}} T^{4}\n", - " = \\sigma T^{4}\n", - "\\tag{2}\n", - "$$ (eq:Et)\n", - "\n", - "met $\\sigma = \\text{constante van Stefan–Boltzmann} = 5.670 \\cdot 10^{-8}\\ \\mathrm{W/(m^2K^4)}$\n", - "\n", - "Integraal kunnen we benaderen met een zogenaamde Riemann som. Dit is geïllustreerd in onderstaande figuur.\n", - "\n", - "```{figure} https://upload.wikimedia.org/wikipedia/commons/1/19/Riemann_sum_%28leftbox%29.gif\n", - ":width: 50%\n", - ":label: fig_Riemann\n", - "\n", - "De integraal, of oppervlak onder een grafiek, kan benaderd worden met de Riemann som. Figuur van [wiki](https://upload.wikimedia.org/wikipedia/commons/1/19/Riemann_sum_%28leftbox%29.gif)\n", - "\n", - "```\n", - "\n", - "$$\n", - "E_T = 2\\pi h c^2 \\int_{0}^{\\infty} \n", - "\\frac{1}{\\lambda^{5}\\left( e^{\\frac{hc}{\\lambda k T}} - 1 \\right)} \n", - "\\, d\\lambda\n", - "\\approx\n", - "2\\pi h c^2 \n", - "\\lim_{\\Delta \\lambda \\to 0}\n", - "\\sum_{i=1}^{\\infty}\n", - "\\frac{1}{\\lambda_i^{5}\\left( e^{\\frac{hc}{\\lambda_i k T}} - 1 \\right)} \n", - "\\, \\Delta \\lambda\n", - "$$ (eq:riemannnn)\n", - "\n", - "\n", - "met $\\lambda_i = i \\cdot \\Delta \\lambda$\n", - "\n", - "Deze Riemann-som kunnen we oplossen met een Python-script. \n", - "Uiteraard kunnen we niet een oneindig aantal stapjes berekenen in een computer. \n", - "Daarom benaderen we in het Python-programma de Riemann-som met:\n", - "\n", - "$$\n", - "E_T \\approx \n", - "\\lim_{\\Delta \\lambda_0 \\to 0}\n", - "\\sum_{i=1}^{\\infty}\n", - "\\frac{1}{\\lambda_i^{5}\\left( e^{\\frac{hc}{\\lambda_i k T}} - 1 \\right)}\n", - "\\, \\Delta \\lambda_0\n", - "\\approx\n", - "\\sum_{i=1}^{N}\n", - "\\frac{1}{\\lambda_i^{5}\\left( e^{\\frac{hc}{\\lambda_i k T}} - 1 \\right)}\n", - "\\, \\Delta \\lambda_0\n", - "$$ (eq:benadering)\n", - "\n", - "\n", - "met $\\lambda_i = i \\cdot \\Delta \\lambda_0$. \n" - ] - }, - { - "cell_type": "markdown", - "id": "494cbf6d", - "metadata": {}, - "source": [ - "```{exercise}\n", - "Schrijf een Python programma dat de benaderde Riemann som, vergelijking [](#eq:benadering), uitrekent voor \n", - "-\t$\\Delta \\lambda_0= 10 \\; \\mathrm{nm}$\n", - "-\t$N=3000 \\; \\mathrm{nm}/\\Delta \\lambda_0$\n", - "-\t$T=5800 \\; \\mathrm{K}$\n", - "\n", - "Herhaal, om te controleren of we $N$ groot genoeg hebben gekozen en of we $\\Delta \\lambda_0$ klein genoeg hebben gekozen, je berekening voor \n", - "-\t$\\Delta \\lambda_0=3 \\; \\mathrm{nm}$\n", - "-\t$N=6000 \\; \\mathrm{nm}/\\Delta \\lambda_0$\n", - "-\t$T=5800 \\; \\mathrm{K}$\n", - "\n", - "Vergelijk beide resultaten met de verwachte uitkomst gegeven als vergelijking [](#eq:Et).\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "c1dd80c3", - "metadata": {}, - "source": [ - "## Bepaling optimale bandgap-energie van een fotovoltaïsche zonnecel\n", - "Een foton met golflengte $\\lambda$ heeft een energie $E_p=\\frac{hc}{\\lambda}$ . Met vergelijking (1) vinden we nu dat het aantal fotonen dat wordt uitgestraald door een zwart oppervlak met temperatuur $T$ binnen een golflengte-interval tussen $\\lambda$ en $\\lambda+\\Delta \\lambda$ wordt gegeven door \n", - "\n", - "$$\n", - "N_{\\lambda, T}\n", - "= \n", - "\\frac{2\\pi h c^{2}}{\\lambda^{5}}\n", - "\\cdot\n", - "\\frac{1}{\\exp\\left(\\frac{hc}{\\lambda k T}\\right) - 1}\n", - "\\cdot\n", - "\\frac{\\lambda}{hc}\\,\n", - "\\Delta \\lambda\n", - "=\n", - "\\frac{2\\pi c}{\\lambda^{4}}\n", - "\\cdot\n", - "\\frac{1}{\\exp\\left(\\frac{hc}{\\lambda k T}\\right) - 1}\\,\n", - "\\Delta \\lambda\n", - "\\quad\n", - "\\left[ \\frac{\\text{aantal fotonen}}{m^{2}\\, s} \\right]\n", - "$$\n", - "\n", - "Fotonen met een energie $E_p$ groter dan de bandgap-energie $E_g$ leveren een nuttige energie $E_g$. Dit is het geval voor fotonen met een golflengte $\\lambda < hc/E_g$.\n", - "Fotonen met een energie $E_p$ kleiner dan de bandgap-energie $E_g$ leveren een nuttige energie 0. Dit is het geval voor fotonen met een golflengte $\\lambda > hc/E_g$.\n", - "De totale energie geleverd door de zonnecel bedraagt dus\n", - "\n", - "$$\n", - "E_{zc, E_g, T}\n", - "= \n", - "2\\pi c E_g \n", - "\\int_{0}^{hc/E_g}\n", - "\\frac{1}{\\lambda^{4}\n", - "\\left( \\exp\\left( \\frac{hc}{\\lambda k T} \\right) - 1 \\right)}\n", - "\\, d\\lambda\n", - "\\approx\n", - "2\\pi c E_g \n", - "\\sum_{i=1}^{hc/(E_g \\Delta\\lambda_0)}\n", - "\\frac{1}{\\lambda_i^{4}\n", - "\\left( \\exp\\left( \\frac{hc}{\\lambda_i k T} \\right) - 1 \\right)}\n", - "\\, \\Delta\\lambda_0\n", - "$$\n", - "\n", - "met $\\lambda_i = i \\cdot \\Delta \\lambda_0$." - ] - }, - { - "cell_type": "markdown", - "id": "1f30427c", - "metadata": {}, - "source": [ - "```{exercise}\n", - "1.\tSchrijf een Python programma dat bovenstaande Riemann som (4) uitrekent voor een gegeven Eg en \n", - "-\t$\\Delta \\lambda_0=10\\mathrm{nm}$\n", - "-\t$T=5800\\mathrm{K}$\n", - "1.\tbereken en plot als functie van $E_g$ voor $0.5 \\mathrm{eV} \\leq E_g \\leq 2 \\mathrm{eV}$.\n", - "1.\tWat is, volgens deze modelberekeningen, de optimale bandgap-energie (in eV) voor een zonnecel?\n", - "\n", - "```" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.5" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/Content/PySim/1_intro_sim.ipynb b/Content/PySim/1_intro_sim.ipynb index b1acd09..874bcf0 100644 --- a/Content/PySim/1_intro_sim.ipynb +++ b/Content/PySim/1_intro_sim.ipynb @@ -5,6 +5,9 @@ "id": "25ff12d9", "metadata": {}, "source": [ + "---\n", + "authors: Freek Pols\n", + "---\n", "# Introductie thermo simulaties (1h)\n", "\n", "In Q2 werken we aan een deeltjes model.\n", diff --git a/Content/PySim/2_deeltjes_model.ipynb b/Content/PySim/2_deeltjes_model.ipynb index a7d1a61..fd7dda9 100644 --- a/Content/PySim/2_deeltjes_model.ipynb +++ b/Content/PySim/2_deeltjes_model.ipynb @@ -5,6 +5,9 @@ "id": "8c6d2412", "metadata": {}, "source": [ + "---\n", + "authors: Freek Pols\n", + "---\n", "# Deeltjes model\n", "\n", "Zoom je heel ver in, dan zie je deeltjes rond vliegen.\n", diff --git a/Content/PySim/3_recap.ipynb b/Content/PySim/3_recap.ipynb index c8e3f0f..1696e82 100644 --- a/Content/PySim/3_recap.ipynb +++ b/Content/PySim/3_recap.ipynb @@ -5,6 +5,11 @@ "id": "a8a4e11a-5e7d-43f9-a240-acb45772d5e5", "metadata": {}, "source": [ + "---\n", + "authors: \n", + " - Freek Pols\n", + " - Roel Smit\n", + "---\n", "# Recap\n" ] }, @@ -629,7 +634,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "base", "language": "python", "name": "python3" }, diff --git a/Content/PySim/4_brownianmotion.ipynb b/Content/PySim/4_brownianmotion.ipynb index 908bdd4..bec9ce3 100644 --- a/Content/PySim/4_brownianmotion.ipynb +++ b/Content/PySim/4_brownianmotion.ipynb @@ -5,6 +5,9 @@ "id": "b9c4ac10", "metadata": {}, "source": [ + "---\n", + "authors: Freek Pols\n", + "---\n", "# Brownian motion\n", "\n", "Een van de bekendste voorbeelden van botsende deeltjes in de natuur is Brownian motion.\n", diff --git a/Content/PySim/5_druktemp.ipynb b/Content/PySim/5_druktemp.ipynb index a8cff23..371f37a 100644 --- a/Content/PySim/5_druktemp.ipynb +++ b/Content/PySim/5_druktemp.ipynb @@ -5,6 +5,9 @@ "id": "f7148c20", "metadata": {}, "source": [ + "---\n", + "authors: Roel Smit\n", + "---\n", "# Druk, temperatuur en energie\n", "\n", "## Doelen\n", @@ -527,7 +530,7 @@ "id": "fb8f785a", "metadata": {}, "source": [ - "Met de middeling kan je beter zien wat de gemiddelde waarde voor de druk in dit systeem is. Als dit voor jouw simulatie lastig is kan je de factor `alpha` aanpassen in de functie 'handle_walls`, zodat je over een langere periode het gemiddelde neemt. Hou daarbij wel rekening met de eis in [bovenstaande formule](int_norm)\n", + "Met de middeling kan je beter zien wat de gemiddelde waarde voor de druk in dit systeem is. Als dit voor jouw simulatie lastig is kan je de factor `alpha` aanpassen in de functie 'handle_walls`, zodat je over een langere periode het gemiddelde neemt. Hou daarbij wel rekening met de eis in [bovenstaande formule](#int_norm)\n", "\n", "```{exercise}\n", ":label: ex-druktemp-7\n", @@ -579,7 +582,7 @@ "id": "5916caed", "metadata": {}, "source": [ - "In [de formule voor de tweedimensionale druk](2d_pressure) kan je de structuur van de ideale gaswet herkennen. \n", + "In [de formule voor de tweedimensionale druk](#2d_pressure) kan je de structuur van de ideale gaswet herkennen. \n", "\n", "```{exercise}\n", ":label: ex-druktemp9b\n", diff --git a/Content/PySim/6_boltzmann.ipynb b/Content/PySim/6_boltzmann.ipynb index 4fb3f7c..65c5c6b 100644 --- a/Content/PySim/6_boltzmann.ipynb +++ b/Content/PySim/6_boltzmann.ipynb @@ -5,6 +5,11 @@ "id": "c632e392", "metadata": {}, "source": [ + "---\n", + "authors: \n", + " - Roel Smit\n", + " - Freek Pols\n", + "---\n", "# De Maxwell-Boltzmann snelheidsverdeling\n", "\n", "## Doelen\n", diff --git a/Content/PySim/7_arbeid.ipynb b/Content/PySim/7_arbeid.ipynb new file mode 100644 index 0000000..dc7ebf0 --- /dev/null +++ b/Content/PySim/7_arbeid.ipynb @@ -0,0 +1,846 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8aeddfa6", + "metadata": {}, + "source": [ + "---\n", + "author: Roel Smit\n", + "updated: December 12, 2025\n", + "---\n", + "\n", + "# Arbeid\n", + "\n", + "## Doelen\n", + "\n", + "Nu we de microscopische grootheden van de moleculen hebben verbonden aan de macroscopische grootheden van het gas kunnen we de thermodynamica van het gas echt bestuderen met onze simulatie. In dit werkblad kijken we hoe de temperatuur en de druk veranderen onder invloed van een zuiger die het volume verandert. \n", + "\n", + "Eerst herhalen we de delen van de code die we nodig hebben:\n", + "\n", + "- klasse voor het deeltje met bijbehorende functies\n", + "- variabelen en randcondities van de controle volume\n", + "- functies voor (een lijst) deeltjes\n", + "\n", + "Daarna voegen we code toe voor de dynamiek van de zuiger:\n", + "\n", + "- zuiger implementeren in volume en dynamische formules\n", + "\n", + "En vervolgens:\n", + "- bestuderen van temperatuur en druk als functie van volume\n", + "- onderzoeken of we terug kunnen keren naar startcondities\n", + "\n", + "In onderstaande animatie laten we het proces zien dat je gaat programmeren.\n", + "\n", + "\n", + "\n", + "## Laden van eerdere code\n", + "\n", + "We beginnen weer met de noodzakelijke pakketten en de constanten. Daar voegen we nu een constante aan toe: de startsnelheid van de zuiger. \n", + "\n", + "```{exercise} Startwaardes\n", + ":label: ex-arbeid-01\n", + "Neem de constanten die je in het vorige werkblad hebt gekozen hieronder over. Let op dat de snelheid van de zuiger tien maal zo laag is als de startsnelheid van de deeltjes.\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b09c1c22", + "metadata": {}, + "outputs": [], + "source": [ + "# ruimte voor uitwerking\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib.animation import FuncAnimation\n", + "from scipy.optimize import curve_fit\n", + "\n", + "BOX_SIZE_0 = 0 # Hoogte en lengte startvolume\n", + "N = 40 # Aantal deeltjes\n", + "V_0 = 0 # Startsnelheid van deeltjes\n", + "RADIUS = 0 # Straal van moleculen\n", + "DT = 0 # Tijdstap om geen botsing te missen\n", + "V_PISTON_0 = -0.1 * V_0 # Startsnelheid van zuiger \n", + "# (negatief betekent zowel links als rechts naar binnen gericht)\n", + "\n", + "#your code/answer\n" + ] + }, + { + "cell_type": "markdown", + "id": "e775e6b7", + "metadata": {}, + "source": [ + "Zoals altijd laden we de klasse voor de gasmoleculen en de functies voor hun onderlinge interactie:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15942646", + "metadata": {}, + "outputs": [], + "source": [ + "class ParticleClass:\n", + " def __init__(self, m, v, r, R):\n", + " \"\"\" maakt een deeltje (constructor) \"\"\"\n", + " self.m = m \n", + " self.v = np.array(v, dtype=float) \n", + " self.r = np.array(r, dtype=float) \n", + " self.R = R\n", + "\n", + " def update_position(self):\n", + " \"\"\" verandert positie voor één tijdstap \"\"\"\n", + " self.r += self.v * DT \n", + " \n", + " @property\n", + " def momentum(self):\n", + " return self.m * self.v\n", + " \n", + " @property\n", + " def kin_energy(self):\n", + " return 1/2 * self.m * np.dot(self.v, self.v)\n", + " \n", + "def collide_detection(p1: ParticleClass, p2: ParticleClass) -> bool:\n", + " \"\"\" Geeft TRUE als de deeltjes overlappen \"\"\"\n", + " dx = p1.r[0]-p2.r[0]\n", + " dy = p1.r[1]-p2.r[1]\n", + " rr = p1.R + p2.R\n", + " return dx**2+dy**2 < rr**2 \n", + "\n", + "def particle_collision(p1: ParticleClass, p2: ParticleClass):\n", + " \"\"\" past snelheden aan uitgaande van overlap \"\"\"\n", + " m1, m2 = p1.m, p2.m\n", + " delta_r = p1.r - p2.r\n", + " delta_v = p1.v - p2.v\n", + " dot_product = np.dot(delta_r, delta_v)\n", + " # Als deeltjes van elkaar weg bewegen dan geen botsing\n", + " if dot_product >= 0: # '='-teken voorkomt ook problemen als delta_r == \\vec{0}\n", + " return\n", + " distance_squared = np.dot(delta_r, delta_r) \n", + " # Botsing oplossen volgens elastische botsing in 2D\n", + " p1.v -= 2 * m2 / (m1 + m2) * dot_product / distance_squared * delta_r\n", + " p2.v += 2 * m1 / (m1 + m2) * dot_product / distance_squared * delta_r" + ] + }, + { + "cell_type": "markdown", + "id": "562264b9", + "metadata": {}, + "source": [ + "Het volume en de randvoorwaarden zullen we moeten aanpassen aan onze simulatie met bewegende zuiger: Het volume zal nu niet meer altijd een vierkant zijn. \n", + "\n", + "```{figure} ../../Figures/zuiger.png\n", + ":width: 50%\n", + "\n", + "De simulatie bestaat uit een volume met links en rechts een bewegende wand: de zuiger.\n", + "```\n", + "\n", + "Laten we aannemen dat de zuiger altijd in de horizontale richting verplaatst en het volume symmetrisch houdt ten opzichte van de oorsprong, d.w.z. er is een zuiger aan de linker wand die een tegengestelde verplaatsing heeft aan die in de rechter wand. \n", + "\n", + "We maken eerst een aantal variabelen aan die bij het volume horen:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67afb166", + "metadata": {}, + "outputs": [], + "source": [ + "box_height = BOX_SIZE_0 # hoogte van beheersvolume\n", + "box_length = BOX_SIZE_0 # breedte van beheersvolume\n", + "impulse_outward = 0.0 # totale stoot van deeltjes naar buiten gericht\n", + "pressure = 0.0 # druk in beheersvolume\n", + "v_piston = V_PISTON_0 # huidige snelheid van zuiger \n", + "work = 0.0 # arbeid uitgevoerd door gas" + ] + }, + { + "cell_type": "markdown", + "id": "dbf49e65", + "metadata": {}, + "source": [ + "De functies die bij het volume en de randvoorwaarden horen moeten we een klein beetje aanpassen, zodat we niet langer uitgaan van de constante waarde van de lengte en hoogte. Om de variabelen zoals `box_height` en `box_length` die we hierboven gedefinieerd hebben, later in functies te gebruiken, moeten we ze telkens oproepen met het keyword `global`. Dit is hieronder uitgewerkt. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1532f73", + "metadata": {}, + "outputs": [], + "source": [ + "def top_down_collision(particle: ParticleClass):\n", + " \"\"\" botsingen met wanden onder en boven controleren en totale stoot bepalen \"\"\"\n", + " global impulse_outward, box_height\n", + " if abs(particle.r[1]) + particle.R > box_height / 2:\n", + " particle.r[1] = np.sign(particle.r[1]) * (box_height/2 - particle.R)\n", + " impulse_outward += abs(particle.momentum[1]) * 2\n", + " particle.v[1] *= -1\n", + " \n", + "def left_right_collision(particle: ParticleClass):\n", + " \"\"\" botsingen met wanden links en rechts controleren en totale stoot bepalen \"\"\"\n", + " global impulse_outward, box_length\n", + " if abs(particle.r[0]) + particle.R > box_length / 2:\n", + " particle.r[0] = np.sign(particle.r[0]) * (box_length/2 - particle.R)\n", + " impulse_outward += abs(particle.momentum[0]) * 2\n", + " particle.v[0] *= -1" + ] + }, + { + "cell_type": "markdown", + "id": "b3b0cc7f", + "metadata": {}, + "source": [ + "En dan laden we ook alle functies die over de gehele lijst met deeltjes werken, waarbij een paar kleine aanpassingen nodig zijn vanwege de splitsing in het botsen met de wanden. Ook hier roepen we een aantal variabelen met `global` aan.\n", + "\n", + "```{exercise} Startfuncties\n", + ":label: ex-arbeid-02\n", + "Pas de code voor de temperatuur weer aan naar de code die je in de vorige werkbladen hebt gebruikt.\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40f14a8b", + "metadata": {}, + "outputs": [], + "source": [ + "def create_particles(particles):\n", + " \"\"\" Leegmaken en opnieuw aanmaken van deeltjes in lijst \"\"\"\n", + " global box_length, box_height\n", + " particles.clear()\n", + " for _ in range(N):\n", + " vx = np.random.uniform(-V_0, V_0)\n", + " vy = np.random.choice([-1, 1]) * np.sqrt(V_0**2 - vx**2) \n", + " x = np.random.uniform(-box_length/2 + RADIUS, box_length/2 - RADIUS)\n", + " y = np.random.uniform(-box_height/2 + RADIUS, box_height/2 - RADIUS)\n", + " particles.append(ParticleClass(m=1.0, v=[vx, vy], r=[x, y], R=RADIUS))\n", + " \n", + "def temperature(particles) -> float:\n", + " # De oplossing van je vorige werkblad\n", + "#your code/answer\n", + " return 0.0\n", + " \n", + "def handle_collisions(particles):\n", + " \"\"\" alle onderlinge botsingen afhandelen voor deeltjes in lijst \"\"\"\n", + " num_particles = len(particles)\n", + " for i in range(num_particles):\n", + " for j in range(i+1, num_particles):\n", + " if collide_detection(particles[i], particles[j]):\n", + " particle_collision(particles[i], particles[j])\n", + "\n", + "def handle_walls(particles):\n", + " \"\"\" botsing met wanden controleren voor alle deeltjes in lijst en gemiddeld bepaling druk \"\"\"\n", + " global pressure, impulse_outward, box_height, box_length # om pressure buiten de functie te kunnen gebruiken\n", + " impulse_outward = 0.0\n", + " for p in particles:\n", + " left_right_collision(p)\n", + " top_down_collision(p) \n", + " pressure = 0.95 * pressure + 0.05 * impulse_outward / ((2 * box_length + 2 * box_height) * DT) \n", + "\n", + "def take_time_step(particles):\n", + " \"\"\" zet tijdstap voor een lijst deeltjes en verwerk alle botsingen onderling en met wanden \"\"\"\n", + " for p in particles:\n", + " p.update_position()\n", + " handle_collisions(particles)\n", + " handle_walls(particles) \n" + ] + }, + { + "cell_type": "markdown", + "id": "f8e7fe08", + "metadata": {}, + "source": [ + "## Implementeren (symmetrische) zuiger\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "9a2157bd", + "metadata": {}, + "source": [ + "Voordat we nog meer veranderingen aan de code doorvoeren, moeten we eerst controleren of alles nog werkt. Onderstaande functie is een beetje aangepast ten opzichte van vorige werkbladen, omdat we merkten dat de vorm van de pijlen wel eens de mist in ging bij een heel andere keuze voor eenheden in de constanten." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04974e09", + "metadata": {}, + "outputs": [], + "source": [ + "particles = []\n", + "create_particles(particles)\n", + "for i in range(100):\n", + " take_time_step(particles)\n", + "\n", + "plt.figure()\n", + "plt.xlabel('x')\n", + "plt.ylabel('y')\n", + "plt.gca().set_aspect('equal')\n", + "plt.xlim(-BOX_SIZE_0/2, BOX_SIZE_0/2)\n", + "plt.ylim(-BOX_SIZE_0/2, BOX_SIZE_0/2)\n", + "\n", + "for p in particles:\n", + " plt.plot(p.r[0], p.r[1], 'k.', ms=25)\n", + " plt.arrow(p.r[0], p.r[1], p.v[0]*DT*30, p.v[1]*DT*30, width=.2*RADIUS,\n", + " head_width=RADIUS, head_length=RADIUS, color='red')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "d9d983b7", + "metadata": {}, + "source": [ + "Nu implementeren we de zuiger door het toegestane gebied voor de gasdeeltjes bij elke tijdstap te verkleinen met een stap $2v_{\\text{piston}}dt$. De factor $2$ is opgenomen omdat zowel de linker en de rechter wand een zuigerwand zijn. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9fa07f5", + "metadata": {}, + "outputs": [], + "source": [ + "def take_time_step(particles):\n", + " \"\"\" zet tijdstap voor een lijst deeltjes en verwerk alle botsingen onderling en met wanden \"\"\"\n", + " global box_length, v_piston\n", + " box_length += 2 * v_piston * DT # zowel links als rechts zuiger\n", + " for p in particles:\n", + " p.update_position()\n", + " handle_walls(particles) \n", + " handle_collisions(particles)" + ] + }, + { + "cell_type": "markdown", + "id": "911b807e", + "metadata": {}, + "source": [ + "Hieronder draaien we een kleine simulatie om te kijken of we de box kleiner zien worden en vervolgens te bestuderen hoe de temperatuur zich gedraagt als functie van het oppervlak/volume. \n", + "\n", + "```{warning} \n", + "Verwijder de cel outputs alvorens te pushen naar GitHub! Het filmpje neemt nogal wat geheugen in!\n", + "```\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efef4ce8", + "metadata": {}, + "outputs": [], + "source": [ + "# Deel van de animated simulatie\n", + "from matplotlib.animation import FuncAnimation\n", + "from IPython.display import HTML\n", + "\n", + "fig, ax = plt.subplots()\n", + "ax.set_xlabel('x')\n", + "ax.set_ylabel('y')\n", + "ax.set_xlim(-BOX_SIZE_0/2, BOX_SIZE_0/2)\n", + "ax.set_ylim(-BOX_SIZE_0/2, BOX_SIZE_0/2)\n", + "ax.set_aspect('equal')\n", + "dot, = ax.plot([], [], 'ro', ms=14);\n", + "\n", + "def init():\n", + " dot.set_data([], [])\n", + " return dot,\n", + "\n", + "\n", + "# we kiezen het aantal datapunten zodat het volume tot 1/3 van het begin volume reduceert\n", + "num_steps = round(2/3 * BOX_SIZE_0 / (2 * -V_PISTON_0 * DT))\n", + "\n", + "particles = []\n", + "volumes = np.zeros(num_steps, dtype=float)\n", + "temperatures = np.zeros(num_steps, dtype=float)\n", + "\n", + "box_length = BOX_SIZE_0 # zetten zuiger terug\n", + "v_piston = V_PISTON_0\n", + "create_particles(particles) # resetten deeltjes \n", + "\n", + "def update(frame):\n", + " take_time_step(particles)\n", + " dot.set_data([p.r[0] for p in particles], [p.r[1] for p in particles])\n", + " volumes[i] = box_length * box_height\n", + " temperatures[i] = temperature(particles)\n", + " return dot,\n", + " \n", + "ani = FuncAnimation(fig, update, frames=int(num_steps/2), init_func=init, blit=True, interval=50)\n", + "HTML(ani.to_jshtml())\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d4ab5a6-13ab-46e1-bd27-dd40b071ce6a", + "metadata": {}, + "outputs": [], + "source": [ + "# we kiezen het aantal datapunten zodat het volume tot 1/3 van het begin volume reduceert\n", + "num_steps = round(2/3 * BOX_SIZE_0 / (2 * -V_PISTON_0 * DT))\n", + "\n", + "particles = []\n", + "volumes = np.zeros(num_steps, dtype=float)\n", + "temperatures = np.zeros(num_steps, dtype=float)\n", + "\n", + "box_length = BOX_SIZE_0 # zetten zuiger terug\n", + "v_piston = V_PISTON_0\n", + "create_particles(particles) # resetten deeltjes \n", + "\n", + "for i in range(num_steps):\n", + " take_time_step(particles)\n", + " volumes[i] = box_length * box_height\n", + " temperatures[i] = temperature(particles)\n", + "\n", + "temperatures = np.asarray(temperatures)\n", + "\n", + "plt.figure()\n", + "plt.xlabel('Volume')\n", + "plt.ylabel('Temperature')\n", + "\n", + "plt.plot(volumes, temperatures, '-r')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "ac85ac83", + "metadata": {}, + "source": [ + "Dit kan niet kloppen. Hier zien we dat de temperatuur vrijwel constant is (let op de vermenigvuldigingsfactor vermeld aan de bovenkant van de verticale as). Maar de zuiger voert arbeid uit, op baiss van de wet van behoud van energie betekent zou de temperatuur moet veranderen!\n", + "\n", + "Om het model kloppend te maken moeten we kijken naar de botsing van de deeltjes met de wand. In de vorige werkbladen stonden de wanden stil en veranderde de snelheid van de deeltjes alleen van teken in de component loodrecht op de wand. Nu dat de wanden een zuiger zijn en snelheid hebben, moeten we daarvoor corrigeren.\n", + "\n", + "De snelheid van de deeltjes klapt nog steeds om van teken in het referentiestelsel van de wand, maar omdat de wand beweegt ten opzichte van het volume met snelheid $v_{\\text{piston}}$, wordt de juiste functie:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c795e31", + "metadata": {}, + "outputs": [], + "source": [ + "def left_right_collision(particle: ParticleClass):\n", + " \"\"\" verzorgen van botsingen met wand links en rechts, die als zuiger kunnen bewegen \"\"\"\n", + " global box_length, v_piston, impulse_outward\n", + " if abs(particle.r[0]) + particle.R > box_length / 2:\n", + " particle.r[0] = np.sign(particle.r[0]) * (box_length/2 - particle.R)\n", + " piston_velocity = np.sign(particle.r[0]) * v_piston\n", + " relative_velocity = particle.v[0] - piston_velocity # stelsel zuiger\n", + " particle.v[0] = -relative_velocity + piston_velocity # stelsel waarnemer\n", + " impulse_outward += 2 * particle.m * abs(relative_velocity) # stoot gevoeld door zuiger" + ] + }, + { + "cell_type": "markdown", + "id": "3ac90c57", + "metadata": {}, + "source": [ + "Nu kunnen we de simulatie opnieuw uitvoeren:\n", + "\n", + "\n", + "```{exercise} Getallen en eenheden\n", + ":label: ex-arbeid-03\n", + "Pas de code aan, zodat de getalwaardes op de assen kloppen en de juiste eenheden in de titels van de assen vermeld staan.\n", + "\n", + "Check eventueel ook of de deeltjes in je eerdere animatie ook sneller bewegen!\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df1afb88", + "metadata": {}, + "outputs": [], + "source": [ + "num_steps = round(2/3 * BOX_SIZE_0 / (2 * -V_PISTON_0 * DT))\n", + "\n", + "particles = []\n", + "volumes = np.zeros(num_steps, dtype=float)\n", + "temperatures = np.zeros(num_steps, dtype=float)\n", + "\n", + "box_length = BOX_SIZE_0 # zetten zuiger terug\n", + "v_piston = V_PISTON_0\n", + "create_particles(particles) # resetten deeltjes \n", + "for i in range(num_steps):\n", + " take_time_step(particles)\n", + " volumes[i] = box_length * box_height\n", + " temperatures[i] = temperature(particles)\n", + "\n", + "plt.figure()\n", + "plt.xlabel('Volume')\n", + "plt.ylabel('Temperature')\n", + "\n", + "temperatures = np.asarray(temperatures)\n", + "\n", + "#your code/answer\n", + "\n", + "plt.plot(volumes, temperatures, '-r')\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "15a5221c", + "metadata": {}, + "source": [ + "We zien nu een heel duidelijke afhankelijkheid van de temperatuur op het volume, zoals we ook verwachten vanwege de wet van behoud van energie. Een volgende logische stap is of deze grafiek ook daadwerkelijk overeenkomt met onze verwachting, maar daarvoor is het waardevol om ook informatie te halen uit de andere grootheden. \n", + "\n", + "```{exercise} Druksimulatie\n", + ":label: ex-arbeid-04\n", + "Maak een simulatie waarin niet de temperatuur, maar de druk als functie van het volume wordt geplot. Gebruik dezelfde waarden voor het aantal tijdstappen en het volume, zoals in de startcode die je hieronder al ziet. Let ook nu op getallen en eenheden.\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "03ac97c1", + "metadata": {}, + "outputs": [], + "source": [ + "# breid deze code uit met jouw antwoord\n", + "\n", + "num_steps = round(2/3 * BOX_SIZE_0 / (2 * -V_PISTON_0 * DT))\n", + "particles = []\n", + "volumes = np.zeros(num_steps, dtype=float)\n", + "pressures = np.zeros(num_steps, dtype=float)\n", + "\n", + "pressure = 0.0\n", + "box_length = BOX_SIZE_0 # zetten zuiger terug\n", + "v_piston = V_PISTON_0\n", + "create_particles(particles) # resetten deeltjes \n", + "\n", + "for i in range(num_steps):\n", + " take_time_step(particles)\n", + " # RUIMTE VOOR UITWERKING\n", + "#your code/answer\n", + "\n", + "# PLOT GRAFIEK" + ] + }, + { + "cell_type": "markdown", + "id": "144ac953", + "metadata": {}, + "source": [ + "Als je simulatie klopt heeft deze grafiek de vorm van een machtsfunctie met een negatieve exponent. \n", + "\n", + "```{exercise} Exponent\n", + ":label: ex-arbeid-05\n", + "Leg uit welke factor je hier verwacht voor de exponent op basis van de theorie van het boek.\n", + "```\n", + "\n", + "```{solution} ex-arbeid-05\n", + "Jouw antwoord hier...\n", + "\n", + "#your code/answer\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "b7f9d7ff", + "metadata": {}, + "source": [ + "```{exercise}\n", + ":label: ex-arbeid-06\n", + "Bepaal met behulp van een fit wat het resultaat van de simulatie voor de machtsterm is. \n", + "\n", + "Let op dat je goed beginwaardes meegeeft bij de code `p0=...`, anders dan breekt de fitfunctie af, omdat er geen goed antwoord gevonden kan worden.\n", + "\n", + "Daarnaast kun je gebruik maken van bounds: `bounds=((a_min, n_min),(a_max, n_max))`. Alleen de waarden hiertussen worden beschouwd.\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f11776b9", + "metadata": {}, + "outputs": [], + "source": [ + "def power_law(vol, a, n):\n", + " \"\"\" de fitfunctie voor het P,V-diagram \"\"\"\n", + " return a * (vol)**n \n", + "\n", + "# RUIMTE VOOR VERDERE UITWERKING\n", + "\n", + "#your code/answer\n" + ] + }, + { + "cell_type": "markdown", + "id": "132fc8c7", + "metadata": {}, + "source": [ + "Als je de simulatie een aantal keer uitvoert, zie je dat er een structureel verschil zit tussen de waarde die je verwacht en de waarde die je uit de fit krijgt.\n", + "\n", + "```{exercise} Afwijking\n", + ":label: ex-arbeid-07\n", + "Wat is de belangrijkste reden voor deze afwijking? Geef een methode van aanpak waarmee je voor deze simulatie kan verifiëren of dit inderdaad de juiste is. (Je hoeft dit niet uit te voeren, maar het mag natuurlijk wel)\n", + "```\n", + "\n", + "```{solution} reason_power\n", + "Jouw antwoord hier...\n", + "#your code/answer\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef48bce9-f57a-4e83-8df3-d0ac3b3ed3b6", + "metadata": {}, + "outputs": [], + "source": [ + "# Ruimte voorverificatie\n", + "\n", + "#your code/answer\n" + ] + }, + { + "cell_type": "markdown", + "id": "aca7ca3d", + "metadata": {}, + "source": [ + "## Eerste hoofdwet\n", + "Een goede controle voor ons simulatie is te onderzoeken of deze voldoet aan de eerste hoofdwet van de thermodynamica. \n", + "\n", + "Omdat er (nog) geen warmte wordt uitgewisseld, moeten we alleen de hoeveelheid arbeid bepalen. De arbeid wordt geleverd door de zuiger(s) op de deeltjes. Dus in de functie waar we de botsingen van de deeltjes met de wand behandelen, kunnen we ook de verrichte arbeid bepalen. De arbeid wordt gegeven\\ door:\n", + "\n", + "$$\n", + " W = \\int_1^2 P dV\n", + "$$\n", + "\n", + "Hier staan de $1$ voor de begintoestand en de $2$ voor de eindtoestand van het proces. In de differentiaalvorm wordt dit geschreven als:\n", + "\n", + "$$\n", + " \\delta W = P dV\n", + "$$\n", + "\n", + "Zoals dat in het boek gebeurt, kiezen we voor de notatie met $\\delta W$ in plaats van $dW$, om aan te geven dat deze integraal afhankelijk is van het gekozen proces en niet alleen van het begin- en eindpunt. Voor ons experiment, waar het volume niet geleidelijk maar in stapjes verandert, kan je de hoeveelheid arbeid per tijdstip dus herschrijven tot:\n", + "\n", + "$$ \n", + " \\Delta W = P \\Delta V \\stackrel{2D}{=} P \\Delta A = P h v_{\\text{piston}} \\Delta t = \\frac{\\Delta p}{h \\Delta t} h v_{\\text{piston}} \\Delta t = v_{\\text{piston}}\\Delta p\n", + "$$\n", + "\n", + "waarin $h$ de hoogte is van de zuiger.\n", + "\n", + "Dit betekent dat we de arbeid per tijdstap in onze code kunnen bepalen op hetzelfde moment dat we de druk bepalen met behulp van de gezamenlijke stoot van de moleculen. Daarvoor moeten we de code voor de functie `left_right_collision` nogmaals aanpassen:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f32ca609", + "metadata": {}, + "outputs": [], + "source": [ + "def left_right_collision(particle: ParticleClass):\n", + " \"\"\" verzorgen van botsingen met wand links en rechts, die als zuiger kunnen bewegen \"\"\"\n", + " global box_length, v_piston, impulse_outward, work\n", + " if abs(particle.r[0]) + particle.R > box_length / 2:\n", + " particle.r[0] = np.sign(particle.r[0]) * (box_length/2 - particle.R)\n", + " piston_velocity = np.sign(particle.r[0]) * v_piston\n", + " relative_velocity = particle.v[0] - piston_velocity # stelsel zuiger\n", + " particle.v[0] = -relative_velocity + piston_velocity # stelsel waarnemer\n", + " impulse_outward += 2 * particle.m * abs(relative_velocity)\n", + " work += 2 * particle.m * relative_velocity * piston_velocity" + ] + }, + { + "cell_type": "markdown", + "id": "658869a9", + "metadata": {}, + "source": [ + "Voordat we een simulatie draaien is het goed even stil te staan en na te denken. Net als bij een experiment in het lab doorlopen we bewust de onderzoekscyclus. We moeten daarvoor eerst een hypothese opstellen en daarna pas de simulatie uitvoeren. " + ] + }, + { + "cell_type": "markdown", + "id": "8c7ce9bc", + "metadata": {}, + "source": [ + "```{exercise} Eerste Hoofdwet\n", + ":label: ex-arbeid-08\n", + "Welke twee grootheden kan je nu tegen elkaar uitzetten om te valideren of de simulatie aan de eerste hoofdwet voldoet? En welke grafiek verwacht je dan dat daar uitkomt?\n", + "```\n", + "\n", + "```{solution} ex-arbeid-08\n", + "Hier jouw antwoord...\n", + "#your code/answer\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "2b2a52d7", + "metadata": {}, + "source": [ + "```{exercise} Hoofdwet gecontroleerd\n", + ":label: ex-arbeid-09\n", + "Stel de code op om deze grafiek te produceren. Neem wederom 1000 tijdstappen met de standaard snelheid voor de zuiger en vergelijk het resultaat met je hypothese.\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b64e77a", + "metadata": {}, + "outputs": [], + "source": [ + "particles = []\n", + "pressure = 0.0\n", + "work = 0.0\n", + "box_length = BOX_SIZE_0 # zetten zuiger terug\n", + "v_piston = V_PISTON_0\n", + "create_particles(particles) # resetten deeltjes \n", + "\n", + "# RUIMTE VOOR VERDERE UITWERKING\n", + "#your code/answer\n" + ] + }, + { + "cell_type": "markdown", + "id": "bc8731ff", + "metadata": {}, + "source": [ + "## Reversibiliteit\n", + "Als laatste onderdeel van dit werkblad maken we een eenvoudige cyclus. We comprimeren het volume gedurende 1000 tijdstappen en keren daarna in 1000 gelijke tijdstappen terug naar het startvolume. \n", + "\n", + "```{exercise} Terugkeren\n", + ":label: ex-arbeid-10\n", + "\n", + "Een deel van de vorige code kan hergebruikt worden, maar halverwege moeten de richting van de zuigers omdraaien.\n", + "\n", + "Vul de code aan en voor de volledige simulatie uit. Gebruik een verschillende kleur voor de grafiek tijdens de compressie en de decompressie.\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c62b8a99", + "metadata": {}, + "outputs": [], + "source": [ + "particles = []\n", + "pressure = 0.0\n", + "work = 0.0\n", + "box_length = BOX_SIZE_0 # zetten zuiger terug\n", + "v_piston = V_PISTON_0\n", + "create_particles(particles) # resetten deeltjes \n", + "\n", + "#your code/answer\n", + "v_piston = -V_PISTON_0 # zuiger richting wordt omgedraaid\n", + "#your code/answer\n" + ] + }, + { + "cell_type": "markdown", + "id": "dd359502", + "metadata": {}, + "source": [ + "```{exercise} Afhankelijk van snelheid\n", + ":label: ex-arbeid-11\n", + "Herhaal deze simulatie maar nu met een 5 keer zo hoge snelheid voor de zuiger in beide richtingen. Je hebt dus 5x minder stappen nodig.\n", + "\n", + "Pas het aantal tijdstappen aan tot 400, zodat de zuiger in één 'slag' dezelfde afstand aflegt. \n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b144471", + "metadata": {}, + "outputs": [], + "source": [ + "particles = []\n", + "pressure = 0.0\n", + "work = 0.0\n", + "box_length = BOX_SIZE_0 # zetten zuiger terug\n", + "v_piston = 5 * V_PISTON_0\n", + "create_particles(particles) # resetten deeltjes\n", + "\n", + "#your code/answer\n" + ] + }, + { + "cell_type": "markdown", + "id": "5641b9ad", + "metadata": {}, + "source": [ + "In deze laatste simulatie zie je (bij een correcte code) dat nog altijd netjes wordt voldaan aan de eerste hoofdwet. Ondanks dat, zie je dat het systeem niet terugkeert naar de begintoestand. In het boek wordt dit omschreven in het deel over 'quasi-equilibrium': Processen moeten voldoende traag plaatsvinden zodat er zich een evenwicht kan vormen binnen het gas. Als processen te snel plaatsvinden is er geen sprake van equilibrium en kun je de macroscopische thermodynamische formules niet langer gebruiken.\n", + "\n", + "```{exercise} Getallen en eenheden hh\n", + ":label: ex-arbeid-12\n", + "Zorg ervoor dat er in de grafiek hierboven daadwerkelijk SI-eenheden op de assen staan, als je voor je code andere eenheden hebt gekozen. Bekijk ook wat de snelheid van de zuiger in SI-eenheden is. De kwaliteit van onze simulaties is onvoldoende gedetailleerd om echt uitspraken te kunnen doen over non-equilibrium processen, maar het antwoord laat zien dat we ons in het laboratorium niet direct zorgen hoeven maken.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "0413b895", + "metadata": {}, + "source": [ + "```{exercise}\n", + "Maak je notebook leeg (clear output) en push je werk naar GitHub. Kies of je door gaat met de extra opdrachten voor een 'excellent' beoordeling.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "96789995", + "metadata": {}, + "source": [ + "```{exercise} 🌶 Uitbreiding\n", + "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)\n", + "\n", + "- 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. \n", + "- Varieer de snelheid van de zuigers en de starttemperatuur van het gas en onderzoek de resultaten.\n", + "- 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)\n", + "```" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Figures/zuiger.png b/Figures/zuiger.png new file mode 100644 index 0000000..0070a16 Binary files /dev/null and b/Figures/zuiger.png differ diff --git a/autoupdater.mjs b/autoupdater.mjs new file mode 100644 index 0000000..7fa6c45 --- /dev/null +++ b/autoupdater.mjs @@ -0,0 +1,75 @@ +import { execSync } from 'child_process'; +import path from 'path'; + +// Cache per build-run (key = absolute file path) +const gitDateCache = new Map(); + +function getRepoRoot() { + return execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim(); +} + +function getGitUpdatedISOForFile(filePathAbs) { + if (gitDateCache.has(filePathAbs)) return gitDateCache.get(filePathAbs); + + try { + const repoRoot = getRepoRoot(); + + // BELANGRIJK: relative pad vanaf repo root + const rel = path.relative(repoRoot, filePathAbs).replace(/\\/g, '/'); // windows-safe + + // --follow = volg ook renames/moves (optioneel, maar vaak gewenst) + // %cI = strict ISO 8601 + const iso = execSync(`git log -1 --follow --format=%cI -- "${rel}"`, { + cwd: repoRoot, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }).trim(); + + const result = iso || null; + gitDateCache.set(filePathAbs, result); + return result; + } catch { + gitDateCache.set(filePathAbs, null); + return null; + } +} + +function formatDateNL(iso) { + const d = new Date(iso); + return new Intl.DateTimeFormat('nl-NL', { year: 'numeric', month: 'short', day: '2-digit' }).format(d); +} + +const updateDateTransform = { + name: 'update-date', + stage: 'document', + plugin: () => { + return (node, file) => { + if (!file?.path) return node; + + const iso = getGitUpdatedISOForFile(file.path); + + if (iso) { + node.children.unshift({ + type: 'div', + class: 'font-light text-sm mb-4 updated-date-container', + children: [{ type: 'text', value: `Updated: ${formatDateNL(iso)}` }], + }); + } else { + node.children.unshift({ + type: 'div', + class: 'font-light text-sm mb-4', + children: [], + }); + } + + return node; + }; + }, +}; + +const plugin = { + name: 'Auto Update Date Plugin', + transforms: [updateDateTransform], +}; + +export default plugin; \ No newline at end of file diff --git a/css/TUD_stylesheet.css b/css/TUD_stylesheet.css index 6476d53..cda58db 100644 --- a/css/TUD_stylesheet.css +++ b/css/TUD_stylesheet.css @@ -5,6 +5,23 @@ /* Code toegevoegd 17/05. Leuke kleurtjes zodat het makkelijk te zien is wat waar komt */ +/* Remove style from date node, to allow us to add the update date underneath (remove mb-4) */ +/* Use order to force date and updated date to be in the correct visual order */ + +#skip-to-frontmatter { + margin-bottom: 0; + order: -2; /* Keep the original date first */ +} + +.article.content.article-grid { + display: grid; +} + +.updated-date-container { + order: -1; /* Move it visually below the date */ +} + + /*Title color in dark mode*/ .dark aside.admonition-experiment .dark\:text-white { color: rgb(23 25 25); diff --git a/plugins.yml b/plugins.yml index f2ede53..8b29990 100644 --- a/plugins.yml +++ b/plugins.yml @@ -1,7 +1,11 @@ version: 1 project: + id: 7204695b-7485-4989-863c-16cf6e4db155 plugins: - https://github.com/jupyter-book/myst-plugins/releases/download/Admonitions/experiment-admonition.mjs - https://github.com/jupyter-book/myst-plugins/releases/download/exercise-and-solution-pdf/exercise-admonition-pdf.mjs - https://github.com/jupyter-book/myst-plugins/releases/download/iframe-to-qr-pdf/iframe-to-qr-pdf.mjs + - https://github.com/jupyter-book/myst-plugins/releases/download/updated-date-frontmatter/update-date-frontmatter.mjs + - autoupdater.mjs + \ No newline at end of file diff --git a/toc.yml b/toc.yml index e96d02e..162c84f 100644 --- a/toc.yml +++ b/toc.yml @@ -1,10 +1,11 @@ version: 1 project: + id: 7204695b-7485-4989-863c-16cf6e4db155 toc: - file: index.md - file: intro.md - - title: GitHub + # Github # - file: Content/Github/uitleg.md children: - file: Content/Github/1_intro.md @@ -31,7 +32,7 @@ project: - title: VSC url: https://teachbooks.io/learn-programming/workflows/git/vscode - + # simulaties # - title: Simulaties file: Content/PySim/uitleg.md children: @@ -41,22 +42,29 @@ project: - file: Content/PySim/4_brownianmotion.ipynb - file: Content/PySim/5_druktemp.ipynb - file: Content/PySim/6_boltzmann.ipynb - # - file: Content/PySim/7_arbeid.ipynb + - file: Content/PySim/7_arbeid.ipynb # - file: Content/PySim/8_thermostaat.ipynb # - file: Content/PySim/9_entropie.ipynb + # labs # - title: Labs file: Content/Labs/uitleg.md children: - - title: Soortelijke warmte - children: - - file: Content/Labs/cwater.ipynb - - file: Content/Labs/Verdamping.ipynb - - file: Content/Labs/Warmtecapaciteit.ipynb - - file: Content/Labs/koelbuis.ipynb + - title: Soortelijke warmte + children: + - file: Content/Labs/cwater.ipynb + - file: Content/Labs/Warmtecapaciteit.ipynb + - file: Content/Labs/koelbuis.ipynb - # - title: Druk - # children: - # - file: Content/Labs/druksensorijken.ipynb - # - file: Content/Labs/pV_diagram.ipynb \ No newline at end of file + - title: Druk + children: + - file: Content/Labs/druksensorijken.ipynb + - file: Content/Labs/ballonproef.ipynb + - file: Content/Labs/pV_diagram.ipynb + + - title: Faseovergangen + children: + - file: Content/Labs/Verdamping.ipynb + - file: Content/Labs/cappucino.ipynb + - file: Content/Labs/waxine.ipynb \ No newline at end of file