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:

\[ \Delta U = Q - W \]

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:

\[ \mathbf{F} = m \big{(}\frac{d}{dt} \big{)}^2 \mathbf{x}(t) \]

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:

\[ E_{k,i} = \frac{1}{2} m_i \mathbf{v}_i^2 , \]

or changes in its potential energy in relation to it’s surroundings (for instance the position of the other atoms):

\[ U_i = U(\{ \mathbf{x}_{j \neq i} \}), \]

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:

\[\mathbf{p} = m \cdot \mathbf{v}\]

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\(}}\)