velitherm
Version:
Basic Thermodynamics Equations for Soaring Flight (from velivole.fr/meteo.guru)
279 lines (243 loc) • 8.62 kB
text/typescript
import * as velitherm from '../src';
import * as chai from 'chai';
import * as mocha from 'mocha';
const it = mocha.it;
const assert = chai.assert;
const standardAtmosphere = [
{ alt: 0, pres: 1013.25 },
{ alt: 1000, pres: 898.746 },
{ alt: 2000, pres: 794.952 },
{ alt: 3000, pres: 701.085 },
{ alt: 4000, pres: 616.402 }
];
const realAtmosphere = [
{ p0: 1000, p: 995, t: 284.75 + velitherm.K, z: 41.82 },
{ p0: 995, p: 990, t: 284.25 + velitherm.K, z: 41.96 },
{ p0: 1000, p: 900, t: 280 + velitherm.K, z: 864.38 }
];
const waterVaporSaturationPressure = [
{ t: 0, Preal: 6.113, Ptetens: 6.108 },
{ t: 20, Preal: 23.388, Ptetens: 23.382 },
{ t: 35, Preal: 56.267, Ptetens: 56.225 },
{ t: 35, Preal: 56.267, Ptetens: 56.225 }
];
const humidity = [
{ t: 23, p: 1013.25, h: 35, q: 6.06, w: 6.09, td: 6.7 },
{ t: 3, p: 900, h: 60, q: 3.15, w: 3.16, td: -4.0 },
{ t: -5, p: 800, h: 90, q: 2.95, w: 2.96, td: -6.4 }
];
const dryAir = [
{ t: 35, rho: 1.1455 },
{ t: 30, rho: 1.1644 },
{ t: 25, rho: 1.1839 },
{ t: 5, rho: 1.269 },
{ t: -15, rho: 1.3673 }
];
const humidAir = [
{ t: 0, p: 1013.25, h: 80, rho: 1.29 },
{ t: 10, p: 1013.25, h: 60, rho: 1.243 },
{ t: 20, p: 1013.25, h: 80, rho: 1.198 },
{ t: -10, p: 1013.25, h: 80, rho: 1.34 },
{ t: -10, p: 800, h: 80, rho: 1.058 }
];
const LCL = [
{ t: 25, td: 4, lcl: 2660 },
{ t: -6, td: -10, lcl: 507 }
];
const MALR = [
{ p: 1000, t: -40, malr: 9.5e-3 },
{ p: 1000, t: -20, malr: 8.6e-3 },
{ p: 1000, t: 20, malr: 4.3e-3 },
{ p: 1000, t: 40, malr: 3.0e-3 },
{ p: 600, t: -20, malr: 7.9e-3 }
];
describe('velitherm', () => {
describe('ICAO Standard Atmosphere (Barometric equation)', () => {
describe('altitudeFromStandardPressure', () => {
for (const lvl of standardAtmosphere) {
it(`Altitude of ${lvl.pres}hPa should be ${lvl.alt}m`, () => {
assert.closeTo(velitherm.altitudeFromStandardPressure(
lvl.pres), lvl.alt, 1);
});
}
});
describe('pressureFromStandardAltitude', () => {
for (const lvl of standardAtmosphere) {
it(`Altitude at ${lvl.pres}hPa should be ${lvl.alt}m`, () => {
assert.closeTo(velitherm.pressureFromStandardAltitude(
lvl.alt), lvl.pres, 1);
});
}
});
});
describe('Hypsometric equation', () => {
describe('altitudeFromPressure', () => {
for (const lvl of realAtmosphere) {
it(`(QFF=${lvl.p0}hPa, T=${Math.round(lvl.t)}°C)` +
` Altitude of ${lvl.p}hPa should be ${lvl.z}m`, () => {
assert.closeTo(velitherm.altitudeFromPressure(
lvl.p, lvl.p0, lvl.t), lvl.z, 0.01);
});
}
it(`(QFF=${velitherm.P0}hPa, ` +
`T=${velitherm.T0}°C) Altitude of ${velitherm.P0}hPa should be 0m`,
() => {
assert.closeTo(velitherm.altitudeFromPressure(velitherm.P0), 0, 5);
});
});
describe('pressureFromAltitude', () => {
for (const lvl of realAtmosphere) {
it(`(QFF=${lvl.p0}hPa, ` +
`T=${lvl.t}°C) Pressure at ${lvl.z}m should be ${lvl.p}hPa`,
() => {
assert.closeTo(
velitherm.pressureFromAltitude(
lvl.z, lvl.p0, lvl.t), lvl.p, 0.1);
});
}
it(`(QFF=${velitherm.P0}hPa, ` +
`T=${velitherm.T0}°C) Altitude at ${velitherm.P0}hPa should be 0m`,
() => {
assert.closeTo(velitherm.pressureFromAltitude(0), velitherm.P0, 1);
});
});
});
describe('waterVaporSaturationPressure', () => {
for (const lvl of waterVaporSaturationPressure) {
it(`Water Vapor (Saturation) Pressure at ${lvl.t}°C ` +
`should be ${lvl.Preal}hPa`, () => {
assert.closeTo(velitherm.waterVaporSaturationPressure(
lvl.t), lvl.Ptetens, 0.1);
assert.closeTo(velitherm.waterVaporSaturationPressure(
lvl.t), lvl.Preal, 1);
});
}
it(`Water Vapor (Saturation) Pressure at ${velitherm.T0}°C ` +
`should be ${17.0529}hPa`, () => {
assert.closeTo(velitherm.waterVaporSaturationPressure(), 17.0529, 0.1);
});
});
describe('Humidity', () => {
describe('specificHumidity', () => {
for (const lvl of humidity) {
it(`RH=${lvl.h}%, P=${lvl.p}hPa, T=${lvl.t}°C ` +
`=> q=${lvl.q}g/kg`, () => {
assert.closeTo(velitherm.specificHumidity(
lvl.h, lvl.p, lvl.t), lvl.q, 1e-1);
});
}
});
describe('mixingRatio', () => {
for (const lvl of humidity) {
it(`RH=${lvl.h}%, P=${lvl.p}hPa, T=${lvl.t}°C => ` +
`w=${lvl.w}g/kg`, () => {
assert.closeTo(velitherm.mixingRatio(velitherm.specificHumidity(
lvl.h, lvl.p, lvl.t)), lvl.w, 1e-1);
});
}
for (const lvl of humidity) {
it(`RH=${lvl.h}%, P=${lvl.p}hPa, T=${lvl.t}°C => ` +
`q=${lvl.q}g/kg`, () => {
assert.closeTo(velitherm.specificHumidityFromMixingRatio(
lvl.w), lvl.q, 1e-1);
});
}
});
describe('relativeHumidity', () => {
for (const lvl of humidity) {
it(`q=${lvl.q}g/kg, P=${lvl.p}hPa, T=${lvl.t}°C => ` +
`RH=${lvl.h}%`, () => {
assert.closeTo(velitherm.relativeHumidity(
lvl.q, lvl.p, lvl.t), lvl.h, 2);
});
}
});
describe('dewPoint', () => {
for (const lvl of humidity) {
it(`RH=${lvl.h}%, T=${lvl.t}°C => Td=${lvl.td}°C`, () => {
assert.closeTo(velitherm.dewPoint(lvl.h, lvl.t), lvl.td, 0.1);
});
}
});
describe('relativeHumidityFromDewPoint', () => {
for (const lvl of humidity) {
it(`Td=${lvl.td}°C, T=${lvl.t}°C => RH=${lvl.h}%`, () => {
assert.closeTo(velitherm.relativeHumidityFromDewPoint(
lvl.td, lvl.t), lvl.h, 1);
});
}
});
});
describe('airDensity', () => {
for (const lvl of dryAir) {
it(`Dry air, sea level, ${lvl.t}°C ` +
`=> rho = ${lvl.rho}kg/m3`, () => {
assert.closeTo(velitherm.airDensity(
0, velitherm.P0, lvl.t), lvl.rho, 1e-3);
});
}
for (const lvl of humidAir) {
it(`Humid air ${lvl.h}%, ${lvl.p}hPa, ${lvl.t}°C => ` +
`rho = ${lvl.rho}kg/m3`, () => {
assert.closeTo(velitherm.airDensity(
lvl.h, lvl.p, lvl.t), lvl.rho, 1e-2);
});
}
});
describe('LCL', () => {
for (const lvl of LCL) {
it(`T ${lvl.t}°C - Td ${lvl.td}°C => LCL = ${lvl.lcl}m`, () => {
assert.closeTo(velitherm.LCL(lvl.t, lvl.td), lvl.lcl, 1);
});
}
});
describe('gammaMoist', () => {
for (const lvl of MALR) {
it(`T ${lvl.t}°C, P ${lvl.p}hPa => gamma = ${lvl.malr}°C/m`, () => {
assert.closeTo(velitherm.gammaMoist(lvl.t, lvl.p), lvl.malr, 2e-4);
});
}
});
describe('adiabaticCooling', () => {
const HEIGHT = 100;
it('should come very close to the fixed constant', () => {
const gamma = (velitherm.T0 -
velitherm.adiabaticCooling(velitherm.T0,
velitherm.pressureFromStandardAltitude(HEIGHT),
velitherm.pressureFromStandardAltitude(0))
) / HEIGHT;
assert.closeTo(gamma, velitherm.gamma, 1e-5);
});
it('should support being called without ground pressure', () => {
const gamma = (velitherm.T0 -
velitherm.adiabaticCooling(velitherm.T0,
velitherm.pressureFromStandardAltitude(HEIGHT))
) / HEIGHT;
assert.closeTo(gamma, velitherm.gamma, 1e-5);
});
});
describe('adiabaticExpansion', () => {
it('satisfy the Ideal Gas Law', () => {
const expansion = velitherm.adiabaticExpansion(1,
velitherm.pressureFromStandardAltitude(100),
velitherm.pressureFromStandardAltitude(0));
// combined gas law, for a given gas, k should always be constant
const k0 = velitherm.pressureFromStandardAltitude(0) /
(velitherm.T0 + velitherm.K);
const k1 = expansion *
velitherm.pressureFromStandardAltitude(100) /
(velitherm.T0 + velitherm.K - velitherm.gamma * 100);
assert.closeTo(k0, k1, 0.1);
});
});
describe('Flight Levels', () => {
it('convert Flight Level to pressure', () => {
const pressure = velitherm.pressureFromFL(100);
assert.closeTo(pressure, 697, 1);
});
it('convert pressure to Flight Level', () => {
const pressure = velitherm.FLFromPressure(657);
assert.closeTo(pressure, 115, 1);
});
});
});