Air exchange¶
A space's molar flux to the ambient atmosphere through the area it exchanges across, a linear exchange driven by the pressure's excess over the ambient, n = C (p - p_a) A: out where the space is over the ambient, in where it is under, nothing at equilibrium.
The ambient pressure and the face's conductance are the game's (lusine.laws.standard_atmosphere gives one). Source: Bird, Stewart & Lightfoot, Transport Phenomena, 2nd ed., ch. 22. Checked against a reference in numpy, which has its own tests.
- Source
- Bird, Stewart & Lightfoot, Transport Phenomena, 2nd ed., ch. 22 (interphase transport, a mass transfer coefficient times a driving force).
- Proof
- invariant, limit.
air_exchange.trait
namespace lusine.laws;
float<mol/s> air_exchange(float<Pa> pressure, float<m2> area, float<Pa> ambient_pressure,
float<mol/(s Pa m2)> conductance)
{
return conductance * (pressure - ambient_pressure) * area;
}
A space's molar flux to the ambient atmosphere through the area it exchanges across, n = C (p - p_a) A.
import os, sys
root = os.getcwd()
while not os.path.isfile(os.path.join(root, 'docsite.json')):
root = os.path.dirname(root)
sys.path.insert(0, os.path.join(root, 'tools'))
import numpy as np
import matplotlib.pyplot as plt
from lusine import trait_run
EPS = float(np.finfo(np.float32).eps)
The kernel¶
The law as the library states it, and as this page runs it.
print(trait_run.source('laws/air_exchange.trait'))
// lusine.laws.air_exchange: a space's molar flux to the ambient atmosphere through the area it exchanges across, a
// linear exchange driven by the pressure's excess over the ambient, n = C (p - p_a) A: out where the space is over
// the ambient, in where it is under, nothing at equilibrium. The ambient pressure and the face's conductance are the
// game's (lusine.laws.standard_atmosphere gives one).
// Source: Bird, Stewart & Lightfoot, Transport Phenomena, 2nd ed., ch. 22.
// Checked against a reference in numpy, which has its own tests.
namespace lusine.laws;
float<mol/s> air_exchange(float<Pa> pressure, float<m2> area, float<Pa> ambient_pressure,
float<mol/(s Pa m2)> conductance)
{
return conductance * (pressure - ambient_pressure) * area;
}
The program¶
Each section runs the kernel over a lattice of items, one a case: the host sets every parameter, and main() binds the kernel over the items.
print(trait_run.law_program('laws/air_exchange.trait', 'air_exchange'))
namespace probe;
lusine.version version = { { number = 1 } };
struct item {
host float<Pa> pressure;
host float<m2> area;
host float<Pa> ambient_pressure;
host float<mol/(s Pa m2)> conductance;
float<mol/s> result_0;
};
lattice<item, 1> items(spacing = 1 m, boundary = wall);
void main() { items.result_0 = lusine.laws.air_exchange(items.pressure, items.area, items.ambient_pressure, items.conductance); }
Invariant: the flux follows the pressure's excess¶
Over the ambient, gas leaves; under it, gas enters; the same excess either way gives the same flux the other way, and none at equilibrium. The ambient here is the International Standard Atmosphere's at 1500 m, 84,556 Pa, as a game taking lusine.laws.standard_atmosphere for it would have.
pa, c, a = 84556.0, 2e-7, 0.01
excess = np.arange(-2000.0, 2001.0, 100.0)
n = trait_run.law('laws/air_exchange.trait', 'air_exchange', pressure=pa + excess, area=a, ambient_pressure=pa,
conductance=c)
assert np.all((n > 0) == (excess > 0)) and np.all((n < 0) == (excess < 0))
assert np.array_equal(n, -n[::-1]) and n[len(n) // 2] == 0.0
fig, ax = plt.subplots(figsize=(7, 3))
ax.plot(excess, n, 'o-')
ax.axhline(0, lw=0.5)
ax.set_xlabel('p - p_a (Pa)')
ax.set_ylabel('flux out (mol/s)')
plt.show()
Limit: no area, no driving force, no flux¶
A face of no area passes nothing, and the flux is linear in both the excess and the area: doubling either doubles it, bit for bit.
pa = 101325.0
n = trait_run.law('laws/air_exchange.trait', 'air_exchange', pressure=[pa + 500, pa + 500, pa + 1000, pa + 500],
area=[0.0, 0.01, 0.01, 0.02], ambient_pressure=pa, conductance=2e-7)
assert n[0] == 0.0 and n[2] == 2 * n[1] and n[3] == 2 * n[1], n
print(f'{n[1]:.3e} mol/s, doubled by either')
1.000e-06 mol/s, doubled by either