diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml new file mode 100644 index 0000000..2f69821 --- /dev/null +++ b/.github/workflows/basic.yml @@ -0,0 +1,38 @@ +name: Basic Validation + +on: + push: + pull_request: + workflow_dispatch: + schedule: + - cron: '26 3 20 * *' + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: "-Dwarnings" + +jobs: + build-and-test: + + name: Build and Test + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Prepare environment + run: | + rustup update stable + cargo clean + - name: Build with cargo + run: | + cargo build --features "double_precision" + cargo build --features "debug" + cargo build --release + cargo clean + - name: Test with cargo + run: | + cargo test + cargo test --features "double_precision" + cargo test --features "debug" + cargo clean diff --git a/.github/workflows/clippy-check.yml b/.github/workflows/clippy-check.yml new file mode 100644 index 0000000..78dddbf --- /dev/null +++ b/.github/workflows/clippy-check.yml @@ -0,0 +1,19 @@ +on: pull_request + +name: Cargo Clippy + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: "-Dwarnings" + +jobs: + clippy_check: + name: Check + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Clippy + run: | + rustup component add clippy + cargo clippy -- -D warnings diff --git a/.github/workflows/doc-coverage.yml b/.github/workflows/doc-coverage.yml new file mode 100644 index 0000000..6ed7ab3 --- /dev/null +++ b/.github/workflows/doc-coverage.yml @@ -0,0 +1,45 @@ +name: Cargo Doc + +on: + pull_request + +jobs: + pr-comment: + name: Check Coverage + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + - name: Fetch base + run: git fetch origin ${{ github.event.pull_request.base.sha }} + - name: Checkout base + run: git checkout ${{ github.event.pull_request.base.sha }} + - name: Calculate base doc coverage + uses: bewee/rustdoc-coverage-action@v1 + - name: Fetch head + run: git fetch origin ${{ github.event.pull_request.head.sha }} + - name: Checkout head + run: git checkout ${{ github.event.pull_request.head.sha }} + - name: Calculate doc coverage + id: coverage + uses: bewee/rustdoc-coverage-action@v1 + - name: Find Comment + uses: peter-evans/find-comment@v1 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "## Documentation Coverage:" + - name: Create or update comment + uses: peter-evans/create-or-update-comment@v1 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Documentation Coverage: + ${{ steps.coverage.outputs.table }} + edit-mode: replace \ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 989d3fc..0000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: cargo - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_dispatch: - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Prepare environment - run: | - sudo apt-get update - sudo apt-get install clang - sudo apt-get install libclang1 - sudo apt-get install gnuplot - rustup update stable - cargo install cargo-criterion - cargo clean - - name: Build with cargo - run: | - cargo build --release - cargo build --release --features "double_precision" - cargo clean - - name: Check with clippy - run: | - cargo clippy -- -W clippy::pedantic - cargo clean - - name: Test with cargo - run: | - cargo test - cargo test --features "double_precision" - cargo clean - - name: Benchmark with criterion - run: | - cargo criterion - cargo criterion --features "double_precision" diff --git a/Cargo.toml b/Cargo.toml index 80b73e4..4e8bdaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "floccus" -version = "0.3.7" +version = "0.4.0" authors = ["Jakub Lewandowski "] edition = "2021" description = "Formulae for air thermodynamic calculations" diff --git a/README.md b/README.md index 7282a4e..958f710 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![License](https://img.shields.io/github/license/ScaleWeather/floccus)](https://choosealicense.com/licenses/apache-2.0/) [![Crates.io](https://img.shields.io/crates/v/floccus)](https://crates.io/crates/floccus) [![dependency status](https://deps.rs/repo/github/ScaleWeather/floccus/status.svg)](https://deps.rs/repo/github/ScaleWeather/floccus) -[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ScaleWeather/floccus/rust.yml?branch=main&label=cargo%20build)](https://github.com/ScaleWeather/floccus/actions) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ScaleWeather/floccus/basic.yml?branch=main&label=cargo%20build)](https://github.com/ScaleWeather/floccus/actions) Rust crate providing formulae for air thermodynamic calculations. @@ -58,6 +58,9 @@ To prevent any unexpected behavior, all functions check whether provided inputs Exact limits are specified in the documentation of each function. If the input is out of range the function will return an `InputError::OutOfRange` with erroneous input specified. +Each function also has `_unchecked` and `_validate` versions. The `_validate` version only checks the inputs with bounds defined for its "parent" function. +The `_unchecked` version performs only the calculation without any input checking. All "parent" functions simply call `_validate` and then `_unchecked`. + ## Debugging If additional information is needed about which function returns the error and why, `debug` feature can be enabled. @@ -66,6 +69,4 @@ information about the error. This feature potentially is not zero-cost so it is ## Benchmarks -Functions provided in this crate are intended for use in, i. a., numerical models. To provide the user information about performance overhead of each function all functions are benchmarked using [criterion.rs](https://bheisler.github.io/criterion.rs/book/index.html). Github Actions automatically runs all benchmarks. - -To check the latest benchmark results the newest workflow on [Github Actions page of floccus](https://github.com/ScaleWeather/floccus/actions). +Functions provided in this crate are intended for use in, i. a., numerical models. To provide the user information about performance overhead of each function all functions are can be benchmarked using [criterion.rs](https://bheisler.github.io/criterion.rs/book/index.html). diff --git a/benches/results/0_3_7.txt b/benches/results/0_3_7.txt new file mode 100644 index 0000000..525834c --- /dev/null +++ b/benches/results/0_3_7.txt @@ -0,0 +1,92 @@ +equivalent_potential_temperature::bryan1 + time: [37.789 ns 37.794 ns 37.799 ns] + +mixing_ratio::general1 + time: [2.2452 ns 2.2497 ns 2.2572 ns] + +mixing_ratio::performance1 + time: [6.9944 ns 6.9992 ns 7.0044 ns] + +mixing_ratio::accuracy1 + time: [10.147 ns 10.163 ns 10.178 ns] + +potential_temperature::davies_jones1 + time: [11.339 ns 11.340 ns 11.341 ns] + +relative_humidity::general1 + time: [1.3266 ns 1.3268 ns 1.3273 ns] + +relative_humidity::general2 + time: [1.3271 ns 1.3276 ns 1.3281 ns] + +relative_humidity::general3 + time: [8.7762 ns 8.7767 ns 8.7772 ns] + +relative_humidity::general4 + time: [10.631 ns 10.640 ns 10.649 ns] + +relative_humidity::general5 + time: [22.791 ns 22.795 ns 22.800 ns] + +specific_humidity::general1 + time: [1.4529 ns 1.4541 ns 1.4554 ns] + +vapour_pressure::general1 + time: [1.3643 ns 1.3831 ns 1.4079 ns] + +vapour_pressure::tetens1 + time: [4.0723 ns 4.0742 ns 4.0760 ns] + +vapour_pressure::buck1 + time: [5.8277 ns 5.8288 ns 5.8304 ns] + +vapour_pressure::buck2 + time: [5.8274 ns 5.8276 ns 5.8279 ns] + +vapour_pressure::buck3 + time: [4.7666 ns 4.7670 ns 4.7673 ns] + +vapour_pressure::buck4 + time: [4.7652 ns 4.7657 ns 4.7661 ns] + +vapour_pressure::buck3_simplified + time: [4.0155 ns 4.0195 ns 4.0232 ns] + +vapour_pressure::buck4_simplified + time: [4.0485 ns 4.0518 ns 4.0553 ns] + +vapour_pressure::saturation_specific1 + time: [1.3148 ns 1.3183 ns 1.3218 ns] + +vapour_pressure::saturation_specific2 + time: [1.3265 ns 1.3265 ns 1.3266 ns] + +vapour_pressure::wexler1 + time: [8.5303 ns 8.5305 ns 8.5308 ns] + +vapour_pressure::wexler2 + time: [7.8834 ns 7.8839 ns 7.8846 ns] + +vapour_pressure_deficit::general1 + time: [1.2693 ns 1.2754 ns 1.2816 ns] + +vapour_pressure_deficit::general2 + time: [9.3893 ns 9.3896 ns 9.3900 ns] + +vapour_pressure_deficit::general3 + time: [6.4139 ns 6.4157 ns 6.4180 ns] + +virtual_temperature::general1 + time: [1.6099 ns 1.6103 ns 1.6109 ns] + +virtual_temperature::general2 + time: [1.8563 ns 1.8564 ns 1.8564 ns] + +virtual_temperature::general3 + time: [1.2598 ns 1.2603 ns 1.2608 ns] + +wet_bulb_potential_temperature::davies_jones1 + time: [10.616 ns 10.616 ns 10.617 ns] + +wet_bulb_temperature::stull1 + time: [27.765 ns 27.767 ns 27.770 ns] diff --git a/src/equivalent_potential_temperature.rs b/src/equivalent_potential_temperature.rs index cd737bb..0722248 100644 --- a/src/equivalent_potential_temperature.rs +++ b/src/equivalent_potential_temperature.rs @@ -15,6 +15,8 @@ use floccus_proc::logerr; /// ///Implementation of this formula assumes no liquid or solid water in the air parcel. /// +///First appeared in Paluch, Ilga (1979). J. Atmos. Sci., 36, 2467-2478 +/// ///Provided in Emmanuel, Kerry (1994). Atmospheric Convection. Oxford University Press. /// ///# Errors @@ -23,12 +25,23 @@ use floccus_proc::logerr; ///Valid `temperature` range: 253K - 324K\ ///Valid `pressure` range: 100Pa - 150000Pa\ ///Valid `vapour_pressure` range: 0Pa - 10000Pa -#[cfg_attr(feature = "debug", logerr)] -pub fn general1( +pub fn paluch1( temperature: Float, pressure: Float, vapour_pressure: Float, ) -> Result { + paluch1_validate(temperature, pressure, vapour_pressure)?; + Ok(paluch1_unchecked(temperature, pressure, vapour_pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn paluch1_validate( + temperature: Float, + pressure: Float, + vapour_pressure: Float, +) -> Result<(), InputError> { if !(253.0..=324.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); } @@ -41,20 +54,23 @@ pub fn general1( return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } + Ok(()) +} + +#[allow(missing_docs)] +pub fn paluch1_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { let p0 = 100_000.0; - let mixing_ratio = mixing_ratio::general1(pressure, vapour_pressure)?; - let saturation_vapour_pressure = vapour_pressure::buck1(temperature, pressure)?; + let mixing_ratio = mixing_ratio::general1_unchecked(pressure, vapour_pressure); + let saturation_vapour_pressure = vapour_pressure::buck1_unchecked(temperature, pressure); let relative_humidity = - relative_humidity::general2(vapour_pressure, saturation_vapour_pressure)?; + relative_humidity::general2_unchecked(vapour_pressure, saturation_vapour_pressure); - let result = temperature + temperature * (p0 / pressure).powf(R_D / (C_P + mixing_ratio * C_L)) * relative_humidity.powf((-mixing_ratio * R_V) / (C_P + mixing_ratio * C_L)) - * ((L_V * mixing_ratio) / (temperature * (C_P + mixing_ratio * C_L))).exp(); - - Ok(result) + * ((L_V * mixing_ratio) / (temperature * (C_P + mixing_ratio * C_L))).exp() } ///Formula for computing equivalent potential temperature of unsaturated air from @@ -68,12 +84,23 @@ pub fn general1( ///Valid `temperature` range: 253K - 324K\ ///Valid `pressure` range: 100Pa - 150000Pa\ ///Valid `vapour_pressure` range: 0Pa - 10000Pa -#[cfg_attr(feature = "debug", logerr)] pub fn bryan1( temperature: Float, pressure: Float, vapour_pressure: Float, ) -> Result { + bryan1_validate(temperature, pressure, vapour_pressure)?; + Ok(bryan1_unchecked(temperature, pressure, vapour_pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn bryan1_validate( + temperature: Float, + pressure: Float, + vapour_pressure: Float, +) -> Result<(), InputError> { if !(253.0..=324.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); } @@ -86,22 +113,25 @@ pub fn bryan1( return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } + Ok(()) +} + +#[allow(missing_docs)] +pub fn bryan1_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { let kappa = R_D / C_P; let potential_temperature = - potential_temperature::davies_jones1(temperature, pressure, vapour_pressure)?; + potential_temperature::davies_jones1_unchecked(temperature, pressure, vapour_pressure); - let saturation_vapour_pressure = vapour_pressure::buck3(temperature, pressure)?; + let saturation_vapour_pressure = vapour_pressure::buck3_unchecked(temperature, pressure); let relative_humidity = - relative_humidity::general2(vapour_pressure, saturation_vapour_pressure)?; + relative_humidity::general2_unchecked(vapour_pressure, saturation_vapour_pressure); - let mixing_ratio = mixing_ratio::general1(pressure, vapour_pressure)?; + let mixing_ratio = mixing_ratio::general1_unchecked(pressure, vapour_pressure); - let result = potential_temperature + potential_temperature * relative_humidity.powf((-kappa) * (mixing_ratio / EPSILON)) - * ((L_V * mixing_ratio) / (C_P * temperature)).exp(); - - Ok(result) + * ((L_V * mixing_ratio) / (C_P * temperature)).exp() } ///Approximate formula for computing equivalent potential temperature of unsaturated air from @@ -116,8 +146,19 @@ pub fn bryan1( ///Valid `pressure` range: 100Pa - 150000Pa\ ///Valid `temperature` range: 253K - 324K\ ///Valid `dewpoint` range: 253K - 324K -#[cfg_attr(feature = "debug", logerr)] pub fn bolton1(pressure: Float, temperature: Float, dewpoint: Float) -> Result { + bolton1_validate(pressure, temperature, dewpoint)?; + Ok(bolton1_unchecked(pressure, temperature, dewpoint)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn bolton1_validate( + pressure: Float, + temperature: Float, + dewpoint: Float, +) -> Result<(), InputError> { if !(20000.0..=150_000.0).contains(&pressure) { return Err(InputError::OutOfRange(String::from("pressure"))); } @@ -130,22 +171,24 @@ pub fn bolton1(pressure: Float, temperature: Float, dewpoint: Float) -> Result Float { let kappa = R_D / C_P; - let vapour_pressure = vapour_pressure::buck3(dewpoint, pressure)?; - let mixing_ratio = mixing_ratio::general1(pressure, vapour_pressure)?; + let vapour_pressure = vapour_pressure::buck3_unchecked(dewpoint, pressure); + let mixing_ratio = mixing_ratio::general1_unchecked(pressure, vapour_pressure); let lcl_temp = (1.0 / ((1.0 / (dewpoint - 56.0)) + ((temperature / dewpoint).ln() / 800.0))) + 56.0; let theta_dl = temperature - * (100000.0 / (pressure - vapour_pressure)).powf(kappa) + * (100_000.0 / (pressure - vapour_pressure)).powf(kappa) * (temperature / lcl_temp).powf(0.28 * mixing_ratio); - let result = theta_dl - * (((3036.0 / lcl_temp) - 1.78) * mixing_ratio * (1.0 + 0.448 * mixing_ratio)).exp(); - - Ok(result) + theta_dl * (((3036.0 / lcl_temp) - 1.78) * mixing_ratio * (1.0 + 0.448 * mixing_ratio)).exp() } #[cfg(test)] @@ -156,9 +199,9 @@ mod tests { }; #[test] - fn general1() { + fn paluch1() { assert!(tests_framework::test_with_3args( - &equivalent_potential_temperature::general1, + &equivalent_potential_temperature::paluch1, Argument { name: "temperature", def_val: 300.0, diff --git a/src/lib.rs b/src/lib.rs index 76ebfae..2bfc9f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,9 @@ +#![warn(clippy::pedantic)] +#![warn(missing_docs)] +#![warn(clippy::cargo)] +#![allow(clippy::excessive_precision)] +#![allow(clippy::must_use_candidate)] + //! Crate providing formulae for air thermodynamic calculations. //! //! # How to use @@ -48,6 +54,9 @@ //! Exact limits are specified in the documentation of each function. //! If the input is out of range the function will return an [`InputError::OutOfRange`](errors::InputError::OutOfRange) with erronous input specified. //! +//! Each function also has `_unchecked` and `_validate` versions. The `_validate` version only checks the inputs with bounds defined for its "parent" function. +//! The `_unchecked` version performs only the calculation without any input checking. All "parent" functions simply call `_validate` and then `_unchecked`. +//! //! # Units //! //! This crate uses basic SI units in the interface. diff --git a/src/mixing_ratio.rs b/src/mixing_ratio.rs index 1d8f045..54826d5 100644 --- a/src/mixing_ratio.rs +++ b/src/mixing_ratio.rs @@ -3,11 +3,11 @@ //!To calculate saturation mixing ratio input dry-bulb temperature in place of dewpoint //!or saturation vapour pressure in place of vapour pressure. +use crate::Float; use crate::{constants::EPSILON, errors::InputError, vapour_pressure}; use float_cmp::approx_eq; -use crate::Float; -#[cfg(feature="debug")] +#[cfg(feature = "debug")] use floccus_proc::logerr; ///Formula for computing mixing ratio of unsaturated air from air pressure and vapour pressure @@ -20,9 +20,15 @@ use floccus_proc::logerr; /// ///Returns [`InputError::IncorrectArgumentSet`] when inputs are equal, in which ///case division by 0 occurs. -#[cfg_attr(feature = "debug", logerr)] pub fn general1(pressure: Float, vapour_pressure: Float) -> Result { - //validate inputs + general1_validate(pressure, vapour_pressure)?; + Ok(general1_unchecked(pressure, vapour_pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn general1_validate(pressure: Float, vapour_pressure: Float) -> Result<(), InputError> { if !(100.0..=150_000.0).contains(&pressure) { return Err(InputError::OutOfRange(String::from("pressure"))); } @@ -36,21 +42,31 @@ pub fn general1(pressure: Float, vapour_pressure: Float) -> Result Float { + EPSILON * (vapour_pressure / (pressure - vapour_pressure)) } ///Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. -///Optimised by performance. +///Optimised for performance. /// ///# Errors /// ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 273K - 353K\ ///Valid `pressure` range: 100Pa - 150000Pa -#[cfg_attr(feature = "debug", logerr)] pub fn performance1(dewpoint: Float, pressure: Float) -> Result { + performance1_validate(dewpoint, pressure)?; + Ok(performance1_unchecked(dewpoint, pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn performance1_validate(dewpoint: Float, pressure: Float) -> Result<(), InputError> { //validate inputs if !(273.0..=353.0).contains(&dewpoint) { return Err(InputError::OutOfRange(String::from("dewpoint"))); @@ -60,22 +76,33 @@ pub fn performance1(dewpoint: Float, pressure: Float) -> Result Float { + let vapour_pressure = vapour_pressure::tetens1_unchecked(dewpoint); + + general1_unchecked(pressure, vapour_pressure) } ///Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. -///Optimised by accuracy. +///Optimised for accuracy. /// ///# Errors /// ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 232K - 324K\ ///Valid `pressure` range: 100Pa - 150000Pa -#[cfg_attr(feature = "debug", logerr)] pub fn accuracy1(dewpoint: Float, pressure: Float) -> Result { - //validate inputs + accuracy1_validate(dewpoint, pressure)?; + Ok(accuracy1_unchecked(dewpoint, pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn accuracy1_validate(dewpoint: Float, pressure: Float) -> Result<(), InputError> { if !(232.0..=324.0).contains(&dewpoint) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } @@ -83,10 +110,14 @@ pub fn accuracy1(dewpoint: Float, pressure: Float) -> Result if !(100.0..=150_000.0).contains(&pressure) { return Err(InputError::OutOfRange(String::from("pressure"))); } + Ok(()) +} + +#[allow(missing_docs)] +pub fn accuracy1_unchecked(dewpoint: Float, pressure: Float) -> Float { + let vapour_pressure = vapour_pressure::buck1_unchecked(dewpoint, pressure); - let vapour_pressure = vapour_pressure::buck1(dewpoint, pressure)?; - let result = general1(pressure, vapour_pressure)?; - Ok(result) + general1_unchecked(pressure, vapour_pressure) } #[cfg(test)] diff --git a/src/potential_temperature.rs b/src/potential_temperature.rs index 66610f8..2580178 100644 --- a/src/potential_temperature.rs +++ b/src/potential_temperature.rs @@ -1,13 +1,13 @@ //!Functions to calculate potential temperature of dry air in K. -use float_cmp::approx_eq; use crate::Float; use crate::{ constants::{C_P, R_D}, errors::InputError, }; +use float_cmp::approx_eq; -#[cfg(feature="debug")] +#[cfg(feature = "debug")] use floccus_proc::logerr; ///Formula for computing potential temperature of dry air from temperature, pressure and vapour pressure. @@ -26,12 +26,27 @@ use floccus_proc::logerr; /// ///Returns [`InputError::IncorrectArgumentSet`] when `pressure` is lower than `vapour_pressure`, ///in which case floating-point exponentation of negative number occurs. -#[cfg_attr(feature = "debug", logerr)] pub fn davies_jones1( temperature: Float, pressure: Float, vapour_pressure: Float, ) -> Result { + davies_jones1_validate(temperature, pressure, vapour_pressure)?; + Ok(davies_jones1_unchecked( + temperature, + pressure, + vapour_pressure, + )) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn davies_jones1_validate( + temperature: Float, + pressure: Float, + vapour_pressure: Float, +) -> Result<(), InputError> { if !(253.0..=324.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); } @@ -56,18 +71,24 @@ pub fn davies_jones1( ))); } - let kappa = R_D / C_P; - - let result = temperature * (100_000.0 / (pressure - vapour_pressure)).powf(kappa); + Ok(()) +} - Ok(result) +#[allow(missing_docs)] +pub fn davies_jones1_unchecked( + temperature: Float, + pressure: Float, + vapour_pressure: Float, +) -> Float { + let kappa = R_D / C_P; + temperature * (100_000.0 / (pressure - vapour_pressure)).powf(kappa) } #[cfg(test)] mod tests { use crate::{ - tests_framework::{self, Argument}, potential_temperature, + tests_framework::{self, Argument}, }; #[test] diff --git a/src/relative_humidity.rs b/src/relative_humidity.rs index c65895c..a625b48 100644 --- a/src/relative_humidity.rs +++ b/src/relative_humidity.rs @@ -1,9 +1,9 @@ //!Functions to calculate relative humidity in %/100 -use crate::{errors::InputError, mixing_ratio, vapour_pressure}; use crate::Float; +use crate::{errors::InputError, mixing_ratio, vapour_pressure}; -#[cfg(feature="debug")] +#[cfg(feature = "debug")] use floccus_proc::logerr; ///Formula for computing relative humidity from mixing ratio and saturation mixing ratio. @@ -17,8 +17,18 @@ use floccus_proc::logerr; ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `mixing_ratio` range: 0.00001 - 0.5\ ///Valid `saturation_mixing_ratio` range: 0.00001 - 0.5 -#[cfg_attr(feature = "debug", logerr)] pub fn general1(mixing_ratio: Float, saturation_mixing_ratio: Float) -> Result { + general1_validate(mixing_ratio, saturation_mixing_ratio)?; + Ok(general1_unchecked(mixing_ratio, saturation_mixing_ratio)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn general1_validate( + mixing_ratio: Float, + saturation_mixing_ratio: Float, +) -> Result<(), InputError> { if !(0.00001..=10.0).contains(&mixing_ratio) { return Err(InputError::OutOfRange(String::from("mixing_ratio"))); } @@ -29,7 +39,12 @@ pub fn general1(mixing_ratio: Float, saturation_mixing_ratio: Float) -> Result Float { + mixing_ratio / saturation_mixing_ratio } ///Formula for computing relative humidity from vapour pressure and saturation vapour pressure. @@ -40,8 +55,24 @@ pub fn general1(mixing_ratio: Float, saturation_mixing_ratio: Float) -> Result Result { + general2_validate(vapour_pressure, saturation_vapour_pressure)?; + Ok(general2_unchecked( + vapour_pressure, + saturation_vapour_pressure, + )) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] #[cfg_attr(feature = "debug", logerr)] -pub fn general2(vapour_pressure: Float, saturation_vapour_pressure: Float) -> Result { +pub fn general2_validate( + vapour_pressure: Float, + saturation_vapour_pressure: Float, +) -> Result<(), InputError> { if !(0.0..=50_000.0).contains(&vapour_pressure) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } @@ -52,9 +83,13 @@ pub fn general2(vapour_pressure: Float, saturation_vapour_pressure: Float) -> Re ))); } - Ok(vapour_pressure / saturation_vapour_pressure) + Ok(()) } +#[allow(missing_docs)] +pub fn general2_unchecked(vapour_pressure: Float, saturation_vapour_pressure: Float) -> Float { + vapour_pressure / saturation_vapour_pressure +} ///Formula for computing relative humidity from temperature and dewpoint using [`tetens1`](vapour_pressure::tetens1) ///function for vapour pressure calculation /// @@ -63,21 +98,32 @@ pub fn general2(vapour_pressure: Float, saturation_vapour_pressure: Float) -> Re ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `temperature` range: 273K - 353K ///Valid `dewpoint` range: 273K - 353K -#[cfg_attr(feature = "debug", logerr)] pub fn general3(temperature: Float, dewpoint: Float) -> Result { + general3_validate(temperature, dewpoint)?; + Ok(general3_unchecked(temperature, dewpoint)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn general3_validate(temperature: Float, dewpoint: Float) -> Result<(), InputError> { if !(273.0..=353.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(273.0..=353.0).contains(&temperature) { - return Err(InputError::OutOfRange(String::from("temperature"))); + if !(273.0..=353.0).contains(&dewpoint) { + return Err(InputError::OutOfRange(String::from("dewpoint"))); } - let vapour_pressure = vapour_pressure::tetens1(dewpoint)?; - let saturation_vapour_pressure = vapour_pressure::tetens1(temperature)?; - let result = general2(vapour_pressure, saturation_vapour_pressure)?; + Ok(()) +} + +#[allow(missing_docs)] +pub fn general3_unchecked(temperature: Float, dewpoint: Float) -> Float { + let vapour_pressure = vapour_pressure::tetens1_unchecked(dewpoint); + let saturation_vapour_pressure = vapour_pressure::tetens1_unchecked(temperature); - Ok(result) + general2_unchecked(vapour_pressure, saturation_vapour_pressure) } ///Formula for computing relative humidity from temperature, dewpoint and pressure using [`buck3`](vapour_pressure::buck3) @@ -89,25 +135,40 @@ pub fn general3(temperature: Float, dewpoint: Float) -> Result Result { + general4_validate(temperature, dewpoint, pressure)?; + Ok(general4_unchecked(temperature, dewpoint, pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn general4_validate( + temperature: Float, + dewpoint: Float, + pressure: Float, +) -> Result<(), InputError> { if !(253.0..=324.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(253.0..=324.0).contains(&temperature) { - return Err(InputError::OutOfRange(String::from("temperature"))); + if !(253.0..=324.0).contains(&dewpoint) { + return Err(InputError::OutOfRange(String::from("dewpoint"))); } if !(100.0..=150_000.0).contains(&pressure) { return Err(InputError::OutOfRange(String::from("pressure"))); } - let vapour_pressure = vapour_pressure::buck3(dewpoint, pressure)?; - let saturation_vapour_pressure = vapour_pressure::buck3(temperature, pressure)?; - let result = general2(vapour_pressure, saturation_vapour_pressure)?; + Ok(()) +} + +#[allow(missing_docs)] +pub fn general4_unchecked(temperature: Float, dewpoint: Float, pressure: Float) -> Float { + let vapour_pressure = vapour_pressure::buck3_unchecked(dewpoint, pressure); + let saturation_vapour_pressure = vapour_pressure::buck3_unchecked(temperature, pressure); - Ok(result) + general2_unchecked(vapour_pressure, saturation_vapour_pressure) } ///Formula for computing relative humidity from temperature, dewpoint and pressure using [`accuracy1`](mixing_ratio::accuracy1) @@ -119,8 +180,19 @@ pub fn general4(temperature: Float, dewpoint: Float, pressure: Float) -> Result< ///Valid `temperature` range: 232K - 324K\ ///Valid `dewpoint` range: 232K - 324K\ ///Valid `pressure` range: 100Pa - 150000Pa -#[cfg_attr(feature = "debug", logerr)] pub fn general5(temperature: Float, dewpoint: Float, pressure: Float) -> Result { + general5_validate(temperature, dewpoint, pressure)?; + Ok(general5_unchecked(temperature, dewpoint, pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn general5_validate( + temperature: Float, + dewpoint: Float, + pressure: Float, +) -> Result<(), InputError> { if !(232.0..=314.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); } @@ -133,12 +205,15 @@ pub fn general5(temperature: Float, dewpoint: Float, pressure: Float) -> Result< return Err(InputError::OutOfRange(String::from("pressure"))); } - let mixing_ratio = mixing_ratio::accuracy1(dewpoint, pressure)?; - let saturation_mixing_ratio = mixing_ratio::accuracy1(temperature, pressure)?; - //println!("{} {}", mixing_ratio, saturation_mixing_ratio); - let result = general1(mixing_ratio, saturation_mixing_ratio)?; + Ok(()) +} + +#[allow(missing_docs)] +pub fn general5_unchecked(temperature: Float, dewpoint: Float, pressure: Float) -> Float { + let mixing_ratio = mixing_ratio::accuracy1_unchecked(dewpoint, pressure); + let saturation_mixing_ratio = mixing_ratio::accuracy1_unchecked(temperature, pressure); - Ok(result) + general1_unchecked(mixing_ratio, saturation_mixing_ratio) } #[cfg(test)] diff --git a/src/specific_humidity.rs b/src/specific_humidity.rs index 4f4af45..c88e690 100644 --- a/src/specific_humidity.rs +++ b/src/specific_humidity.rs @@ -5,10 +5,10 @@ //! //!Specific humidity is approximately equal to mixing ratio. -use crate::{constants::EPSILON, errors::InputError}; use crate::Float; +use crate::{constants::EPSILON, errors::InputError}; -#[cfg(feature="debug")] +#[cfg(feature = "debug")] use floccus_proc::logerr; ///Formula for computing specific humidity from vapour pressure and pressure. @@ -22,8 +22,15 @@ use floccus_proc::logerr; ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `vapour_pressure` range: 0Pa - 50000OPa\, ///Valid `pressure` range: 100Pa - 150000Pa -#[cfg_attr(feature = "debug", logerr)] pub fn general1(vapour_pressure: Float, pressure: Float) -> Result { + general1_validate(vapour_pressure, pressure)?; + Ok(general1_unchecked(vapour_pressure, pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn general1_validate(vapour_pressure: Float, pressure: Float) -> Result<(), InputError> { if !(0.0..=50_000.0).contains(&vapour_pressure) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } @@ -32,8 +39,12 @@ pub fn general1(vapour_pressure: Float, pressure: Float) -> Result Float { + EPSILON * (vapour_pressure / (pressure - (vapour_pressure * (1.0 - EPSILON)))) } #[cfg(test)] diff --git a/src/tests_framework.rs b/src/tests_framework.rs index ed35883..2cd896a 100644 --- a/src/tests_framework.rs +++ b/src/tests_framework.rs @@ -59,7 +59,7 @@ pub fn test_with_2args( if result.is_err() { assert!( - discriminant(&InputError::IncorrectArgumentSet(String::from(""))) + discriminant(&InputError::IncorrectArgumentSet(String::new())) == discriminant(&result.unwrap_err()) ); } else { @@ -110,7 +110,7 @@ pub fn test_with_1arg( if result.is_err() { assert!( - discriminant(&InputError::IncorrectArgumentSet(String::from(""))) + discriminant(&InputError::IncorrectArgumentSet(String::new())) == discriminant(&result.unwrap_err()) ); } else { @@ -165,11 +165,10 @@ pub fn test_with_3args( if result.is_err() { assert!( - discriminant(&InputError::IncorrectArgumentSet(String::from(""))) + discriminant(&InputError::IncorrectArgumentSet(String::new())) == discriminant(&result.unwrap_err()) ); } else { - println!("{} {} {} {:?}", arg1_tmp, arg2_tmp, arg3_tmp, result); assert!(result.unwrap().is_finite()); } } diff --git a/src/vapour_pressure.rs b/src/vapour_pressure.rs index ab98289..cb6c6b1 100644 --- a/src/vapour_pressure.rs +++ b/src/vapour_pressure.rs @@ -1,3 +1,7 @@ +#![allow(clippy::needless_range_loop)] +#![allow(clippy::cast_possible_truncation)] +#![allow(clippy::cast_possible_wrap)] + //!Functions to calculate partial vapour pressure of the unsaturated air in Pa. //! //!To compute saturation vapour pressure input dry-bulb temperature in place of dewpoint temperature. @@ -8,7 +12,7 @@ use crate::{ errors::InputError, }; -#[cfg(feature="debug")] +#[cfg(feature = "debug")] use floccus_proc::logerr; ///Formula for computing vapour pressure from specific humidity and pressure. @@ -21,9 +25,15 @@ use floccus_proc::logerr; ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `specific_humidity` range: 0.00001 - 2.0\ ///Valid `pressure` range: 100Pa - 150000Pa -#[cfg_attr(feature = "debug", logerr)] pub fn general1(specific_humidity: Float, pressure: Float) -> Result { - //validate inputs + general1_validate(specific_humidity, pressure)?; + Ok(general1_unchecked(specific_humidity, pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn general1_validate(specific_humidity: Float, pressure: Float) -> Result<(), InputError> { if !(0.00001..=2.0).contains(&specific_humidity) { return Err(InputError::OutOfRange(String::from("specific_humidity"))); } @@ -32,10 +42,12 @@ pub fn general1(specific_humidity: Float, pressure: Float) -> Result Float { + -((pressure * specific_humidity) / ((specific_humidity * (EPSILON - 1.0)) - EPSILON)) } ///Formula for computing vapour pressure from dewpoint temperature and pressure. @@ -47,9 +59,15 @@ pub fn general1(specific_humidity: Float, pressure: Float) -> Result Result { - //validate inputs + buck1_validate(dewpoint, pressure)?; + Ok(buck1_unchecked(dewpoint, pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn buck1_validate(dewpoint: Float, pressure: Float) -> Result<(), InputError> { if !(232.0..=324.0).contains(&dewpoint) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } @@ -58,6 +76,11 @@ pub fn buck1(dewpoint: Float, pressure: Float) -> Result { return Err(InputError::OutOfRange(String::from("pressure"))); } + Ok(()) +} + +#[allow(missing_docs)] +pub fn buck1_unchecked(dewpoint: Float, pressure: Float) -> Float { let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C let pressure = pressure / 100.0; //convert to hPa @@ -74,7 +97,7 @@ pub fn buck1(dewpoint: Float, pressure: Float) -> Result { lower_a * (((lower_b - (dewpoint / lower_d)) * dewpoint) / (dewpoint + lower_c)).exp(); let lower_f = 1.0 + upper_a + (pressure * (upper_b + (upper_c * dewpoint * dewpoint))); - Ok((lower_e * lower_f) * 100.0) //return in Pa + (lower_e * lower_f) * 100.0 //return in Pa } ///Formula for computing vapour pressure from dewpoint temperature and pressure. @@ -86,9 +109,15 @@ pub fn buck1(dewpoint: Float, pressure: Float) -> Result { ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 193K - 274K\ ///Valid `pressure` range: 100Pa - 150000Pa -#[cfg_attr(feature = "debug", logerr)] pub fn buck2(dewpoint: Float, pressure: Float) -> Result { - //validate inputs + buck2_validate(dewpoint, pressure)?; + Ok(buck2_unchecked(dewpoint, pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn buck2_validate(dewpoint: Float, pressure: Float) -> Result<(), InputError> { if !(193.0..=274.0).contains(&dewpoint) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } @@ -97,6 +126,11 @@ pub fn buck2(dewpoint: Float, pressure: Float) -> Result { return Err(InputError::OutOfRange(String::from("pressure"))); } + Ok(()) +} + +#[allow(missing_docs)] +pub fn buck2_unchecked(dewpoint: Float, pressure: Float) -> Float { let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C let pressure = pressure / 100.0; //convert to hPa @@ -113,7 +147,7 @@ pub fn buck2(dewpoint: Float, pressure: Float) -> Result { lower_a * (((lower_b - (dewpoint / lower_d)) * dewpoint) / (dewpoint + lower_c)).exp(); let lower_f = 1.0 + upper_a + (pressure * (upper_b + (upper_c * dewpoint * dewpoint))); - Ok((lower_e * lower_f) * 100.0) //return in Pa + (lower_e * lower_f) * 100.0 //return in Pa } ///Formula for computing vapour pressure from dewpoint temperature and pressure. @@ -125,9 +159,15 @@ pub fn buck2(dewpoint: Float, pressure: Float) -> Result { ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 253K - 324K\ ///Valid `pressure` range: 100Pa - 150000Pa -#[cfg_attr(feature = "debug", logerr)] pub fn buck3(dewpoint: Float, pressure: Float) -> Result { - //validate inputs + buck3_validate(dewpoint, pressure)?; + Ok(buck3_unchecked(dewpoint, pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn buck3_validate(dewpoint: Float, pressure: Float) -> Result<(), InputError> { if !(253.0..=324.0).contains(&dewpoint) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } @@ -136,6 +176,11 @@ pub fn buck3(dewpoint: Float, pressure: Float) -> Result { return Err(InputError::OutOfRange(String::from("pressure"))); } + Ok(()) +} + +#[allow(missing_docs)] +pub fn buck3_unchecked(dewpoint: Float, pressure: Float) -> Float { let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C let pressure = pressure / 100.0; //convert to hPa @@ -149,7 +194,7 @@ pub fn buck3(dewpoint: Float, pressure: Float) -> Result { let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); let lower_f = 1.0 + upper_a + (pressure * upper_b); - Ok((lower_e * lower_f) * 100.0) //return in Pa + (lower_e * lower_f) * 100.0 //return in Pa } ///Formula for computing vapour pressure from dewpoint temperature. @@ -160,13 +205,24 @@ pub fn buck3(dewpoint: Float, pressure: Float) -> Result { /// ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 253K - 324K -#[cfg_attr(feature = "debug", logerr)] pub fn buck3_simplified(dewpoint: Float) -> Result { - //validate inputs + buck3_simplified_validate(dewpoint)?; + Ok(buck3_simplified_unchecked(dewpoint)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn buck3_simplified_validate(dewpoint: Float) -> Result<(), InputError> { if !(253.0..=324.0).contains(&dewpoint) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } + Ok(()) +} + +#[allow(missing_docs)] +pub fn buck3_simplified_unchecked(dewpoint: Float) -> Float { let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C let lower_a = 6.1121; @@ -175,7 +231,7 @@ pub fn buck3_simplified(dewpoint: Float) -> Result { let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); - Ok(lower_e * 100.0) //return in Pa + lower_e * 100.0 //return in Pa } ///Formula for computing vapour pressure from dewpoint temperature and pressure. @@ -187,9 +243,15 @@ pub fn buck3_simplified(dewpoint: Float) -> Result { ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 223K - 274K\ ///Valid `pressure` range: 100Pa - 150000Pa -#[cfg_attr(feature = "debug", logerr)] pub fn buck4(dewpoint: Float, pressure: Float) -> Result { - //validate inputs + buck4_validate(dewpoint, pressure)?; + Ok(buck4_unchecked(dewpoint, pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn buck4_validate(dewpoint: Float, pressure: Float) -> Result<(), InputError> { if !(223.0..=274.0).contains(&dewpoint) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } @@ -198,6 +260,11 @@ pub fn buck4(dewpoint: Float, pressure: Float) -> Result { return Err(InputError::OutOfRange(String::from("pressure"))); } + Ok(()) +} + +#[allow(missing_docs)] +pub fn buck4_unchecked(dewpoint: Float, pressure: Float) -> Float { let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C let pressure = pressure / 100.0; //convert to hPa @@ -211,7 +278,7 @@ pub fn buck4(dewpoint: Float, pressure: Float) -> Result { let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); let lower_f = 1.0 + upper_a + (pressure * upper_b); - Ok((lower_e * lower_f) * 100.0) //return in Pa + (lower_e * lower_f) * 100.0 //return in Pa } ///Formula for computing vapour pressure from dewpoint temperature. @@ -222,13 +289,25 @@ pub fn buck4(dewpoint: Float, pressure: Float) -> Result { /// ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 223K - 274K -#[cfg_attr(feature = "debug", logerr)] pub fn buck4_simplified(dewpoint: Float) -> Result { + buck4_simplified_validate(dewpoint)?; + Ok(buck4_simplified_unchecked(dewpoint)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn buck4_simplified_validate(dewpoint: Float) -> Result<(), InputError> { //validate inputs if !(223.0..=274.0).contains(&dewpoint) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } + Ok(()) +} + +#[allow(missing_docs)] +pub fn buck4_simplified_unchecked(dewpoint: Float) -> Float { let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C let lower_a = 6.1115; @@ -237,7 +316,7 @@ pub fn buck4_simplified(dewpoint: Float) -> Result { let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); - Ok(lower_e * 100.0) //return in Pa + lower_e * 100.0 //return in Pa } ///Formula for computing vapour pressure over water from dewpoint temperature. @@ -249,13 +328,24 @@ pub fn buck4_simplified(dewpoint: Float) -> Result { /// ///Returns [`InputError::OutOfRange`] when input is out of range.\ ///Valid `dewpoint` range: 273K - 353K -#[cfg_attr(feature = "debug", logerr)] pub fn tetens1(dewpoint: Float) -> Result { - //validate inputs + tetens1_validate(dewpoint)?; + Ok(tetens1_unchecked(dewpoint)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn tetens1_validate(dewpoint: Float) -> Result<(), InputError> { if !(273.0..=353.0).contains(&dewpoint) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } + Ok(()) +} + +#[allow(missing_docs)] +pub fn tetens1_unchecked(dewpoint: Float) -> Float { let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C let lower_a = 0.61078; @@ -264,7 +354,7 @@ pub fn tetens1(dewpoint: Float) -> Result { let result = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); - Ok(result * 1000.0) //return in Pa + result * 1000.0 //return in Pa } ///Formula for computing **ONLY** vapour pressure from saturation vapour pressure and relative humidity. @@ -275,11 +365,24 @@ pub fn tetens1(dewpoint: Float) -> Result { ///Returns [`InputError::OutOfRange`] when input is out of range.\ ///Valid `saturation_vapour_pressure` range: 0Pa - 10000Pa\ ///Valid `relative_humidity` range: 0.0 - 1.0 -#[cfg_attr(feature = "debug", logerr)] pub fn saturation_specific1( saturation_vapour_pressure: Float, relative_humidity: Float, ) -> Result { + saturation_specific1_validate(saturation_vapour_pressure, relative_humidity)?; + Ok(saturation_specific1_unchecked( + saturation_vapour_pressure, + relative_humidity, + )) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn saturation_specific1_validate( + saturation_vapour_pressure: Float, + relative_humidity: Float, +) -> Result<(), InputError> { if !(0.0..=2.0).contains(&relative_humidity) { return Err(InputError::OutOfRange(String::from("relative_humidity"))); } @@ -290,7 +393,15 @@ pub fn saturation_specific1( ))); } - Ok(saturation_vapour_pressure * relative_humidity) + Ok(()) +} + +#[allow(missing_docs)] +pub fn saturation_specific1_unchecked( + saturation_vapour_pressure: Float, + relative_humidity: Float, +) -> Float { + saturation_vapour_pressure * relative_humidity } ///Formula for computing **ONLY** saturation vapour pressure from vapour pressure and relative humidity. @@ -301,11 +412,24 @@ pub fn saturation_specific1( ///Returns [`InputError::OutOfRange`] when input is out of range.\ ///Valid `vapour_pressure` range: 0Pa - 10000Pa\ ///Valid `relative_humidity` range: 0.00001 - 1.0 -#[cfg_attr(feature = "debug", logerr)] pub fn saturation_specific2( vapour_pressure: Float, relative_humidity: Float, ) -> Result { + saturation_specific2_validate(vapour_pressure, relative_humidity)?; + Ok(saturation_specific2_uchecked( + vapour_pressure, + relative_humidity, + )) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn saturation_specific2_validate( + vapour_pressure: Float, + relative_humidity: Float, +) -> Result<(), InputError> { if !(0.00001..=2.0).contains(&relative_humidity) { return Err(InputError::OutOfRange(String::from("relative_humidity"))); } @@ -314,7 +438,12 @@ pub fn saturation_specific2( return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } - Ok(vapour_pressure / relative_humidity) + Ok(()) +} + +#[allow(missing_docs)] +pub fn saturation_specific2_uchecked(vapour_pressure: Float, relative_humidity: Float) -> Float { + vapour_pressure / relative_humidity } ///Formula for computing vapour pressure over water from dewpoint temperature. @@ -327,22 +456,34 @@ pub fn saturation_specific2( /// ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 273K - 374K -#[cfg_attr(feature = "debug", logerr)] pub fn wexler1(dewpoint: Float) -> Result { + wexler1_validate(dewpoint)?; + Ok(wexler1_unchecked(dewpoint)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn wexler1_validate(dewpoint: Float) -> Result<(), InputError> { if !(273.0..=374.0).contains(&dewpoint) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } + Ok(()) +} + +#[allow(missing_docs)] +pub fn wexler1_unchecked(dewpoint: Float) -> Float { // constants from the paper let g: [Float; 8] = [ -2991.2729, -6017.0128, - 18.87643854, - -0.028354721, - 0.0000178383, - -0.00000000084150417, - 0.00000000000044412543, - 2.858487, + 18.876_438_54, + -0.028_354_721, + 0.000_017_838_3, + -0.000_000_000_841_504_17, + 0.000_000_000_000_444_125_43, + 2.858_487, ]; let mut ln_p = g[7] * dewpoint.ln(); @@ -351,7 +492,7 @@ pub fn wexler1(dewpoint: Float) -> Result { ln_p += g[i] * dewpoint.powi(i as i32 - 2); } - Ok(ln_p.exp()) + ln_p.exp() } ///Formula for computing vapour over ice pressure from dewpoint temperature. @@ -364,20 +505,31 @@ pub fn wexler1(dewpoint: Float) -> Result { /// ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 173K - 274K -#[cfg_attr(feature = "debug", logerr)] pub fn wexler2(dewpoint: Float) -> Result { + wexler2_validate(dewpoint)?; + Ok(wexler2_unchecked(dewpoint)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn wexler2_validate(dewpoint: Float) -> Result<(), InputError> { if !(173.0..=274.0).contains(&dewpoint) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } + Ok(()) +} +#[allow(missing_docs)] +pub fn wexler2_unchecked(dewpoint: Float) -> Float { // constants from the paper let big_k: [Float; 6] = [ -5865.3696, - 22.241033, - 0.013749042, - -0.00003403177, - 0.000000026967687, - 0.6918651, + 22.241_033, + 0.013_749_042, + -0.000_034_031_77, + 0.000_000_026_967_687, + 0.691_865_1, ]; let mut ln_p = big_k[5] * dewpoint.ln(); @@ -386,7 +538,7 @@ pub fn wexler2(dewpoint: Float) -> Result { ln_p += big_k[j] * dewpoint.powi(j as i32 - 1); } - Ok(ln_p.exp()) + ln_p.exp() } #[cfg(test)] diff --git a/src/vapour_pressure_deficit.rs b/src/vapour_pressure_deficit.rs index f42d17a..4d8a399 100644 --- a/src/vapour_pressure_deficit.rs +++ b/src/vapour_pressure_deficit.rs @@ -4,10 +4,10 @@ //!the amount of moisture in the air and how much moisture the air can hold //!when it is saturated ([Wikipedia](https://en.wikipedia.org/wiki/Vapour-pressure_deficit)). -use crate::{errors::InputError, vapour_pressure}; use crate::Float; +use crate::{errors::InputError, vapour_pressure}; -#[cfg(feature="debug")] +#[cfg(feature = "debug")] use floccus_proc::logerr; ///Formula for computing vapour pressure deficit from vapour pressure and saturation vapour pressure @@ -17,8 +17,24 @@ use floccus_proc::logerr; ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `vapour_pressure` range: 0Pa - 10000Pa ///Valid `saturation_vapour_pressure` range: 0Pa - 10000Pa +pub fn general1( + vapour_pressure: Float, + saturation_vapour_pressure: Float, +) -> Result { + general1_validate(vapour_pressure, saturation_vapour_pressure)?; + Ok(general1_unchecked( + vapour_pressure, + saturation_vapour_pressure, + )) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] #[cfg_attr(feature = "debug", logerr)] -pub fn general1(vapour_pressure: Float, saturation_vapour_pressure: Float) -> Result { +pub fn general1_validate( + vapour_pressure: Float, + saturation_vapour_pressure: Float, +) -> Result<(), InputError> { if !(0.0..=50_000.0).contains(&vapour_pressure) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } @@ -29,7 +45,12 @@ pub fn general1(vapour_pressure: Float, saturation_vapour_pressure: Float) -> Re ))); } - Ok(saturation_vapour_pressure - vapour_pressure) + Ok(()) +} + +#[allow(missing_docs)] +pub fn general1_unchecked(vapour_pressure: Float, saturation_vapour_pressure: Float) -> Float { + saturation_vapour_pressure - vapour_pressure } ///Formula for computing vapour pressure deficit from temperature, dewpoint and pressure @@ -40,8 +61,19 @@ pub fn general1(vapour_pressure: Float, saturation_vapour_pressure: Float) -> Re ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `vapour_pressure` range: 0Pa - 10000Pa ///Valid `saturation_vapour_pressure` range: 0Pa - 10000Pa -#[cfg_attr(feature = "debug", logerr)] pub fn general2(temperature: Float, dewpoint: Float, pressure: Float) -> Result { + general2_validate(temperature, dewpoint, pressure)?; + Ok(general2_unchecked(temperature, dewpoint, pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn general2_validate( + temperature: Float, + dewpoint: Float, + pressure: Float, +) -> Result<(), InputError> { if !(253.0..=324.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); } @@ -53,13 +85,15 @@ pub fn general2(temperature: Float, dewpoint: Float, pressure: Float) -> Result< if !(100.0..=150_000.0).contains(&pressure) { return Err(InputError::OutOfRange(String::from("pressure"))); } + Ok(()) +} - let vapour_pressure = vapour_pressure::buck3(dewpoint, pressure)?; - let saturation_vapour_pressure = vapour_pressure::buck3(temperature, pressure)?; - - let result = general1(vapour_pressure, saturation_vapour_pressure)?; +#[allow(missing_docs)] +pub fn general2_unchecked(temperature: Float, dewpoint: Float, pressure: Float) -> Float { + let vapour_pressure = vapour_pressure::buck3_unchecked(dewpoint, pressure); + let saturation_vapour_pressure = vapour_pressure::buck3_unchecked(temperature, pressure); - Ok(result) + general1_unchecked(vapour_pressure, saturation_vapour_pressure) } ///Formula for computing vapour pressure deficit from temperature, relative humidity and pressure @@ -70,12 +104,23 @@ pub fn general2(temperature: Float, dewpoint: Float, pressure: Float) -> Result< ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `vapour_pressure` range: 0Pa - 10000Pa ///Valid `saturation_vapour_pressure` range: 0Pa - 10000Pa -#[cfg_attr(feature = "debug", logerr)] pub fn general3( temperature: Float, relative_humidity: Float, pressure: Float, ) -> Result { + general3_validate(temperature, relative_humidity, pressure)?; + Ok(general3_unchecked(temperature, relative_humidity, pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn general3_validate( + temperature: Float, + relative_humidity: Float, + pressure: Float, +) -> Result<(), InputError> { if !(253.0..=319.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); } @@ -88,13 +133,18 @@ pub fn general3( return Err(InputError::OutOfRange(String::from("pressure"))); } - let saturation_vapour_pressure = vapour_pressure::buck3(temperature, pressure)?; - let vapour_pressure = - vapour_pressure::saturation_specific1(saturation_vapour_pressure, relative_humidity)?; + Ok(()) +} - let result = general1(vapour_pressure, saturation_vapour_pressure)?; +#[allow(missing_docs)] +pub fn general3_unchecked(temperature: Float, relative_humidity: Float, pressure: Float) -> Float { + let saturation_vapour_pressure = vapour_pressure::buck3_unchecked(temperature, pressure); + let vapour_pressure = vapour_pressure::saturation_specific1_unchecked( + saturation_vapour_pressure, + relative_humidity, + ); - Ok(result) + general1_unchecked(vapour_pressure, saturation_vapour_pressure) } #[cfg(test)] diff --git a/src/virtual_temperature.rs b/src/virtual_temperature.rs index 80c26ed..5bff00b 100644 --- a/src/virtual_temperature.rs +++ b/src/virtual_temperature.rs @@ -4,10 +4,10 @@ //!at which a theoretical dry air parcel would have a total pressure and density equal //!to the moist parcel of air ([Wikipedia](https://en.wikipedia.org/wiki/Virtual_temperature)). -use crate::{constants::EPSILON, errors::InputError}; use crate::Float; +use crate::{constants::EPSILON, errors::InputError}; -#[cfg(feature="debug")] +#[cfg(feature = "debug")] use floccus_proc::logerr; ///Formula for computing virtual temperature from temperature and mixing ratio. @@ -17,8 +17,15 @@ use floccus_proc::logerr; ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `temperature` range: 173K - 373K\ ///Valid `mixing_ratio` range: 0.0000000001 - 0.5 -#[cfg_attr(feature = "debug", logerr)] pub fn general1(temperature: Float, mixing_ratio: Float) -> Result { + general1_validate(temperature, mixing_ratio)?; + Ok(general1_unchecked(temperature, mixing_ratio)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn general1_validate(temperature: Float, mixing_ratio: Float) -> Result<(), InputError> { if !(173.0..=354.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); } @@ -27,9 +34,12 @@ pub fn general1(temperature: Float, mixing_ratio: Float) -> Result Float { + temperature * ((mixing_ratio + EPSILON) / (EPSILON * (1.0 + mixing_ratio))) } ///Formula for computing virtual temperature from air temperature, pressure and vapour pressure. @@ -40,8 +50,23 @@ pub fn general1(temperature: Float, mixing_ratio: Float) -> Result Result { + general2_validate(temperature, pressure, vapour_pressure)?; + Ok(general2_unchecked(temperature, pressure, vapour_pressure)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] #[cfg_attr(feature = "debug", logerr)] -pub fn general2(temperature: Float, pressure: Float, vapour_pressure: Float) -> Result { +pub fn general2_validate( + temperature: Float, + pressure: Float, + vapour_pressure: Float, +) -> Result<(), InputError> { if !(173.0..=354.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); } @@ -53,10 +78,12 @@ pub fn general2(temperature: Float, pressure: Float, vapour_pressure: Float) -> if !(0.0..=10_000.0).contains(&vapour_pressure) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } + Ok(()) +} - let result = temperature / (1.0 - ((vapour_pressure / pressure) * (1.0 - EPSILON))); - - Ok(result) +#[allow(missing_docs)] +pub fn general2_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { + temperature / (1.0 - ((vapour_pressure / pressure) * (1.0 - EPSILON))) } ///Formula for computing virtual temperature from air temperature and specific humidity. @@ -66,19 +93,29 @@ pub fn general2(temperature: Float, pressure: Float, vapour_pressure: Float) -> ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `temperature` range: 173K - 373K\ ///Valid `specific_humidity` range: 100Pa - 150000Pa -#[cfg_attr(feature = "debug", logerr)] pub fn general3(temperature: Float, specific_humidity: Float) -> Result { + general3_validate(temperature, specific_humidity)?; + Ok(general3_unchecked(temperature, specific_humidity)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn general3_validate(temperature: Float, specific_humidity: Float) -> Result<(), InputError> { if !(173.0..=354.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(0.000000001..=2.0).contains(&specific_humidity) { + if !(0.000_000_001..=2.0).contains(&specific_humidity) { return Err(InputError::OutOfRange(String::from("specific_humidity"))); } - let result = temperature * (1.0 + (specific_humidity * ((1.0 / EPSILON) - 1.0))); + Ok(()) +} - Ok(result) +#[allow(missing_docs)] +pub fn general3_unchecked(temperature: Float, specific_humidity: Float) -> Float { + temperature * (1.0 + (specific_humidity * ((1.0 / EPSILON) - 1.0))) } #[cfg(test)] diff --git a/src/wet_bulb_potential_temperature.rs b/src/wet_bulb_potential_temperature.rs index 192e303..f0947f6 100644 --- a/src/wet_bulb_potential_temperature.rs +++ b/src/wet_bulb_potential_temperature.rs @@ -6,7 +6,7 @@ use crate::{ errors::InputError, }; -#[cfg(feature="debug")] +#[cfg(feature = "debug")] use floccus_proc::logerr; ///Formula for computing wet bulb potential temperature from equivalent potential temperature. @@ -17,18 +17,29 @@ use floccus_proc::logerr; /// ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `temperature` range: 257K - 377K\ -#[cfg_attr(feature = "debug", logerr)] pub fn davies_jones1(equivalent_potential_temperature: Float) -> Result { + davies_jones1_validate(equivalent_potential_temperature)?; + Ok(davies_jones1_unchecked(equivalent_potential_temperature)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn davies_jones1_validate(equivalent_potential_temperature: Float) -> Result<(), InputError> { if !(257.0..=377.0).contains(&equivalent_potential_temperature) { return Err(InputError::OutOfRange(String::from( "equivalent_potential_temperature", ))); } - let lambda = C_P / R_D; + Ok(()) +} +#[allow(missing_docs)] +pub fn davies_jones1_unchecked(equivalent_potential_temperature: Float) -> Float { + let lambda = C_P / R_D; let result = 45.114 - 51.489 * (ZERO_CELSIUS / equivalent_potential_temperature).powf(lambda); - Ok(result + ZERO_CELSIUS) + result + ZERO_CELSIUS } #[cfg(test)] diff --git a/src/wet_bulb_temperature.rs b/src/wet_bulb_temperature.rs index 1801500..c162d1d 100644 --- a/src/wet_bulb_temperature.rs +++ b/src/wet_bulb_temperature.rs @@ -1,9 +1,9 @@ //!Functions to calculate wet bulb temperature of unsaturated air in K. -use crate::{constants::ZERO_CELSIUS, errors::InputError}; use crate::Float; +use crate::{constants::ZERO_CELSIUS, errors::InputError}; -#[cfg(feature="debug")] +#[cfg(feature = "debug")] use floccus_proc::logerr; ///Formula for computing wet bulb temperature pressure from dry bulb temperature and relative humidity. @@ -17,8 +17,15 @@ use floccus_proc::logerr; ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `temperature` range: 253K - 324K\ ///Valid `relative_humidity` range: 0.05 - 0.99 -#[cfg_attr(feature = "debug", logerr)] pub fn stull1(temperature: Float, relative_humidity: Float) -> Result { + stull1_validate(temperature, relative_humidity)?; + Ok(stull1_unchecked(temperature, relative_humidity)) +} + +#[allow(missing_docs)] +#[allow(clippy::missing_errors_doc)] +#[cfg_attr(feature = "debug", logerr)] +pub fn stull1_validate(temperature: Float, relative_humidity: Float) -> Result<(), InputError> { if !(253.0..=324.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); } @@ -26,7 +33,11 @@ pub fn stull1(temperature: Float, relative_humidity: Float) -> Result Float { //convert units let temperature = temperature - ZERO_CELSIUS; let relative_humidity = relative_humidity * 100.0; @@ -37,7 +48,7 @@ pub fn stull1(temperature: Float, relative_humidity: Float) -> Result