How does temperature relate to kinetic energy in gases?#
Author: Hanan Gharayba ( ) Editor: Audun Skau Hansen ✉️
The Hylleraas Centre for Quantum Molecular Sciences and The Centre for Computing in Science Education, 2022
“True Knowledge Is Knowing What You Don’t Know”
If you find yourself wondering around some concepts in this Notebook, you might want to check
The concept of temperature establishes a strong connection between the microscopic and macroscopic world, and between theory and experiment. Furthermore, the proportionality between \(\color{red}{\text{temperature}}\) and \(\color{red}{\text{kinetic energy}}\), \( K_E \propto T\), may for many students of thermodynamics offer an efficient path into a deeper understanding.
This notebook is for those who enjoy having both maths and physics as a defence for their common knowledge. Our main aim is to establish the proportionality relation.
Installation#
We will mainly use bubblebox.mdbox
-module for simulations in this experience. Install it using
!pip install bubblebox
then load the relevant modules using
import bubblebox as bb
from bubblebox.mdbox import no_forces
import matplotlib.pyplot as plt
import numpy as np
%matplotlib notebook
Preliminaries#
Thermodynamical energy#
The concept of energy (\(U\)) lies deeply rooted in our understanding of nature. Energy is the ability to do work (\(W\)) or heat (\(Q\)), as expressed in the first law of thermodynamics:
Understanding temperature in the lab is rather straightforward: it is the number we read off the thermometer. At the microscopic level, however, it is a little more diffuse. The key is to realize that molecules and atoms have mechanisms of storing and transmitting energy.
Classical mechanics at the microscopic level#
Although the microscopic world is ultimately most accurately described by quantum mechanics, there are many situations where a classical depiction is more than sufficient. By classical, we here mean to consider the microscopic world as consisting of small bodies (\(i\)) of mass \(m_i\), with well defined positions (\(\mathbf{x}_i\)) and velocities (\(\mathbf{v}_i\)).
In this picture, the classical laws of physics comes into play. Most notably, the motion of these objects will be governed by Newton’s second law:
where the forces \(\mathbf{F}\) are determined by the interaction between the bodies. The bodies themselves can be molecules, atoms, or even smaller quantities (although at the electronic level the model fails to reproduce reality).
In the following, we will use these basic building blocks to gain insight into the collective and intensive property of temperature.
Energy at the microscopic level#
In the classical picture, molecules and atoms can store and transmit energy in many ways. A single atom, interacting with its surroundings, may experience changes in its kinetic energy:
or changes in its potential energy in relation to it’s surroundings (for instance the position of the other atoms):
Step 1: Set up the calculation of the change in momentum#
Consider a box with a volume V, containing one gas particle with a mass \(m\) and a velocity \(v_x>0\), hence moving with a velocity v in the positive x axes direction.
b1 = bb.mdbox(n_bubbles = 1, size = (5,5, 5))
b1.set_vel(np.array([[10],[0], [0]])) #any array of size (3,1)
b1.view()
b1.run(1000) #evolve the system 1000 steps
The momentum (\(\mathbf{p}\)) of a body is by definition its mass multiplied by its velocity:
The moving particle as shown in the box will collide with the wall and reflect. Assuming that the collision with the wall is perfectly elastic, meaning that it results in zero loss in net kinetic energy, the absolute change in momentum can be computed as:
\(\vert \Delta \mathbf{p} \vert ~ = | momentum_{after~collision} – momentum_{before~ collision}|\)
\(~~~~~~~~~~~~~~~~~~~~~= | -m v_x – m v_x| \)
\(~~~~~~~~~~~~~~~~~~~~~= |-2mv_x| \)
\(~~~~~~~~~~~~~~~~~~~~~= 2mv_x\)
Note that because the collisions are elastic, the particle will end up having the same velocity, just in the opposite direction.
In a more complicated system, many molecules will collide with the wall in the interval \(\Delta t\), and the Total change in momentum is the product of the change in momentum of each molecule, multiplied by the total number of molecules that reach the wall during the interval.
Discussion
Q1
Q2
Step 2: Enlarge the system and calculate the change in momentum#
Let’s add more particles and enlarge the system to 3 dimentions. Setting the box to have a side area = A, and a total volum = V
Keep in mind: In order to calculate the total change in momentum, we need to calculate the total amount of particles that will collide with the wall in the given time interval
b2 = bb.mdbox(n_bubbles = 200, size = (15,5,5),vel = 0)
b2.pos[0]*=5/20
selection_1 = np.random.choice(b2.n_bubbles, 200)
selection_2 = np.random.choice(b2.n_bubbles, 200)
b2.vel_[0, selection_1] -= 5.0
b2.vel_[0, selection_2] += 10.0
b2.view()
b2.run(10000)
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Input In [23], in <cell line: 1>()
----> 1 b2.run(10000)
File ~/Library/Python/3.8/lib/python/site-packages/bubblebox/mdbox.py:1050, in mdbox.run(self, nsteps, n_iterations_per_step)
1048 for j in range(n_iterations_per_step):
1049 self.advance()
-> 1050 self.update_view()
File ~/Library/Python/3.8/lib/python/site-packages/bubblebox/mdbox.py:1057, in mdbox.update_view(self)
1056 def update_view(self):
-> 1057 self.mview.pos = self.pos.T.tolist()
File ~/Library/Python/3.8/lib/python/site-packages/traitlets/traitlets.py:715, in TraitType.__set__(self, obj, value)
713 raise TraitError('The "%s" trait is read-only.' % self.name)
714 else:
--> 715 self.set(obj, value)
File ~/Library/Python/3.8/lib/python/site-packages/traitlets/traitlets.py:2845, in List.set(self, obj, value)
2843 return super().set(obj, [value])
2844 else:
-> 2845 return super().set(obj, value)
File ~/Library/Python/3.8/lib/python/site-packages/traitlets/traitlets.py:704, in TraitType.set(self, obj, value)
700 silent = False
701 if silent is not True:
702 # we explicitly compare silent to True just in case the equality
703 # comparison above returns something other than True/False
--> 704 obj._notify_trait(self.name, old_value, new_value)
File ~/Library/Python/3.8/lib/python/site-packages/traitlets/traitlets.py:1374, in HasTraits._notify_trait(self, name, old_value, new_value)
1373 def _notify_trait(self, name, old_value, new_value):
-> 1374 self.notify_change(
1375 Bunch(
1376 name=name,
1377 old=old_value,
1378 new=new_value,
1379 owner=self,
1380 type="change",
1381 )
1382 )
File /Library/Python/3.8/site-packages/ipywidgets/widgets/widget.py:685, in Widget.notify_change(self, change)
681 if self.comm is not None and self.comm.kernel is not None:
682 # Make sure this isn't information that the front-end just sent us.
683 if name in self.keys and self._should_send_property(name, getattr(self, name)):
684 # Send new state to front-end
--> 685 self.send_state(key=name)
686 super(Widget, self).notify_change(change)
File /Library/Python/3.8/site-packages/ipywidgets/widgets/widget.py:554, in Widget.send_state(self, key)
552 state, buffer_paths, buffers = _remove_buffers(state)
553 msg = {'method': 'update', 'state': state, 'buffer_paths': buffer_paths}
--> 554 self._send(msg, buffers=buffers)
File /Library/Python/3.8/site-packages/ipywidgets/widgets/widget.py:817, in Widget._send(self, msg, buffers)
815 """Sends a message to the model in the front-end."""
816 if self.comm is not None and self.comm.kernel is not None:
--> 817 self.comm.send(data=msg, buffers=buffers)
File /Library/Python/3.8/site-packages/ipykernel/comm/comm.py:137, in Comm.send(self, data, metadata, buffers)
135 def send(self, data=None, metadata=None, buffers=None):
136 """Send a message to the frontend-side version of this comm"""
--> 137 self._publish_msg(
138 "comm_msg",
139 data=data,
140 metadata=metadata,
141 buffers=buffers,
142 )
File /Library/Python/3.8/site-packages/ipykernel/comm/comm.py:71, in Comm._publish_msg(self, msg_type, data, metadata, buffers, **keys)
69 metadata = {} if metadata is None else metadata
70 content = json_clean(dict(data=data, comm_id=self.comm_id, **keys))
---> 71 self.kernel.session.send(
72 self.kernel.iopub_socket,
73 msg_type,
74 content,
75 metadata=json_clean(metadata),
76 parent=self.kernel.get_parent("shell"),
77 ident=self.topic,
78 buffers=buffers,
79 )
File ~/Library/Python/3.8/lib/python/site-packages/jupyter_client/session.py:844, in Session.send(self, stream, msg_or_type, content, parent, ident, buffers, track, header, metadata)
842 if self.adapt_version:
843 msg = adapt(msg, self.adapt_version)
--> 844 to_send = self.serialize(msg, ident)
845 to_send.extend(buffers)
846 longest = max([len(s) for s in to_send])
File ~/Library/Python/3.8/lib/python/site-packages/jupyter_client/session.py:718, in Session.serialize(self, msg, ident)
716 content = self.none
717 elif isinstance(content, dict):
--> 718 content = self.pack(content)
719 elif isinstance(content, bytes):
720 # content is already packed, as in a relayed message
721 pass
File ~/Library/Python/3.8/lib/python/site-packages/jupyter_client/session.py:99, in json_packer(obj)
97 def json_packer(obj):
98 try:
---> 99 return json.dumps(
100 obj,
101 default=json_default,
102 ensure_ascii=False,
103 allow_nan=False,
104 ).encode("utf8", errors="surrogateescape")
105 except (TypeError, ValueError) as e:
106 # Fallback to trying to clean the json before serializing
107 packed = json.dumps(
108 json_clean(obj),
109 default=json_default,
110 ensure_ascii=False,
111 allow_nan=False,
112 ).encode("utf8", errors="surrogateescape")
File /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/json/__init__.py:234, in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
232 if cls is None:
233 cls = JSONEncoder
--> 234 return cls(
235 skipkeys=skipkeys, ensure_ascii=ensure_ascii,
236 check_circular=check_circular, allow_nan=allow_nan, indent=indent,
237 separators=separators, default=default, sort_keys=sort_keys,
238 **kw).encode(obj)
File /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/json/encoder.py:199, in JSONEncoder.encode(self, o)
195 return encode_basestring(o)
196 # This doesn't pass the iterator directly to ''.join() because the
197 # exceptions aren't as detailed. The list call should be roughly
198 # equivalent to the PySequence_Fast that ''.join() would do.
--> 199 chunks = self.iterencode(o, _one_shot=True)
200 if not isinstance(chunks, (list, tuple)):
201 chunks = list(chunks)
KeyboardInterrupt:
Knowing that the velocity of a particle is defined as the distance travelled at a time interval \(v_x = \frac{\Delta x}{\Delta t}\), the distance a molecule travels by is \(\Delta x = {v_x} \Delta t\)
This means that all the particles with the given velocity \({v_x}\) and a position within a distance \(\Delta x = {v_x} \Delta t\) from the boxes wall will collide with it (given that the particles are moving towards that wall)
As the box is sat to have a wall area = A . All the particles in the volume \(|{v_x}\cdot \Delta t|\cdot A\) will collide with the wall (given that they are moving toward it)
Assuming the box contain n mol of molecules. The density of the molecules in the whole box is the number of molecules divided by the boxes volume
\(particle~density = \frac{n\cdot N_A}{V}\)
Where \(N_A\) = Avogadro’s constant
This formula for particle density can be used to calculate the amount of particles at any volume fraction of the box.
For the volume \(|{v_x}\cdot \Delta t|\cdot A\):
The number of particles = \( \frac{n\cdot N_A}{V} \cdot |{v_x}\cdot \Delta t|\cdot A\)
At any instant half of the particles will move towards the wall and the other half away from it, meaning that the average number of collision with the wall during the time interval \(\Delta t\) is halv the number of the particles,
Mean number of collisions = \(\frac{1}{2} \cdot \frac{n\cdot N_A}{V} \cdot |{v_x}\cdot \Delta t|\cdot A \)
Now: that we know the amount of patricles colliding & the change in momentum for one particle; Let us calculate the momentum change for the whole amount of particles:
\(\Delta momentum = mean~number~of~collisions \cdot 2m v_x\)
\(\Delta momentum =\frac{1}{2} \cdot \frac{n\cdot N_A}{V} \cdot |{v_x}\cdot \Delta t|\cdot A \cdot 2m v_x \)
Given that \(M = m \cdot N_A\) we can rewrite the expression over to become:
\(\Delta momentum = \frac{n~M~A~v_x^2\Delta t}{V}\)
Step 3: Calculate the pressure P from the change in momentum#
Pressure is equal to the force divided by the area. According to Newtons second law of motion, this rate of change in momentum is equal to the force.
Therefore:
The rate of change in moemntum \(\frac{\Delta momentum}{\Delta t} = \frac{n~M~A~v_x^2}{V}\)
Pressure = \(\frac{ \frac{n~M~A~v_x^2}{V}}{A}\)
Pressure = \(\frac{n~M~v_x^2}{V}\)
\(PV = {n~M~v_x^2}\)
Step 4: Add more dimensions#
The average values of \(v_x^2 , v_y^2, v_z^2 \) are all the same
Given that \( v^2 = v_x^2 + v_y^2 + v_z^2 \)
It follows that \(v_x^2 = \frac{1}{3}\cdot v^2\)
Sitting \(v_x^2 = \frac{1}{3}\cdot v^2\) in the pressure expression:
\(PV = \frac{1}{3} n~M~v^2\)
\(PV = \frac{1}{3} n~M~v^2\)
Step 5: Insert temperature T in our expression#
Using the ideal gass equation \(PV = nRT\)
\(nRT = \frac{1}{3} n~M~v^2 \)
\(RT = \frac{1}{3} M~v^2\)
Setting back M to its value \(M = m \cdot N_A\)
\(RT = \frac{1}{3} m \cdot N_A~v^2\)
From here, we can rearrange the equation to get the expression of the kinetic energy in the right side, by multiplying both sides with \(\frac{3}{2 N_A}\)
\(\frac{3}{2} RT = \frac{1}{2} m \cdot N_A v^2\)
\(\frac{3}{2} \frac{RT}{N_A} = \frac{1}{2} m v^2\)
Given that Boltzmann’s constant \(k_B = \frac{R}{N_A}\)
\(\frac{3}{2} k_B T = \frac{1}{2} m v^2\)
resulting to:
\(\color{red}{\text{\)E_k = \frac{3}{2} k_B T\(}}\)