From ad0992a36bce978c8ac2e3e8d54070ab97bca2e3 Mon Sep 17 00:00:00 2001 From: Jakub Lewandowski <22771850+Quba1@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:09:32 +0100 Subject: [PATCH 001/102] Separate input validation (#3) * remove leftover prints * virtual temperature * vapour pressure * bump version * define clippy * start refactor * update readme and add scheduled jobs * extend basic tests * add doc coverage action * add clippy check * use basic clippy * add 0.3.7 benchmark results * fix clippy * fix unreadble literals * mixing ratio * relative humidity * clippy * potential temperature * specific humidity * vpd * theta e * wet bulb temperature * theta w * update docs --- .github/workflows/basic.yml | 38 ++++ .github/workflows/clippy-check.yml | 19 ++ .github/workflows/doc-coverage.yml | 45 +++++ .github/workflows/rust.yml | 46 ----- Cargo.toml | 2 +- README.md | 9 +- benches/results/0_3_7.txt | 92 +++++++++ src/equivalent_potential_temperature.rs | 99 +++++++--- src/lib.rs | 9 + src/mixing_ratio.rs | 65 +++++-- src/potential_temperature.rs | 37 +++- src/relative_humidity.rs | 127 ++++++++++--- src/specific_humidity.rs | 21 +- src/tests_framework.rs | 7 +- src/vapour_pressure.rs | 242 +++++++++++++++++++----- src/vapour_pressure_deficit.rs | 82 ++++++-- src/virtual_temperature.rs | 63 ++++-- src/wet_bulb_potential_temperature.rs | 19 +- src/wet_bulb_temperature.rs | 19 +- 19 files changed, 820 insertions(+), 221 deletions(-) create mode 100644 .github/workflows/basic.yml create mode 100644 .github/workflows/clippy-check.yml create mode 100644 .github/workflows/doc-coverage.yml delete mode 100644 .github/workflows/rust.yml create mode 100644 benches/results/0_3_7.txt 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 Date: Mon, 19 Feb 2024 16:38:19 +0100 Subject: [PATCH 002/102] update dependencies --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4e8bdaf..132f8d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,13 +14,13 @@ exclude = [ ] [dependencies] -thiserror = "^1.0.30" -float-cmp = "^0.9.0" +thiserror = "1.0" +float-cmp = "0.9" floccus-proc = {version = "0.2.5", optional = true} -log = "^0.4.14" +log = "0.4" [dev-dependencies] -criterion = "0.4.0" +criterion = "0.5.1" [features] debug = ["floccus-proc"] From be37c8fd718ce58ee23827c63a5a1bd91fe34207 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 19 Feb 2024 18:45:52 +0100 Subject: [PATCH 003/102] add vector macro --- Cargo.toml | 10 +++++--- src/array_compute.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 src/array_compute.rs diff --git a/Cargo.toml b/Cargo.toml index 132f8d0..c38bfd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,22 +9,24 @@ readme = "README.md" keywords = ["oceanography", "thermodynamics", "meteorology", "weather"] categories = ["mathematics", "science"] license = "Apache-2.0" -exclude = [ - ".github/*", -] +exclude = [".github/*"] [dependencies] thiserror = "1.0" float-cmp = "0.9" -floccus-proc = {version = "0.2.5", optional = true} +floccus-proc = { version = "0.2.5", optional = true } log = "0.4" +itertools = { version = "0.12", default-features = false, optional = true, features = ["use_std"]} +ndarray = { version = "0.15", default-features = false, optional = true } [dev-dependencies] criterion = "0.5.1" [features] +default = ["array_compute"] debug = ["floccus-proc"] double_precision = [] +array_compute = ["ndarray", "itertools"] [[bench]] name = "virtual_temperature" diff --git a/src/array_compute.rs b/src/array_compute.rs new file mode 100644 index 0000000..07018d1 --- /dev/null +++ b/src/array_compute.rs @@ -0,0 +1,59 @@ +#[macro_export] +macro_rules! compute_vec { + ($fn_id:expr,$slice1:expr) => { + $slice1 + .iter() + .map(|a| $fn_id(*a)) + .collect::, crate::errors::InputError>>() + }; + ($fn_id:expr,$slice1:expr,$slice2:expr) => { + itertools::izip!($slice1, $slice2) + .map(|(a, b)| $fn_id(a, b)) + .collect::, crate::errors::InputError>>() + }; + ($fn_id:expr,$slice1:expr,$slice2:expr,$slice3:expr) => { + itertools::izip!($slice1, $slice2, $slice3) + .map(|(a, b, c)| $fn_id(a, b, c)) + .collect::, crate::errors::InputError>>() + }; +} + +#[cfg(test)] +mod tests { + use float_cmp::assert_approx_eq; + + use crate::{vapour_pressure, vapour_pressure_deficit, Float}; + + #[test] + fn vec_macro_1arg() { + let temp = vec![300.0; 100]; + let result = compute_vec!(vapour_pressure::buck3_simplified, temp).unwrap(); + + assert_approx_eq!(Float, result[50], 3533.6421536199978, epsilon = 0.01); + } + + #[test] + fn vec_macro_2arg() { + let temp = vec![300.0; 100]; + let pressure = vec![101325.0; 100]; + let result = compute_vec!(vapour_pressure::buck3, temp, pressure).unwrap(); + + assert_approx_eq!(Float, result[50], 3548.5041048035896, epsilon = 0.01); + } + + #[test] + fn vec_macro_3arg() { + let temp = vec![300.0; 100]; + let pressure = vec![101325.0; 100]; + let relative_humidity = vec![0.5; 100]; + let result = compute_vec!( + vapour_pressure_deficit::general3, + temp, + relative_humidity, + pressure + ) + .unwrap(); + + assert_approx_eq!(Float, result[50], 1774.2520524017948, epsilon = 0.01); + } +} diff --git a/src/lib.rs b/src/lib.rs index 2bfc9f0..fe93e17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,8 @@ pub mod vapour_pressure_deficit; pub mod virtual_temperature; pub mod wet_bulb_potential_temperature; pub mod wet_bulb_temperature; +#[cfg(feature = "array_compute")] +pub mod array_compute; #[cfg(not(feature = "double_precision"))] type Float = f32; From d45b729fbe0944dee5aff5e963172f17138200bb Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 20 Feb 2024 00:09:28 +0100 Subject: [PATCH 004/102] preliminary finish array macros --- Cargo.toml | 8 +- src/array_compute.rs | 204 +++++++++++++++++++++++++++++++--- src/lib.rs | 4 + src/par_array_compute.rs | 234 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 432 insertions(+), 18 deletions(-) create mode 100644 src/par_array_compute.rs diff --git a/Cargo.toml b/Cargo.toml index c38bfd6..36cca03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,17 +16,21 @@ thiserror = "1.0" float-cmp = "0.9" floccus-proc = { version = "0.2.5", optional = true } log = "0.4" -itertools = { version = "0.12", default-features = false, optional = true, features = ["use_std"]} +itertools = { version = "0.12", default-features = false, optional = true, features = [ + "use_std", +] } ndarray = { version = "0.15", default-features = false, optional = true } +rayon = { version = "1.8", default-features = false, optional = true } [dev-dependencies] criterion = "0.5.1" [features] -default = ["array_compute"] +default = ["array_compute", "array_compute_parallel"] debug = ["floccus-proc"] double_precision = [] array_compute = ["ndarray", "itertools"] +array_compute_parallel = ["array_compute", "ndarray/rayon", "rayon"] [[bench]] name = "virtual_temperature" diff --git a/src/array_compute.rs b/src/array_compute.rs index 07018d1..b7efb47 100644 --- a/src/array_compute.rs +++ b/src/array_compute.rs @@ -1,33 +1,205 @@ +use itertools::izip; +use ndarray::{Array, ArrayView, Dimension, FoldWhile}; + +use crate::{errors::InputError, Float}; + #[macro_export] macro_rules! compute_vec { ($fn_id:expr,$slice1:expr) => { - $slice1 - .iter() - .map(|a| $fn_id(*a)) - .collect::, crate::errors::InputError>>() + $crate::array_compute::compute_vec_1($fn_id, $slice1) }; ($fn_id:expr,$slice1:expr,$slice2:expr) => { - itertools::izip!($slice1, $slice2) - .map(|(a, b)| $fn_id(a, b)) - .collect::, crate::errors::InputError>>() + $crate::array_compute::compute_vec_2($fn_id, $slice1, $slice2) }; ($fn_id:expr,$slice1:expr,$slice2:expr,$slice3:expr) => { - itertools::izip!($slice1, $slice2, $slice3) - .map(|(a, b, c)| $fn_id(a, b, c)) - .collect::, crate::errors::InputError>>() + $crate::array_compute::compute_vec_3($fn_id, $slice1, $slice2, $slice3) + }; +} + +#[macro_export] +macro_rules! compute_ndarray { + ($cmp_fn:expr,$vld_fn:expr,$arr1:expr) => { + $crate::array_compute::compute_ndarray_1($cmp_fn, $vld_fn, $arr1) }; + + ($cmp_fn:expr,$vld_fn:expr,$arr1:expr,$arr2:expr) => {{ + $crate::array_compute::compute_ndarray_2($cmp_fn, $vld_fn, $arr1, $arr2) + }}; + + ($cmp_fn:expr,$vld_fn:expr,$arr1:expr,$arr2:expr,$arr3:expr) => {{ + $crate::array_compute::compute_ndarray_3( + $cmp_fn, + $vld_fn, + $arr1, + $arr2, + $arr3, + ) + }}; +} + +#[doc(hidden)] +#[inline(always)] +pub fn compute_vec_1( + fn_id: fn(Float) -> Result, + slice1: &[Float], +) -> Result, InputError> { + slice1 + .iter() + .map(|a| fn_id(*a)) + .collect::, InputError>>() +} + +#[doc(hidden)] +#[inline(always)] +pub fn compute_vec_2( + fn_id: fn(Float, Float) -> Result, + slice1: &[Float], + slice2: &[Float], +) -> Result, InputError> { + izip!(slice1, slice2) + .map(|(&a, &b)| fn_id(a, b)) + .collect::, InputError>>() +} + +#[doc(hidden)] +#[inline(always)] +pub fn compute_vec_3( + fn_id: fn(Float, Float, Float) -> Result, + slice1: &[Float], + slice2: &[Float], + slice3: &[Float], +) -> Result, InputError> { + izip!(slice1, slice2, slice3) + .map(|(&a, &b, &c)| fn_id(a, b, c)) + .collect::, InputError>>() +} + +#[doc(hidden)] +#[inline(always)] +pub fn compute_ndarray_1( + cmp_fn: fn(Float) -> Float, + vld_fn: fn(Float) -> Result<(), InputError>, + arr1: ArrayView<'_, Float, D>, +) -> Result, InputError> { + ndarray::Zip::from(&arr1) + .fold_while(Ok(()), |_, &a| match vld_fn(a) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(ndarray::Zip::from(&arr1).map_collect(|&a| cmp_fn(a))) +} + +#[doc(hidden)] +#[inline(always)] +pub fn compute_ndarray_2( + cmp_fn: fn(Float, Float) -> Float, + vld_fn: fn(Float, Float) -> Result<(), InputError>, + arr1: ArrayView<'_, Float, D>, + arr2: ArrayView<'_, Float, D>, +) -> Result, InputError> { + ndarray::Zip::from(&arr1) + .and(&arr2) + .fold_while(Ok(()), |_, &a, &b| match vld_fn(a, b) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(ndarray::Zip::from(&arr1) + .and(&arr2) + .map_collect(|&a, &b| cmp_fn(a, b))) +} + +#[doc(hidden)] +#[inline(always)] +pub fn compute_ndarray_3( + cmp_fn: fn(Float, Float, Float) -> Float, + vld_fn: fn(Float, Float, Float) -> Result<(), InputError>, + arr1: ArrayView<'_, Float, D>, + arr2: ArrayView<'_, Float, D>, + arr3: ArrayView<'_, Float, D>, +) -> Result, InputError> { + ndarray::Zip::from(&arr1) + .and(&arr2) + .and(&arr3) + .fold_while(Ok(()), |_, &a, &b, &c| match vld_fn(a, b, c) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(ndarray::Zip::from(&arr1) + .and(&arr2) + .and(&arr3) + .map_collect(|&a, &b, &c| cmp_fn(a, b, c))) } #[cfg(test)] mod tests { use float_cmp::assert_approx_eq; + use ndarray::Array2; - use crate::{vapour_pressure, vapour_pressure_deficit, Float}; + use crate::{ + vapour_pressure::{self}, + vapour_pressure_deficit, Float, + }; + + #[test] + fn arr_macro_1arg() -> Result<(), crate::errors::InputError> { + let temp = Array2::from_elem((10, 10), 300.0); + let result = compute_ndarray!( + vapour_pressure::buck3_simplified_unchecked, + vapour_pressure::buck3_simplified_validate, + temp.view() + )?; + + assert_approx_eq!(Float, result[[5, 5]], 3533.6421536199978, epsilon = 0.01); + + Ok(()) + } + + #[test] + fn arr_macro_2arg() -> Result<(), crate::errors::InputError> { + let temp = Array2::from_elem((10, 10), 300.0); + let pressure = Array2::from_elem((10, 10), 101325.0); + + let result = compute_ndarray!( + vapour_pressure::buck3_unchecked, + vapour_pressure::buck3_validate, + temp.view(), + pressure.view() + )?; + + assert_approx_eq!(Float, result[[5, 5]], 3548.5041048035896, epsilon = 0.01); + + Ok(()) + } + + #[test] + fn arr_macro_3arg() -> Result<(), crate::errors::InputError> { + let temp = Array2::from_elem((10, 10), 300.0); + let pressure = Array2::from_elem((10, 10), 101325.0); + let relative_humidity = Array2::from_elem((10, 10), 0.5); + + let result = compute_ndarray!( + vapour_pressure_deficit::general3_unchecked, + vapour_pressure_deficit::general3_validate, + temp.view(), + relative_humidity.view(), + pressure.view() + )?; + + assert_approx_eq!(Float, result[[5, 5]], 1774.2520524017948, epsilon = 0.01); + + Ok(()) + } #[test] fn vec_macro_1arg() { let temp = vec![300.0; 100]; - let result = compute_vec!(vapour_pressure::buck3_simplified, temp).unwrap(); + let result = compute_vec!(vapour_pressure::buck3_simplified, &temp).unwrap(); assert_approx_eq!(Float, result[50], 3533.6421536199978, epsilon = 0.01); } @@ -36,7 +208,7 @@ mod tests { fn vec_macro_2arg() { let temp = vec![300.0; 100]; let pressure = vec![101325.0; 100]; - let result = compute_vec!(vapour_pressure::buck3, temp, pressure).unwrap(); + let result = compute_vec!(vapour_pressure::buck3, &temp, &pressure).unwrap(); assert_approx_eq!(Float, result[50], 3548.5041048035896, epsilon = 0.01); } @@ -48,9 +220,9 @@ mod tests { let relative_humidity = vec![0.5; 100]; let result = compute_vec!( vapour_pressure_deficit::general3, - temp, - relative_humidity, - pressure + &temp, + &relative_humidity, + &pressure ) .unwrap(); diff --git a/src/lib.rs b/src/lib.rs index fe93e17..003c521 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,9 +93,13 @@ pub mod vapour_pressure_deficit; pub mod virtual_temperature; pub mod wet_bulb_potential_temperature; pub mod wet_bulb_temperature; + #[cfg(feature = "array_compute")] pub mod array_compute; +#[cfg(feature = "array_compute_parallel")] +pub mod par_array_compute; + #[cfg(not(feature = "double_precision"))] type Float = f32; diff --git a/src/par_array_compute.rs b/src/par_array_compute.rs new file mode 100644 index 0000000..1fd17b0 --- /dev/null +++ b/src/par_array_compute.rs @@ -0,0 +1,234 @@ +use itertools::izip; +use ndarray::{Array, ArrayView, Dimension, FoldWhile}; +use rayon::iter::{IntoParallelRefIterator, ParallelBridge, ParallelIterator}; + +use crate::{errors::InputError, Float}; + +#[macro_export] +macro_rules! par_compute_vec { + ($fn_id:expr,$slice1:expr) => { + $crate::par_array_compute::compute_vec_1($fn_id, $slice1) + }; + ($fn_id:expr,$slice1:expr,$slice2:expr) => { + $crate::par_array_compute::compute_vec_2($fn_id, $slice1, $slice2) + }; + ($fn_id:expr,$slice1:expr,$slice2:expr,$slice3:expr) => { + $crate::par_array_compute::compute_vec_3($fn_id, $slice1, $slice2, $slice3) + }; +} + +#[macro_export] +macro_rules! par_compute_ndarray { + ($cmp_fn:expr,$vld_fn:expr,$arr1:expr) => { + $crate::par_array_compute::compute_ndarray_1($cmp_fn, $vld_fn, $arr1.view()) + }; + + ($cmp_fn:expr,$vld_fn:expr,$arr1:expr,$arr2:expr) => { + $crate::par_array_compute::compute_ndarray_2($cmp_fn, $vld_fn, $arr1.view(), $arr2.view()) + }; + + ($cmp_fn:expr,$vld_fn:expr,$arr1:expr,$arr2:expr,$arr3:expr) => { + $crate::par_array_compute::compute_ndarray_3( + $cmp_fn, + $vld_fn, + $arr1.view(), + $arr2.view(), + $arr3.view(), + ) + }; +} + +#[doc(hidden)] +#[inline(always)] +pub fn compute_vec_1( + fn_id: fn(Float) -> Result, + slice1: &[Float], +) -> Result, InputError> { + slice1 + .par_iter() + .map(|a| fn_id(*a)) + .collect::, InputError>>() +} + +#[doc(hidden)] +#[inline(always)] +pub fn compute_vec_2( + fn_id: fn(Float, Float) -> Result, + slice1: &[Float], + slice2: &[Float], +) -> Result, InputError> { + izip!(slice1, slice2) + .par_bridge() + .map(|(&a, &b)| fn_id(a, b)) + .collect::, InputError>>() +} + +#[doc(hidden)] +#[inline(always)] +pub fn compute_vec_3( + fn_id: fn(Float, Float, Float) -> Result, + slice1: &[Float], + slice2: &[Float], + slice3: &[Float], +) -> Result, InputError> { + izip!(slice1, slice2, slice3) + .par_bridge() + .map(|(&a, &b, &c)| fn_id(a, b, c)) + .collect::, InputError>>() +} + +#[doc(hidden)] +#[inline(always)] +pub fn compute_ndarray_1( + cmp_fn: fn(Float) -> Float, + vld_fn: fn(Float) -> Result<(), InputError>, + arr1: ArrayView<'_, Float, D>, +) -> Result, InputError> { + ndarray::Zip::from(&arr1) + .fold_while(Ok(()), |_, &a| match vld_fn(a) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(ndarray::Zip::from(&arr1).par_map_collect(|&a| cmp_fn(a))) +} + +#[doc(hidden)] +#[inline(always)] +pub fn compute_ndarray_2( + cmp_fn: fn(Float, Float) -> Float, + vld_fn: fn(Float, Float) -> Result<(), InputError>, + arr1: ArrayView<'_, Float, D>, + arr2: ArrayView<'_, Float, D>, +) -> Result, InputError> { + ndarray::Zip::from(&arr1) + .and(&arr2) + .fold_while(Ok(()), |_, &a, &b| match vld_fn(a, b) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(ndarray::Zip::from(&arr1) + .and(&arr2) + .par_map_collect(|&a, &b| cmp_fn(a, b))) +} + +#[doc(hidden)] +#[inline(always)] +pub fn compute_ndarray_3( + cmp_fn: fn(Float, Float, Float) -> Float, + vld_fn: fn(Float, Float, Float) -> Result<(), InputError>, + arr1: ArrayView<'_, Float, D>, + arr2: ArrayView<'_, Float, D>, + arr3: ArrayView<'_, Float, D>, +) -> Result, InputError> { + ndarray::Zip::from(&arr1) + .and(&arr2) + .and(&arr3) + .fold_while(Ok(()), |_, &a, &b, &c| match vld_fn(a, b, c) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(ndarray::Zip::from(&arr1) + .and(&arr2) + .and(&arr3) + .par_map_collect(|&a, &b, &c| cmp_fn(a, b, c))) +} + +#[cfg(test)] +mod tests { + use float_cmp::assert_approx_eq; + use ndarray::Array2; + + use crate::{ + vapour_pressure::{self}, + vapour_pressure_deficit, Float, + }; + + #[test] + fn arr_macro_1arg() -> Result<(), crate::errors::InputError> { + let temp = Array2::from_elem((10, 10), 300.0); + let result = par_compute_ndarray!( + vapour_pressure::buck3_simplified_unchecked, + vapour_pressure::buck3_simplified_validate, + temp + )?; + + assert_approx_eq!(Float, result[[5, 5]], 3533.6421536199978, epsilon = 0.01); + + Ok(()) + } + + #[test] + fn arr_macro_2arg() -> Result<(), crate::errors::InputError> { + let temp = Array2::from_elem((10, 10), 300.0); + let pressure = Array2::from_elem((10, 10), 101325.0); + + let result = par_compute_ndarray!( + vapour_pressure::buck3_unchecked, + vapour_pressure::buck3_validate, + temp, + pressure + )?; + + assert_approx_eq!(Float, result[[5, 5]], 3548.5041048035896, epsilon = 0.01); + + Ok(()) + } + + #[test] + fn arr_macro_3arg() -> Result<(), crate::errors::InputError> { + let temp = Array2::from_elem((10, 10), 300.0); + let pressure = Array2::from_elem((10, 10), 101325.0); + let relative_humidity = Array2::from_elem((10, 10), 0.5); + + let result = par_compute_ndarray!( + vapour_pressure_deficit::general3_unchecked, + vapour_pressure_deficit::general3_validate, + temp, + relative_humidity, + pressure + )?; + + assert_approx_eq!(Float, result[[5, 5]], 1774.2520524017948, epsilon = 0.01); + + Ok(()) + } + + #[test] + fn vec_macro_1arg() { + let temp = vec![300.0; 100]; + let result = par_compute_vec!(vapour_pressure::buck3_simplified, &temp).unwrap(); + + assert_approx_eq!(Float, result[50], 3533.6421536199978, epsilon = 0.01); + } + + #[test] + fn vec_macro_2arg() { + let temp = vec![300.0; 100]; + let pressure = vec![101325.0; 100]; + let result = par_compute_vec!(vapour_pressure::buck3, &temp, &pressure).unwrap(); + + assert_approx_eq!(Float, result[50], 3548.5041048035896, epsilon = 0.01); + } + + #[test] + fn vec_macro_3arg() { + let temp = vec![300.0; 100]; + let pressure = vec![101325.0; 100]; + let relative_humidity = vec![0.5; 100]; + let result = par_compute_vec!( + vapour_pressure_deficit::general3, + &temp, + &relative_humidity, + &pressure + ) + .unwrap(); + + assert_approx_eq!(Float, result[50], 1774.2520524017948, epsilon = 0.01); + } +} From 48fe5da192224f4b4c6453852615d95f0a126db4 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:31:53 +0100 Subject: [PATCH 005/102] start compute macros --- src/{array_compute.rs => compute_macros.rs} | 120 ++++++++++++++++---- src/lib.rs | 3 +- src/wet_bulb_potential_temperature.rs | 43 +++++++ 3 files changed, 139 insertions(+), 27 deletions(-) rename src/{array_compute.rs => compute_macros.rs} (63%) diff --git a/src/array_compute.rs b/src/compute_macros.rs similarity index 63% rename from src/array_compute.rs rename to src/compute_macros.rs index b7efb47..69709d6 100644 --- a/src/array_compute.rs +++ b/src/compute_macros.rs @@ -3,40 +3,110 @@ use ndarray::{Array, ArrayView, Dimension, FoldWhile}; use crate::{errors::InputError, Float}; -#[macro_export] -macro_rules! compute_vec { - ($fn_id:expr,$slice1:expr) => { - $crate::array_compute::compute_vec_1($fn_id, $slice1) +macro_rules! generate_compute { + ($qnt:tt, $arg1:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute($arg1: Float) -> Result { + Self::validate_inputs($arg1)?; + Ok(Self::compute_unchecked($arg1)) + } + } }; - ($fn_id:expr,$slice1:expr,$slice2:expr) => { - $crate::array_compute::compute_vec_2($fn_id, $slice1, $slice2) + + ($qnt:tt, $arg1:tt, $arg2:tt) => { + impl $qnt { + pub fn compute($arg1: Float, $arg2: Float) -> Result { + Self::validate_inputs($arg1, $b)?; + Ok(Self::compute_unchecked($arg1, $b)) + } + } }; - ($fn_id:expr,$slice1:expr,$slice2:expr,$slice3:expr) => { - $crate::array_compute::compute_vec_3($fn_id, $slice1, $slice2, $slice3) + + ($qnt:tt, $arg1:tt, $arg2:tt, $arg3:tt) => { + impl $qnt { + pub fn compute($arg1: Float, $arg2: Float, $arg3: Float) -> Result { + Self::validate_inputs($arg1, $arg2, $arg3)?; + Ok(Self::compute_unchecked($arg1, $arg2, $arg3)) + } + } }; } -#[macro_export] -macro_rules! compute_ndarray { - ($cmp_fn:expr,$vld_fn:expr,$arr1:expr) => { - $crate::array_compute::compute_ndarray_1($cmp_fn, $vld_fn, $arr1) +macro_rules! generate_vec_compute { + ($qnt:tt, $slice1:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute_vec($slice1: &[Float]) -> Result, InputError> { + $slice1 + .iter() + .map(|a| Self::compute(*a)) + .collect::, InputError>>() + } + } }; +} - ($cmp_fn:expr,$vld_fn:expr,$arr1:expr,$arr2:expr) => {{ - $crate::array_compute::compute_ndarray_2($cmp_fn, $vld_fn, $arr1, $arr2) - }}; - - ($cmp_fn:expr,$vld_fn:expr,$arr1:expr,$arr2:expr,$arr3:expr) => {{ - $crate::array_compute::compute_ndarray_3( - $cmp_fn, - $vld_fn, - $arr1, - $arr2, - $arr3, - ) - }}; +macro_rules! generate_ndarray_compute { + ($qnt:tt, $arr1:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute_ndarray( + $arr1: &Array, + ) -> Result, InputError> { + ndarray::Zip::from($arr1) + .fold_while(Ok(()), |_, &a| match Self::validate_inputs(a) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(ndarray::Zip::from($arr1).map_collect(|&a| Self::compute_unchecked(a))) + } + } + }; +} + +macro_rules! generate_par_vec_compute { + ($qnt:tt, $slice1:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute_vec_parallel($slice1: &[Float]) -> Result, InputError> { + $slice1 + .par_iter() + .map(|a| Self::compute(*a)) + .collect::, InputError>>() + } + } + }; +} + +macro_rules! generate_par_ndarray_compute { + ($qnt:tt, $arr1:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute_ndarray_parallel( + $arr1: &Array, + ) -> Result, InputError> { + ndarray::Zip::from($arr1) + .fold_while(Ok(()), |_, &a| match Self::validate_inputs(a) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(ndarray::Zip::from($arr1).par_map_collect(|&a| Self::compute_unchecked(a))) + } + } + }; } +pub(crate) use generate_compute; +pub(crate) use generate_ndarray_compute; +pub(crate) use generate_par_ndarray_compute; +pub(crate) use generate_par_vec_compute; +pub(crate) use generate_vec_compute; + #[doc(hidden)] #[inline(always)] pub fn compute_vec_1( diff --git a/src/lib.rs b/src/lib.rs index 003c521..c1ed518 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,8 +94,7 @@ pub mod virtual_temperature; pub mod wet_bulb_potential_temperature; pub mod wet_bulb_temperature; -#[cfg(feature = "array_compute")] -pub mod array_compute; +pub(crate) mod compute_macros; #[cfg(feature = "array_compute_parallel")] pub mod par_array_compute; diff --git a/src/wet_bulb_potential_temperature.rs b/src/wet_bulb_potential_temperature.rs index f0947f6..f6b2e29 100644 --- a/src/wet_bulb_potential_temperature.rs +++ b/src/wet_bulb_potential_temperature.rs @@ -1,11 +1,54 @@ //!Functions to calculate dry bulb potential temperature of unsaturated air in K. +use crate::compute_macros::{generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, generate_par_vec_compute, generate_vec_compute}; +use ndarray::{Array, Dimension, FoldWhile}; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use crate::Float; use crate::{ constants::{C_P, R_D, ZERO_CELSIUS}, errors::InputError, }; +/// Formula for computing wet bulb potential temperature from equivalent potential temperature. +/// +/// Derived by R. Davies-Jones (2008) [(doi:10.1175/2007MWR2224.1)](https://doi.org/10.1175/2007MWR2224.1) +/// +/// # Errors +/// +/// Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ +/// Valid `temperature` range: 257K - 377K\ +pub struct DaviesJones1; + +impl DaviesJones1 { + #[allow(missing_docs)] + #[inline(always)] + pub fn compute_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); + result + ZERO_CELSIUS + } + + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + pub fn validate_inputs(equivalent_potential_temperature: Float) -> Result<(), InputError> { + if !(257.0..=377.0).contains(&equivalent_potential_temperature) { + return Err(InputError::OutOfRange(String::from( + "equivalent_potential_temperature", + ))); + } + + Ok(()) + } +} + +generate_compute!(DaviesJones1, equivalent_potential_temperature); +generate_vec_compute!(DaviesJones1, equivalent_potential_temperature); +generate_ndarray_compute!(DaviesJones1, equivalent_potential_temperature); +generate_par_vec_compute!(DaviesJones1, equivalent_potential_temperature); +generate_par_ndarray_compute!(DaviesJones1, equivalent_potential_temperature); + #[cfg(feature = "debug")] use floccus_proc::logerr; From 2e7cb3cbcdb843917e2b0968e5f6ff2d9045c542 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:17:17 +0100 Subject: [PATCH 006/102] preliminary finish macros --- src/compute_macros.rs | 450 ++++++++++++++------------ src/par_array_compute.rs | 234 -------------- src/wet_bulb_potential_temperature.rs | 43 +-- 3 files changed, 256 insertions(+), 471 deletions(-) delete mode 100644 src/par_array_compute.rs diff --git a/src/compute_macros.rs b/src/compute_macros.rs index 69709d6..6c10a7f 100644 --- a/src/compute_macros.rs +++ b/src/compute_macros.rs @@ -1,8 +1,3 @@ -use itertools::izip; -use ndarray::{Array, ArrayView, Dimension, FoldWhile}; - -use crate::{errors::InputError, Float}; - macro_rules! generate_compute { ($qnt:tt, $arg1:tt) => { impl $qnt { @@ -40,7 +35,35 @@ macro_rules! generate_vec_compute { pub fn compute_vec($slice1: &[Float]) -> Result, InputError> { $slice1 .iter() - .map(|a| Self::compute(*a)) + .map(|&a| Self::compute(a)) + .collect::, InputError>>() + } + } + }; + + ($qnt:tt, $slice1:tt, $slice2:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute_vec( + $slice1: &[Float], + $slice2: &[Float], + ) -> Result, InputError> { + izip!($slice1, $slice2) + .map(|(&a, &b)| Self::compute(a, b)) + .collect::, InputError>>() + } + } + }; + + ($qnt:tt, $slice1:tt, $slice2:tt, $slice3:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute_vec( + $slice1: &[Float], + $slice2: &[Float], + ) -> Result, InputError> { + izip!($slice1, $slice2, $slice3) + .map(|(&a, &b, &c)| Self::compute(a, b, c)) .collect::, InputError>>() } } @@ -65,6 +88,55 @@ macro_rules! generate_ndarray_compute { } } }; + + ($qnt:tt, $arr1:tt, $arr2:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute_ndarray( + $arr1: &Array, + $arr2: &Array, + ) -> Result, InputError> { + ndarray::Zip::from($arr1) + .and($arr2) + .fold_while(Ok(()), |_, &a, &b| match Self::validate_inputs(a, b) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(ndarray::Zip::from($arr1) + .and($arr2) + .map_collect(|&a, &b| Self::compute_unchecked(a, b))) + } + } + }; + + ($qnt:tt, $arr1:tt, $arr2:tt, $arr3:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute_ndarray( + $arr1: &Array, + $arr2: &Array, + $arr3: &Array, + ) -> Result, InputError> { + ndarray::Zip::from($arr1) + .and($arr2) + .and($arr3) + .fold_while(Ok(()), |_, &a, &b, &c| { + match Self::validate_inputs(a, b, c) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + } + }) + .into_inner()?; + + Ok(ndarray::Zip::from($arr1) + .and($arr2) + .and($arr3) + .map_collect(|&a, &b, &c| Self::compute_unchecked(a, b, c))) + } + } + }; } macro_rules! generate_par_vec_compute { @@ -74,7 +146,37 @@ macro_rules! generate_par_vec_compute { pub fn compute_vec_parallel($slice1: &[Float]) -> Result, InputError> { $slice1 .par_iter() - .map(|a| Self::compute(*a)) + .map(|&a| Self::compute(a)) + .collect::, InputError>>() + } + } + }; + + ($qnt:tt, $slice1:tt, $slice2:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute_vec_parallel( + $slice1: &[Float], + $slice2: &[Float], + ) -> Result, InputError> { + izip!($slice1, $slice2) + .par_bridge() + .map(|(&a, &b)| Self::compute(a, b)) + .collect::, InputError>>() + } + } + }; + + ($qnt:tt, $slice1:tt, $slice2:tt, $slice3:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute_vec_parallel( + $slice1: &[Float], + $slice2: &[Float], + ) -> Result, InputError> { + izip!($slice1, $slice2, $slice3) + .par_bridge() + .map(|(&a, &b, &c)| Self::compute(a, b, c)) .collect::, InputError>>() } } @@ -99,6 +201,55 @@ macro_rules! generate_par_ndarray_compute { } } }; + + ($qnt:tt, $arr1:tt, $arr2:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute_ndarray_parallel( + $arr1: &Array, + $arr2: &Array, + ) -> Result, InputError> { + ndarray::Zip::from($arr1) + .and($arr2) + .fold_while(Ok(()), |_, &a, &b| match Self::validate_inputs(a, b) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(ndarray::Zip::from($arr1) + .and($arr2) + .par_map_collect(|&a, &b| Self::compute_unchecked(a, b))) + } + } + }; + + ($qnt:tt, $arr1:tt, $arr2:tt, $arr3:tt) => { + impl $qnt { + #[allow(missing_docs)] + pub fn compute_ndarray_parallel( + $arr1: &Array, + $arr2: &Array, + $arr3: &Array, + ) -> Result, InputError> { + ndarray::Zip::from($arr1) + .and($arr2) + .and($arr3) + .fold_while(Ok(()), |_, &a, &b, &c| { + match Self::validate_inputs(a, b, c) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + } + }) + .into_inner()?; + + Ok(ndarray::Zip::from($arr1) + .and($arr2) + .and($arr3) + .par_map_collect(|&a, &b, &c| Self::compute_unchecked(a, b, c))) + } + } + }; } pub(crate) use generate_compute; @@ -107,195 +258,96 @@ pub(crate) use generate_par_ndarray_compute; pub(crate) use generate_par_vec_compute; pub(crate) use generate_vec_compute; -#[doc(hidden)] -#[inline(always)] -pub fn compute_vec_1( - fn_id: fn(Float) -> Result, - slice1: &[Float], -) -> Result, InputError> { - slice1 - .iter() - .map(|a| fn_id(*a)) - .collect::, InputError>>() -} - -#[doc(hidden)] -#[inline(always)] -pub fn compute_vec_2( - fn_id: fn(Float, Float) -> Result, - slice1: &[Float], - slice2: &[Float], -) -> Result, InputError> { - izip!(slice1, slice2) - .map(|(&a, &b)| fn_id(a, b)) - .collect::, InputError>>() -} - -#[doc(hidden)] -#[inline(always)] -pub fn compute_vec_3( - fn_id: fn(Float, Float, Float) -> Result, - slice1: &[Float], - slice2: &[Float], - slice3: &[Float], -) -> Result, InputError> { - izip!(slice1, slice2, slice3) - .map(|(&a, &b, &c)| fn_id(a, b, c)) - .collect::, InputError>>() -} - -#[doc(hidden)] -#[inline(always)] -pub fn compute_ndarray_1( - cmp_fn: fn(Float) -> Float, - vld_fn: fn(Float) -> Result<(), InputError>, - arr1: ArrayView<'_, Float, D>, -) -> Result, InputError> { - ndarray::Zip::from(&arr1) - .fold_while(Ok(()), |_, &a| match vld_fn(a) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }) - .into_inner()?; - - Ok(ndarray::Zip::from(&arr1).map_collect(|&a| cmp_fn(a))) -} - -#[doc(hidden)] -#[inline(always)] -pub fn compute_ndarray_2( - cmp_fn: fn(Float, Float) -> Float, - vld_fn: fn(Float, Float) -> Result<(), InputError>, - arr1: ArrayView<'_, Float, D>, - arr2: ArrayView<'_, Float, D>, -) -> Result, InputError> { - ndarray::Zip::from(&arr1) - .and(&arr2) - .fold_while(Ok(()), |_, &a, &b| match vld_fn(a, b) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }) - .into_inner()?; - - Ok(ndarray::Zip::from(&arr1) - .and(&arr2) - .map_collect(|&a, &b| cmp_fn(a, b))) -} - -#[doc(hidden)] -#[inline(always)] -pub fn compute_ndarray_3( - cmp_fn: fn(Float, Float, Float) -> Float, - vld_fn: fn(Float, Float, Float) -> Result<(), InputError>, - arr1: ArrayView<'_, Float, D>, - arr2: ArrayView<'_, Float, D>, - arr3: ArrayView<'_, Float, D>, -) -> Result, InputError> { - ndarray::Zip::from(&arr1) - .and(&arr2) - .and(&arr3) - .fold_while(Ok(()), |_, &a, &b, &c| match vld_fn(a, b, c) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }) - .into_inner()?; - - Ok(ndarray::Zip::from(&arr1) - .and(&arr2) - .and(&arr3) - .map_collect(|&a, &b, &c| cmp_fn(a, b, c))) -} - -#[cfg(test)] -mod tests { - use float_cmp::assert_approx_eq; - use ndarray::Array2; - - use crate::{ - vapour_pressure::{self}, - vapour_pressure_deficit, Float, - }; - - #[test] - fn arr_macro_1arg() -> Result<(), crate::errors::InputError> { - let temp = Array2::from_elem((10, 10), 300.0); - let result = compute_ndarray!( - vapour_pressure::buck3_simplified_unchecked, - vapour_pressure::buck3_simplified_validate, - temp.view() - )?; - - assert_approx_eq!(Float, result[[5, 5]], 3533.6421536199978, epsilon = 0.01); - - Ok(()) - } - - #[test] - fn arr_macro_2arg() -> Result<(), crate::errors::InputError> { - let temp = Array2::from_elem((10, 10), 300.0); - let pressure = Array2::from_elem((10, 10), 101325.0); - - let result = compute_ndarray!( - vapour_pressure::buck3_unchecked, - vapour_pressure::buck3_validate, - temp.view(), - pressure.view() - )?; - - assert_approx_eq!(Float, result[[5, 5]], 3548.5041048035896, epsilon = 0.01); - - Ok(()) - } - - #[test] - fn arr_macro_3arg() -> Result<(), crate::errors::InputError> { - let temp = Array2::from_elem((10, 10), 300.0); - let pressure = Array2::from_elem((10, 10), 101325.0); - let relative_humidity = Array2::from_elem((10, 10), 0.5); - - let result = compute_ndarray!( - vapour_pressure_deficit::general3_unchecked, - vapour_pressure_deficit::general3_validate, - temp.view(), - relative_humidity.view(), - pressure.view() - )?; - - assert_approx_eq!(Float, result[[5, 5]], 1774.2520524017948, epsilon = 0.01); - - Ok(()) - } - - #[test] - fn vec_macro_1arg() { - let temp = vec![300.0; 100]; - let result = compute_vec!(vapour_pressure::buck3_simplified, &temp).unwrap(); - - assert_approx_eq!(Float, result[50], 3533.6421536199978, epsilon = 0.01); - } - - #[test] - fn vec_macro_2arg() { - let temp = vec![300.0; 100]; - let pressure = vec![101325.0; 100]; - let result = compute_vec!(vapour_pressure::buck3, &temp, &pressure).unwrap(); - - assert_approx_eq!(Float, result[50], 3548.5041048035896, epsilon = 0.01); - } - - #[test] - fn vec_macro_3arg() { - let temp = vec![300.0; 100]; - let pressure = vec![101325.0; 100]; - let relative_humidity = vec![0.5; 100]; - let result = compute_vec!( - vapour_pressure_deficit::general3, - &temp, - &relative_humidity, - &pressure - ) - .unwrap(); - - assert_approx_eq!(Float, result[50], 1774.2520524017948, epsilon = 0.01); - } -} +// #[cfg(test)] +// mod tests { +// use float_cmp::assert_approx_eq; +// use ndarray::Array2; + +// use crate::{ +// vapour_pressure::{self}, +// vapour_pressure_deficit, Float, +// }; + +// #[test] +// fn arr_macro_1arg() -> Result<(), crate::errors::InputError> { +// let temp = Array2::from_elem((10, 10), 300.0); +// let result = compute_ndarray!( +// vapour_pressure::buck3_simplified_unchecked, +// vapour_pressure::buck3_simplified_validate, +// temp.view() +// )?; + +// assert_approx_eq!(Float, result[[5, 5]], 3533.6421536199978, epsilon = 0.01); + +// Ok(()) +// } + +// #[test] +// fn arr_macro_2arg() -> Result<(), crate::errors::InputError> { +// let temp = Array2::from_elem((10, 10), 300.0); +// let pressure = Array2::from_elem((10, 10), 101325.0); + +// let result = compute_ndarray!( +// vapour_pressure::buck3_unchecked, +// vapour_pressure::buck3_validate, +// temp.view(), +// pressure.view() +// )?; + +// assert_approx_eq!(Float, result[[5, 5]], 3548.5041048035896, epsilon = 0.01); + +// Ok(()) +// } + +// #[test] +// fn arr_macro_3arg() -> Result<(), crate::errors::InputError> { +// let temp = Array2::from_elem((10, 10), 300.0); +// let pressure = Array2::from_elem((10, 10), 101325.0); +// let relative_humidity = Array2::from_elem((10, 10), 0.5); + +// let result = compute_ndarray!( +// vapour_pressure_deficit::general3_unchecked, +// vapour_pressure_deficit::general3_validate, +// temp.view(), +// relative_humidity.view(), +// pressure.view() +// )?; + +// assert_approx_eq!(Float, result[[5, 5]], 1774.2520524017948, epsilon = 0.01); + +// Ok(()) +// } + +// #[test] +// fn vec_macro_1arg() { +// let temp = vec![300.0; 100]; +// let result = compute_vec!(vapour_pressure::buck3_simplified, &temp).unwrap(); + +// assert_approx_eq!(Float, result[50], 3533.6421536199978, epsilon = 0.01); +// } + +// #[test] +// fn vec_macro_2arg() { +// let temp = vec![300.0; 100]; +// let pressure = vec![101325.0; 100]; +// let result = compute_vec!(vapour_pressure::buck3, &temp, &pressure).unwrap(); + +// assert_approx_eq!(Float, result[50], 3548.5041048035896, epsilon = 0.01); +// } + +// #[test] +// fn vec_macro_3arg() { +// let temp = vec![300.0; 100]; +// let pressure = vec![101325.0; 100]; +// let relative_humidity = vec![0.5; 100]; +// let result = compute_vec!( +// vapour_pressure_deficit::general3, +// &temp, +// &relative_humidity, +// &pressure +// ) +// .unwrap(); + +// assert_approx_eq!(Float, result[50], 1774.2520524017948, epsilon = 0.01); +// } +// } diff --git a/src/par_array_compute.rs b/src/par_array_compute.rs deleted file mode 100644 index 1fd17b0..0000000 --- a/src/par_array_compute.rs +++ /dev/null @@ -1,234 +0,0 @@ -use itertools::izip; -use ndarray::{Array, ArrayView, Dimension, FoldWhile}; -use rayon::iter::{IntoParallelRefIterator, ParallelBridge, ParallelIterator}; - -use crate::{errors::InputError, Float}; - -#[macro_export] -macro_rules! par_compute_vec { - ($fn_id:expr,$slice1:expr) => { - $crate::par_array_compute::compute_vec_1($fn_id, $slice1) - }; - ($fn_id:expr,$slice1:expr,$slice2:expr) => { - $crate::par_array_compute::compute_vec_2($fn_id, $slice1, $slice2) - }; - ($fn_id:expr,$slice1:expr,$slice2:expr,$slice3:expr) => { - $crate::par_array_compute::compute_vec_3($fn_id, $slice1, $slice2, $slice3) - }; -} - -#[macro_export] -macro_rules! par_compute_ndarray { - ($cmp_fn:expr,$vld_fn:expr,$arr1:expr) => { - $crate::par_array_compute::compute_ndarray_1($cmp_fn, $vld_fn, $arr1.view()) - }; - - ($cmp_fn:expr,$vld_fn:expr,$arr1:expr,$arr2:expr) => { - $crate::par_array_compute::compute_ndarray_2($cmp_fn, $vld_fn, $arr1.view(), $arr2.view()) - }; - - ($cmp_fn:expr,$vld_fn:expr,$arr1:expr,$arr2:expr,$arr3:expr) => { - $crate::par_array_compute::compute_ndarray_3( - $cmp_fn, - $vld_fn, - $arr1.view(), - $arr2.view(), - $arr3.view(), - ) - }; -} - -#[doc(hidden)] -#[inline(always)] -pub fn compute_vec_1( - fn_id: fn(Float) -> Result, - slice1: &[Float], -) -> Result, InputError> { - slice1 - .par_iter() - .map(|a| fn_id(*a)) - .collect::, InputError>>() -} - -#[doc(hidden)] -#[inline(always)] -pub fn compute_vec_2( - fn_id: fn(Float, Float) -> Result, - slice1: &[Float], - slice2: &[Float], -) -> Result, InputError> { - izip!(slice1, slice2) - .par_bridge() - .map(|(&a, &b)| fn_id(a, b)) - .collect::, InputError>>() -} - -#[doc(hidden)] -#[inline(always)] -pub fn compute_vec_3( - fn_id: fn(Float, Float, Float) -> Result, - slice1: &[Float], - slice2: &[Float], - slice3: &[Float], -) -> Result, InputError> { - izip!(slice1, slice2, slice3) - .par_bridge() - .map(|(&a, &b, &c)| fn_id(a, b, c)) - .collect::, InputError>>() -} - -#[doc(hidden)] -#[inline(always)] -pub fn compute_ndarray_1( - cmp_fn: fn(Float) -> Float, - vld_fn: fn(Float) -> Result<(), InputError>, - arr1: ArrayView<'_, Float, D>, -) -> Result, InputError> { - ndarray::Zip::from(&arr1) - .fold_while(Ok(()), |_, &a| match vld_fn(a) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }) - .into_inner()?; - - Ok(ndarray::Zip::from(&arr1).par_map_collect(|&a| cmp_fn(a))) -} - -#[doc(hidden)] -#[inline(always)] -pub fn compute_ndarray_2( - cmp_fn: fn(Float, Float) -> Float, - vld_fn: fn(Float, Float) -> Result<(), InputError>, - arr1: ArrayView<'_, Float, D>, - arr2: ArrayView<'_, Float, D>, -) -> Result, InputError> { - ndarray::Zip::from(&arr1) - .and(&arr2) - .fold_while(Ok(()), |_, &a, &b| match vld_fn(a, b) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }) - .into_inner()?; - - Ok(ndarray::Zip::from(&arr1) - .and(&arr2) - .par_map_collect(|&a, &b| cmp_fn(a, b))) -} - -#[doc(hidden)] -#[inline(always)] -pub fn compute_ndarray_3( - cmp_fn: fn(Float, Float, Float) -> Float, - vld_fn: fn(Float, Float, Float) -> Result<(), InputError>, - arr1: ArrayView<'_, Float, D>, - arr2: ArrayView<'_, Float, D>, - arr3: ArrayView<'_, Float, D>, -) -> Result, InputError> { - ndarray::Zip::from(&arr1) - .and(&arr2) - .and(&arr3) - .fold_while(Ok(()), |_, &a, &b, &c| match vld_fn(a, b, c) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }) - .into_inner()?; - - Ok(ndarray::Zip::from(&arr1) - .and(&arr2) - .and(&arr3) - .par_map_collect(|&a, &b, &c| cmp_fn(a, b, c))) -} - -#[cfg(test)] -mod tests { - use float_cmp::assert_approx_eq; - use ndarray::Array2; - - use crate::{ - vapour_pressure::{self}, - vapour_pressure_deficit, Float, - }; - - #[test] - fn arr_macro_1arg() -> Result<(), crate::errors::InputError> { - let temp = Array2::from_elem((10, 10), 300.0); - let result = par_compute_ndarray!( - vapour_pressure::buck3_simplified_unchecked, - vapour_pressure::buck3_simplified_validate, - temp - )?; - - assert_approx_eq!(Float, result[[5, 5]], 3533.6421536199978, epsilon = 0.01); - - Ok(()) - } - - #[test] - fn arr_macro_2arg() -> Result<(), crate::errors::InputError> { - let temp = Array2::from_elem((10, 10), 300.0); - let pressure = Array2::from_elem((10, 10), 101325.0); - - let result = par_compute_ndarray!( - vapour_pressure::buck3_unchecked, - vapour_pressure::buck3_validate, - temp, - pressure - )?; - - assert_approx_eq!(Float, result[[5, 5]], 3548.5041048035896, epsilon = 0.01); - - Ok(()) - } - - #[test] - fn arr_macro_3arg() -> Result<(), crate::errors::InputError> { - let temp = Array2::from_elem((10, 10), 300.0); - let pressure = Array2::from_elem((10, 10), 101325.0); - let relative_humidity = Array2::from_elem((10, 10), 0.5); - - let result = par_compute_ndarray!( - vapour_pressure_deficit::general3_unchecked, - vapour_pressure_deficit::general3_validate, - temp, - relative_humidity, - pressure - )?; - - assert_approx_eq!(Float, result[[5, 5]], 1774.2520524017948, epsilon = 0.01); - - Ok(()) - } - - #[test] - fn vec_macro_1arg() { - let temp = vec![300.0; 100]; - let result = par_compute_vec!(vapour_pressure::buck3_simplified, &temp).unwrap(); - - assert_approx_eq!(Float, result[50], 3533.6421536199978, epsilon = 0.01); - } - - #[test] - fn vec_macro_2arg() { - let temp = vec![300.0; 100]; - let pressure = vec![101325.0; 100]; - let result = par_compute_vec!(vapour_pressure::buck3, &temp, &pressure).unwrap(); - - assert_approx_eq!(Float, result[50], 3548.5041048035896, epsilon = 0.01); - } - - #[test] - fn vec_macro_3arg() { - let temp = vec![300.0; 100]; - let pressure = vec![101325.0; 100]; - let relative_humidity = vec![0.5; 100]; - let result = par_compute_vec!( - vapour_pressure_deficit::general3, - &temp, - &relative_humidity, - &pressure - ) - .unwrap(); - - assert_approx_eq!(Float, result[50], 1774.2520524017948, epsilon = 0.01); - } -} diff --git a/src/wet_bulb_potential_temperature.rs b/src/wet_bulb_potential_temperature.rs index f6b2e29..1060d70 100644 --- a/src/wet_bulb_potential_temperature.rs +++ b/src/wet_bulb_potential_temperature.rs @@ -1,4 +1,4 @@ -//!Functions to calculate dry bulb potential temperature of unsaturated air in K. +//!Functions to calculate wet bulb potential temperature of unsaturated air in K. use crate::compute_macros::{generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, generate_par_vec_compute, generate_vec_compute}; use ndarray::{Array, Dimension, FoldWhile}; @@ -8,6 +8,8 @@ use crate::{ constants::{C_P, R_D, ZERO_CELSIUS}, errors::InputError, }; +#[cfg(feature = "debug")] +use floccus_proc::logerr; /// Formula for computing wet bulb potential temperature from equivalent potential temperature. /// @@ -32,6 +34,7 @@ impl DaviesJones1 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] pub fn validate_inputs(equivalent_potential_temperature: Float) -> Result<(), InputError> { if !(257.0..=377.0).contains(&equivalent_potential_temperature) { return Err(InputError::OutOfRange(String::from( @@ -49,42 +52,6 @@ generate_ndarray_compute!(DaviesJones1, equivalent_potential_temperature); generate_par_vec_compute!(DaviesJones1, equivalent_potential_temperature); generate_par_ndarray_compute!(DaviesJones1, equivalent_potential_temperature); -#[cfg(feature = "debug")] -use floccus_proc::logerr; - -///Formula for computing wet bulb potential temperature from equivalent potential temperature. -/// -///Derived by R. Davies-Jones (2008) [(doi:10.1175/2007MWR2224.1)](https://doi.org/10.1175/2007MWR2224.1) -/// -///# Errors -/// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `temperature` range: 257K - 377K\ -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", - ))); - } - - 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); - result + ZERO_CELSIUS -} - #[cfg(test)] mod tests { use crate::{ @@ -95,7 +62,7 @@ mod tests { #[test] fn davies_jones1() { assert!(tests_framework::test_with_1arg( - &wet_bulb_potential_temperature::davies_jones1, + &wet_bulb_potential_temperature::DaviesJones1::compute, Argument { name: "equivalent_potential_temperature", def_val: 300.0, From d3653844cf6177a8bc41e31621b265a10a5ac0a1 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:53:02 +0100 Subject: [PATCH 007/102] macro vapour pressure --- src/compute_macros.rs | 6 +- src/lib.rs | 3 - src/vapour_pressure.rs | 757 +++++++++++++++----------- src/wet_bulb_potential_temperature.rs | 15 +- 4 files changed, 439 insertions(+), 342 deletions(-) diff --git a/src/compute_macros.rs b/src/compute_macros.rs index 6c10a7f..4d2b66d 100644 --- a/src/compute_macros.rs +++ b/src/compute_macros.rs @@ -11,15 +11,17 @@ macro_rules! generate_compute { ($qnt:tt, $arg1:tt, $arg2:tt) => { impl $qnt { + #[allow(missing_docs)] pub fn compute($arg1: Float, $arg2: Float) -> Result { - Self::validate_inputs($arg1, $b)?; - Ok(Self::compute_unchecked($arg1, $b)) + Self::validate_inputs($arg1, $arg2)?; + Ok(Self::compute_unchecked($arg1, $arg2)) } } }; ($qnt:tt, $arg1:tt, $arg2:tt, $arg3:tt) => { impl $qnt { + #[allow(missing_docs)] pub fn compute($arg1: Float, $arg2: Float, $arg3: Float) -> Result { Self::validate_inputs($arg1, $arg2, $arg3)?; Ok(Self::compute_unchecked($arg1, $arg2, $arg3)) diff --git a/src/lib.rs b/src/lib.rs index c1ed518..c708ad4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,9 +96,6 @@ pub mod wet_bulb_temperature; pub(crate) mod compute_macros; -#[cfg(feature = "array_compute_parallel")] -pub mod par_array_compute; - #[cfg(not(feature = "double_precision"))] type Float = f32; diff --git a/src/vapour_pressure.rs b/src/vapour_pressure.rs index cb6c6b1..8a92019 100644 --- a/src/vapour_pressure.rs +++ b/src/vapour_pressure.rs @@ -6,14 +6,20 @@ //! //!To compute saturation vapour pressure input dry-bulb temperature in place of dewpoint temperature. +use crate::compute_macros::{ + generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, + generate_par_vec_compute, generate_vec_compute, +}; use crate::Float; use crate::{ constants::{EPSILON, ZERO_CELSIUS}, errors::InputError, }; - #[cfg(feature = "debug")] use floccus_proc::logerr; +use itertools::izip; +use ndarray::{Array, Dimension, FoldWhile}; +use rayon::iter::{IntoParallelRefIterator, ParallelBridge, ParallelIterator}; ///Formula for computing vapour pressure from specific humidity and pressure. ///This function is theoretical not empirical. @@ -25,30 +31,37 @@ 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 -pub fn general1(specific_humidity: Float, pressure: Float) -> Result { - general1_validate(specific_humidity, pressure)?; - Ok(general1_unchecked(specific_humidity, pressure)) -} +pub struct General1; -#[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"))); - } +impl General1 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(specific_humidity: Float, pressure: Float) -> Result<(), InputError> { + if !(0.00001..=2.0).contains(&specific_humidity) { + return Err(InputError::OutOfRange(String::from("specific_humidity"))); + } + + if !(100.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } - if !(100.0..=150_000.0).contains(&pressure) { - return Err(InputError::OutOfRange(String::from("pressure"))); + Ok(()) } - Ok(()) + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(specific_humidity: Float, pressure: Float) -> Float { + -((pressure * specific_humidity) / ((specific_humidity * (EPSILON - 1.0)) - EPSILON)) + } } -#[allow(missing_docs)] -pub fn general1_unchecked(specific_humidity: Float, pressure: Float) -> Float { - -((pressure * specific_humidity) / ((specific_humidity * (EPSILON - 1.0)) - EPSILON)) -} +generate_compute!(General1, specific_humidity, pressure); +generate_vec_compute!(General1, specific_humidity, pressure); +generate_ndarray_compute!(General1, specific_humidity, pressure); +generate_par_vec_compute!(General1, specific_humidity, pressure); +generate_par_ndarray_compute!(General1, specific_humidity, pressure); ///Formula for computing vapour pressure from dewpoint temperature and pressure. ///Should be used for air over water when accuracy is desired. @@ -59,47 +72,54 @@ pub fn general1_unchecked(specific_humidity: Float, pressure: Float) -> Float { ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 232K - 324K\ ///Valid `pressure` range: 100Pa - 150000Pa -pub fn buck1(dewpoint: Float, pressure: Float) -> Result { - buck1_validate(dewpoint, pressure)?; - Ok(buck1_unchecked(dewpoint, pressure)) -} +pub struct Buck1; -#[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"))); - } +impl Buck1 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + #[inline(always)] + pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { + if !(232.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"))); - } + if !(100.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } - Ok(()) -} + 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 + #[allow(missing_docs)] + #[inline(always)] + pub fn compute_unchecked(dewpoint: Float, pressure: Float) -> Float { + let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C + let pressure = pressure / 100.0; //convert to hPa - let lower_a = 6.1121; - let lower_b = 18.729; - let lower_c = 257.87; - let lower_d = 227.3; + let lower_a = 6.1121; + let lower_b = 18.729; + let lower_c = 257.87; + let lower_d = 227.3; - let upper_a = 0.000_72; - let upper_b = 0.000_003_2; - let upper_c = 0.000_000_000_59; + let upper_a = 0.000_72; + let upper_b = 0.000_003_2; + let upper_c = 0.000_000_000_59; - let lower_e = - 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))); + let lower_e = + 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))); - (lower_e * lower_f) * 100.0 //return in Pa + (lower_e * lower_f) * 100.0 //return in Pa + } } +generate_compute!(Buck1, dewpoint, pressure); +generate_vec_compute!(Buck1, dewpoint, pressure); +generate_ndarray_compute!(Buck1, dewpoint, pressure); +generate_par_vec_compute!(Buck1, dewpoint, pressure); +generate_par_ndarray_compute!(Buck1, dewpoint, pressure); + ///Formula for computing vapour pressure from dewpoint temperature and pressure. ///Should be used for air over ice when accuracy is desired. /// @@ -109,47 +129,54 @@ pub fn buck1_unchecked(dewpoint: Float, pressure: Float) -> Float { ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 193K - 274K\ ///Valid `pressure` range: 100Pa - 150000Pa -pub fn buck2(dewpoint: Float, pressure: Float) -> Result { - buck2_validate(dewpoint, pressure)?; - Ok(buck2_unchecked(dewpoint, pressure)) -} +pub struct Buck2; -#[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"))); - } +impl Buck2 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { + if !(193.0..=274.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"))); - } + if !(100.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } - Ok(()) -} + 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 + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(dewpoint: Float, pressure: Float) -> Float { + let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C + let pressure = pressure / 100.0; //convert to hPa - let lower_a = 6.1115; - let lower_b = 23.036; - let lower_c = 279.82; - let lower_d = 333.7; + let lower_a = 6.1115; + let lower_b = 23.036; + let lower_c = 279.82; + let lower_d = 333.7; - let upper_a = 0.000_22; - let upper_b = 0.000_003_83; - let upper_c = 0.000_000_000_64; + let upper_a = 0.000_22; + let upper_b = 0.000_003_83; + let upper_c = 0.000_000_000_64; - let lower_e = - 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))); + let lower_e = + 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))); - (lower_e * lower_f) * 100.0 //return in Pa + (lower_e * lower_f) * 100.0 //return in Pa + } } +generate_compute!(Buck2, dewpoint, pressure); +generate_vec_compute!(Buck2, dewpoint, pressure); +generate_ndarray_compute!(Buck2, dewpoint, pressure); +generate_par_vec_compute!(Buck2, dewpoint, pressure); +generate_par_ndarray_compute!(Buck2, dewpoint, pressure); + ///Formula for computing vapour pressure from dewpoint temperature and pressure. ///Should be used for air over water for general use. /// @@ -159,44 +186,51 @@ pub fn buck2_unchecked(dewpoint: Float, pressure: Float) -> Float { ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 253K - 324K\ ///Valid `pressure` range: 100Pa - 150000Pa -pub fn buck3(dewpoint: Float, pressure: Float) -> Result { - buck3_validate(dewpoint, pressure)?; - Ok(buck3_unchecked(dewpoint, pressure)) -} +pub struct Buck3; -#[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"))); - } +impl Buck3 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { + 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"))); - } + if !(100.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } - Ok(()) -} + 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 + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(dewpoint: Float, pressure: Float) -> Float { + let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C + let pressure = pressure / 100.0; //convert to hPa - let lower_a = 6.1121; - let lower_b = 17.502; - let lower_c = 240.97; + let lower_a = 6.1121; + let lower_b = 17.502; + let lower_c = 240.97; - let upper_a = 0.000_7; - let upper_b = 0.000_003_46; + let upper_a = 0.000_7; + let upper_b = 0.000_003_46; - let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); - let lower_f = 1.0 + upper_a + (pressure * upper_b); + let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); + let lower_f = 1.0 + upper_a + (pressure * upper_b); - (lower_e * lower_f) * 100.0 //return in Pa + (lower_e * lower_f) * 100.0 //return in Pa + } } +generate_compute!(Buck3, dewpoint, pressure); +generate_vec_compute!(Buck3, dewpoint, pressure); +generate_ndarray_compute!(Buck3, dewpoint, pressure); +generate_par_vec_compute!(Buck3, dewpoint, pressure); +generate_par_ndarray_compute!(Buck3, dewpoint, pressure); + ///Formula for computing vapour pressure from dewpoint temperature. ///Simplified version of [`buck3`]. Very popular in meteorological sources. /// @@ -205,35 +239,42 @@ pub fn buck3_unchecked(dewpoint: Float, pressure: Float) -> Float { /// ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 253K - 324K -pub fn buck3_simplified(dewpoint: Float) -> Result { - buck3_simplified_validate(dewpoint)?; - Ok(buck3_simplified_unchecked(dewpoint)) -} +pub struct Buck3Simplified; -#[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"))); - } +impl Buck3Simplified { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(dewpoint: Float) -> Result<(), InputError> { + if !(253.0..=324.0).contains(&dewpoint) { + return Err(InputError::OutOfRange(String::from("dewpoint"))); + } - Ok(()) -} + Ok(()) + } -#[allow(missing_docs)] -pub fn buck3_simplified_unchecked(dewpoint: Float) -> Float { - let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(dewpoint: Float) -> Float { + let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C - let lower_a = 6.1121; - let lower_b = 17.502; - let lower_c = 240.97; + let lower_a = 6.1121; + let lower_b = 17.502; + let lower_c = 240.97; - let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); + let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); - lower_e * 100.0 //return in Pa + lower_e * 100.0 //return in Pa + } } +generate_compute!(Buck3Simplified, dewpoint); +generate_vec_compute!(Buck3Simplified, dewpoint); +generate_ndarray_compute!(Buck3Simplified, dewpoint); +generate_par_vec_compute!(Buck3Simplified, dewpoint); +generate_par_ndarray_compute!(Buck3Simplified, dewpoint); + ///Formula for computing vapour pressure from dewpoint temperature and pressure. ///Should be used for air over ice for general use. /// @@ -243,44 +284,51 @@ pub fn buck3_simplified_unchecked(dewpoint: Float) -> Float { ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 223K - 274K\ ///Valid `pressure` range: 100Pa - 150000Pa -pub fn buck4(dewpoint: Float, pressure: Float) -> Result { - buck4_validate(dewpoint, pressure)?; - Ok(buck4_unchecked(dewpoint, pressure)) -} +pub struct Buck4; -#[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"))); - } +impl Buck4 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { + if !(223.0..=274.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"))); - } + if !(100.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } - Ok(()) -} + 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 + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(dewpoint: Float, pressure: Float) -> Float { + let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C + let pressure = pressure / 100.0; //convert to hPa - let lower_a = 6.1115; - let lower_b = 22.452; - let lower_c = 272.55; + let lower_a = 6.1115; + let lower_b = 22.452; + let lower_c = 272.55; - let upper_a = 0.000_3; - let upper_b = 0.000_004_18; + let upper_a = 0.000_3; + let upper_b = 0.000_004_18; - let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); - let lower_f = 1.0 + upper_a + (pressure * upper_b); + let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); + let lower_f = 1.0 + upper_a + (pressure * upper_b); - (lower_e * lower_f) * 100.0 //return in Pa + (lower_e * lower_f) * 100.0 //return in Pa + } } +generate_compute!(Buck4, dewpoint, pressure); +generate_vec_compute!(Buck4, dewpoint, pressure); +generate_ndarray_compute!(Buck4, dewpoint, pressure); +generate_par_vec_compute!(Buck4, dewpoint, pressure); +generate_par_ndarray_compute!(Buck4, dewpoint, pressure); + ///Formula for computing vapour pressure from dewpoint temperature. ///Simplified version of [`buck4`], analogical to [`buck3_simplified`]. /// @@ -289,36 +337,43 @@ pub fn buck4_unchecked(dewpoint: Float, pressure: Float) -> Float { /// ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 223K - 274K -pub fn buck4_simplified(dewpoint: Float) -> Result { - buck4_simplified_validate(dewpoint)?; - Ok(buck4_simplified_unchecked(dewpoint)) -} +pub struct Buck4Simplified; -#[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"))); - } +impl Buck4Simplified { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(dewpoint: Float) -> Result<(), InputError> { + //validate inputs + if !(223.0..=274.0).contains(&dewpoint) { + return Err(InputError::OutOfRange(String::from("dewpoint"))); + } - Ok(()) -} + Ok(()) + } -#[allow(missing_docs)] -pub fn buck4_simplified_unchecked(dewpoint: Float) -> Float { - let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(dewpoint: Float) -> Float { + let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C - let lower_a = 6.1115; - let lower_b = 22.452; - let lower_c = 272.55; + let lower_a = 6.1115; + let lower_b = 22.452; + let lower_c = 272.55; - let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); + let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); - lower_e * 100.0 //return in Pa + lower_e * 100.0 //return in Pa + } } +generate_compute!(Buck4Simplified, dewpoint); +generate_vec_compute!(Buck4Simplified, dewpoint); +generate_ndarray_compute!(Buck4Simplified, dewpoint); +generate_par_vec_compute!(Buck4Simplified, dewpoint); +generate_par_ndarray_compute!(Buck4Simplified, dewpoint); + ///Formula for computing vapour pressure over water from dewpoint temperature. ///Should be used for temperatures above 273K. /// @@ -328,35 +383,42 @@ pub fn buck4_simplified_unchecked(dewpoint: Float) -> Float { /// ///Returns [`InputError::OutOfRange`] when input is out of range.\ ///Valid `dewpoint` range: 273K - 353K -pub fn tetens1(dewpoint: Float) -> Result { - tetens1_validate(dewpoint)?; - Ok(tetens1_unchecked(dewpoint)) -} +pub struct Tetens1; -#[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"))); - } +impl Tetens1 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(dewpoint: Float) -> Result<(), InputError> { + if !(273.0..=353.0).contains(&dewpoint) { + return Err(InputError::OutOfRange(String::from("dewpoint"))); + } - Ok(()) -} + Ok(()) + } -#[allow(missing_docs)] -pub fn tetens1_unchecked(dewpoint: Float) -> Float { - let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(dewpoint: Float) -> Float { + let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C - let lower_a = 0.61078; - let lower_b = 17.27; - let lower_c = 237.3; + let lower_a = 0.61078; + let lower_b = 17.27; + let lower_c = 237.3; - let result = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); + let result = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); - result * 1000.0 //return in Pa + result * 1000.0 //return in Pa + } } +generate_compute!(Tetens1, dewpoint); +generate_vec_compute!(Tetens1, dewpoint); +generate_ndarray_compute!(Tetens1, dewpoint); +generate_par_vec_compute!(Tetens1, dewpoint); +generate_par_ndarray_compute!(Tetens1, dewpoint); + ///Formula for computing **ONLY** vapour pressure from saturation vapour pressure and relative humidity. ///For saturation vapour pressure use [`saturation_specific2`] /// @@ -365,44 +427,62 @@ pub fn tetens1_unchecked(dewpoint: Float) -> Float { ///Returns [`InputError::OutOfRange`] when input is out of range.\ ///Valid `saturation_vapour_pressure` range: 0Pa - 10000Pa\ ///Valid `relative_humidity` range: 0.0 - 1.0 -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"))); - } - - if !(0.0..=50_000.0).contains(&saturation_vapour_pressure) { - return Err(InputError::OutOfRange(String::from( - "saturation_vapour_pressure", - ))); - } - - Ok(()) -} - -#[allow(missing_docs)] -pub fn saturation_specific1_unchecked( - saturation_vapour_pressure: Float, - relative_humidity: Float, -) -> Float { - saturation_vapour_pressure * relative_humidity -} +pub struct SaturationSpecific1; + +impl SaturationSpecific1 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + 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"))); + } + + if !(0.0..=50_000.0).contains(&saturation_vapour_pressure) { + return Err(InputError::OutOfRange(String::from( + "saturation_vapour_pressure", + ))); + } + + Ok(()) + } + + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(saturation_vapour_pressure: Float, relative_humidity: Float) -> Float { + saturation_vapour_pressure * relative_humidity + } +} + +generate_compute!( + SaturationSpecific1, + saturation_vapour_pressure, + relative_humidity +); +generate_vec_compute!( + SaturationSpecific1, + saturation_vapour_pressure, + relative_humidity +); +generate_ndarray_compute!( + SaturationSpecific1, + saturation_vapour_pressure, + relative_humidity +); +generate_par_vec_compute!( + SaturationSpecific1, + saturation_vapour_pressure, + relative_humidity +); +generate_par_ndarray_compute!( + SaturationSpecific1, + saturation_vapour_pressure, + relative_humidity +); ///Formula for computing **ONLY** saturation vapour pressure from vapour pressure and relative humidity. ///For vapour pressure use [`saturation_specific1`] @@ -412,39 +492,40 @@ pub fn saturation_specific1_unchecked( ///Returns [`InputError::OutOfRange`] when input is out of range.\ ///Valid `vapour_pressure` range: 0Pa - 10000Pa\ ///Valid `relative_humidity` range: 0.00001 - 1.0 -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, - )) -} +pub struct SaturationSpecific2; -#[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"))); - } +impl SaturationSpecific2 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + vapour_pressure: Float, + relative_humidity: Float, + ) -> Result<(), InputError> { + if !(0.00001..=2.0).contains(&relative_humidity) { + return Err(InputError::OutOfRange(String::from("relative_humidity"))); + } + + if !(0.0..=10_000.0).contains(&vapour_pressure) { + return Err(InputError::OutOfRange(String::from("vapour_pressure"))); + } - if !(0.0..=10_000.0).contains(&vapour_pressure) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); + Ok(()) } - Ok(()) + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(vapour_pressure: Float, relative_humidity: Float) -> Float { + vapour_pressure / relative_humidity + } } -#[allow(missing_docs)] -pub fn saturation_specific2_uchecked(vapour_pressure: Float, relative_humidity: Float) -> Float { - vapour_pressure / relative_humidity -} +generate_compute!(SaturationSpecific2, vapour_pressure, relative_humidity); +generate_vec_compute!(SaturationSpecific2, vapour_pressure, relative_humidity); +generate_ndarray_compute!(SaturationSpecific2, vapour_pressure, relative_humidity); +generate_par_vec_compute!(SaturationSpecific2, vapour_pressure, relative_humidity); +generate_par_ndarray_compute!(SaturationSpecific2, vapour_pressure, relative_humidity); ///Formula for computing vapour pressure over water from dewpoint temperature. ///Should be used when accuracy is required as it is @@ -456,45 +537,52 @@ pub fn saturation_specific2_uchecked(vapour_pressure: Float, relative_humidity: /// ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 273K - 374K -pub fn wexler1(dewpoint: Float) -> Result { - wexler1_validate(dewpoint)?; - Ok(wexler1_unchecked(dewpoint)) -} +pub struct Wexler1; + +impl Wexler1 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(dewpoint: Float) -> Result<(), InputError> { + if !(273.0..=374.0).contains(&dewpoint) { + return Err(InputError::OutOfRange(String::from("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(()) } - Ok(()) -} + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(dewpoint: Float) -> Float { + // constants from the paper + let g: [Float; 8] = [ + -2991.2729, + -6017.0128, + 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, + ]; -#[allow(missing_docs)] -pub fn wexler1_unchecked(dewpoint: Float) -> Float { - // constants from the paper - let g: [Float; 8] = [ - -2991.2729, - -6017.0128, - 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(); - let mut ln_p = g[7] * dewpoint.ln(); + for i in 0..=6 { + ln_p += g[i] * dewpoint.powi(i as i32 - 2); + } - for i in 0..=6 { - ln_p += g[i] * dewpoint.powi(i as i32 - 2); + ln_p.exp() } - - ln_p.exp() } +generate_compute!(Wexler1, dewpoint); +generate_vec_compute!(Wexler1, dewpoint); +generate_ndarray_compute!(Wexler1, dewpoint); +generate_par_vec_compute!(Wexler1, dewpoint); +generate_par_ndarray_compute!(Wexler1, dewpoint); + ///Formula for computing vapour over ice pressure from dewpoint temperature. ///Should be used when accuracy is required as it is ///computationally expensive. @@ -505,41 +593,48 @@ pub fn wexler1_unchecked(dewpoint: Float) -> Float { /// ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 173K - 274K -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.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(); - - for j in 0..=4 { - ln_p += big_k[j] * dewpoint.powi(j as i32 - 1); - } - - ln_p.exp() -} +pub struct Wexler2; + +impl Wexler2 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(dewpoint: Float) -> Result<(), InputError> { + if !(173.0..=274.0).contains(&dewpoint) { + return Err(InputError::OutOfRange(String::from("dewpoint"))); + } + Ok(()) + } + + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(dewpoint: Float) -> Float { + // constants from the paper + let big_k: [Float; 6] = [ + -5865.3696, + 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(); + + for j in 0..=4 { + ln_p += big_k[j] * dewpoint.powi(j as i32 - 1); + } + + ln_p.exp() + } +} + +generate_compute!(Wexler2, dewpoint); +generate_vec_compute!(Wexler2, dewpoint); +generate_ndarray_compute!(Wexler2, dewpoint); +generate_par_vec_compute!(Wexler2, dewpoint); +generate_par_ndarray_compute!(Wexler2, dewpoint); #[cfg(test)] mod tests { diff --git a/src/wet_bulb_potential_temperature.rs b/src/wet_bulb_potential_temperature.rs index 1060d70..7e9cbac 100644 --- a/src/wet_bulb_potential_temperature.rs +++ b/src/wet_bulb_potential_temperature.rs @@ -1,8 +1,9 @@ //!Functions to calculate wet bulb potential temperature of unsaturated air in K. -use crate::compute_macros::{generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, generate_par_vec_compute, generate_vec_compute}; -use ndarray::{Array, Dimension, FoldWhile}; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use crate::compute_macros::{ + generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, + generate_par_vec_compute, generate_vec_compute, +}; use crate::Float; use crate::{ constants::{C_P, R_D, ZERO_CELSIUS}, @@ -10,13 +11,15 @@ use crate::{ }; #[cfg(feature = "debug")] use floccus_proc::logerr; +use ndarray::{Array, Dimension, FoldWhile}; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; /// Formula for computing wet bulb potential temperature from equivalent potential temperature. -/// +/// /// Derived by R. Davies-Jones (2008) [(doi:10.1175/2007MWR2224.1)](https://doi.org/10.1175/2007MWR2224.1) -/// +/// /// # Errors -/// +/// /// Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ /// Valid `temperature` range: 257K - 377K\ pub struct DaviesJones1; From de8f09939a902c961ef496edba0976012de9ef79 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:00:00 +0100 Subject: [PATCH 008/102] macro vpd --- src/compute_macros.rs | 2 + src/vapour_pressure_deficit.rs | 219 ++++++++++++++++++--------------- 2 files changed, 123 insertions(+), 98 deletions(-) diff --git a/src/compute_macros.rs b/src/compute_macros.rs index 4d2b66d..e8e9045 100644 --- a/src/compute_macros.rs +++ b/src/compute_macros.rs @@ -63,6 +63,7 @@ macro_rules! generate_vec_compute { pub fn compute_vec( $slice1: &[Float], $slice2: &[Float], + $slice3: &[Float], ) -> Result, InputError> { izip!($slice1, $slice2, $slice3) .map(|(&a, &b, &c)| Self::compute(a, b, c)) @@ -175,6 +176,7 @@ macro_rules! generate_par_vec_compute { pub fn compute_vec_parallel( $slice1: &[Float], $slice2: &[Float], + $slice3: &[Float], ) -> Result, InputError> { izip!($slice1, $slice2, $slice3) .par_bridge() diff --git a/src/vapour_pressure_deficit.rs b/src/vapour_pressure_deficit.rs index 4d8a399..2711dd0 100644 --- a/src/vapour_pressure_deficit.rs +++ b/src/vapour_pressure_deficit.rs @@ -4,11 +4,17 @@ //!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::Float; -use crate::{errors::InputError, vapour_pressure}; - +use crate::compute_macros::{ + generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, + generate_par_vec_compute, generate_vec_compute, +}; +use crate::errors::InputError; +use crate::{vapour_pressure, Float}; #[cfg(feature = "debug")] use floccus_proc::logerr; +use itertools::izip; +use ndarray::{Array, Dimension, FoldWhile}; +use rayon::iter::{ParallelBridge, ParallelIterator}; ///Formula for computing vapour pressure deficit from vapour pressure and saturation vapour pressure /// @@ -17,41 +23,42 @@ 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_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"))); +pub struct General1; + +impl General1 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + 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"))); + } + + if !(0.0..=50_000.0).contains(&saturation_vapour_pressure) { + return Err(InputError::OutOfRange(String::from( + "saturation_vapour_pressure", + ))); + } + + Ok(()) } - if !(0.0..=50_000.0).contains(&saturation_vapour_pressure) { - return Err(InputError::OutOfRange(String::from( - "saturation_vapour_pressure", - ))); + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(vapour_pressure: Float, saturation_vapour_pressure: Float) -> Float { + 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 -} +generate_compute!(General1, vapour_pressure, saturation_vapour_pressure); +generate_vec_compute!(General1, vapour_pressure, saturation_vapour_pressure); +generate_ndarray_compute!(General1, vapour_pressure, saturation_vapour_pressure); +generate_par_vec_compute!(General1, vapour_pressure, saturation_vapour_pressure); +generate_par_ndarray_compute!(General1, vapour_pressure, saturation_vapour_pressure); ///Formula for computing vapour pressure deficit from temperature, dewpoint and pressure ///using [`buck3`](vapour_pressure::buck3) function for vapour pressure calculation @@ -61,40 +68,48 @@ pub fn general1_unchecked(vapour_pressure: Float, saturation_vapour_pressure: Fl ///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 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"))); +pub struct General2; + +impl General2 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + 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(&dewpoint) { + return Err(InputError::OutOfRange(String::from("dewpoint"))); + } + + if !(100.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + Ok(()) } - if !(253.0..=324.0).contains(&dewpoint) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(temperature: Float, dewpoint: Float, pressure: Float) -> Float { + let vapour_pressure = vapour_pressure::Buck3::compute_unchecked(dewpoint, pressure); + let saturation_vapour_pressure = + vapour_pressure::Buck3::compute_unchecked(temperature, pressure); - if !(100.0..=150_000.0).contains(&pressure) { - return Err(InputError::OutOfRange(String::from("pressure"))); + General1::compute_unchecked(vapour_pressure, saturation_vapour_pressure) } - Ok(()) } -#[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); - - general1_unchecked(vapour_pressure, saturation_vapour_pressure) -} +generate_compute!(General2, temperature, dewpoint, pressure); +generate_vec_compute!(General2, temperature, dewpoint, pressure); +generate_ndarray_compute!(General2, temperature, dewpoint, pressure); +generate_par_vec_compute!(General2, temperature, dewpoint, pressure); +generate_par_ndarray_compute!(General2, temperature, dewpoint, pressure); ///Formula for computing vapour pressure deficit from temperature, relative humidity and pressure ///using [`buck3`](vapour_pressure::buck3) function for vapour pressure calculation @@ -104,48 +119,56 @@ pub fn general2_unchecked(temperature: Float, dewpoint: Float, pressure: Float) ///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 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"))); - } - - if !(0.05..=1.0).contains(&relative_humidity) { - return Err(InputError::OutOfRange(String::from("relative_humidity"))); +pub struct General3; + +impl General3 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + temperature: Float, + relative_humidity: Float, + pressure: Float, + ) -> Result<(), InputError> { + if !(253.0..=319.0).contains(&temperature) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(0.05..=1.0).contains(&relative_humidity) { + return Err(InputError::OutOfRange(String::from("relative_humidity"))); + } + + if !(10000.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + Ok(()) } - if !(10000.0..=150_000.0).contains(&pressure) { - return Err(InputError::OutOfRange(String::from("pressure"))); + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked( + temperature: Float, + relative_humidity: Float, + pressure: Float, + ) -> Float { + let saturation_vapour_pressure = + vapour_pressure::Buck3::compute_unchecked(temperature, pressure); + let vapour_pressure = vapour_pressure::SaturationSpecific1::compute_unchecked( + saturation_vapour_pressure, + relative_humidity, + ); + + General1::compute_unchecked(vapour_pressure, saturation_vapour_pressure) } - - Ok(()) } -#[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, - ); - - general1_unchecked(vapour_pressure, saturation_vapour_pressure) -} +generate_compute!(General3, temperature, relative_humidity, pressure); +generate_vec_compute!(General3, temperature, relative_humidity, pressure); +generate_ndarray_compute!(General3, temperature, relative_humidity, pressure); +generate_par_vec_compute!(General3, temperature, relative_humidity, pressure); +generate_par_ndarray_compute!(General3, temperature, relative_humidity, pressure); #[cfg(test)] mod tests { From 7abc80ce726d40cf7dfab9b168c09d4b1f6f5ff8 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:04:37 +0100 Subject: [PATCH 009/102] mixing ratio --- src/mixing_ratio.rs | 161 +++++++++++++++++++++++++------------------- 1 file changed, 91 insertions(+), 70 deletions(-) diff --git a/src/mixing_ratio.rs b/src/mixing_ratio.rs index 54826d5..f4fb124 100644 --- a/src/mixing_ratio.rs +++ b/src/mixing_ratio.rs @@ -3,12 +3,18 @@ //!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 crate::compute_macros::{ + generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, + generate_par_vec_compute, generate_vec_compute, +}; +use crate::{constants::EPSILON, errors::InputError}; +use crate::{vapour_pressure, Float}; use float_cmp::approx_eq; - #[cfg(feature = "debug")] use floccus_proc::logerr; +use itertools::izip; +use ndarray::{Array, Dimension, FoldWhile}; +use rayon::iter::{ParallelBridge, ParallelIterator}; ///Formula for computing mixing ratio of unsaturated air from air pressure and vapour pressure /// @@ -20,35 +26,42 @@ use floccus_proc::logerr; /// ///Returns [`InputError::IncorrectArgumentSet`] when inputs are equal, in which ///case division by 0 occurs. -pub fn general1(pressure: Float, vapour_pressure: Float) -> Result { - 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"))); - } - - if !(0.0..=50_000.0).contains(&vapour_pressure) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); +pub struct General1; + +impl General1 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(pressure: Float, vapour_pressure: Float) -> Result<(), InputError> { + if !(100.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + if !(0.0..=50_000.0).contains(&vapour_pressure) { + return Err(InputError::OutOfRange(String::from("vapour_pressure"))); + } + + if approx_eq!(Float, pressure, vapour_pressure, ulps = 2) { + return Err(InputError::IncorrectArgumentSet(String::from( + "pressure and vapour_pressure cannot be equal", + ))); + } + Ok(()) } - if approx_eq!(Float, pressure, vapour_pressure, ulps = 2) { - return Err(InputError::IncorrectArgumentSet(String::from( - "pressure and vapour_pressure cannot be equal", - ))); + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(pressure: Float, vapour_pressure: Float) -> Float { + EPSILON * (vapour_pressure / (pressure - vapour_pressure)) } - Ok(()) } -#[allow(missing_docs)] -pub fn general1_unchecked(pressure: Float, vapour_pressure: Float) -> Float { - EPSILON * (vapour_pressure / (pressure - vapour_pressure)) -} +generate_compute!(General1, pressure, vapour_pressure); +generate_vec_compute!(General1, pressure, vapour_pressure); +generate_ndarray_compute!(General1, pressure, vapour_pressure); +generate_par_vec_compute!(General1, pressure, vapour_pressure); +generate_par_ndarray_compute!(General1, pressure, vapour_pressure); ///Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. ///Optimised for performance. @@ -58,33 +71,40 @@ pub fn general1_unchecked(pressure: Float, vapour_pressure: Float) -> Float { ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 273K - 353K\ ///Valid `pressure` range: 100Pa - 150000Pa -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"))); +pub struct Performance1; + +impl Performance1 { + #[inline(always)] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { + //validate inputs + if !(273.0..=353.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"))); + } + + Ok(()) } - if !(100.0..=150_000.0).contains(&pressure) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(dewpoint: Float, pressure: Float) -> Float { + let vapour_pressure = vapour_pressure::Tetens1::compute_unchecked(dewpoint); - Ok(()) + General1::compute_unchecked(pressure, vapour_pressure) + } } -#[allow(missing_docs)] -pub fn performance1_unchecked(dewpoint: Float, pressure: Float) -> Float { - let vapour_pressure = vapour_pressure::tetens1_unchecked(dewpoint); - - general1_unchecked(pressure, vapour_pressure) -} +generate_compute!(Performance1, dewpoint, pressure); +generate_vec_compute!(Performance1, dewpoint, pressure); +generate_ndarray_compute!(Performance1, dewpoint, pressure); +generate_par_vec_compute!(Performance1, dewpoint, pressure); +generate_par_ndarray_compute!(Performance1, dewpoint, pressure); ///Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. ///Optimised for accuracy. @@ -94,30 +114,31 @@ pub fn performance1_unchecked(dewpoint: Float, pressure: Float) -> Float { ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `dewpoint` range: 232K - 324K\ ///Valid `pressure` range: 100Pa - 150000Pa -pub fn accuracy1(dewpoint: Float, pressure: Float) -> Result { - 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"))); +pub struct Accuracy1; + +impl Accuracy1 { + #[inline(always)] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { + if !(232.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"))); + } + Ok(()) } - if !(100.0..=150_000.0).contains(&pressure) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - Ok(()) -} + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(dewpoint: Float, pressure: Float) -> Float { + let vapour_pressure = vapour_pressure::Buck1::compute_unchecked(dewpoint, pressure); -#[allow(missing_docs)] -pub fn accuracy1_unchecked(dewpoint: Float, pressure: Float) -> Float { - let vapour_pressure = vapour_pressure::buck1_unchecked(dewpoint, pressure); - - general1_unchecked(pressure, vapour_pressure) + General1::compute_unchecked(pressure, vapour_pressure) + } } #[cfg(test)] From f8e2e597a1f889d2959a3567957da3b7af65d6bb Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:39:53 +0100 Subject: [PATCH 010/102] macro relative humidity --- src/relative_humidity.rs | 308 +++++++++++++++++++++------------------ 1 file changed, 170 insertions(+), 138 deletions(-) diff --git a/src/relative_humidity.rs b/src/relative_humidity.rs index a625b48..cffdc68 100644 --- a/src/relative_humidity.rs +++ b/src/relative_humidity.rs @@ -1,10 +1,16 @@ //!Functions to calculate relative humidity in %/100 -use crate::Float; -use crate::{errors::InputError, mixing_ratio, vapour_pressure}; - +use crate::compute_macros::{ + generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, + generate_par_vec_compute, generate_vec_compute, +}; +use crate::errors::InputError; +use crate::{mixing_ratio, vapour_pressure, Float}; #[cfg(feature = "debug")] use floccus_proc::logerr; +use itertools::izip; +use ndarray::{Array, Dimension, FoldWhile}; +use rayon::iter::{ParallelBridge, ParallelIterator}; ///Formula for computing relative humidity from mixing ratio and saturation mixing ratio. ///Can be used interchangeably with [`general2`]. @@ -17,35 +23,42 @@ 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 -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"))); +pub struct General1; + +impl General1 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + 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"))); + } + + if !(0.00001..=10.0).contains(&saturation_mixing_ratio) { + return Err(InputError::OutOfRange(String::from( + "saturation_mixing_ratio", + ))); + } + + Ok(()) } - if !(0.00001..=10.0).contains(&saturation_mixing_ratio) { - return Err(InputError::OutOfRange(String::from( - "saturation_mixing_ratio", - ))); + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(mixing_ratio: Float, saturation_mixing_ratio: Float) -> Float { + mixing_ratio / saturation_mixing_ratio } - - Ok(()) } -#[allow(missing_docs)] -pub fn general1_unchecked(mixing_ratio: Float, saturation_mixing_ratio: Float) -> Float { - mixing_ratio / saturation_mixing_ratio -} +generate_compute!(General1, mixing_ratio, saturation_mixing_ratio); +generate_vec_compute!(General1, mixing_ratio, saturation_mixing_ratio); +generate_ndarray_compute!(General1, mixing_ratio, saturation_mixing_ratio); +generate_par_vec_compute!(General1, mixing_ratio, saturation_mixing_ratio); +generate_par_ndarray_compute!(General1, mixing_ratio, saturation_mixing_ratio); ///Formula for computing relative humidity from vapour pressure and saturation vapour pressure. ///Can be used interchangeably with [`general1`]. @@ -55,41 +68,43 @@ pub fn general1_unchecked(mixing_ratio: Float, saturation_mixing_ratio: Float) - ///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 general2( - vapour_pressure: Float, - saturation_vapour_pressure: Float, -) -> 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_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"))); +pub struct General2; + +impl General2 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + 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"))); + } + + if !(0.1..=50_000.0).contains(&saturation_vapour_pressure) { + return Err(InputError::OutOfRange(String::from( + "saturation_vapour_pressure", + ))); + } + + Ok(()) } - if !(0.1..=50_000.0).contains(&saturation_vapour_pressure) { - return Err(InputError::OutOfRange(String::from( - "saturation_vapour_pressure", - ))); + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(vapour_pressure: Float, saturation_vapour_pressure: Float) -> Float { + 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 -} +generate_compute!(General2, vapour_pressure, saturation_vapour_pressure); +generate_vec_compute!(General2, vapour_pressure, saturation_vapour_pressure); +generate_ndarray_compute!(General2, vapour_pressure, saturation_vapour_pressure); +generate_par_vec_compute!(General2, vapour_pressure, saturation_vapour_pressure); +generate_par_ndarray_compute!(General2, vapour_pressure, saturation_vapour_pressure); + ///Formula for computing relative humidity from temperature and dewpoint using [`tetens1`](vapour_pressure::tetens1) ///function for vapour pressure calculation /// @@ -98,33 +113,40 @@ pub fn general2_unchecked(vapour_pressure: Float, saturation_vapour_pressure: Fl ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `temperature` range: 273K - 353K ///Valid `dewpoint` range: 273K - 353K -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"))); +pub struct General3; + +impl General3 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(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(&dewpoint) { + return Err(InputError::OutOfRange(String::from("dewpoint"))); + } + + Ok(()) } - if !(273.0..=353.0).contains(&dewpoint) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(temperature: Float, dewpoint: Float) -> Float { + let vapour_pressure = vapour_pressure::Tetens1::compute_unchecked(dewpoint); + let saturation_vapour_pressure = vapour_pressure::Tetens1::compute_unchecked(temperature); - Ok(()) + General2::compute_unchecked(vapour_pressure, saturation_vapour_pressure) + } } -#[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); - - general2_unchecked(vapour_pressure, saturation_vapour_pressure) -} +generate_compute!(General3, temperature, dewpoint); +generate_vec_compute!(General3, temperature, dewpoint); +generate_ndarray_compute!(General3, temperature, dewpoint); +generate_par_vec_compute!(General3, temperature, dewpoint); +generate_par_ndarray_compute!(General3, temperature, dewpoint); ///Formula for computing relative humidity from temperature, dewpoint and pressure using [`buck3`](vapour_pressure::buck3) ///function for vapour pressure calculation @@ -135,41 +157,49 @@ pub fn general3_unchecked(temperature: Float, dewpoint: Float) -> Float { ///Valid `temperature` range: 253K - 324K\ ///Valid `dewpoint` range: 253K - 324K\ ///Valid `pressure` range: 100Pa - 150000Pa -pub fn general4(temperature: Float, dewpoint: Float, pressure: Float) -> 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"))); +pub struct General4; + +impl General4 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + 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(&dewpoint) { + return Err(InputError::OutOfRange(String::from("dewpoint"))); + } + + if !(100.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + Ok(()) } - if !(253.0..=324.0).contains(&dewpoint) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(temperature: Float, dewpoint: Float, pressure: Float) -> Float { + let vapour_pressure = vapour_pressure::Buck3::compute_unchecked(dewpoint, pressure); + let saturation_vapour_pressure = + vapour_pressure::Buck3::compute_unchecked(temperature, pressure); - if !(100.0..=150_000.0).contains(&pressure) { - return Err(InputError::OutOfRange(String::from("pressure"))); + General2::compute_unchecked(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); - - general2_unchecked(vapour_pressure, saturation_vapour_pressure) -} +generate_compute!(General4, temperature, dewpoint, pressure); +generate_vec_compute!(General4, temperature, dewpoint, pressure); +generate_ndarray_compute!(General4, temperature, dewpoint, pressure); +generate_par_vec_compute!(General4, temperature, dewpoint, pressure); +generate_par_ndarray_compute!(General4, temperature, dewpoint, pressure); ///Formula for computing relative humidity from temperature, dewpoint and pressure using [`accuracy1`](mixing_ratio::accuracy1) ///function for mixing ratio calculation @@ -180,40 +210,42 @@ pub fn general4_unchecked(temperature: Float, dewpoint: Float, pressure: Float) ///Valid `temperature` range: 232K - 324K\ ///Valid `dewpoint` range: 232K - 324K\ ///Valid `pressure` range: 100Pa - 150000Pa -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"))); +pub struct General5; + +impl General5 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + temperature: Float, + dewpoint: Float, + pressure: Float, + ) -> Result<(), InputError> { + if !(232.0..=314.0).contains(&temperature) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(232.0..=314.0).contains(&dewpoint) { + return Err(InputError::OutOfRange(String::from("dewpoint"))); + } + + if !(10000.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + Ok(()) } - if !(232.0..=314.0).contains(&dewpoint) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(temperature: Float, dewpoint: Float, pressure: Float) -> Float { + let mixing_ratio = mixing_ratio::Accuracy1::compute_unchecked(dewpoint, pressure); + let saturation_mixing_ratio = + mixing_ratio::Accuracy1::compute_unchecked(temperature, pressure); - if !(10000.0..=150_000.0).contains(&pressure) { - return Err(InputError::OutOfRange(String::from("pressure"))); + General1::compute_unchecked(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); - - general1_unchecked(mixing_ratio, saturation_mixing_ratio) } #[cfg(test)] From 84756e564a4e4957f553481e54c2c0a8087ce3a5 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:46:20 +0100 Subject: [PATCH 011/102] macro potential temperature --- src/potential_temperature.rs | 99 ++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/src/potential_temperature.rs b/src/potential_temperature.rs index 2580178..19ffe13 100644 --- a/src/potential_temperature.rs +++ b/src/potential_temperature.rs @@ -1,14 +1,20 @@ //!Functions to calculate potential temperature of dry air in K. +use crate::compute_macros::{ + generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, + generate_par_vec_compute, generate_vec_compute, +}; use crate::Float; use crate::{ constants::{C_P, R_D}, errors::InputError, }; use float_cmp::approx_eq; - #[cfg(feature = "debug")] use floccus_proc::logerr; +use itertools::izip; +use ndarray::{Array, Dimension, FoldWhile}; +use rayon::iter::{ParallelBridge, ParallelIterator}; ///Formula for computing potential temperature of dry air from temperature, pressure and vapour pressure. /// @@ -26,63 +32,58 @@ use floccus_proc::logerr; /// ///Returns [`InputError::IncorrectArgumentSet`] when `pressure` is lower than `vapour_pressure`, ///in which case floating-point exponentation of negative number occurs. -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, - )) -} +pub struct DaviesJones1; -#[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"))); - } +impl DaviesJones1 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + temperature: Float, + pressure: Float, + vapour_pressure: Float, + ) -> Result<(), InputError> { + if !(253.0..=324.0).contains(&temperature) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } - if !(100.0..=150_000.0).contains(&pressure) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + if !(100.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } - if !(0.0..=10_000.0).contains(&vapour_pressure) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); - } + if !(0.0..=10_000.0).contains(&vapour_pressure) { + return Err(InputError::OutOfRange(String::from("vapour_pressure"))); + } - if approx_eq!(Float, pressure, vapour_pressure, ulps = 2) { - return Err(InputError::IncorrectArgumentSet(String::from( - "pressure and vapour_pressure cannot be equal", - ))); - } + if approx_eq!(Float, pressure, vapour_pressure, ulps = 2) { + return Err(InputError::IncorrectArgumentSet(String::from( + "pressure and vapour_pressure cannot be equal", + ))); + } + + if vapour_pressure > pressure { + return Err(InputError::IncorrectArgumentSet(String::from( + "vapour_pressure cannot be higher than pressure", + ))); + } - if vapour_pressure > pressure { - return Err(InputError::IncorrectArgumentSet(String::from( - "vapour_pressure cannot be higher than pressure", - ))); + Ok(()) } - Ok(()) + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { + let kappa = R_D / C_P; + temperature * (100_000.0 / (pressure - vapour_pressure)).powf(kappa) + } } -#[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) -} +generate_compute!(DaviesJones1, temperature, pressure, vapour_pressure); +generate_vec_compute!(DaviesJones1, temperature, pressure, vapour_pressure); +generate_par_vec_compute!(DaviesJones1, temperature, pressure, vapour_pressure); +generate_ndarray_compute!(DaviesJones1, temperature, pressure, vapour_pressure); +generate_par_ndarray_compute!(DaviesJones1, temperature, pressure, vapour_pressure); #[cfg(test)] mod tests { From cc2f8802b0273c35840793f73271d3ca2eeacb2c Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:48:21 +0100 Subject: [PATCH 012/102] macro theta e --- src/equivalent_potential_temperature.rs | 283 +++++++++++++----------- 1 file changed, 154 insertions(+), 129 deletions(-) diff --git a/src/equivalent_potential_temperature.rs b/src/equivalent_potential_temperature.rs index 0722248..1ee1acf 100644 --- a/src/equivalent_potential_temperature.rs +++ b/src/equivalent_potential_temperature.rs @@ -1,14 +1,17 @@ //!Functions to calculate equivalent potential temperature of air in K. -use crate::constants::{C_L, R_V}; -use crate::Float; -use crate::{ - constants::{C_P, EPSILON, L_V, R_D}, - errors::InputError, - mixing_ratio, potential_temperature, relative_humidity, vapour_pressure, -}; +use crate::compute_macros::{ + generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, + generate_par_vec_compute, generate_vec_compute, +}; +use crate::constants::{C_L, C_P, EPSILON, L_V, R_D, R_V}; +use crate::errors::InputError; +use crate::{mixing_ratio, potential_temperature, relative_humidity, vapour_pressure, Float}; #[cfg(feature = "debug")] use floccus_proc::logerr; +use itertools::izip; +use ndarray::{Array, Dimension, FoldWhile}; +use rayon::iter::{ParallelBridge, ParallelIterator}; ///Most accuarte formula for computing equivalent potential temperature of unsaturated air from ///temperature, pressure and vapour pressure. @@ -25,54 +28,60 @@ use floccus_proc::logerr; ///Valid `temperature` range: 253K - 324K\ ///Valid `pressure` range: 100Pa - 150000Pa\ ///Valid `vapour_pressure` range: 0Pa - 10000Pa -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"))); - } - - if !(20000.0..=150_000.0).contains(&pressure) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - - if !(0.0..=10_000.0).contains(&vapour_pressure) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); +pub struct Paluch1; + +impl Paluch1 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + temperature: Float, + pressure: Float, + vapour_pressure: Float, + ) -> Result<(), InputError> { + if !(253.0..=324.0).contains(&temperature) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(20000.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + if !(0.0..=10_000.0).contains(&vapour_pressure) { + return Err(InputError::OutOfRange(String::from("vapour_pressure"))); + } + + Ok(()) } - Ok(()) -} - -#[allow(missing_docs)] -pub fn paluch1_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { - let p0 = 100_000.0; + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { + let p0 = 100_000.0; - let mixing_ratio = mixing_ratio::general1_unchecked(pressure, vapour_pressure); - let saturation_vapour_pressure = vapour_pressure::buck1_unchecked(temperature, pressure); + let mixing_ratio = mixing_ratio::General1::compute_unchecked(pressure, vapour_pressure); + let saturation_vapour_pressure = + vapour_pressure::Buck1::compute_unchecked(temperature, pressure); - let relative_humidity = - relative_humidity::general2_unchecked(vapour_pressure, saturation_vapour_pressure); + let relative_humidity = relative_humidity::General2::compute_unchecked( + vapour_pressure, + saturation_vapour_pressure, + ); - 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() + 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() + } } +generate_compute!(Paluch1, temperature, pressure, vapour_pressure); +generate_vec_compute!(Paluch1, temperature, pressure, vapour_pressure); +generate_par_vec_compute!(Paluch1, temperature, pressure, vapour_pressure); +generate_ndarray_compute!(Paluch1, temperature, pressure, vapour_pressure); +generate_par_ndarray_compute!(Paluch1, temperature, pressure, vapour_pressure); + ///Formula for computing equivalent potential temperature of unsaturated air from ///temperature, pressure and vapour pressure. /// @@ -84,55 +93,64 @@ pub fn paluch1_unchecked(temperature: Float, pressure: Float, vapour_pressure: F ///Valid `temperature` range: 253K - 324K\ ///Valid `pressure` range: 100Pa - 150000Pa\ ///Valid `vapour_pressure` range: 0Pa - 10000Pa -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"))); - } - - if !(20000.0..=150_000.0).contains(&pressure) { - return Err(InputError::OutOfRange(String::from("pressure"))); +pub struct Bryan1; + +impl Bryan1 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + temperature: Float, + pressure: Float, + vapour_pressure: Float, + ) -> Result<(), InputError> { + if !(253.0..=324.0).contains(&temperature) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(20000.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + if !(0.0..=10_000.0).contains(&vapour_pressure) { + return Err(InputError::OutOfRange(String::from("vapour_pressure"))); + } + + Ok(()) } - if !(0.0..=10_000.0).contains(&vapour_pressure) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { + let kappa = R_D / C_P; + + let potential_temperature = potential_temperature::DaviesJones1::compute_unchecked( + temperature, + pressure, + vapour_pressure, + ); + + let saturation_vapour_pressure = + vapour_pressure::Buck3::compute_unchecked(temperature, pressure); + let relative_humidity = relative_humidity::General2::compute_unchecked( + vapour_pressure, + saturation_vapour_pressure, + ); + + let mixing_ratio = mixing_ratio::General1::compute_unchecked(pressure, vapour_pressure); + + potential_temperature + * relative_humidity.powf((-kappa) * (mixing_ratio / EPSILON)) + * ((L_V * mixing_ratio) / (C_P * temperature)).exp() } - - 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_unchecked(temperature, pressure, vapour_pressure); - - let saturation_vapour_pressure = vapour_pressure::buck3_unchecked(temperature, pressure); - let relative_humidity = - relative_humidity::general2_unchecked(vapour_pressure, saturation_vapour_pressure); - - let mixing_ratio = mixing_ratio::general1_unchecked(pressure, vapour_pressure); - - potential_temperature - * relative_humidity.powf((-kappa) * (mixing_ratio / EPSILON)) - * ((L_V * mixing_ratio) / (C_P * temperature)).exp() -} +generate_compute!(Bryan1, temperature, pressure, vapour_pressure); +generate_vec_compute!(Bryan1, temperature, pressure, vapour_pressure); +generate_par_vec_compute!(Bryan1, temperature, pressure, vapour_pressure); +generate_ndarray_compute!(Bryan1, temperature, pressure, vapour_pressure); +generate_par_ndarray_compute!(Bryan1, temperature, pressure, vapour_pressure); ///Approximate formula for computing equivalent potential temperature of unsaturated air from ///temperature, pressure and dewpoint. @@ -146,51 +164,58 @@ pub fn bryan1_unchecked(temperature: Float, pressure: Float, vapour_pressure: Fl ///Valid `pressure` range: 100Pa - 150000Pa\ ///Valid `temperature` range: 253K - 324K\ ///Valid `dewpoint` range: 253K - 324K -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"))); +pub struct Bolton1; + +impl Bolton1 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + pressure: Float, + temperature: Float, + dewpoint: Float, + ) -> Result<(), InputError> { + if !(20000.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + 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"))); + } + + Ok(()) } - 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"))); - } - - Ok(()) -} - -#[allow(missing_docs)] -pub fn bolton1_unchecked(pressure: Float, temperature: Float, dewpoint: Float) -> Float { - let kappa = R_D / C_P; + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(pressure: Float, temperature: Float, dewpoint: Float) -> Float { + let kappa = R_D / C_P; - let vapour_pressure = vapour_pressure::buck3_unchecked(dewpoint, pressure); - let mixing_ratio = mixing_ratio::general1_unchecked(pressure, vapour_pressure); + let vapour_pressure = vapour_pressure::Buck3::compute_unchecked(dewpoint, pressure); + let mixing_ratio = mixing_ratio::General1::compute_unchecked(pressure, vapour_pressure); - let lcl_temp = - (1.0 / ((1.0 / (dewpoint - 56.0)) + ((temperature / dewpoint).ln() / 800.0))) + 56.0; + let lcl_temp = + (1.0 / ((1.0 / (dewpoint - 56.0)) + ((temperature / dewpoint).ln() / 800.0))) + 56.0; - let theta_dl = temperature - * (100_000.0 / (pressure - vapour_pressure)).powf(kappa) - * (temperature / lcl_temp).powf(0.28 * mixing_ratio); + let theta_dl = temperature + * (100_000.0 / (pressure - vapour_pressure)).powf(kappa) + * (temperature / lcl_temp).powf(0.28 * mixing_ratio); - theta_dl * (((3036.0 / lcl_temp) - 1.78) * mixing_ratio * (1.0 + 0.448 * mixing_ratio)).exp() + theta_dl + * (((3036.0 / lcl_temp) - 1.78) * mixing_ratio * (1.0 + 0.448 * mixing_ratio)).exp() + } } +generate_compute!(Bolton1, pressure, temperature, dewpoint); +generate_vec_compute!(Bolton1, pressure, temperature, dewpoint); +generate_par_vec_compute!(Bolton1, pressure, temperature, dewpoint); +generate_ndarray_compute!(Bolton1, pressure, temperature, dewpoint); + #[cfg(test)] mod tests { use crate::{ From e5307db7afdbd37471d73532fe0692b2eb81cb77 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:50:40 +0100 Subject: [PATCH 013/102] macro specific humidity --- src/specific_humidity.rs | 51 +++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/specific_humidity.rs b/src/specific_humidity.rs index c88e690..eec9547 100644 --- a/src/specific_humidity.rs +++ b/src/specific_humidity.rs @@ -5,11 +5,17 @@ //! //!Specific humidity is approximately equal to mixing ratio. +use crate::compute_macros::{ + generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, + generate_par_vec_compute, generate_vec_compute, +}; use crate::Float; use crate::{constants::EPSILON, errors::InputError}; - #[cfg(feature = "debug")] use floccus_proc::logerr; +use itertools::izip; +use ndarray::{Array, Dimension, FoldWhile}; +use rayon::iter::{ParallelBridge, ParallelIterator}; ///Formula for computing specific humidity from vapour pressure and pressure. ///Reverse function of [`vapour_pressure::general1`](crate::vapour_pressure::general1). @@ -22,30 +28,37 @@ 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 -pub fn general1(vapour_pressure: Float, pressure: Float) -> Result { - general1_validate(vapour_pressure, pressure)?; - Ok(general1_unchecked(vapour_pressure, pressure)) -} +pub struct General1; -#[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"))); - } +impl General1 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(vapour_pressure: Float, pressure: Float) -> Result<(), InputError> { + if !(0.0..=50_000.0).contains(&vapour_pressure) { + return Err(InputError::OutOfRange(String::from("vapour_pressure"))); + } - if !(100.0..=150_000.0).contains(&pressure) { - return Err(InputError::OutOfRange(String::from("pressure"))); + if !(100.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + Ok(()) } - Ok(()) + #[allow(missing_docs)] + #[inline(always)] + pub fn compute_unchecked(vapour_pressure: Float, pressure: Float) -> Float { + EPSILON * (vapour_pressure / (pressure - (vapour_pressure * (1.0 - EPSILON)))) + } } -#[allow(missing_docs)] -pub fn general1_unchecked(vapour_pressure: Float, pressure: Float) -> Float { - EPSILON * (vapour_pressure / (pressure - (vapour_pressure * (1.0 - EPSILON)))) -} +generate_compute!(General1, vapour_pressure, pressure); +generate_vec_compute!(General1, vapour_pressure, pressure); +generate_par_vec_compute!(General1, vapour_pressure, pressure); +generate_ndarray_compute!(General1, vapour_pressure, pressure); +generate_par_ndarray_compute!(General1, vapour_pressure, pressure); #[cfg(test)] mod tests { From 322fc3b672fd176eecb444ae33aa2c73e7e62288 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:54:40 +0100 Subject: [PATCH 014/102] macro wet bulb temperature --- src/wet_bulb_temperature.rs | 67 ++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/src/wet_bulb_temperature.rs b/src/wet_bulb_temperature.rs index c162d1d..137c568 100644 --- a/src/wet_bulb_temperature.rs +++ b/src/wet_bulb_temperature.rs @@ -1,10 +1,16 @@ //!Functions to calculate wet bulb temperature of unsaturated air in K. +use crate::compute_macros::{ + generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, + generate_par_vec_compute, generate_vec_compute, +}; use crate::Float; use crate::{constants::ZERO_CELSIUS, errors::InputError}; - #[cfg(feature = "debug")] use floccus_proc::logerr; +use itertools::izip; +use ndarray::{Array, Dimension, FoldWhile}; +use rayon::iter::{ParallelBridge, ParallelIterator}; ///Formula for computing wet bulb temperature pressure from dry bulb temperature and relative humidity. /// @@ -17,40 +23,47 @@ 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 -pub fn stull1(temperature: Float, relative_humidity: Float) -> Result { - stull1_validate(temperature, relative_humidity)?; - Ok(stull1_unchecked(temperature, relative_humidity)) -} +pub struct Stull1; -#[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"))); - } +impl Stull1 { + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline(always)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(temperature: Float, relative_humidity: Float) -> Result<(), InputError> { + if !(253.0..=324.0).contains(&temperature) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } - if !(0.05..=0.99).contains(&relative_humidity) { - return Err(InputError::OutOfRange(String::from("relative_humidity"))); + if !(0.05..=0.99).contains(&relative_humidity) { + return Err(InputError::OutOfRange(String::from("relative_humidity"))); + } + Ok(()) } - Ok(()) -} -#[allow(missing_docs)] -pub fn stull1_unchecked(temperature: Float, relative_humidity: Float) -> Float { - //convert units - let temperature = temperature - ZERO_CELSIUS; - let relative_humidity = relative_humidity * 100.0; + #[allow(missing_docs)] + #[inline(always)] + pub fn compute_unchecked(temperature: Float, relative_humidity: Float) -> Float { + //convert units + let temperature = temperature - ZERO_CELSIUS; + let relative_humidity = relative_humidity * 100.0; - let result = (temperature * (0.151_977 * (relative_humidity + 8.313_659).sqrt()).atan()) - + (temperature + relative_humidity).atan() - - (relative_humidity - 1.676_331).atan() - + (0.003_918_38 * relative_humidity.powf(1.5) * (0.023_101 * relative_humidity).atan()) - - 4.686_035; + let result = (temperature * (0.151_977 * (relative_humidity + 8.313_659).sqrt()).atan()) + + (temperature + relative_humidity).atan() + - (relative_humidity - 1.676_331).atan() + + (0.003_918_38 * relative_humidity.powf(1.5) * (0.023_101 * relative_humidity).atan()) + - 4.686_035; - result + ZERO_CELSIUS + result + ZERO_CELSIUS + } } +generate_compute!(Stull1, temperature, relative_humidity); +generate_vec_compute!(Stull1, temperature, relative_humidity); +generate_par_vec_compute!(Stull1, temperature, relative_humidity); +generate_ndarray_compute!(Stull1, temperature, relative_humidity); +generate_par_ndarray_compute!(Stull1, temperature, relative_humidity); + #[cfg(test)] mod tests { use crate::{ From 37c223035abc46a94d218cee6c3d7fcc5a7f5855 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:58:50 +0100 Subject: [PATCH 015/102] macro virtual temperature --- src/virtual_temperature.rs | 161 +++++++++++++++++++++---------------- 1 file changed, 92 insertions(+), 69 deletions(-) diff --git a/src/virtual_temperature.rs b/src/virtual_temperature.rs index 5bff00b..4d5a105 100644 --- a/src/virtual_temperature.rs +++ b/src/virtual_temperature.rs @@ -4,11 +4,17 @@ //!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::compute_macros::{ + generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, + generate_par_vec_compute, generate_vec_compute, +}; use crate::Float; use crate::{constants::EPSILON, errors::InputError}; - #[cfg(feature = "debug")] use floccus_proc::logerr; +use itertools::izip; +use ndarray::{Array, Dimension, FoldWhile}; +use rayon::iter::{ParallelBridge, ParallelIterator}; ///Formula for computing virtual temperature from temperature and mixing ratio. /// @@ -17,30 +23,37 @@ 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 -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"))); +pub struct General1; + +impl General1 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(temperature: Float, mixing_ratio: Float) -> Result<(), InputError> { + if !(173.0..=354.0).contains(&temperature) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(0.000_000_000_1..=0.5).contains(&mixing_ratio) { + return Err(InputError::OutOfRange(String::from("mixing_ratio"))); + } + + Ok(()) } - if !(0.000_000_000_1..=0.5).contains(&mixing_ratio) { - return Err(InputError::OutOfRange(String::from("mixing_ratio"))); + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(temperature: Float, mixing_ratio: Float) -> Float { + temperature * ((mixing_ratio + EPSILON) / (EPSILON * (1.0 + mixing_ratio))) } - - Ok(()) } -#[allow(missing_docs)] -pub fn general1_unchecked(temperature: Float, mixing_ratio: Float) -> Float { - temperature * ((mixing_ratio + EPSILON) / (EPSILON * (1.0 + mixing_ratio))) -} +generate_compute!(General1, temperature, mixing_ratio); +generate_vec_compute!(General1, temperature, mixing_ratio); +generate_par_vec_compute!(General1, temperature, mixing_ratio); +generate_ndarray_compute!(General1, temperature, mixing_ratio); +generate_par_ndarray_compute!(General1, temperature, mixing_ratio); ///Formula for computing virtual temperature from air temperature, pressure and vapour pressure. /// @@ -50,41 +63,44 @@ pub fn general1_unchecked(temperature: Float, mixing_ratio: Float) -> Float { ///Valid `temperature` range: 173K - 373K\ ///Valid `pressure` range: 100Pa - 150000Pa\ ///Valid `vapour_pressure` range: 0Pa - 10000Pa -pub fn general2( - temperature: Float, - pressure: Float, - vapour_pressure: Float, -) -> 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_validate( - temperature: Float, - pressure: Float, - vapour_pressure: Float, -) -> Result<(), InputError> { - if !(173.0..=354.0).contains(&temperature) { - return Err(InputError::OutOfRange(String::from("temperature"))); +pub struct General2; + +impl General2 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( + temperature: Float, + pressure: Float, + vapour_pressure: Float, + ) -> Result<(), InputError> { + if !(173.0..=354.0).contains(&temperature) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(100.0..=150_000.0).contains(&pressure) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + if !(0.0..=10_000.0).contains(&vapour_pressure) { + return Err(InputError::OutOfRange(String::from("vapour_pressure"))); + } + Ok(()) } - if !(100.0..=150_000.0).contains(&pressure) { - return Err(InputError::OutOfRange(String::from("pressure"))); + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { + temperature / (1.0 - ((vapour_pressure / pressure) * (1.0 - EPSILON))) } - - if !(0.0..=10_000.0).contains(&vapour_pressure) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); - } - Ok(()) } -#[allow(missing_docs)] -pub fn general2_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { - temperature / (1.0 - ((vapour_pressure / pressure) * (1.0 - EPSILON))) -} +generate_compute!(General2, temperature, pressure, vapour_pressure); +generate_vec_compute!(General2, temperature, pressure, vapour_pressure); +generate_par_vec_compute!(General2, temperature, pressure, vapour_pressure); +generate_ndarray_compute!(General2, temperature, pressure, vapour_pressure); +generate_par_ndarray_compute!(General2, temperature, pressure, vapour_pressure); ///Formula for computing virtual temperature from air temperature and specific humidity. /// @@ -93,30 +109,37 @@ pub fn general2_unchecked(temperature: Float, pressure: Float, vapour_pressure: ///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ ///Valid `temperature` range: 173K - 373K\ ///Valid `specific_humidity` range: 100Pa - 150000Pa -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"))); +pub struct General3; + +impl General3 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(temperature: Float, specific_humidity: Float) -> Result<(), InputError> { + if !(173.0..=354.0).contains(&temperature) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(0.000_000_001..=2.0).contains(&specific_humidity) { + return Err(InputError::OutOfRange(String::from("specific_humidity"))); + } + + Ok(()) } - if !(0.000_000_001..=2.0).contains(&specific_humidity) { - return Err(InputError::OutOfRange(String::from("specific_humidity"))); + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(temperature: Float, specific_humidity: Float) -> Float { + temperature * (1.0 + (specific_humidity * ((1.0 / EPSILON) - 1.0))) } - - Ok(()) } -#[allow(missing_docs)] -pub fn general3_unchecked(temperature: Float, specific_humidity: Float) -> Float { - temperature * (1.0 + (specific_humidity * ((1.0 / EPSILON) - 1.0))) -} +generate_compute!(General3, temperature, specific_humidity); +generate_vec_compute!(General3, temperature, specific_humidity); +generate_par_vec_compute!(General3, temperature, specific_humidity); +generate_ndarray_compute!(General3, temperature, specific_humidity); +generate_par_ndarray_compute!(General3, temperature, specific_humidity); #[cfg(test)] mod tests { From 16031213b8429c1d0a4c5bb2b390e2f17cf0a3d0 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 21 Feb 2024 02:00:29 +0100 Subject: [PATCH 016/102] hmmm --- src/lib.rs | 8 ++++++-- src/quantities.rs | 20 ++++++++++++++++++++ src/units.rs | 39 +++++++++++++++++++++++++++++++++++++++ src/variable.rs | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 src/quantities.rs create mode 100644 src/units.rs create mode 100644 src/variable.rs diff --git a/src/lib.rs b/src/lib.rs index c708ad4..3c1f7ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,14 +87,18 @@ pub mod mixing_ratio; pub mod potential_temperature; pub mod relative_humidity; pub mod specific_humidity; -mod tests_framework; pub mod vapour_pressure; pub mod vapour_pressure_deficit; pub mod virtual_temperature; pub mod wet_bulb_potential_temperature; pub mod wet_bulb_temperature; -pub(crate) mod compute_macros; +mod compute_macros; +mod tests_framework; + +pub mod variable; +pub mod units; +pub mod quantities; #[cfg(not(feature = "double_precision"))] type Float = f32; diff --git a/src/quantities.rs b/src/quantities.rs new file mode 100644 index 0000000..4626017 --- /dev/null +++ b/src/quantities.rs @@ -0,0 +1,20 @@ +#![allow(missing_docs)] + +use std::marker::PhantomData; + +pub trait Quantity {} + +pub trait Family {} + +pub trait Length: Family {} +pub trait Mass: Family {} +pub trait Time: Family {} +pub trait Temperature: Family {} +pub trait Pressure: Family {} +pub trait Ratio: Family {} + +pub struct VaporPressure { + _family: PhantomData, +} + +impl Quantity for VaporPressure {} diff --git a/src/units.rs b/src/units.rs new file mode 100644 index 0000000..ad8fc6b --- /dev/null +++ b/src/units.rs @@ -0,0 +1,39 @@ +#![allow(missing_docs)] + +use std::marker::PhantomData; + +use crate::quantities::{Family, Pressure}; + +pub trait Unit {} + +// pub struct Meter; +// pub struct Kilometer; +// pub struct Millimeter; + +// pub struct Kilogram; +// pub struct Gram; + +// pub struct Kelvin; +// pub struct Celsius; +// pub struct Fahrenheit; + +pub struct Pascal { + _family: PhantomData, +} +pub struct HectoPascal { + _family: PhantomData, +} +pub struct KiloPascal { + _family: PhantomData, +} + +// pub struct Percent; +// pub struct Decimal; + +// pub struct Second; +// pub struct Minute; +// pub struct Hour; + +impl Unit for Pascal {} +impl Unit for HectoPascal {} +impl Unit for KiloPascal {} diff --git a/src/variable.rs b/src/variable.rs new file mode 100644 index 0000000..51de717 --- /dev/null +++ b/src/variable.rs @@ -0,0 +1,39 @@ +#![allow(dead_code)] +#![allow(missing_docs)] + +use std::marker::PhantomData; + +use crate::{ + quantities::{Family, Pressure, Quantity}, + units::{KiloPascal, Pascal, Unit}, +}; + +pub struct Variable, Q: Quantity> { + pub value: f64, + _quantity: PhantomData, + _unit: PhantomData, + _family: PhantomData, +} + +impl, Q: Quantity> Variable { + pub fn new(value: f64) -> Self { + Variable { + value, + _quantity: PhantomData::, + _unit: PhantomData::, + _family: PhantomData::, + } + } +} + +pub trait UnitFrom: Sized { + fn from_convert(value: T) -> Self; +} + +impl> UnitFrom, Q>> + for Variable, Q> +{ + fn from_convert(value: Variable, Q>) -> Self { + todo!() + } +} From 07c80194e2a93c1e909c477f87990b10c552dd6e Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:15:46 +0100 Subject: [PATCH 017/102] WIP initial Formula implementation --- Cargo.toml | 70 +++-- src/equivalent_potential_temperature.rs | 152 ++++----- src/formula.rs | 101 ++++++ src/lib.rs | 11 +- src/mixing_ratio.rs | 122 ++++---- src/potential_temperature.rs | 58 ++-- src/quantities.rs | 46 ++- src/relative_humidity.rs | 214 ++++++------- src/specific_humidity.rs | 48 +-- src/units.rs | 39 --- src/vapour_pressure.rs | 396 ++++++++++++------------ src/vapour_pressure_deficit.rs | 142 ++++----- src/variable.rs | 39 --- src/virtual_temperature.rs | 132 ++++---- src/wet_bulb_temperature.rs | 48 +-- 15 files changed, 838 insertions(+), 780 deletions(-) create mode 100644 src/formula.rs delete mode 100644 src/units.rs delete mode 100644 src/variable.rs diff --git a/Cargo.toml b/Cargo.toml index 36cca03..2f86b2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,53 +21,61 @@ itertools = { version = "0.12", default-features = false, optional = true, featu ] } ndarray = { version = "0.15", default-features = false, optional = true } rayon = { version = "1.8", default-features = false, optional = true } +uom = { version = "0.35", default-features = false, features = [ + "si", + "std", + "autoconvert", + "f32", + "f64", +] } +num-traits = { version = "0.2", default-features = false, features = ["std"] } [dev-dependencies] criterion = "0.5.1" [features] -default = ["array_compute", "array_compute_parallel"] +default = ["array_compute", "array_compute_parallel", "debug", "double_precision"] debug = ["floccus-proc"] double_precision = [] array_compute = ["ndarray", "itertools"] array_compute_parallel = ["array_compute", "ndarray/rayon", "rayon"] -[[bench]] -name = "virtual_temperature" -harness = false +# [[bench]] +# name = "virtual_temperature" +# harness = false -[[bench]] -name = "vapour_pressure" -harness = false +# [[bench]] +# name = "vapour_pressure" +# harness = false -[[bench]] -name = "vapour_pressure_deficit" -harness = false +# [[bench]] +# name = "vapour_pressure_deficit" +# harness = false -[[bench]] -name = "mixing_ratio" -harness = false +# [[bench]] +# name = "mixing_ratio" +# harness = false -[[bench]] -name = "wet_bulb_temperature" -harness = false +# [[bench]] +# name = "wet_bulb_temperature" +# harness = false -[[bench]] -name = "relative_humidity" -harness = false +# [[bench]] +# name = "relative_humidity" +# harness = false -[[bench]] -name = "specific_humidity" -harness = false +# [[bench]] +# name = "specific_humidity" +# harness = false -[[bench]] -name = "potential_temperature" -harness = false +# [[bench]] +# name = "potential_temperature" +# harness = false -[[bench]] -name = "equivalent_potential_temperature" -harness = false +# [[bench]] +# name = "equivalent_potential_temperature" +# harness = false -[[bench]] -name = "wet_bulb_potential_temperature" -harness = false +# [[bench]] +# name = "wet_bulb_potential_temperature" +# harness = false diff --git a/src/equivalent_potential_temperature.rs b/src/equivalent_potential_temperature.rs index 1ee1acf..d80f7ed 100644 --- a/src/equivalent_potential_temperature.rs +++ b/src/equivalent_potential_temperature.rs @@ -216,79 +216,79 @@ generate_vec_compute!(Bolton1, pressure, temperature, dewpoint); generate_par_vec_compute!(Bolton1, pressure, temperature, dewpoint); generate_ndarray_compute!(Bolton1, pressure, temperature, dewpoint); -#[cfg(test)] -mod tests { - use crate::{ - equivalent_potential_temperature, - tests_framework::{self, Argument}, - }; - - #[test] - fn paluch1() { - assert!(tests_framework::test_with_3args( - &equivalent_potential_temperature::paluch1, - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [20000.0, 150_000.0] - }, - Argument { - name: "vapour_pressure", - def_val: 991.189131, - range: [0.0, 10_000.0] - }, - 315.23724970376776 - )); - } - - #[test] - fn bryan1() { - assert!(tests_framework::test_with_3args( - &equivalent_potential_temperature::bryan1, - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [20000.0, 150_000.0] - }, - Argument { - name: "vapour_pressure", - def_val: 991.189131, - range: [0.0, 10_000.0] - }, - 316.52762026634014 - )); - } - - #[test] - fn bolton1() { - assert!(tests_framework::test_with_3args( - &equivalent_potential_temperature::bolton1, - Argument { - name: "pressure", - def_val: 101325.0, - range: [20000.0, 150_000.0] - }, - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0] - }, - Argument { - name: "dewpoint", - def_val: 280.0, - range: [253.0, 324.0] - }, - 317.3855211897774 - )); - } -} +// #[cfg(test)] +// mod tests { +// use crate::{ +// equivalent_potential_temperature, +// tests_framework::{self, Argument}, +// }; + +// #[test] +// fn paluch1() { +// assert!(tests_framework::test_with_3args( +// &equivalent_potential_temperature::paluch1, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [253.0, 324.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [20000.0, 150_000.0] +// }, +// Argument { +// name: "vapour_pressure", +// def_val: 991.189131, +// range: [0.0, 10_000.0] +// }, +// 315.23724970376776 +// )); +// } + +// #[test] +// fn bryan1() { +// assert!(tests_framework::test_with_3args( +// &equivalent_potential_temperature::bryan1, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [253.0, 324.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [20000.0, 150_000.0] +// }, +// Argument { +// name: "vapour_pressure", +// def_val: 991.189131, +// range: [0.0, 10_000.0] +// }, +// 316.52762026634014 +// )); +// } + +// #[test] +// fn bolton1() { +// assert!(tests_framework::test_with_3args( +// &equivalent_potential_temperature::bolton1, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [20000.0, 150_000.0] +// }, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [253.0, 324.0] +// }, +// Argument { +// name: "dewpoint", +// def_val: 280.0, +// range: [253.0, 324.0] +// }, +// 317.3855211897774 +// )); +// } +// } diff --git a/src/formula.rs b/src/formula.rs new file mode 100644 index 0000000..3521729 --- /dev/null +++ b/src/formula.rs @@ -0,0 +1,101 @@ +#![allow(missing_docs)] + +use uom::si::{ + pressure::hectopascal, ratio::ratio, temperature_coefficient, temperature_interval, + thermodynamic_temperature, +}; + +use crate::{ + errors::InputError, + quantities::{DewPointTemperature, DryBulbTemperature, ThermodynamicQuantity, VapourPressure}, + vapour_pressure::Buck3Simplified, + Storage::{ + Pressure, Ratio, TemperatureCoefficient, TemperatureInterval, ThermodynamicTemperature, + }, +}; + +pub trait Formula1 { + #[allow(missing_docs)] + fn compute_unchecked(i1: I1) -> O; + + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn validate_inputs(i1: I1) -> Result<(), InputError>; + + #[allow(clippy::missing_errors_doc)] + #[allow(missing_docs)] + #[inline] + fn compute(i1: I1) -> Result { + #[cfg(not(feature = "debug"))] + Self::validate_inputs(i1)?; + #[cfg(feature = "debug")] + Self::validate_inputs_loggerr(i1)?; + + Ok(Self::compute_unchecked(i1)) + } + + #[cfg(feature = "debug")] + #[inline(always)] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn validate_inputs_loggerr(i1: I1) -> Result<(), InputError> { + use std::any::type_name; + + Self::validate_inputs(i1).or_else(|err| { + log::error!( + "Formula {} calculating {} from inputs {:?} returned error: {}", + type_name::(), + type_name::(), + i1, + err + ); + Err(err) + }) + } +} + +struct BuckTest; + +impl Formula1 for BuckTest { + #[inline(always)] + fn validate_inputs(i1: DewPointTemperature) -> Result<(), InputError> { + let i1_si = i1.0.get::(); + + if !(253.0..=324.0).contains(&i1_si) { + return Err(InputError::OutOfRange(String::from("dewpoint"))); + } + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked(i1: DewPointTemperature) -> VapourPressure { + let dewpoint = i1.0.get::(); + let dewpoint = Ratio::new::(dewpoint); + + let lower_a = Pressure::new::(6.1121); + let lower_b = Ratio::new::(17.502); + let lower_c = Ratio::new::(240.97); + + let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); + + VapourPressure(lower_e) + } +} + +#[cfg(test)] +mod tests { + use uom::si::f64::ThermodynamicTemperature; + + use super::*; + + #[test] + fn test_buck3_simplified() { + let input = ThermodynamicTemperature::new::(300.0); + let input = DewPointTemperature(input); + + let result = BuckTest::compute(input).unwrap(); + + println!("{:?}", result.0); + } +} diff --git a/src/lib.rs b/src/lib.rs index 3c1f7ae..5b1edf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,12 +96,19 @@ pub mod wet_bulb_temperature; mod compute_macros; mod tests_framework; -pub mod variable; -pub mod units; pub mod quantities; +pub mod formula; #[cfg(not(feature = "double_precision"))] type Float = f32; #[cfg(feature = "double_precision")] type Float = f64; + + +#[cfg(not(feature = "double_precision"))] +pub(crate) use uom::si::f32 as Storage; + +#[cfg(feature = "double_precision")] +pub(crate) use uom::si::f64 as Storage; + diff --git a/src/mixing_ratio.rs b/src/mixing_ratio.rs index f4fb124..2d0ef0e 100644 --- a/src/mixing_ratio.rs +++ b/src/mixing_ratio.rs @@ -141,64 +141,64 @@ impl Accuracy1 { } } -#[cfg(test)] -mod tests { - use crate::{ - mixing_ratio, - tests_framework::{self, Argument}, - }; - - #[test] - fn general1() { - assert!(tests_framework::test_with_2args( - &mixing_ratio::general1, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - Argument { - name: "vapour_pressure", - def_val: 3500.0, - range: [0.0, 50_000.0] - }, - 0.022253316630823517 - )); - } - - #[test] - fn performance1() { - assert!(tests_framework::test_with_2args( - &mixing_ratio::performance1, - Argument { - name: "dewpoint", - def_val: 300.0, - range: [273.0, 353.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - 0.022477100514593465 - )); - } - - #[test] - fn accuracy1() { - assert!(tests_framework::test_with_2args( - &mixing_ratio::accuracy1, - Argument { - name: "dewpoint", - def_val: 300.0, - range: [232.0, 324.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - 0.022587116896465847 - )); - } -} +// #[cfg(test)] +// mod tests { +// use crate::{ +// mixing_ratio, +// tests_framework::{self, Argument}, +// }; + +// #[test] +// fn general1() { +// assert!(tests_framework::test_with_2args( +// &mixing_ratio::general1, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// Argument { +// name: "vapour_pressure", +// def_val: 3500.0, +// range: [0.0, 50_000.0] +// }, +// 0.022253316630823517 +// )); +// } + +// #[test] +// fn performance1() { +// assert!(tests_framework::test_with_2args( +// &mixing_ratio::performance1, +// Argument { +// name: "dewpoint", +// def_val: 300.0, +// range: [273.0, 353.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// 0.022477100514593465 +// )); +// } + +// #[test] +// fn accuracy1() { +// assert!(tests_framework::test_with_2args( +// &mixing_ratio::accuracy1, +// Argument { +// name: "dewpoint", +// def_val: 300.0, +// range: [232.0, 324.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// 0.022587116896465847 +// )); +// } +// } diff --git a/src/potential_temperature.rs b/src/potential_temperature.rs index 19ffe13..bb71cc6 100644 --- a/src/potential_temperature.rs +++ b/src/potential_temperature.rs @@ -85,33 +85,33 @@ generate_par_vec_compute!(DaviesJones1, temperature, pressure, vapour_pressure); generate_ndarray_compute!(DaviesJones1, temperature, pressure, vapour_pressure); generate_par_ndarray_compute!(DaviesJones1, temperature, pressure, vapour_pressure); -#[cfg(test)] -mod tests { - use crate::{ - potential_temperature, - tests_framework::{self, Argument}, - }; +// #[cfg(test)] +// mod tests { +// use crate::{ +// potential_temperature, +// tests_framework::{self, Argument}, +// }; - #[test] - fn davies_jones1() { - assert!(tests_framework::test_with_3args( - &potential_temperature::davies_jones1, - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - Argument { - name: "vapour_pressure", - def_val: 3000.0, - range: [0.0, 10_000.0] - }, - 301.45136519081666 - )); - } -} +// #[test] +// fn davies_jones1() { +// assert!(tests_framework::test_with_3args( +// &potential_temperature::davies_jones1, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [253.0, 324.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// Argument { +// name: "vapour_pressure", +// def_val: 3000.0, +// range: [0.0, 10_000.0] +// }, +// 301.45136519081666 +// )); +// } +// } diff --git a/src/quantities.rs b/src/quantities.rs index 4626017..0574f50 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -1,20 +1,40 @@ #![allow(missing_docs)] -use std::marker::PhantomData; +use crate::Storage; +use std::fmt::Debug; -pub trait Quantity {} +pub trait ThermodynamicQuantity: Debug + Clone + Copy + PartialEq + PartialOrd + Default {} -pub trait Family {} +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct DryBulbTemperature(pub Storage::ThermodynamicTemperature); -pub trait Length: Family {} -pub trait Mass: Family {} -pub trait Time: Family {} -pub trait Temperature: Family {} -pub trait Pressure: Family {} -pub trait Ratio: Family {} +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct WetBulbTemperature(pub Storage::TemperatureInterval); -pub struct VaporPressure { - _family: PhantomData, -} +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct DewPointTemperature(pub Storage::ThermodynamicTemperature); -impl Quantity for VaporPressure {} +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct VirtualTemperature(pub Storage::TemperatureInterval); + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct PotentialTemperature(pub Storage::TemperatureInterval); + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct EquivalentPotentialTemperature(pub Storage::TemperatureInterval); + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct WetBulbPotentialTemperature(pub Storage::TemperatureInterval); + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct VapourPressure(pub Storage::Pressure); + +impl ThermodynamicQuantity for DryBulbTemperature {} +impl ThermodynamicQuantity for WetBulbTemperature {} +impl ThermodynamicQuantity for DewPointTemperature {} +impl ThermodynamicQuantity for VirtualTemperature {} +impl ThermodynamicQuantity for PotentialTemperature {} +impl ThermodynamicQuantity for EquivalentPotentialTemperature {} +impl ThermodynamicQuantity for WetBulbPotentialTemperature {} + +impl ThermodynamicQuantity for VapourPressure {} diff --git a/src/relative_humidity.rs b/src/relative_humidity.rs index cffdc68..2fd2ecc 100644 --- a/src/relative_humidity.rs +++ b/src/relative_humidity.rs @@ -248,110 +248,110 @@ impl General5 { } } -#[cfg(test)] -mod tests { - use crate::{ - relative_humidity, - tests_framework::{self, Argument}, - }; - - #[test] - fn general1() { - assert!(tests_framework::test_with_2args( - &relative_humidity::general1, - Argument { - name: "mixing_ratio", - def_val: 0.01064, - range: [0.00001, 10.0] - }, - Argument { - name: "saturation_mixing_ratio", - def_val: 0.01467, - range: [0.00001, 10.0] - }, - 0.7252897068847989 - )); - } - - #[test] - fn general2() { - assert!(tests_framework::test_with_2args( - &relative_humidity::general2, - Argument { - name: "vapour_pressure", - def_val: 1706.0, - range: [0.0, 50_000.0] - }, - Argument { - name: "saturation_vapour_pressure", - def_val: 2339.0, - range: [0.1, 50_000.0] - }, - 0.7293715262932877 - )); - } - - #[test] - fn general3() { - assert!(tests_framework::test_with_2args( - &relative_humidity::general3, - Argument { - name: "temperature", - def_val: 300.0, - range: [273.0, 353.0] - }, - Argument { - name: "dewpoint", - def_val: 290.0, - range: [273.0, 353.0] - }, - 0.5431069897660531 - )); - } - - #[test] - fn general4() { - assert!(tests_framework::test_with_3args( - &relative_humidity::general4, - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0] - }, - Argument { - name: "dewpoint", - def_val: 290.0, - range: [253.0, 324.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - 0.5429224562155812 - )); - } - - #[test] - fn general5() { - assert!(tests_framework::test_with_3args( - &relative_humidity::general5, - Argument { - name: "temperature", - def_val: 300.0, - range: [232.0, 314.0] - }, - Argument { - name: "dewpoint", - def_val: 290.0, - range: [232.0, 314.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [10000.0, 150_000.0] - }, - 0.5338747953552858 - )); - } -} +// #[cfg(test)] +// mod tests { +// use crate::{ +// relative_humidity, +// tests_framework::{self, Argument}, +// }; + +// #[test] +// fn general1() { +// assert!(tests_framework::test_with_2args( +// &relative_humidity::general1, +// Argument { +// name: "mixing_ratio", +// def_val: 0.01064, +// range: [0.00001, 10.0] +// }, +// Argument { +// name: "saturation_mixing_ratio", +// def_val: 0.01467, +// range: [0.00001, 10.0] +// }, +// 0.7252897068847989 +// )); +// } + +// #[test] +// fn general2() { +// assert!(tests_framework::test_with_2args( +// &relative_humidity::general2, +// Argument { +// name: "vapour_pressure", +// def_val: 1706.0, +// range: [0.0, 50_000.0] +// }, +// Argument { +// name: "saturation_vapour_pressure", +// def_val: 2339.0, +// range: [0.1, 50_000.0] +// }, +// 0.7293715262932877 +// )); +// } + +// #[test] +// fn general3() { +// assert!(tests_framework::test_with_2args( +// &relative_humidity::general3, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [273.0, 353.0] +// }, +// Argument { +// name: "dewpoint", +// def_val: 290.0, +// range: [273.0, 353.0] +// }, +// 0.5431069897660531 +// )); +// } + +// #[test] +// fn general4() { +// assert!(tests_framework::test_with_3args( +// &relative_humidity::general4, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [253.0, 324.0] +// }, +// Argument { +// name: "dewpoint", +// def_val: 290.0, +// range: [253.0, 324.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// 0.5429224562155812 +// )); +// } + +// #[test] +// fn general5() { +// assert!(tests_framework::test_with_3args( +// &relative_humidity::general5, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [232.0, 314.0] +// }, +// Argument { +// name: "dewpoint", +// def_val: 290.0, +// range: [232.0, 314.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [10000.0, 150_000.0] +// }, +// 0.5338747953552858 +// )); +// } +// } diff --git a/src/specific_humidity.rs b/src/specific_humidity.rs index eec9547..f853579 100644 --- a/src/specific_humidity.rs +++ b/src/specific_humidity.rs @@ -60,28 +60,28 @@ generate_par_vec_compute!(General1, vapour_pressure, pressure); generate_ndarray_compute!(General1, vapour_pressure, pressure); generate_par_ndarray_compute!(General1, vapour_pressure, pressure); -#[cfg(test)] -mod tests { - use crate::{ - specific_humidity, - tests_framework::{self, Argument}, - }; +// #[cfg(test)] +// mod tests { +// use crate::{ +// specific_humidity, +// tests_framework::{self, Argument}, +// }; - #[test] - fn general1() { - assert!(tests_framework::test_with_2args( - &specific_humidity::general1, - Argument { - name: "vapour_pressure", - def_val: 3000.0, - range: [0.0, 50_000.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - 0.018623845512674677 - )); - } -} +// #[test] +// fn general1() { +// assert!(tests_framework::test_with_2args( +// &specific_humidity::general1, +// Argument { +// name: "vapour_pressure", +// def_val: 3000.0, +// range: [0.0, 50_000.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// 0.018623845512674677 +// )); +// } +// } diff --git a/src/units.rs b/src/units.rs deleted file mode 100644 index ad8fc6b..0000000 --- a/src/units.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![allow(missing_docs)] - -use std::marker::PhantomData; - -use crate::quantities::{Family, Pressure}; - -pub trait Unit {} - -// pub struct Meter; -// pub struct Kilometer; -// pub struct Millimeter; - -// pub struct Kilogram; -// pub struct Gram; - -// pub struct Kelvin; -// pub struct Celsius; -// pub struct Fahrenheit; - -pub struct Pascal { - _family: PhantomData, -} -pub struct HectoPascal { - _family: PhantomData, -} -pub struct KiloPascal { - _family: PhantomData, -} - -// pub struct Percent; -// pub struct Decimal; - -// pub struct Second; -// pub struct Minute; -// pub struct Hour; - -impl Unit for Pascal {} -impl Unit for HectoPascal {} -impl Unit for KiloPascal {} diff --git a/src/vapour_pressure.rs b/src/vapour_pressure.rs index 8a92019..f81b5e9 100644 --- a/src/vapour_pressure.rs +++ b/src/vapour_pressure.rs @@ -636,201 +636,201 @@ generate_ndarray_compute!(Wexler2, dewpoint); generate_par_vec_compute!(Wexler2, dewpoint); generate_par_ndarray_compute!(Wexler2, dewpoint); -#[cfg(test)] -mod tests { - use crate::{ - tests_framework::{self, Argument}, - vapour_pressure, - }; - - #[test] - fn general1() { - assert!(tests_framework::test_with_2args( - &vapour_pressure::general1, - Argument { - name: "specific_humidity", - def_val: 0.022, - range: [0.00001, 2.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - 3536.6680935251343 - )); - } - - #[test] - fn buck1() { - assert!(tests_framework::test_with_2args( - &vapour_pressure::buck1, - Argument { - name: "dewpoint", - def_val: 300.0, - range: [232.0, 324.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - 3550.6603579471303 - )); - } - - #[test] - fn buck2() { - assert!(tests_framework::test_with_2args( - &vapour_pressure::buck2, - Argument { - name: "dewpoint", - def_val: 250.0, - range: [193.0, 274.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - 76.38781790372722 - )); - } - - #[test] - fn buck3() { - assert!(tests_framework::test_with_2args( - &vapour_pressure::buck3, - Argument { - name: "dewpoint", - def_val: 300.0, - range: [253.0, 324.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - 3548.5041048035896 - )); - } - - #[test] - fn buck4() { - assert!(tests_framework::test_with_2args( - &vapour_pressure::buck4, - Argument { - name: "dewpoint", - def_val: 250.0, - range: [223.0, 274.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - 76.38685471836712 - )); - } - - #[test] - fn buck3_simplified() { - assert!(tests_framework::test_with_1arg( - &vapour_pressure::buck3_simplified, - Argument { - name: "dewpoint", - def_val: 300.0, - range: [253.0, 324.0] - }, - 3533.6421536199978 - )); - } - - #[test] - fn buck4_simplified() { - assert!(tests_framework::test_with_1arg( - &vapour_pressure::buck4_simplified, - Argument { - name: "dewpoint", - def_val: 250.0, - range: [223.0, 274.0] - }, - 76.04197508519536 - )); - } - - #[test] - fn tetens1() { - assert!(tests_framework::test_with_1arg( - &vapour_pressure::tetens1, - Argument { - name: "dewpoint", - def_val: 300.0, - range: [273.0, 353.0] - }, - 3533.969137160892 - )); - } - - #[test] - fn saturation_specific1() { - assert!(tests_framework::test_with_2args( - &vapour_pressure::saturation_specific1, - Argument { - name: "saturation_vapour_pressure", - def_val: 3550.0, - range: [0.0, 50_000.0] - }, - Argument { - name: "relative_humidity", - def_val: 0.5, - range: [0.0, 2.0] - }, - 1775.0 - )); - } - - #[test] - fn saturation_specific2() { - assert!(tests_framework::test_with_2args( - &vapour_pressure::saturation_specific2, - Argument { - name: "vapour_pressure", - def_val: 3000.0, - range: [0.0, 10_000.0] - }, - Argument { - name: "relative_humidity", - def_val: 0.5, - range: [0.00001, 2.0] - }, - 6000.0 - )); - } - - #[test] - fn wexler1() { - assert!(tests_framework::test_with_1arg( - &vapour_pressure::wexler1, - Argument { - name: "dewpoint", - def_val: 300.0, - range: [273.0, 374.0] - }, - 3535.4235919263083 - )); - } - - #[test] - fn wexler2() { - assert!(tests_framework::test_with_1arg( - &vapour_pressure::wexler2, - Argument { - name: "dewpoint", - def_val: 250.0, - range: [173.0, 274.0] - }, - 76.04351136780438 - )); - } -} +// #[cfg(test)] +// mod tests { +// use crate::{ +// tests_framework::{self, Argument}, +// vapour_pressure, +// }; + +// #[test] +// fn general1() { +// assert!(tests_framework::test_with_2args( +// &vapour_pressure::general1, +// Argument { +// name: "specific_humidity", +// def_val: 0.022, +// range: [0.00001, 2.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// 3536.6680935251343 +// )); +// } + +// #[test] +// fn buck1() { +// assert!(tests_framework::test_with_2args( +// &vapour_pressure::buck1, +// Argument { +// name: "dewpoint", +// def_val: 300.0, +// range: [232.0, 324.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// 3550.6603579471303 +// )); +// } + +// #[test] +// fn buck2() { +// assert!(tests_framework::test_with_2args( +// &vapour_pressure::buck2, +// Argument { +// name: "dewpoint", +// def_val: 250.0, +// range: [193.0, 274.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// 76.38781790372722 +// )); +// } + +// #[test] +// fn buck3() { +// assert!(tests_framework::test_with_2args( +// &vapour_pressure::buck3, +// Argument { +// name: "dewpoint", +// def_val: 300.0, +// range: [253.0, 324.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// 3548.5041048035896 +// )); +// } + +// #[test] +// fn buck4() { +// assert!(tests_framework::test_with_2args( +// &vapour_pressure::buck4, +// Argument { +// name: "dewpoint", +// def_val: 250.0, +// range: [223.0, 274.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// 76.38685471836712 +// )); +// } + +// #[test] +// fn buck3_simplified() { +// assert!(tests_framework::test_with_1arg( +// &vapour_pressure::buck3_simplified, +// Argument { +// name: "dewpoint", +// def_val: 300.0, +// range: [253.0, 324.0] +// }, +// 3533.6421536199978 +// )); +// } + +// #[test] +// fn buck4_simplified() { +// assert!(tests_framework::test_with_1arg( +// &vapour_pressure::buck4_simplified, +// Argument { +// name: "dewpoint", +// def_val: 250.0, +// range: [223.0, 274.0] +// }, +// 76.04197508519536 +// )); +// } + +// #[test] +// fn tetens1() { +// assert!(tests_framework::test_with_1arg( +// &vapour_pressure::tetens1, +// Argument { +// name: "dewpoint", +// def_val: 300.0, +// range: [273.0, 353.0] +// }, +// 3533.969137160892 +// )); +// } + +// #[test] +// fn saturation_specific1() { +// assert!(tests_framework::test_with_2args( +// &vapour_pressure::saturation_specific1, +// Argument { +// name: "saturation_vapour_pressure", +// def_val: 3550.0, +// range: [0.0, 50_000.0] +// }, +// Argument { +// name: "relative_humidity", +// def_val: 0.5, +// range: [0.0, 2.0] +// }, +// 1775.0 +// )); +// } + +// #[test] +// fn saturation_specific2() { +// assert!(tests_framework::test_with_2args( +// &vapour_pressure::saturation_specific2, +// Argument { +// name: "vapour_pressure", +// def_val: 3000.0, +// range: [0.0, 10_000.0] +// }, +// Argument { +// name: "relative_humidity", +// def_val: 0.5, +// range: [0.00001, 2.0] +// }, +// 6000.0 +// )); +// } + +// #[test] +// fn wexler1() { +// assert!(tests_framework::test_with_1arg( +// &vapour_pressure::wexler1, +// Argument { +// name: "dewpoint", +// def_val: 300.0, +// range: [273.0, 374.0] +// }, +// 3535.4235919263083 +// )); +// } + +// #[test] +// fn wexler2() { +// assert!(tests_framework::test_with_1arg( +// &vapour_pressure::wexler2, +// Argument { +// name: "dewpoint", +// def_val: 250.0, +// range: [173.0, 274.0] +// }, +// 76.04351136780438 +// )); +// } +// } diff --git a/src/vapour_pressure_deficit.rs b/src/vapour_pressure_deficit.rs index 2711dd0..6d2e449 100644 --- a/src/vapour_pressure_deficit.rs +++ b/src/vapour_pressure_deficit.rs @@ -170,74 +170,74 @@ generate_ndarray_compute!(General3, temperature, relative_humidity, pressure); generate_par_vec_compute!(General3, temperature, relative_humidity, pressure); generate_par_ndarray_compute!(General3, temperature, relative_humidity, pressure); -#[cfg(test)] -mod tests { - use crate::{ - tests_framework::{self, Argument}, - vapour_pressure_deficit, - }; - - #[test] - fn general1() { - assert!(tests_framework::test_with_2args( - &vapour_pressure_deficit::general1, - Argument { - name: "vapour_pressure", - def_val: 3000.0, - range: [0.0, 50_000.0] - }, - Argument { - name: "saturation_vapour_pressure", - def_val: 3550.0, - range: [0.0, 50_000.0] - }, - 550.0 - )); - } - - #[test] - fn general2() { - assert!(tests_framework::test_with_3args( - &vapour_pressure_deficit::general2, - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0] - }, - Argument { - name: "dewpoint", - def_val: 290.0, - range: [253.0, 324.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - 1621.9415403325527 - )); - } - - #[test] - fn general3() { - assert!(tests_framework::test_with_3args( - &vapour_pressure_deficit::general3, - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 319.0] - }, - Argument { - name: "relative_humidity", - def_val: 0.5, - range: [0.05, 1.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [10000.0, 150_000.0] - }, - 1774.2520524017948 - )); - } -} +// #[cfg(test)] +// mod tests { +// use crate::{ +// tests_framework::{self, Argument}, +// vapour_pressure_deficit, +// }; + +// #[test] +// fn general1() { +// assert!(tests_framework::test_with_2args( +// &vapour_pressure_deficit::general1, +// Argument { +// name: "vapour_pressure", +// def_val: 3000.0, +// range: [0.0, 50_000.0] +// }, +// Argument { +// name: "saturation_vapour_pressure", +// def_val: 3550.0, +// range: [0.0, 50_000.0] +// }, +// 550.0 +// )); +// } + +// #[test] +// fn general2() { +// assert!(tests_framework::test_with_3args( +// &vapour_pressure_deficit::general2, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [253.0, 324.0] +// }, +// Argument { +// name: "dewpoint", +// def_val: 290.0, +// range: [253.0, 324.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// 1621.9415403325527 +// )); +// } + +// #[test] +// fn general3() { +// assert!(tests_framework::test_with_3args( +// &vapour_pressure_deficit::general3, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [253.0, 319.0] +// }, +// Argument { +// name: "relative_humidity", +// def_val: 0.5, +// range: [0.05, 1.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [10000.0, 150_000.0] +// }, +// 1774.2520524017948 +// )); +// } +// } diff --git a/src/variable.rs b/src/variable.rs deleted file mode 100644 index 51de717..0000000 --- a/src/variable.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![allow(dead_code)] -#![allow(missing_docs)] - -use std::marker::PhantomData; - -use crate::{ - quantities::{Family, Pressure, Quantity}, - units::{KiloPascal, Pascal, Unit}, -}; - -pub struct Variable, Q: Quantity> { - pub value: f64, - _quantity: PhantomData, - _unit: PhantomData, - _family: PhantomData, -} - -impl, Q: Quantity> Variable { - pub fn new(value: f64) -> Self { - Variable { - value, - _quantity: PhantomData::, - _unit: PhantomData::, - _family: PhantomData::, - } - } -} - -pub trait UnitFrom: Sized { - fn from_convert(value: T) -> Self; -} - -impl> UnitFrom, Q>> - for Variable, Q> -{ - fn from_convert(value: Variable, Q>) -> Self { - todo!() - } -} diff --git a/src/virtual_temperature.rs b/src/virtual_temperature.rs index 4d5a105..340cf6a 100644 --- a/src/virtual_temperature.rs +++ b/src/virtual_temperature.rs @@ -141,69 +141,69 @@ generate_par_vec_compute!(General3, temperature, specific_humidity); generate_ndarray_compute!(General3, temperature, specific_humidity); generate_par_ndarray_compute!(General3, temperature, specific_humidity); -#[cfg(test)] -mod tests { - use crate::{ - tests_framework::{self, Argument}, - virtual_temperature, - }; - - #[test] - fn general1() { - assert!(tests_framework::test_with_2args( - &virtual_temperature::general1, - Argument { - name: "temperature", - def_val: 300.0, - range: [173.0, 354.0] - }, - Argument { - name: "mixing_ratio", - def_val: 0.022, - range: [0.000_000_000_1, 0.5] - }, - 303.9249219815806 - )); - } - - #[test] - fn general2() { - assert!(tests_framework::test_with_3args( - &virtual_temperature::general2, - Argument { - name: "temperature", - def_val: 300.0, - range: [173.0, 354.0] - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0] - }, - Argument { - name: "vapour_pressure", - def_val: 3550.0, - range: [0.0, 10_000.0] - }, - 304.0265941965307 - )); - } - - #[test] - fn general3() { - assert!(tests_framework::test_with_2args( - &virtual_temperature::general3, - Argument { - name: "temperature", - def_val: 300.0, - range: [173.0, 354.0] - }, - Argument { - name: "specific_humidity", - def_val: 0.022, - range: [0.000000001, 2.0] - }, - 304.0112702651753 - )); - } -} +// #[cfg(test)] +// mod tests { +// use crate::{ +// tests_framework::{self, Argument}, +// virtual_temperature, +// }; + +// #[test] +// fn general1() { +// assert!(tests_framework::test_with_2args( +// &virtual_temperature::general1, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [173.0, 354.0] +// }, +// Argument { +// name: "mixing_ratio", +// def_val: 0.022, +// range: [0.000_000_000_1, 0.5] +// }, +// 303.9249219815806 +// )); +// } + +// #[test] +// fn general2() { +// assert!(tests_framework::test_with_3args( +// &virtual_temperature::general2, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [173.0, 354.0] +// }, +// Argument { +// name: "pressure", +// def_val: 101325.0, +// range: [100.0, 150_000.0] +// }, +// Argument { +// name: "vapour_pressure", +// def_val: 3550.0, +// range: [0.0, 10_000.0] +// }, +// 304.0265941965307 +// )); +// } + +// #[test] +// fn general3() { +// assert!(tests_framework::test_with_2args( +// &virtual_temperature::general3, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [173.0, 354.0] +// }, +// Argument { +// name: "specific_humidity", +// def_val: 0.022, +// range: [0.000000001, 2.0] +// }, +// 304.0112702651753 +// )); +// } +// } diff --git a/src/wet_bulb_temperature.rs b/src/wet_bulb_temperature.rs index 137c568..c2d3ae6 100644 --- a/src/wet_bulb_temperature.rs +++ b/src/wet_bulb_temperature.rs @@ -64,28 +64,28 @@ generate_par_vec_compute!(Stull1, temperature, relative_humidity); generate_ndarray_compute!(Stull1, temperature, relative_humidity); generate_par_ndarray_compute!(Stull1, temperature, relative_humidity); -#[cfg(test)] -mod tests { - use crate::{ - tests_framework::{self, Argument}, - wet_bulb_temperature, - }; +// #[cfg(test)] +// mod tests { +// use crate::{ +// tests_framework::{self, Argument}, +// wet_bulb_temperature, +// }; - #[test] - fn stull1() { - assert!(tests_framework::test_with_2args( - &wet_bulb_temperature::stull1, - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0] - }, - Argument { - name: "relative_humidity", - def_val: 0.5, - range: [0.05, 0.99] - }, - 292.73867410526674 - )); - } -} +// #[test] +// fn stull1() { +// assert!(tests_framework::test_with_2args( +// &wet_bulb_temperature::stull1, +// Argument { +// name: "temperature", +// def_val: 300.0, +// range: [253.0, 324.0] +// }, +// Argument { +// name: "relative_humidity", +// def_val: 0.5, +// range: [0.05, 0.99] +// }, +// 292.73867410526674 +// )); +// } +// } From d241da92279a354241163c263fad4bbafcf2ad35 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 01:22:39 +0100 Subject: [PATCH 018/102] WIP (slowly making API and tests work) --- Cargo.toml | 12 +- src/compute_macros.rs | 357 ------------- src/constants.rs | 144 +++-- src/equivalent_potential_temperature.rs | 30 +- src/formula.rs | 115 ++-- src/lib.rs | 30 +- src/mixing_ratio.rs | 25 +- src/potential_temperature.rs | 16 +- src/quantities.rs | 284 +++++++++- src/relative_humidity.rs | 39 +- src/saturation_vapour_pressure.rs | 36 ++ src/specific_humidity.rs | 16 +- src/tests.rs | 204 ++++++++ src/tests_framework.rs | 197 ------- src/vapour_pressure.rs | 667 ++++++++++-------------- src/vapour_pressure_deficit.rs | 30 +- src/virtual_temperature.rs | 30 +- src/wet_bulb_potential_temperature.rs | 54 +- src/wet_bulb_temperature.rs | 16 +- 19 files changed, 1046 insertions(+), 1256 deletions(-) delete mode 100644 src/compute_macros.rs create mode 100644 src/saturation_vapour_pressure.rs create mode 100644 src/tests.rs delete mode 100644 src/tests_framework.rs diff --git a/Cargo.toml b/Cargo.toml index 2f86b2a..5cd16f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,7 @@ exclude = [".github/*"] [dependencies] thiserror = "1.0" float-cmp = "0.9" -floccus-proc = { version = "0.2.5", optional = true } -log = "0.4" +log = { version = "0.4", optional = true } itertools = { version = "0.12", default-features = false, optional = true, features = [ "use_std", ] } @@ -34,8 +33,13 @@ num-traits = { version = "0.2", default-features = false, features = ["std"] } criterion = "0.5.1" [features] -default = ["array_compute", "array_compute_parallel", "debug", "double_precision"] -debug = ["floccus-proc"] +default = [ + "array_compute", + "array_compute_parallel", + "debug", + "double_precision", +] +debug = ["log"] double_precision = [] array_compute = ["ndarray", "itertools"] array_compute_parallel = ["array_compute", "ndarray/rayon", "rayon"] diff --git a/src/compute_macros.rs b/src/compute_macros.rs deleted file mode 100644 index e8e9045..0000000 --- a/src/compute_macros.rs +++ /dev/null @@ -1,357 +0,0 @@ -macro_rules! generate_compute { - ($qnt:tt, $arg1:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute($arg1: Float) -> Result { - Self::validate_inputs($arg1)?; - Ok(Self::compute_unchecked($arg1)) - } - } - }; - - ($qnt:tt, $arg1:tt, $arg2:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute($arg1: Float, $arg2: Float) -> Result { - Self::validate_inputs($arg1, $arg2)?; - Ok(Self::compute_unchecked($arg1, $arg2)) - } - } - }; - - ($qnt:tt, $arg1:tt, $arg2:tt, $arg3:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute($arg1: Float, $arg2: Float, $arg3: Float) -> Result { - Self::validate_inputs($arg1, $arg2, $arg3)?; - Ok(Self::compute_unchecked($arg1, $arg2, $arg3)) - } - } - }; -} - -macro_rules! generate_vec_compute { - ($qnt:tt, $slice1:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute_vec($slice1: &[Float]) -> Result, InputError> { - $slice1 - .iter() - .map(|&a| Self::compute(a)) - .collect::, InputError>>() - } - } - }; - - ($qnt:tt, $slice1:tt, $slice2:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute_vec( - $slice1: &[Float], - $slice2: &[Float], - ) -> Result, InputError> { - izip!($slice1, $slice2) - .map(|(&a, &b)| Self::compute(a, b)) - .collect::, InputError>>() - } - } - }; - - ($qnt:tt, $slice1:tt, $slice2:tt, $slice3:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute_vec( - $slice1: &[Float], - $slice2: &[Float], - $slice3: &[Float], - ) -> Result, InputError> { - izip!($slice1, $slice2, $slice3) - .map(|(&a, &b, &c)| Self::compute(a, b, c)) - .collect::, InputError>>() - } - } - }; -} - -macro_rules! generate_ndarray_compute { - ($qnt:tt, $arr1:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute_ndarray( - $arr1: &Array, - ) -> Result, InputError> { - ndarray::Zip::from($arr1) - .fold_while(Ok(()), |_, &a| match Self::validate_inputs(a) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }) - .into_inner()?; - - Ok(ndarray::Zip::from($arr1).map_collect(|&a| Self::compute_unchecked(a))) - } - } - }; - - ($qnt:tt, $arr1:tt, $arr2:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute_ndarray( - $arr1: &Array, - $arr2: &Array, - ) -> Result, InputError> { - ndarray::Zip::from($arr1) - .and($arr2) - .fold_while(Ok(()), |_, &a, &b| match Self::validate_inputs(a, b) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }) - .into_inner()?; - - Ok(ndarray::Zip::from($arr1) - .and($arr2) - .map_collect(|&a, &b| Self::compute_unchecked(a, b))) - } - } - }; - - ($qnt:tt, $arr1:tt, $arr2:tt, $arr3:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute_ndarray( - $arr1: &Array, - $arr2: &Array, - $arr3: &Array, - ) -> Result, InputError> { - ndarray::Zip::from($arr1) - .and($arr2) - .and($arr3) - .fold_while(Ok(()), |_, &a, &b, &c| { - match Self::validate_inputs(a, b, c) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - } - }) - .into_inner()?; - - Ok(ndarray::Zip::from($arr1) - .and($arr2) - .and($arr3) - .map_collect(|&a, &b, &c| Self::compute_unchecked(a, b, c))) - } - } - }; -} - -macro_rules! generate_par_vec_compute { - ($qnt:tt, $slice1:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute_vec_parallel($slice1: &[Float]) -> Result, InputError> { - $slice1 - .par_iter() - .map(|&a| Self::compute(a)) - .collect::, InputError>>() - } - } - }; - - ($qnt:tt, $slice1:tt, $slice2:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute_vec_parallel( - $slice1: &[Float], - $slice2: &[Float], - ) -> Result, InputError> { - izip!($slice1, $slice2) - .par_bridge() - .map(|(&a, &b)| Self::compute(a, b)) - .collect::, InputError>>() - } - } - }; - - ($qnt:tt, $slice1:tt, $slice2:tt, $slice3:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute_vec_parallel( - $slice1: &[Float], - $slice2: &[Float], - $slice3: &[Float], - ) -> Result, InputError> { - izip!($slice1, $slice2, $slice3) - .par_bridge() - .map(|(&a, &b, &c)| Self::compute(a, b, c)) - .collect::, InputError>>() - } - } - }; -} - -macro_rules! generate_par_ndarray_compute { - ($qnt:tt, $arr1:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute_ndarray_parallel( - $arr1: &Array, - ) -> Result, InputError> { - ndarray::Zip::from($arr1) - .fold_while(Ok(()), |_, &a| match Self::validate_inputs(a) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }) - .into_inner()?; - - Ok(ndarray::Zip::from($arr1).par_map_collect(|&a| Self::compute_unchecked(a))) - } - } - }; - - ($qnt:tt, $arr1:tt, $arr2:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute_ndarray_parallel( - $arr1: &Array, - $arr2: &Array, - ) -> Result, InputError> { - ndarray::Zip::from($arr1) - .and($arr2) - .fold_while(Ok(()), |_, &a, &b| match Self::validate_inputs(a, b) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }) - .into_inner()?; - - Ok(ndarray::Zip::from($arr1) - .and($arr2) - .par_map_collect(|&a, &b| Self::compute_unchecked(a, b))) - } - } - }; - - ($qnt:tt, $arr1:tt, $arr2:tt, $arr3:tt) => { - impl $qnt { - #[allow(missing_docs)] - pub fn compute_ndarray_parallel( - $arr1: &Array, - $arr2: &Array, - $arr3: &Array, - ) -> Result, InputError> { - ndarray::Zip::from($arr1) - .and($arr2) - .and($arr3) - .fold_while(Ok(()), |_, &a, &b, &c| { - match Self::validate_inputs(a, b, c) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - } - }) - .into_inner()?; - - Ok(ndarray::Zip::from($arr1) - .and($arr2) - .and($arr3) - .par_map_collect(|&a, &b, &c| Self::compute_unchecked(a, b, c))) - } - } - }; -} - -pub(crate) use generate_compute; -pub(crate) use generate_ndarray_compute; -pub(crate) use generate_par_ndarray_compute; -pub(crate) use generate_par_vec_compute; -pub(crate) use generate_vec_compute; - -// #[cfg(test)] -// mod tests { -// use float_cmp::assert_approx_eq; -// use ndarray::Array2; - -// use crate::{ -// vapour_pressure::{self}, -// vapour_pressure_deficit, Float, -// }; - -// #[test] -// fn arr_macro_1arg() -> Result<(), crate::errors::InputError> { -// let temp = Array2::from_elem((10, 10), 300.0); -// let result = compute_ndarray!( -// vapour_pressure::buck3_simplified_unchecked, -// vapour_pressure::buck3_simplified_validate, -// temp.view() -// )?; - -// assert_approx_eq!(Float, result[[5, 5]], 3533.6421536199978, epsilon = 0.01); - -// Ok(()) -// } - -// #[test] -// fn arr_macro_2arg() -> Result<(), crate::errors::InputError> { -// let temp = Array2::from_elem((10, 10), 300.0); -// let pressure = Array2::from_elem((10, 10), 101325.0); - -// let result = compute_ndarray!( -// vapour_pressure::buck3_unchecked, -// vapour_pressure::buck3_validate, -// temp.view(), -// pressure.view() -// )?; - -// assert_approx_eq!(Float, result[[5, 5]], 3548.5041048035896, epsilon = 0.01); - -// Ok(()) -// } - -// #[test] -// fn arr_macro_3arg() -> Result<(), crate::errors::InputError> { -// let temp = Array2::from_elem((10, 10), 300.0); -// let pressure = Array2::from_elem((10, 10), 101325.0); -// let relative_humidity = Array2::from_elem((10, 10), 0.5); - -// let result = compute_ndarray!( -// vapour_pressure_deficit::general3_unchecked, -// vapour_pressure_deficit::general3_validate, -// temp.view(), -// relative_humidity.view(), -// pressure.view() -// )?; - -// assert_approx_eq!(Float, result[[5, 5]], 1774.2520524017948, epsilon = 0.01); - -// Ok(()) -// } - -// #[test] -// fn vec_macro_1arg() { -// let temp = vec![300.0; 100]; -// let result = compute_vec!(vapour_pressure::buck3_simplified, &temp).unwrap(); - -// assert_approx_eq!(Float, result[50], 3533.6421536199978, epsilon = 0.01); -// } - -// #[test] -// fn vec_macro_2arg() { -// let temp = vec![300.0; 100]; -// let pressure = vec![101325.0; 100]; -// let result = compute_vec!(vapour_pressure::buck3, &temp, &pressure).unwrap(); - -// assert_approx_eq!(Float, result[50], 3548.5041048035896, epsilon = 0.01); -// } - -// #[test] -// fn vec_macro_3arg() { -// let temp = vec![300.0; 100]; -// let pressure = vec![101325.0; 100]; -// let relative_humidity = vec![0.5; 100]; -// let result = compute_vec!( -// vapour_pressure_deficit::general3, -// &temp, -// &relative_humidity, -// &pressure -// ) -// .unwrap(); - -// assert_approx_eq!(Float, result[50], 1774.2520524017948, epsilon = 0.01); -// } -// } diff --git a/src/constants.rs b/src/constants.rs index 1b9c7cb..0caed1d 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,48 +1,96 @@ -//!Module containing physical constants - -use crate::Float; - -///Temperature of 0 Celsius in `K` -pub const ZERO_CELSIUS: Float = 273.15; - -///Gravitational acceleration in `m s^-2` -pub const G: Float = 9.80665; - -///Universal gas constant in `J K^-1 mol^-1` -pub const R: Float = 8.314_462_618_153_24; - -///Molar mass of dry air in `kg mol^-1` (ECMWF, 2020) -pub const M_D: Float = 0.028_964_4; - -///Molar mass of water vapour in `kg mol^-1` -pub const M_V: Float = 0.018_015_283_3; - -///Specific heat capacity of dry air at constant pressure in `J kg^-1 K^-1` (ECMWF, 2020) -pub const C_P: Float = 1004.709; - -///Specific heat capacity of dry air at constant volume in `J kg^-1 K^-1` (ECMWF, 2020) -pub const C_V: Float = 717.6493; - -///Specific heat capacity of water vapour at constant pressure in `J kg^-1 K^-1` (ECMWF, 2020) -pub const C_PV: Float = 1846.1; - -///Specific heat capacity of water vapour at constant volume in `J kg^-1 K^-1` (ECMWF, 2020) -pub const C_VV: Float = 1384.575; - -///Specific heat capacity of liquid water in `J kg^-1 K^-1` (ECMWF, 2020) -pub const C_L: Float = 4218.0; - -///Specific heat capacity of solid water in `J kg^-1 K^-1` (ECMWF, 2020) -pub const C_S: Float = 2106.0; - -///Mass latent heat of vapourization of water in `J kg^1` (ECMWF, 2020) -pub const L_V: Float = 2_500_800.0; - -///Ratio of molar masses of dry air and water vapour in `no unit` -pub const EPSILON: Float = M_V / M_D; - -///Specific gas constant for dry air in `J kg^-1 K^-1` -pub const R_D: Float = R / M_D; - -///Specific gas constant for water vapour in `J kg^-1 K^-1` -pub const R_V: Float = R / M_V; +//! Module containing physical constants + +use std::marker::PhantomData; + +use crate::Storage; + +/// Universal gas constant +pub const R: Storage::MolarHeatCapacity = Storage::MolarHeatCapacity { + dimension: PhantomData, + units: PhantomData, + value: 8.314_462_618_153_24, +}; + +/// Molar mass of dry air (ECMWF, 2020) +pub const M_D: Storage::MolarMass = Storage::MolarMass { + dimension: PhantomData, + units: PhantomData, + value: 0.028_964_4, +}; + +/// Molar mass of water vapour +pub const M_V: Storage::MolarMass = Storage::MolarMass { + dimension: PhantomData, + units: PhantomData, + value: 0.018_015_283_3, +}; + +/// Specific heat capacity of dry air at constant pressure (ECMWF, 2020) +pub const C_P: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { + dimension: PhantomData, + units: PhantomData, + value: 1004.709, +}; + +/// Specific heat capacity of dry air at constant volume` (ECMWF, 2020) +pub const C_V: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { + dimension: PhantomData, + units: PhantomData, + value: 717.6493, +}; + +/// Specific heat capacity of water vapour at constant pressure (ECMWF, 2020) +pub const C_PV: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { + dimension: PhantomData, + units: PhantomData, + value: 1846.1, +}; + +/// Specific heat capacity of water vapour at constant volume (ECMWF, 2020) +pub const C_VV: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { + dimension: PhantomData, + units: PhantomData, + value: 1384.575, +}; + +/// Specific heat capacity of liquid water (ECMWF, 2020) +pub const C_L: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { + dimension: PhantomData, + units: PhantomData, + value: 4218.0, +}; + +/// Specific heat capacity of solid water (ECMWF, 2020) +pub const C_S: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { + dimension: PhantomData, + units: PhantomData, + value: 2106.0, +}; + +/// Specific latent heat of vapourization of water (ECMWF, 2020) +pub const L_V: Storage::AvailableEnergy = Storage::AvailableEnergy { + dimension: PhantomData, + units: PhantomData, + value: 2_500_800.0, +}; + +/// Ratio of molar masses of dry air and water vapour +pub const EPSILON: Storage::Ratio = Storage::Ratio { + dimension: PhantomData, + units: PhantomData, + value: M_V.value / M_D.value, +}; + +/// Specific gas constant for dry air +pub const R_D: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { + dimension: PhantomData, + units: PhantomData, + value: R.value / M_D.value, +}; + +/// Specific gas constant for water vapour +pub const R_V: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { + dimension: PhantomData, + units: PhantomData, + value: R.value / M_V.value, +}; diff --git a/src/equivalent_potential_temperature.rs b/src/equivalent_potential_temperature.rs index d80f7ed..6e64f01 100644 --- a/src/equivalent_potential_temperature.rs +++ b/src/equivalent_potential_temperature.rs @@ -1,17 +1,9 @@ //!Functions to calculate equivalent potential temperature of air in K. -use crate::compute_macros::{ - generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, - generate_par_vec_compute, generate_vec_compute, -}; + use crate::constants::{C_L, C_P, EPSILON, L_V, R_D, R_V}; use crate::errors::InputError; use crate::{mixing_ratio, potential_temperature, relative_humidity, vapour_pressure, Float}; -#[cfg(feature = "debug")] -use floccus_proc::logerr; -use itertools::izip; -use ndarray::{Array, Dimension, FoldWhile}; -use rayon::iter::{ParallelBridge, ParallelIterator}; ///Most accuarte formula for computing equivalent potential temperature of unsaturated air from ///temperature, pressure and vapour pressure. @@ -34,7 +26,7 @@ impl Paluch1 { #[allow(missing_docs)] #[inline(always)] #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( temperature: Float, pressure: Float, @@ -76,11 +68,6 @@ impl Paluch1 { } } -generate_compute!(Paluch1, temperature, pressure, vapour_pressure); -generate_vec_compute!(Paluch1, temperature, pressure, vapour_pressure); -generate_par_vec_compute!(Paluch1, temperature, pressure, vapour_pressure); -generate_ndarray_compute!(Paluch1, temperature, pressure, vapour_pressure); -generate_par_ndarray_compute!(Paluch1, temperature, pressure, vapour_pressure); ///Formula for computing equivalent potential temperature of unsaturated air from ///temperature, pressure and vapour pressure. @@ -99,7 +86,7 @@ impl Bryan1 { #[allow(missing_docs)] #[inline(always)] #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( temperature: Float, pressure: Float, @@ -146,11 +133,6 @@ impl Bryan1 { } } -generate_compute!(Bryan1, temperature, pressure, vapour_pressure); -generate_vec_compute!(Bryan1, temperature, pressure, vapour_pressure); -generate_par_vec_compute!(Bryan1, temperature, pressure, vapour_pressure); -generate_ndarray_compute!(Bryan1, temperature, pressure, vapour_pressure); -generate_par_ndarray_compute!(Bryan1, temperature, pressure, vapour_pressure); ///Approximate formula for computing equivalent potential temperature of unsaturated air from ///temperature, pressure and dewpoint. @@ -170,7 +152,7 @@ impl Bolton1 { #[allow(missing_docs)] #[inline(always)] #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( pressure: Float, temperature: Float, @@ -211,10 +193,6 @@ impl Bolton1 { } } -generate_compute!(Bolton1, pressure, temperature, dewpoint); -generate_vec_compute!(Bolton1, pressure, temperature, dewpoint); -generate_par_vec_compute!(Bolton1, pressure, temperature, dewpoint); -generate_ndarray_compute!(Bolton1, pressure, temperature, dewpoint); // #[cfg(test)] // mod tests { diff --git a/src/formula.rs b/src/formula.rs index 3521729..cb4671c 100644 --- a/src/formula.rs +++ b/src/formula.rs @@ -1,18 +1,6 @@ #![allow(missing_docs)] -use uom::si::{ - pressure::hectopascal, ratio::ratio, temperature_coefficient, temperature_interval, - thermodynamic_temperature, -}; - -use crate::{ - errors::InputError, - quantities::{DewPointTemperature, DryBulbTemperature, ThermodynamicQuantity, VapourPressure}, - vapour_pressure::Buck3Simplified, - Storage::{ - Pressure, Ratio, TemperatureCoefficient, TemperatureInterval, ThermodynamicTemperature, - }, -}; +use crate::{errors::InputError, quantities::ThermodynamicQuantity}; pub trait Formula1 { #[allow(missing_docs)] @@ -54,48 +42,91 @@ pub trait Formula1 { } } -struct BuckTest; +pub trait Formula2 { + #[allow(missing_docs)] + fn compute_unchecked(i1: I1, i2: I2) -> O; -impl Formula1 for BuckTest { - #[inline(always)] - fn validate_inputs(i1: DewPointTemperature) -> Result<(), InputError> { - let i1_si = i1.0.get::(); + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn validate_inputs(i1: I1, i2: I2) -> Result<(), InputError>; - if !(253.0..=324.0).contains(&i1_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } + #[allow(clippy::missing_errors_doc)] + #[allow(missing_docs)] + #[inline] + fn compute(i1: I1, i2: I2) -> Result { + #[cfg(not(feature = "debug"))] + Self::validate_inputs(i1, i2)?; + #[cfg(feature = "debug")] + Self::validate_inputs_loggerr(i1, i2)?; - Ok(()) + Ok(Self::compute_unchecked(i1, i2)) } + #[cfg(feature = "debug")] #[inline(always)] - fn compute_unchecked(i1: DewPointTemperature) -> VapourPressure { - let dewpoint = i1.0.get::(); - let dewpoint = Ratio::new::(dewpoint); - - let lower_a = Pressure::new::(6.1121); - let lower_b = Ratio::new::(17.502); - let lower_c = Ratio::new::(240.97); - - let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn validate_inputs_loggerr(i1: I1, i2: I2) -> Result<(), InputError> { + use std::any::type_name; - VapourPressure(lower_e) + Self::validate_inputs(i1, i2).or_else(|err| { + log::error!( + "Formula {} calculating {} from inputs {:?} {:?} returned error: {}", + type_name::(), + type_name::(), + i1, + i2, + err + ); + Err(err) + }) } } -#[cfg(test)] -mod tests { - use uom::si::f64::ThermodynamicTemperature; +pub trait Formula3< + O: ThermodynamicQuantity, + I1: ThermodynamicQuantity, + I2: ThermodynamicQuantity, + I3: ThermodynamicQuantity, +> +{ + #[allow(missing_docs)] + fn compute_unchecked(i1: I1, i2: I2, i3: I3) -> O; - use super::*; + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn validate_inputs(i1: I1, i2: I2, i3: I3) -> Result<(), InputError>; - #[test] - fn test_buck3_simplified() { - let input = ThermodynamicTemperature::new::(300.0); - let input = DewPointTemperature(input); + #[allow(clippy::missing_errors_doc)] + #[allow(missing_docs)] + #[inline] + fn compute(i1: I1, i2: I2, i3: I3) -> Result { + #[cfg(not(feature = "debug"))] + Self::validate_inputs(i1, i2, i3)?; + #[cfg(feature = "debug")] + Self::validate_inputs_loggerr(i1, i2, i3)?; - let result = BuckTest::compute(input).unwrap(); + Ok(Self::compute_unchecked(i1, i2, i3)) + } - println!("{:?}", result.0); + #[cfg(feature = "debug")] + #[inline(always)] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn validate_inputs_loggerr(i1: I1, i2: I2, i3: I3) -> Result<(), InputError> { + use std::any::type_name; + + Self::validate_inputs(i1, i2, i3).or_else(|err| { + log::error!( + "Formula {} calculating {} from inputs {:?} {:?} {:?} returned error: {}", + type_name::(), + type_name::(), + i1, + i2, + i3, + err + ); + Err(err) + }) } } diff --git a/src/lib.rs b/src/lib.rs index 5b1edf9..baa000f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,23 +81,23 @@ //! information about the error. This feature potentially is not zero-cost so it is optional. pub mod constants; -pub mod equivalent_potential_temperature; pub mod errors; -pub mod mixing_ratio; -pub mod potential_temperature; -pub mod relative_humidity; -pub mod specific_humidity; -pub mod vapour_pressure; -pub mod vapour_pressure_deficit; -pub mod virtual_temperature; -pub mod wet_bulb_potential_temperature; -pub mod wet_bulb_temperature; +pub mod formula; +pub mod quantities; -mod compute_macros; -mod tests_framework; +// pub mod equivalent_potential_temperature; +// pub mod mixing_ratio; +// pub mod potential_temperature; +// pub mod relative_humidity; +// pub mod specific_humidity; +pub mod vapour_pressure; +// pub mod vapour_pressure_deficit; +// pub mod virtual_temperature; +// pub mod wet_bulb_potential_temperature; +// pub mod wet_bulb_temperature; -pub mod quantities; -pub mod formula; +#[cfg(test)] +mod tests; #[cfg(not(feature = "double_precision"))] type Float = f32; @@ -105,10 +105,8 @@ type Float = f32; #[cfg(feature = "double_precision")] type Float = f64; - #[cfg(not(feature = "double_precision"))] pub(crate) use uom::si::f32 as Storage; #[cfg(feature = "double_precision")] pub(crate) use uom::si::f64 as Storage; - diff --git a/src/mixing_ratio.rs b/src/mixing_ratio.rs index 2d0ef0e..9b0957b 100644 --- a/src/mixing_ratio.rs +++ b/src/mixing_ratio.rs @@ -3,15 +3,12 @@ //!To calculate saturation mixing ratio input dry-bulb temperature in place of dewpoint //!or saturation vapour pressure in place of vapour pressure. -use crate::compute_macros::{ - generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, - generate_par_vec_compute, generate_vec_compute, -}; + use crate::{constants::EPSILON, errors::InputError}; use crate::{vapour_pressure, Float}; use float_cmp::approx_eq; -#[cfg(feature = "debug")] -use floccus_proc::logerr; + + use itertools::izip; use ndarray::{Array, Dimension, FoldWhile}; use rayon::iter::{ParallelBridge, ParallelIterator}; @@ -32,7 +29,7 @@ impl General1 { #[allow(missing_docs)] #[inline(always)] #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(pressure: Float, vapour_pressure: Float) -> Result<(), InputError> { if !(100.0..=150_000.0).contains(&pressure) { return Err(InputError::OutOfRange(String::from("pressure"))); @@ -57,11 +54,6 @@ impl General1 { } } -generate_compute!(General1, pressure, vapour_pressure); -generate_vec_compute!(General1, pressure, vapour_pressure); -generate_ndarray_compute!(General1, pressure, vapour_pressure); -generate_par_vec_compute!(General1, pressure, vapour_pressure); -generate_par_ndarray_compute!(General1, pressure, vapour_pressure); ///Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. ///Optimised for performance. @@ -77,7 +69,7 @@ impl Performance1 { #[inline(always)] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { //validate inputs if !(273.0..=353.0).contains(&dewpoint) { @@ -100,11 +92,6 @@ impl Performance1 { } } -generate_compute!(Performance1, dewpoint, pressure); -generate_vec_compute!(Performance1, dewpoint, pressure); -generate_ndarray_compute!(Performance1, dewpoint, pressure); -generate_par_vec_compute!(Performance1, dewpoint, pressure); -generate_par_ndarray_compute!(Performance1, dewpoint, pressure); ///Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. ///Optimised for accuracy. @@ -120,7 +107,7 @@ impl Accuracy1 { #[inline(always)] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { if !(232.0..=324.0).contains(&dewpoint) { return Err(InputError::OutOfRange(String::from("dewpoint"))); diff --git a/src/potential_temperature.rs b/src/potential_temperature.rs index bb71cc6..7f1f57f 100644 --- a/src/potential_temperature.rs +++ b/src/potential_temperature.rs @@ -1,17 +1,14 @@ //!Functions to calculate potential temperature of dry air in K. -use crate::compute_macros::{ - generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, - generate_par_vec_compute, generate_vec_compute, -}; + use crate::Float; use crate::{ constants::{C_P, R_D}, errors::InputError, }; use float_cmp::approx_eq; -#[cfg(feature = "debug")] -use floccus_proc::logerr; + + use itertools::izip; use ndarray::{Array, Dimension, FoldWhile}; use rayon::iter::{ParallelBridge, ParallelIterator}; @@ -38,7 +35,7 @@ impl DaviesJones1 { #[allow(missing_docs)] #[inline(always)] #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( temperature: Float, pressure: Float, @@ -79,11 +76,6 @@ impl DaviesJones1 { } } -generate_compute!(DaviesJones1, temperature, pressure, vapour_pressure); -generate_vec_compute!(DaviesJones1, temperature, pressure, vapour_pressure); -generate_par_vec_compute!(DaviesJones1, temperature, pressure, vapour_pressure); -generate_ndarray_compute!(DaviesJones1, temperature, pressure, vapour_pressure); -generate_par_ndarray_compute!(DaviesJones1, temperature, pressure, vapour_pressure); // #[cfg(test)] // mod tests { diff --git a/src/quantities.rs b/src/quantities.rs index 0574f50..fdbbec9 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -1,40 +1,294 @@ #![allow(missing_docs)] -use crate::Storage; +use uom::si::{pressure::pascal, ratio::ratio, thermodynamic_temperature::kelvin}; + +use crate::{Float, Storage}; use std::fmt::Debug; -pub trait ThermodynamicQuantity: Debug + Clone + Copy + PartialEq + PartialOrd + Default {} +pub trait ThermodynamicQuantity: Debug + Clone + Copy + PartialEq + PartialOrd + Default { + fn get_si_value(&self) -> Float; + fn new_si(value: Float) -> Self; +} #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct DryBulbTemperature(pub Storage::ThermodynamicTemperature); #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct WetBulbTemperature(pub Storage::TemperatureInterval); +pub struct WetBulbTemperature(pub Storage::ThermodynamicTemperature); #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct DewPointTemperature(pub Storage::ThermodynamicTemperature); #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct VirtualTemperature(pub Storage::TemperatureInterval); +pub struct VirtualTemperature(pub Storage::ThermodynamicTemperature); + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct PotentialTemperature(pub Storage::ThermodynamicTemperature); #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct PotentialTemperature(pub Storage::TemperatureInterval); +pub struct EquivalentPotentialTemperature(pub Storage::ThermodynamicTemperature); #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct EquivalentPotentialTemperature(pub Storage::TemperatureInterval); +pub struct WetBulbPotentialTemperature(pub Storage::ThermodynamicTemperature); #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct WetBulbPotentialTemperature(pub Storage::TemperatureInterval); +pub struct AtmosphericPressure(pub Storage::Pressure); #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct VapourPressure(pub Storage::Pressure); -impl ThermodynamicQuantity for DryBulbTemperature {} -impl ThermodynamicQuantity for WetBulbTemperature {} -impl ThermodynamicQuantity for DewPointTemperature {} -impl ThermodynamicQuantity for VirtualTemperature {} -impl ThermodynamicQuantity for PotentialTemperature {} -impl ThermodynamicQuantity for EquivalentPotentialTemperature {} -impl ThermodynamicQuantity for WetBulbPotentialTemperature {} +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct SaturationVapourPressure(pub Storage::Pressure); + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct MixingRatio(pub Storage::Ratio); + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct SpecificHumidity(pub Storage::Ratio); + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct RelativeHumidity(pub Storage::Ratio); + +impl DryBulbTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl WetBulbTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl DewPointTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl VirtualTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl PotentialTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl EquivalentPotentialTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl WetBulbPotentialTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl AtmosphericPressure { + pub fn new(value: Float) -> Self + where + T: uom::si::pressure::Unit + uom::si::pressure::Conversion, + { + Self(Storage::Pressure::new::(value)) + } +} + +impl VapourPressure { + pub fn new(value: Float) -> Self + where + T: uom::si::pressure::Unit + uom::si::pressure::Conversion, + { + Self(Storage::Pressure::new::(value)) + } +} + +impl SaturationVapourPressure { + pub fn new(value: Float) -> Self + where + T: uom::si::pressure::Unit + uom::si::pressure::Conversion, + { + Self(Storage::Pressure::new::(value)) + } +} + +impl MixingRatio { + pub fn new(value: Float) -> Self + where + T: uom::si::ratio::Unit + uom::si::ratio::Conversion, + { + Self(Storage::Ratio::new::(value)) + } +} + +impl SpecificHumidity { + pub fn new(value: Float) -> Self + where + T: uom::si::ratio::Unit + uom::si::ratio::Conversion, + { + Self(Storage::Ratio::new::(value)) + } +} + +impl RelativeHumidity { + pub fn new(value: Float) -> Self + where + T: uom::si::ratio::Unit + uom::si::ratio::Conversion, + { + Self(Storage::Ratio::new::(value)) + } +} + +impl ThermodynamicQuantity for DryBulbTemperature { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} +impl ThermodynamicQuantity for WetBulbTemperature { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} +impl ThermodynamicQuantity for DewPointTemperature { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} +impl ThermodynamicQuantity for VirtualTemperature { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} +impl ThermodynamicQuantity for PotentialTemperature { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} +impl ThermodynamicQuantity for EquivalentPotentialTemperature { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} +impl ThermodynamicQuantity for WetBulbPotentialTemperature { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} + +impl ThermodynamicQuantity for AtmosphericPressure { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} +impl ThermodynamicQuantity for VapourPressure { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} +impl ThermodynamicQuantity for SaturationVapourPressure { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} + +impl ThermodynamicQuantity for MixingRatio { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} +impl ThermodynamicQuantity for SpecificHumidity { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} +impl ThermodynamicQuantity for RelativeHumidity { + fn get_si_value(&self) -> Float { + self.0.get::() + } -impl ThermodynamicQuantity for VapourPressure {} + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} diff --git a/src/relative_humidity.rs b/src/relative_humidity.rs index 2fd2ecc..1ce8e12 100644 --- a/src/relative_humidity.rs +++ b/src/relative_humidity.rs @@ -1,13 +1,10 @@ //!Functions to calculate relative humidity in %/100 -use crate::compute_macros::{ - generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, - generate_par_vec_compute, generate_vec_compute, -}; + use crate::errors::InputError; use crate::{mixing_ratio, vapour_pressure, Float}; -#[cfg(feature = "debug")] -use floccus_proc::logerr; + + use itertools::izip; use ndarray::{Array, Dimension, FoldWhile}; use rayon::iter::{ParallelBridge, ParallelIterator}; @@ -29,7 +26,7 @@ impl General1 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( mixing_ratio: Float, saturation_mixing_ratio: Float, @@ -54,11 +51,6 @@ impl General1 { } } -generate_compute!(General1, mixing_ratio, saturation_mixing_ratio); -generate_vec_compute!(General1, mixing_ratio, saturation_mixing_ratio); -generate_ndarray_compute!(General1, mixing_ratio, saturation_mixing_ratio); -generate_par_vec_compute!(General1, mixing_ratio, saturation_mixing_ratio); -generate_par_ndarray_compute!(General1, mixing_ratio, saturation_mixing_ratio); ///Formula for computing relative humidity from vapour pressure and saturation vapour pressure. ///Can be used interchangeably with [`general1`]. @@ -74,7 +66,7 @@ impl General2 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( vapour_pressure: Float, saturation_vapour_pressure: Float, @@ -99,11 +91,6 @@ impl General2 { } } -generate_compute!(General2, vapour_pressure, saturation_vapour_pressure); -generate_vec_compute!(General2, vapour_pressure, saturation_vapour_pressure); -generate_ndarray_compute!(General2, vapour_pressure, saturation_vapour_pressure); -generate_par_vec_compute!(General2, vapour_pressure, saturation_vapour_pressure); -generate_par_ndarray_compute!(General2, vapour_pressure, saturation_vapour_pressure); ///Formula for computing relative humidity from temperature and dewpoint using [`tetens1`](vapour_pressure::tetens1) ///function for vapour pressure calculation @@ -119,7 +106,7 @@ impl General3 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(temperature: Float, dewpoint: Float) -> Result<(), InputError> { if !(273.0..=353.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); @@ -142,11 +129,6 @@ impl General3 { } } -generate_compute!(General3, temperature, dewpoint); -generate_vec_compute!(General3, temperature, dewpoint); -generate_ndarray_compute!(General3, temperature, dewpoint); -generate_par_vec_compute!(General3, temperature, dewpoint); -generate_par_ndarray_compute!(General3, temperature, dewpoint); ///Formula for computing relative humidity from temperature, dewpoint and pressure using [`buck3`](vapour_pressure::buck3) ///function for vapour pressure calculation @@ -163,7 +145,7 @@ impl General4 { #[allow(missing_docs)] #[inline(always)] #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( temperature: Float, dewpoint: Float, @@ -195,11 +177,6 @@ impl General4 { } } -generate_compute!(General4, temperature, dewpoint, pressure); -generate_vec_compute!(General4, temperature, dewpoint, pressure); -generate_ndarray_compute!(General4, temperature, dewpoint, pressure); -generate_par_vec_compute!(General4, temperature, dewpoint, pressure); -generate_par_ndarray_compute!(General4, temperature, dewpoint, pressure); ///Formula for computing relative humidity from temperature, dewpoint and pressure using [`accuracy1`](mixing_ratio::accuracy1) ///function for mixing ratio calculation @@ -216,7 +193,7 @@ impl General5 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( temperature: Float, dewpoint: Float, diff --git a/src/saturation_vapour_pressure.rs b/src/saturation_vapour_pressure.rs new file mode 100644 index 0000000..becb1c9 --- /dev/null +++ b/src/saturation_vapour_pressure.rs @@ -0,0 +1,36 @@ +///Formula for computing **ONLY** saturation vapour pressure from vapour pressure and relative humidity. +///For vapour pressure use [`saturation_specific1`] +/// +///# Errors +/// +///Returns [`InputError::OutOfRange`] when input is out of range.\ +///Valid `vapour_pressure` range: 0Pa - 10000Pa\ +///Valid `relative_humidity` range: 0.00001 - 1.0 +pub struct SaturationSpecific2; + +impl SaturationSpecific2 { + #[allow(missing_docs)] + #[inline(always)] + #[allow(clippy::missing_errors_doc)] + + pub fn validate_inputs( + vapour_pressure: Float, + relative_humidity: Float, + ) -> Result<(), InputError> { + if !(0.00001..=2.0).contains(&relative_humidity) { + return Err(InputError::OutOfRange(String::from("relative_humidity"))); + } + + if !(0.0..=10_000.0).contains(&vapour_pressure) { + return Err(InputError::OutOfRange(String::from("vapour_pressure"))); + } + + Ok(()) + } + + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_unchecked(vapour_pressure: Float, relative_humidity: Float) -> Float { + vapour_pressure / relative_humidity + } +} diff --git a/src/specific_humidity.rs b/src/specific_humidity.rs index f853579..5a61c18 100644 --- a/src/specific_humidity.rs +++ b/src/specific_humidity.rs @@ -5,14 +5,11 @@ //! //!Specific humidity is approximately equal to mixing ratio. -use crate::compute_macros::{ - generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, - generate_par_vec_compute, generate_vec_compute, -}; + use crate::Float; use crate::{constants::EPSILON, errors::InputError}; -#[cfg(feature = "debug")] -use floccus_proc::logerr; + + use itertools::izip; use ndarray::{Array, Dimension, FoldWhile}; use rayon::iter::{ParallelBridge, ParallelIterator}; @@ -34,7 +31,7 @@ impl General1 { #[allow(missing_docs)] #[inline(always)] #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(vapour_pressure: Float, pressure: Float) -> Result<(), InputError> { if !(0.0..=50_000.0).contains(&vapour_pressure) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); @@ -54,11 +51,6 @@ impl General1 { } } -generate_compute!(General1, vapour_pressure, pressure); -generate_vec_compute!(General1, vapour_pressure, pressure); -generate_par_vec_compute!(General1, vapour_pressure, pressure); -generate_ndarray_compute!(General1, vapour_pressure, pressure); -generate_par_ndarray_compute!(General1, vapour_pressure, pressure); // #[cfg(test)] // mod tests { diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..4839d11 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,204 @@ +use crate::errors::InputError; +use crate::formula::Formula2; +use crate::quantities::ThermodynamicQuantity; +use crate::Float; +use float_cmp::assert_approx_eq; +use std::mem::discriminant; + +#[derive(Copy, Clone, Debug)] +pub struct Argument { + pub name: &'static str, + pub def_val: Float, + pub range: [Float; 2], +} + +pub fn test_with_2args< + O: ThermodynamicQuantity, + I1: ThermodynamicQuantity, + I2: ThermodynamicQuantity, + F: Formula2, +>( + arg1: Argument, + arg2: Argument, + expected_result: Float, +) { + //the first promise of the crate is that returned value + //is calculated correctly + let result = F::compute(I1::new_si(arg1.def_val), I2::new_si(arg2.def_val)).unwrap(); + assert_approx_eq!( + Float, + result.get_si_value(), + expected_result, + epsilon = 0.01 + ); + + // the second promise of the crate is to never return NaN or Inf + // here we check several edge cases for that + // the function can only return a finite number or InputError + // check for error implicit as Result<> ensures that if value + // is not Ok() then it is Err() + // here we don't care about error being correct + let results = vec![ + F::compute(I1::new_si(arg1.def_val), I2::new_si(arg2.def_val)), + F::compute(I1::new_si(-9999.0), I2::new_si(arg2.def_val)), + F::compute(I1::new_si(arg1.def_val), I2::new_si(-9999.0)), + F::compute(I1::new_si(-9999.0), I2::new_si(-9999.0)), + ]; + + for result in results { + if let Ok(result) = result { + assert!(result.get_si_value().is_finite()); + } + } + + //the third promise of the crate is to always return finite f64 + //if all inputs are within the range + //the only allowed error is InccorectArgumentsSet as it can occur + //for values within valid range + for arg1_itr in 0..=100 { + for arg2_itr in 0..=100 { + let arg1_tmp = + (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; + let arg2_tmp = + (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; + + let arg1_tmp = I1::new_si(arg1_tmp); + let arg2_tmp = I2::new_si(arg2_tmp); + + let result = F::compute(arg1_tmp, arg2_tmp); + + match result { + Ok(r) => assert!(r.get_si_value().is_finite()), + Err(e) => assert!( + discriminant(&InputError::IncorrectArgumentSet(String::new())) + == discriminant(&e) + ), + } + } + } + + //the fourth promise of the crate is to return an error with + //erronous variable name when input is out of range + let expected = InputError::OutOfRange(String::from(arg1.name)); + let result = F::compute(I1::new_si(arg1.range[0] - 0.1), I2::new_si(arg2.def_val)).unwrap_err(); + assert_eq!(result, expected); + let result = F::compute(I1::new_si(arg1.range[1] + 0.1), I2::new_si(arg2.def_val)).unwrap_err(); + assert_eq!(result, expected); + + let expected = InputError::OutOfRange(String::from(arg2.name)); + let result = F::compute(I1::new_si(arg1.def_val), I2::new_si(arg2.range[0] - 0.1)).unwrap_err(); + assert_eq!(result, expected); + let result = F::compute(I1::new_si(arg1.def_val), I2::new_si(arg2.range[1] + 0.1)).unwrap_err(); + assert_eq!(result, expected); +} + +// #[allow(dead_code)] +// pub fn test_with_1arg( +// tested_function: &dyn Fn(Float) -> Result, +// arg1: Argument, +// expected_result: Float, +// ) -> bool { +// let result = tested_function(arg1.def_val).unwrap(); +// assert_approx_eq!(Float, result, expected_result, epsilon = 0.01); + +// let results = vec![tested_function(0.0)]; + +// for result in results { +// if result.is_ok() { +// assert!(result.unwrap().is_finite()); +// } +// } + +// for arg1_itr in 0..=100 { +// let arg1_tmp = +// (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; + +// let result = tested_function(arg1_tmp); + +// if result.is_err() { +// assert!( +// discriminant(&InputError::IncorrectArgumentSet(String::new())) +// == discriminant(&result.unwrap_err()) +// ); +// } else { +// assert!(result.unwrap().is_finite()); +// } +// } + +// let expected = InputError::OutOfRange(String::from(arg1.name)); +// let result = tested_function(arg1.range[0] - 0.1).unwrap_err(); +// assert_eq!(result, expected); +// let result = tested_function(arg1.range[1] + 0.1).unwrap_err(); +// assert_eq!(result, expected); + +// true +// } + +// #[allow(dead_code)] +// pub fn test_with_3args( +// tested_function: &dyn Fn(Float, Float, Float) -> Result, +// arg1: Argument, +// arg2: Argument, +// arg3: Argument, +// expected_result: Float, +// ) -> bool { +// let result = tested_function(arg1.def_val, arg2.def_val, arg3.def_val).unwrap(); +// assert_approx_eq!(Float, result, expected_result, epsilon = 0.01); + +// let results = vec![ +// tested_function(0.0, arg2.def_val, arg3.def_val), +// tested_function(arg1.def_val, 0.0, arg3.def_val), +// tested_function(arg1.def_val, arg2.def_val, 0.0), +// tested_function(0.0, 0.0, 0.0), +// ]; + +// for result in results { +// if result.is_ok() { +// assert!(result.unwrap().is_finite()); +// } +// } + +// for arg1_itr in 0..=100 { +// for arg2_itr in 0..=100 { +// for arg3_itr in 0..=100 { +// let arg1_tmp = +// (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; +// let arg2_tmp = +// (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; +// let arg3_tmp = +// (((arg3.range[1] - arg3.range[0]) / 100.0) * arg3_itr as Float) + arg3.range[0]; + +// let result = tested_function(arg1_tmp, arg2_tmp, arg3_tmp); + +// if result.is_err() { +// assert!( +// discriminant(&InputError::IncorrectArgumentSet(String::new())) +// == discriminant(&result.unwrap_err()) +// ); +// } else { +// assert!(result.unwrap().is_finite()); +// } +// } +// } +// } + +// let expected = InputError::OutOfRange(String::from(arg1.name)); +// let result = tested_function(arg1.range[0] - 0.1, arg2.def_val, arg3.def_val).unwrap_err(); +// assert_eq!(result, expected); +// let result = tested_function(arg1.range[1] + 0.1, arg2.def_val, arg3.def_val).unwrap_err(); +// assert_eq!(result, expected); + +// let expected = InputError::OutOfRange(String::from(arg2.name)); +// let result = tested_function(arg1.def_val, arg2.range[0] - 0.1, arg3.def_val).unwrap_err(); +// assert_eq!(result, expected); +// let result = tested_function(arg1.def_val, arg2.range[1] + 0.1, arg3.def_val).unwrap_err(); +// assert_eq!(result, expected); + +// let expected = InputError::OutOfRange(String::from(arg3.name)); +// let result = tested_function(arg1.def_val, arg2.def_val, arg3.range[0] - 0.1).unwrap_err(); +// assert_eq!(result, expected); +// let result = tested_function(arg1.def_val, arg2.def_val, arg3.range[1] + 0.1).unwrap_err(); +// assert_eq!(result, expected); + +// true +// } diff --git a/src/tests_framework.rs b/src/tests_framework.rs deleted file mode 100644 index 2cd896a..0000000 --- a/src/tests_framework.rs +++ /dev/null @@ -1,197 +0,0 @@ -use crate::errors::InputError; -use crate::Float; -use float_cmp::assert_approx_eq; -use std::mem::discriminant; - -#[derive(Copy, Clone, Debug, Default)] -pub struct Argument { - pub name: &'static str, - pub def_val: Float, - pub range: [Float; 2], -} - -//due to a bug [https://github.com/rust-lang/rust/issues/46379] -//cargo flags those functions as a dead code even though -//they are used in tests -#[allow(dead_code)] -//this function should work as a reference for other test functions below -pub fn test_with_2args( - tested_function: &dyn Fn(Float, Float) -> Result, - arg1: Argument, - arg2: Argument, - expected_result: Float, -) -> bool { - //the first promise of the crate is that returned value - //is calculated correctly - let result = tested_function(arg1.def_val, arg2.def_val).unwrap(); - assert_approx_eq!(Float, result, expected_result, epsilon = 0.01); - - //the second promise of the crate is to never return NaN or Inf - //here we check several edge cases for that - //the function can only return a finite number or InputError - //check for error implicit as Result<> ensures that if value - //is not Ok() then it is Err() - //here we don't care about error being correct - let results = vec![ - tested_function(0.0, arg2.def_val), - tested_function(arg1.def_val, 0.0), - tested_function(0.0, 0.0), - ]; - - for result in results { - if result.is_ok() { - assert!(result.unwrap().is_finite()); - } - } - - //the third promise of the crate is to always return finite f64 - //if all inputs are within the range - //the only allowed error is InccorectArgumentsSet as it can occur - //for values within valid range - for arg1_itr in 0..=100 { - for arg2_itr in 0..=100 { - let arg1_tmp = - (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; - let arg2_tmp = - (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; - - let result = tested_function(arg1_tmp, arg2_tmp); - - if result.is_err() { - assert!( - discriminant(&InputError::IncorrectArgumentSet(String::new())) - == discriminant(&result.unwrap_err()) - ); - } else { - assert!(result.unwrap().is_finite()); - } - } - } - - //the fourth promise of the crate is to return an error with - //erronous variable name when input is out of range - let expected = InputError::OutOfRange(String::from(arg1.name)); - let result = tested_function(arg1.range[0] - 0.1, arg2.def_val).unwrap_err(); - assert_eq!(result, expected); - let result = tested_function(arg1.range[1] + 0.1, arg2.def_val).unwrap_err(); - assert_eq!(result, expected); - - let expected = InputError::OutOfRange(String::from(arg2.name)); - let result = tested_function(arg1.def_val, arg2.range[0] - 0.1).unwrap_err(); - assert_eq!(result, expected); - let result = tested_function(arg1.def_val, arg2.range[1] + 0.1).unwrap_err(); - assert_eq!(result, expected); - - true -} - -#[allow(dead_code)] -pub fn test_with_1arg( - tested_function: &dyn Fn(Float) -> Result, - arg1: Argument, - expected_result: Float, -) -> bool { - let result = tested_function(arg1.def_val).unwrap(); - assert_approx_eq!(Float, result, expected_result, epsilon = 0.01); - - let results = vec![tested_function(0.0)]; - - for result in results { - if result.is_ok() { - assert!(result.unwrap().is_finite()); - } - } - - for arg1_itr in 0..=100 { - let arg1_tmp = - (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; - - let result = tested_function(arg1_tmp); - - if result.is_err() { - assert!( - discriminant(&InputError::IncorrectArgumentSet(String::new())) - == discriminant(&result.unwrap_err()) - ); - } else { - assert!(result.unwrap().is_finite()); - } - } - - let expected = InputError::OutOfRange(String::from(arg1.name)); - let result = tested_function(arg1.range[0] - 0.1).unwrap_err(); - assert_eq!(result, expected); - let result = tested_function(arg1.range[1] + 0.1).unwrap_err(); - assert_eq!(result, expected); - - true -} - -#[allow(dead_code)] -pub fn test_with_3args( - tested_function: &dyn Fn(Float, Float, Float) -> Result, - arg1: Argument, - arg2: Argument, - arg3: Argument, - expected_result: Float, -) -> bool { - let result = tested_function(arg1.def_val, arg2.def_val, arg3.def_val).unwrap(); - assert_approx_eq!(Float, result, expected_result, epsilon = 0.01); - - let results = vec![ - tested_function(0.0, arg2.def_val, arg3.def_val), - tested_function(arg1.def_val, 0.0, arg3.def_val), - tested_function(arg1.def_val, arg2.def_val, 0.0), - tested_function(0.0, 0.0, 0.0), - ]; - - for result in results { - if result.is_ok() { - assert!(result.unwrap().is_finite()); - } - } - - for arg1_itr in 0..=100 { - for arg2_itr in 0..=100 { - for arg3_itr in 0..=100 { - let arg1_tmp = - (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; - let arg2_tmp = - (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; - let arg3_tmp = - (((arg3.range[1] - arg3.range[0]) / 100.0) * arg3_itr as Float) + arg3.range[0]; - - let result = tested_function(arg1_tmp, arg2_tmp, arg3_tmp); - - if result.is_err() { - assert!( - discriminant(&InputError::IncorrectArgumentSet(String::new())) - == discriminant(&result.unwrap_err()) - ); - } else { - assert!(result.unwrap().is_finite()); - } - } - } - } - - let expected = InputError::OutOfRange(String::from(arg1.name)); - let result = tested_function(arg1.range[0] - 0.1, arg2.def_val, arg3.def_val).unwrap_err(); - assert_eq!(result, expected); - let result = tested_function(arg1.range[1] + 0.1, arg2.def_val, arg3.def_val).unwrap_err(); - assert_eq!(result, expected); - - let expected = InputError::OutOfRange(String::from(arg2.name)); - let result = tested_function(arg1.def_val, arg2.range[0] - 0.1, arg3.def_val).unwrap_err(); - assert_eq!(result, expected); - let result = tested_function(arg1.def_val, arg2.range[1] + 0.1, arg3.def_val).unwrap_err(); - assert_eq!(result, expected); - - let expected = InputError::OutOfRange(String::from(arg3.name)); - let result = tested_function(arg1.def_val, arg2.def_val, arg3.range[0] - 0.1).unwrap_err(); - assert_eq!(result, expected); - let result = tested_function(arg1.def_val, arg2.def_val, arg3.range[1] + 0.1).unwrap_err(); - assert_eq!(result, expected); - - true -} diff --git a/src/vapour_pressure.rs b/src/vapour_pressure.rs index f81b5e9..e0492be 100644 --- a/src/vapour_pressure.rs +++ b/src/vapour_pressure.rs @@ -2,48 +2,45 @@ #![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. +//! Formulae to calculate partial vapour pressure of the unsaturated air. -use crate::compute_macros::{ - generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, - generate_par_vec_compute, generate_vec_compute, +use crate::formula::{Formula1, Formula2}; +use crate::quantities::{ + AtmosphericPressure, DewPointTemperature, RelativeHumidity, SaturationVapourPressure, + SpecificHumidity, ThermodynamicQuantity, VapourPressure, }; use crate::Float; -use crate::{ - constants::{EPSILON, ZERO_CELSIUS}, - errors::InputError, -}; -#[cfg(feature = "debug")] -use floccus_proc::logerr; -use itertools::izip; -use ndarray::{Array, Dimension, FoldWhile}; -use rayon::iter::{IntoParallelRefIterator, ParallelBridge, ParallelIterator}; - -///Formula for computing vapour pressure from specific humidity and pressure. -///This function is theoretical not empirical. +use crate::Storage::{Pressure, Ratio}; +use crate::{constants::EPSILON, errors::InputError}; + +use uom::si::pressure::{hectopascal, kilopascal, pascal}; +use uom::si::ratio::ratio; +use uom::si::thermodynamic_temperature::{degree_celsius, kelvin}; + +/// Formula for computing vapour pressure from specific humidity and pressure. +/// This function is theoretical not empirical. /// -///Provided by [Rogers & Yau (1989)](https://www.elsevier.com/books/a-short-course-in-cloud-physics/yau/978-0-08-057094-5). +/// Provided by [Rogers & Yau (1989)](https://www.elsevier.com/books/a-short-course-in-cloud-physics/yau/978-0-08-057094-5). /// -///# Errors +/// Valid `specific humidity` range: 0.00001 - 2.0 /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `specific_humidity` range: 0.00001 - 2.0\ -///Valid `pressure` range: 100Pa - 150000Pa -pub struct General1; - -impl General1 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] +/// Valid `atmospheric pressure` range: 100Pa - 150000Pa +pub struct Definition1; + +impl Formula2 for Definition1 { #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] - pub fn validate_inputs(specific_humidity: Float, pressure: Float) -> Result<(), InputError> { - if !(0.00001..=2.0).contains(&specific_humidity) { + fn validate_inputs( + specific_humidity: SpecificHumidity, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let specific_humidity_si = specific_humidity.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(0.00001..=2.0).contains(&specific_humidity_si) { return Err(InputError::OutOfRange(String::from("specific_humidity"))); } - if !(100.0..=150_000.0).contains(&pressure) { + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } @@ -51,51 +48,99 @@ impl General1 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(specific_humidity: Float, pressure: Float) -> Float { - -((pressure * specific_humidity) / ((specific_humidity * (EPSILON - 1.0)) - EPSILON)) + fn compute_unchecked( + specific_humidity: SpecificHumidity, + pressure: AtmosphericPressure, + ) -> VapourPressure { + let specific_humidity = specific_humidity.0; + let pressure = pressure.0; + + let one = Ratio::new::(1.0); + + let result = + -((pressure * specific_humidity) / ((specific_humidity * (EPSILON - one)) - EPSILON)); + + VapourPressure(result) } } -generate_compute!(General1, specific_humidity, pressure); -generate_vec_compute!(General1, specific_humidity, pressure); -generate_ndarray_compute!(General1, specific_humidity, pressure); -generate_par_vec_compute!(General1, specific_humidity, pressure); -generate_par_ndarray_compute!(General1, specific_humidity, pressure); +/// Formula for computing vapour pressure from saturation vapour pressure and relative humidity. +/// +/// Valid `saturation_vapour_pressure` range: 0Pa - 10000Pa +/// +/// Valid `relative_humidity` range: 0.0 - 1.0 +pub struct Definition2; + +impl Formula2 for Definition2 { + #[inline(always)] + fn validate_inputs( + saturation_vapour_pressure: SaturationVapourPressure, + relative_humidity: RelativeHumidity, + ) -> Result<(), InputError> { + let saturation_vapour_pressure_si = saturation_vapour_pressure.get_si_value(); + let relative_humidity_si = relative_humidity.get_si_value(); -///Formula for computing vapour pressure from dewpoint temperature and pressure. -///Should be used for air over water when accuracy is desired. + if !(0.0..=2.0).contains(&relative_humidity_si) { + return Err(InputError::OutOfRange(String::from("relative_humidity"))); + } + + if !(0.0..=50_000.0).contains(&saturation_vapour_pressure_si) { + return Err(InputError::OutOfRange(String::from( + "saturation_vapour_pressure", + ))); + } + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + saturation_vapour_pressure: SaturationVapourPressure, + relative_humidity: RelativeHumidity, + ) -> VapourPressure { + let result = saturation_vapour_pressure.0 * relative_humidity.0; + + VapourPressure(result) + } +} + +/// Formula for computing vapour pressure from dewpoint temperature and pressure. +/// Should be used for air over water when accuracy is desired. +/// +/// Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). /// -///Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). -///# Errors +/// Valid `dewpoint temperature` range: 232K - 324K /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `dewpoint` range: 232K - 324K\ -///Valid `pressure` range: 100Pa - 150000Pa +/// Valid `atmospheric pressure` range: 100Pa - 150000Pa pub struct Buck1; -impl Buck1 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] +impl Formula2 for Buck1 { #[inline(always)] - pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { - if !(232.0..=324.0).contains(&dewpoint) { + fn validate_inputs( + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let dewpoint_si = dewpoint.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(232.0..=324.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } - if !(100.0..=150_000.0).contains(&pressure) { + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } Ok(()) } - #[allow(missing_docs)] #[inline(always)] - pub fn compute_unchecked(dewpoint: Float, pressure: Float) -> Float { - let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C - let pressure = pressure / 100.0; //convert to hPa + fn compute_unchecked( + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> VapourPressure { + let dewpoint = dewpoint.0.get::(); + let pressure = pressure.0.get::(); let lower_a = 6.1121; let lower_b = 18.729; @@ -110,38 +155,36 @@ impl Buck1 { 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))); - (lower_e * lower_f) * 100.0 //return in Pa + let result = Pressure::new::(lower_e * lower_f); + + VapourPressure(result) } } -generate_compute!(Buck1, dewpoint, pressure); -generate_vec_compute!(Buck1, dewpoint, pressure); -generate_ndarray_compute!(Buck1, dewpoint, pressure); -generate_par_vec_compute!(Buck1, dewpoint, pressure); -generate_par_ndarray_compute!(Buck1, dewpoint, pressure); - -///Formula for computing vapour pressure from dewpoint temperature and pressure. -///Should be used for air over ice when accuracy is desired. +/// Formula for computing vapour pressure from dewpoint temperature and pressure. +/// Should be used for air over ice when accuracy is desired. /// -///Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). -///# Errors +/// Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `dewpoint` range: 193K - 274K\ -///Valid `pressure` range: 100Pa - 150000Pa +/// Valid `dewpoint` range: 193K - 274K +/// +/// Valid `pressure` range: 100Pa - 150000Pa pub struct Buck2; -impl Buck2 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] +impl Formula2 for Buck2 { #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] - pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { - if !(193.0..=274.0).contains(&dewpoint) { + fn validate_inputs( + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let dewpoint_si = dewpoint.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(193.0..=274.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } - if !(100.0..=150_000.0).contains(&pressure) { + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } @@ -149,10 +192,12 @@ impl Buck2 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(dewpoint: Float, pressure: Float) -> Float { - let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C - let pressure = pressure / 100.0; //convert to hPa + fn compute_unchecked( + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> VapourPressure { + let dewpoint = dewpoint.0.get::(); + let pressure = pressure.0.get::(); let lower_a = 6.1115; let lower_b = 23.036; @@ -167,38 +212,36 @@ impl Buck2 { 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))); - (lower_e * lower_f) * 100.0 //return in Pa + let result = Pressure::new::(lower_e * lower_f); + + VapourPressure(result) } } -generate_compute!(Buck2, dewpoint, pressure); -generate_vec_compute!(Buck2, dewpoint, pressure); -generate_ndarray_compute!(Buck2, dewpoint, pressure); -generate_par_vec_compute!(Buck2, dewpoint, pressure); -generate_par_ndarray_compute!(Buck2, dewpoint, pressure); - -///Formula for computing vapour pressure from dewpoint temperature and pressure. -///Should be used for air over water for general use. +/// Formula for computing vapour pressure from dewpoint temperature and pressure. +/// Should be used for air over water for general use. +/// +/// Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). /// -///Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). -///# Errors +/// Valid `dewpoint` range: 253K - 324K /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `dewpoint` range: 253K - 324K\ -///Valid `pressure` range: 100Pa - 150000Pa +/// Valid `pressure` range: 100Pa - 150000Pa pub struct Buck3; -impl Buck3 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] +impl Formula2 for Buck3 { #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] - pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { - if !(253.0..=324.0).contains(&dewpoint) { + fn validate_inputs( + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let dewpoint_si = dewpoint.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(253.0..=324.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } - if !(100.0..=150_000.0).contains(&pressure) { + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } @@ -206,10 +249,12 @@ impl Buck3 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(dewpoint: Float, pressure: Float) -> Float { - let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C - let pressure = pressure / 100.0; //convert to hPa + fn compute_unchecked( + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> VapourPressure { + let dewpoint = dewpoint.0.get::(); + let pressure = pressure.0.get::(); let lower_a = 6.1121; let lower_b = 17.502; @@ -221,33 +266,26 @@ impl Buck3 { let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); let lower_f = 1.0 + upper_a + (pressure * upper_b); - (lower_e * lower_f) * 100.0 //return in Pa + let result = Pressure::new::(lower_e * lower_f); + + VapourPressure(result) } } -generate_compute!(Buck3, dewpoint, pressure); -generate_vec_compute!(Buck3, dewpoint, pressure); -generate_ndarray_compute!(Buck3, dewpoint, pressure); -generate_par_vec_compute!(Buck3, dewpoint, pressure); -generate_par_ndarray_compute!(Buck3, dewpoint, pressure); - -///Formula for computing vapour pressure from dewpoint temperature. -///Simplified version of [`buck3`]. Very popular in meteorological sources. +/// Formula for computing vapour pressure from dewpoint temperature. +/// Simplified version of [`buck3`]. Very popular in meteorological sources. /// -///Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). -///# Errors +/// Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `dewpoint` range: 253K - 324K +/// Valid `dewpoint` range: 253K - 324K pub struct Buck3Simplified; -impl Buck3Simplified { - #[allow(missing_docs)] +impl Formula1 for Buck3Simplified { #[inline(always)] - #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] - pub fn validate_inputs(dewpoint: Float) -> Result<(), InputError> { - if !(253.0..=324.0).contains(&dewpoint) { + fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { + let dewpoint_si = dewpoint.get_si_value(); + + if !(253.0..=324.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } @@ -255,9 +293,8 @@ impl Buck3Simplified { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(dewpoint: Float) -> Float { - let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C + fn compute_unchecked(dewpoint: DewPointTemperature) -> VapourPressure { + let dewpoint = dewpoint.0.get::(); let lower_a = 6.1121; let lower_b = 17.502; @@ -265,38 +302,36 @@ impl Buck3Simplified { let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); - lower_e * 100.0 //return in Pa + let result = Pressure::new::(lower_e); + + VapourPressure(result) } } -generate_compute!(Buck3Simplified, dewpoint); -generate_vec_compute!(Buck3Simplified, dewpoint); -generate_ndarray_compute!(Buck3Simplified, dewpoint); -generate_par_vec_compute!(Buck3Simplified, dewpoint); -generate_par_ndarray_compute!(Buck3Simplified, dewpoint); - -///Formula for computing vapour pressure from dewpoint temperature and pressure. -///Should be used for air over ice for general use. +/// Formula for computing vapour pressure from dewpoint temperature and pressure. +/// Should be used for air over ice for general use. /// -///Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). -///# Errors +/// Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `dewpoint` range: 223K - 274K\ -///Valid `pressure` range: 100Pa - 150000Pa +/// Valid `dewpoint` range: 223K - 274K +/// +/// Valid `pressure` range: 100Pa - 150000Pa pub struct Buck4; -impl Buck4 { - #[allow(missing_docs)] +impl Formula2 for Buck4 { #[inline(always)] - #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] - pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { - if !(223.0..=274.0).contains(&dewpoint) { + fn validate_inputs( + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let dewpoint_si = dewpoint.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(223.0..=274.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } - if !(100.0..=150_000.0).contains(&pressure) { + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } @@ -304,10 +339,12 @@ impl Buck4 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(dewpoint: Float, pressure: Float) -> Float { - let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C - let pressure = pressure / 100.0; //convert to hPa + fn compute_unchecked( + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> VapourPressure { + let dewpoint = dewpoint.0.get::(); + let pressure = pressure.0.get::(); let lower_a = 6.1115; let lower_b = 22.452; @@ -319,34 +356,26 @@ impl Buck4 { let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); let lower_f = 1.0 + upper_a + (pressure * upper_b); - (lower_e * lower_f) * 100.0 //return in Pa + let result = Pressure::new::(lower_e * lower_f); + + VapourPressure(result) } } -generate_compute!(Buck4, dewpoint, pressure); -generate_vec_compute!(Buck4, dewpoint, pressure); -generate_ndarray_compute!(Buck4, dewpoint, pressure); -generate_par_vec_compute!(Buck4, dewpoint, pressure); -generate_par_ndarray_compute!(Buck4, dewpoint, pressure); - -///Formula for computing vapour pressure from dewpoint temperature. -///Simplified version of [`buck4`], analogical to [`buck3_simplified`]. +/// Formula for computing vapour pressure from dewpoint temperature. +/// Simplified version of [`buck4`], analogical to [`buck3_simplified`]. /// -///Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). -///# Errors +/// Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `dewpoint` range: 223K - 274K +/// Valid `dewpoint` range: 223K - 274K pub struct Buck4Simplified; -impl Buck4Simplified { - #[allow(missing_docs)] +impl Formula1 for Buck4Simplified { #[inline(always)] - #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] - pub fn validate_inputs(dewpoint: Float) -> Result<(), InputError> { - //validate inputs - if !(223.0..=274.0).contains(&dewpoint) { + fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { + let dewpoint_si = dewpoint.get_si_value(); + + if !(223.0..=274.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } @@ -354,9 +383,9 @@ impl Buck4Simplified { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(dewpoint: Float) -> Float { - let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C + + fn compute_unchecked(dewpoint: DewPointTemperature) -> VapourPressure { + let dewpoint = dewpoint.0.get::(); let lower_a = 6.1115; let lower_b = 22.452; @@ -364,34 +393,26 @@ impl Buck4Simplified { let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); - lower_e * 100.0 //return in Pa + let result = Pressure::new::(lower_e); + + VapourPressure(result) } } -generate_compute!(Buck4Simplified, dewpoint); -generate_vec_compute!(Buck4Simplified, dewpoint); -generate_ndarray_compute!(Buck4Simplified, dewpoint); -generate_par_vec_compute!(Buck4Simplified, dewpoint); -generate_par_ndarray_compute!(Buck4Simplified, dewpoint); - -///Formula for computing vapour pressure over water from dewpoint temperature. -///Should be used for temperatures above 273K. -/// -///Derived by O. Tetens (1930). +/// Formula for computing vapour pressure over water from dewpoint temperature. +/// Should be used for temperatures above 273K. /// -///# Errors +/// Derived by O. Tetens (1930). /// -///Returns [`InputError::OutOfRange`] when input is out of range.\ -///Valid `dewpoint` range: 273K - 353K +/// Valid `dewpoint` range: 273K - 353K pub struct Tetens1; -impl Tetens1 { - #[allow(missing_docs)] +impl Formula1 for Tetens1 { #[inline(always)] - #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] - pub fn validate_inputs(dewpoint: Float) -> Result<(), InputError> { - if !(273.0..=353.0).contains(&dewpoint) { + fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { + let dewpoint_si = dewpoint.get_si_value(); + + if !(273.0..=353.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } @@ -399,9 +420,8 @@ impl Tetens1 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(dewpoint: Float) -> Float { - let dewpoint = dewpoint - ZERO_CELSIUS; //convert to C + fn compute_unchecked(dewpoint: DewPointTemperature) -> VapourPressure { + let dewpoint = dewpoint.0.get::(); let lower_a = 0.61078; let lower_b = 17.27; @@ -409,143 +429,27 @@ impl Tetens1 { let result = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); - result * 1000.0 //return in Pa - } -} - -generate_compute!(Tetens1, dewpoint); -generate_vec_compute!(Tetens1, dewpoint); -generate_ndarray_compute!(Tetens1, dewpoint); -generate_par_vec_compute!(Tetens1, dewpoint); -generate_par_ndarray_compute!(Tetens1, dewpoint); - -///Formula for computing **ONLY** vapour pressure from saturation vapour pressure and relative humidity. -///For saturation vapour pressure use [`saturation_specific2`] -/// -///# Errors -/// -///Returns [`InputError::OutOfRange`] when input is out of range.\ -///Valid `saturation_vapour_pressure` range: 0Pa - 10000Pa\ -///Valid `relative_humidity` range: 0.0 - 1.0 -pub struct SaturationSpecific1; - -impl SaturationSpecific1 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] - pub fn validate_inputs( - 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"))); - } - - if !(0.0..=50_000.0).contains(&saturation_vapour_pressure) { - return Err(InputError::OutOfRange(String::from( - "saturation_vapour_pressure", - ))); - } + let result = Pressure::new::(result); - Ok(()) - } - - #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(saturation_vapour_pressure: Float, relative_humidity: Float) -> Float { - saturation_vapour_pressure * relative_humidity + VapourPressure(result) } } -generate_compute!( - SaturationSpecific1, - saturation_vapour_pressure, - relative_humidity -); -generate_vec_compute!( - SaturationSpecific1, - saturation_vapour_pressure, - relative_humidity -); -generate_ndarray_compute!( - SaturationSpecific1, - saturation_vapour_pressure, - relative_humidity -); -generate_par_vec_compute!( - SaturationSpecific1, - saturation_vapour_pressure, - relative_humidity -); -generate_par_ndarray_compute!( - SaturationSpecific1, - saturation_vapour_pressure, - relative_humidity -); - -///Formula for computing **ONLY** saturation vapour pressure from vapour pressure and relative humidity. -///For vapour pressure use [`saturation_specific1`] +/// Formula for computing vapour pressure over water from dewpoint temperature. +/// Should be used when accuracy is required as it is +/// computationally expensive. /// -///# Errors +/// Derived by A. Wexler (1976) [(doi: 10.6028/jres.080A.071)](https://dx.doi.org/10.6028%2Fjres.080A.071). /// -///Returns [`InputError::OutOfRange`] when input is out of range.\ -///Valid `vapour_pressure` range: 0Pa - 10000Pa\ -///Valid `relative_humidity` range: 0.00001 - 1.0 -pub struct SaturationSpecific2; - -impl SaturationSpecific2 { - #[allow(missing_docs)] - #[inline(always)] - #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] - pub fn validate_inputs( - vapour_pressure: Float, - relative_humidity: Float, - ) -> Result<(), InputError> { - if !(0.00001..=2.0).contains(&relative_humidity) { - return Err(InputError::OutOfRange(String::from("relative_humidity"))); - } - - if !(0.0..=10_000.0).contains(&vapour_pressure) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); - } - - Ok(()) - } - - #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(vapour_pressure: Float, relative_humidity: Float) -> Float { - vapour_pressure / relative_humidity - } -} - -generate_compute!(SaturationSpecific2, vapour_pressure, relative_humidity); -generate_vec_compute!(SaturationSpecific2, vapour_pressure, relative_humidity); -generate_ndarray_compute!(SaturationSpecific2, vapour_pressure, relative_humidity); -generate_par_vec_compute!(SaturationSpecific2, vapour_pressure, relative_humidity); -generate_par_ndarray_compute!(SaturationSpecific2, vapour_pressure, relative_humidity); - -///Formula for computing vapour pressure over water from dewpoint temperature. -///Should be used when accuracy is required as it is -///computationally expensive. -/// -///Derived by A. Wexler (1976) [(doi: 10.6028/jres.080A.071)](https://dx.doi.org/10.6028%2Fjres.080A.071). -/// -///# Errors -/// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `dewpoint` range: 273K - 374K +/// Valid `dewpoint` range: 273K - 374K pub struct Wexler1; -impl Wexler1 { - #[allow(missing_docs)] +impl Formula1 for Wexler1 { #[inline(always)] - #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] - pub fn validate_inputs(dewpoint: Float) -> Result<(), InputError> { - if !(273.0..=374.0).contains(&dewpoint) { + fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { + let dewpoint_si = dewpoint.get_si_value(); + + if !(273.0..=374.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } @@ -553,8 +457,9 @@ impl Wexler1 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(dewpoint: Float) -> Float { + fn compute_unchecked(dewpoint: DewPointTemperature) -> VapourPressure { + let dewpoint = dewpoint.get_si_value(); + // constants from the paper let g: [Float; 8] = [ -2991.2729, @@ -573,43 +478,36 @@ impl Wexler1 { ln_p += g[i] * dewpoint.powi(i as i32 - 2); } - ln_p.exp() + let result = Pressure::new::(ln_p.exp()); + + VapourPressure(result) } } -generate_compute!(Wexler1, dewpoint); -generate_vec_compute!(Wexler1, dewpoint); -generate_ndarray_compute!(Wexler1, dewpoint); -generate_par_vec_compute!(Wexler1, dewpoint); -generate_par_ndarray_compute!(Wexler1, dewpoint); - -///Formula for computing vapour over ice pressure from dewpoint temperature. -///Should be used when accuracy is required as it is -///computationally expensive. -/// -///Derived by A. Wexler (1977) [(doi: 10.6028/jres.081A.003)](https://dx.doi.org/10.6028%2Fjres.081A.003). +/// Formula for computing vapour over ice pressure from dewpoint temperature. +/// Should be used when accuracy is required as it is +/// computationally expensive. /// -///# Errors +/// Derived by A. Wexler (1977) [(doi: 10.6028/jres.081A.003)](https://dx.doi.org/10.6028%2Fjres.081A.003). /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `dewpoint` range: 173K - 274K +/// Valid `dewpoint` range: 173K - 274K pub struct Wexler2; -impl Wexler2 { - #[allow(missing_docs)] +impl Formula1 for Wexler2 { #[inline(always)] - #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] - pub fn validate_inputs(dewpoint: Float) -> Result<(), InputError> { - if !(173.0..=274.0).contains(&dewpoint) { + fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { + let dewpoint_si = dewpoint.get_si_value(); + + if !(173.0..=274.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } Ok(()) } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(dewpoint: Float) -> Float { + fn compute_unchecked(dewpoint: DewPointTemperature) -> VapourPressure { + let dewpoint = dewpoint.0.get::(); + // constants from the paper let big_k: [Float; 6] = [ -5865.3696, @@ -626,41 +524,38 @@ impl Wexler2 { ln_p += big_k[j] * dewpoint.powi(j as i32 - 1); } - ln_p.exp() + let result = Pressure::new::(ln_p.exp()); + + VapourPressure(result) } } -generate_compute!(Wexler2, dewpoint); -generate_vec_compute!(Wexler2, dewpoint); -generate_ndarray_compute!(Wexler2, dewpoint); -generate_par_vec_compute!(Wexler2, dewpoint); -generate_par_ndarray_compute!(Wexler2, dewpoint); - -// #[cfg(test)] -// mod tests { -// use crate::{ -// tests_framework::{self, Argument}, -// vapour_pressure, -// }; - -// #[test] -// fn general1() { -// assert!(tests_framework::test_with_2args( -// &vapour_pressure::general1, -// Argument { -// name: "specific_humidity", -// def_val: 0.022, -// range: [0.00001, 2.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// 3536.6680935251343 -// )); -// } - +#[cfg(test)] +mod tests { + use crate::{ + quantities::{AtmosphericPressure, SpecificHumidity, VapourPressure}, + tests::{test_with_2args, Argument}, + }; + + use super::Definition1; + + #[test] + fn definition1() { + test_with_2args::( + Argument { + name: "specific_humidity", + def_val: 0.022, + range: [0.00001, 2.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 3536.6680935251343, + ); + } +} // #[test] // fn buck1() { // assert!(tests_framework::test_with_2args( diff --git a/src/vapour_pressure_deficit.rs b/src/vapour_pressure_deficit.rs index 6d2e449..80d1ae9 100644 --- a/src/vapour_pressure_deficit.rs +++ b/src/vapour_pressure_deficit.rs @@ -4,14 +4,11 @@ //!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::compute_macros::{ - generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, - generate_par_vec_compute, generate_vec_compute, -}; + use crate::errors::InputError; use crate::{vapour_pressure, Float}; -#[cfg(feature = "debug")] -use floccus_proc::logerr; + + use itertools::izip; use ndarray::{Array, Dimension, FoldWhile}; use rayon::iter::{ParallelBridge, ParallelIterator}; @@ -29,7 +26,7 @@ impl General1 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( vapour_pressure: Float, saturation_vapour_pressure: Float, @@ -54,11 +51,6 @@ impl General1 { } } -generate_compute!(General1, vapour_pressure, saturation_vapour_pressure); -generate_vec_compute!(General1, vapour_pressure, saturation_vapour_pressure); -generate_ndarray_compute!(General1, vapour_pressure, saturation_vapour_pressure); -generate_par_vec_compute!(General1, vapour_pressure, saturation_vapour_pressure); -generate_par_ndarray_compute!(General1, vapour_pressure, saturation_vapour_pressure); ///Formula for computing vapour pressure deficit from temperature, dewpoint and pressure ///using [`buck3`](vapour_pressure::buck3) function for vapour pressure calculation @@ -74,7 +66,7 @@ impl General2 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( temperature: Float, dewpoint: Float, @@ -105,11 +97,6 @@ impl General2 { } } -generate_compute!(General2, temperature, dewpoint, pressure); -generate_vec_compute!(General2, temperature, dewpoint, pressure); -generate_ndarray_compute!(General2, temperature, dewpoint, pressure); -generate_par_vec_compute!(General2, temperature, dewpoint, pressure); -generate_par_ndarray_compute!(General2, temperature, dewpoint, pressure); ///Formula for computing vapour pressure deficit from temperature, relative humidity and pressure ///using [`buck3`](vapour_pressure::buck3) function for vapour pressure calculation @@ -125,7 +112,7 @@ impl General3 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( temperature: Float, relative_humidity: Float, @@ -164,11 +151,6 @@ impl General3 { } } -generate_compute!(General3, temperature, relative_humidity, pressure); -generate_vec_compute!(General3, temperature, relative_humidity, pressure); -generate_ndarray_compute!(General3, temperature, relative_humidity, pressure); -generate_par_vec_compute!(General3, temperature, relative_humidity, pressure); -generate_par_ndarray_compute!(General3, temperature, relative_humidity, pressure); // #[cfg(test)] // mod tests { diff --git a/src/virtual_temperature.rs b/src/virtual_temperature.rs index 340cf6a..2cfea81 100644 --- a/src/virtual_temperature.rs +++ b/src/virtual_temperature.rs @@ -4,14 +4,11 @@ //!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::compute_macros::{ - generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, - generate_par_vec_compute, generate_vec_compute, -}; + use crate::Float; use crate::{constants::EPSILON, errors::InputError}; -#[cfg(feature = "debug")] -use floccus_proc::logerr; + + use itertools::izip; use ndarray::{Array, Dimension, FoldWhile}; use rayon::iter::{ParallelBridge, ParallelIterator}; @@ -29,7 +26,7 @@ impl General1 { #[allow(missing_docs)] #[inline(always)] #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(temperature: Float, mixing_ratio: Float) -> Result<(), InputError> { if !(173.0..=354.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); @@ -49,11 +46,6 @@ impl General1 { } } -generate_compute!(General1, temperature, mixing_ratio); -generate_vec_compute!(General1, temperature, mixing_ratio); -generate_par_vec_compute!(General1, temperature, mixing_ratio); -generate_ndarray_compute!(General1, temperature, mixing_ratio); -generate_par_ndarray_compute!(General1, temperature, mixing_ratio); ///Formula for computing virtual temperature from air temperature, pressure and vapour pressure. /// @@ -69,7 +61,7 @@ impl General2 { #[allow(missing_docs)] #[inline(always)] #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs( temperature: Float, pressure: Float, @@ -96,11 +88,6 @@ impl General2 { } } -generate_compute!(General2, temperature, pressure, vapour_pressure); -generate_vec_compute!(General2, temperature, pressure, vapour_pressure); -generate_par_vec_compute!(General2, temperature, pressure, vapour_pressure); -generate_ndarray_compute!(General2, temperature, pressure, vapour_pressure); -generate_par_ndarray_compute!(General2, temperature, pressure, vapour_pressure); ///Formula for computing virtual temperature from air temperature and specific humidity. /// @@ -115,7 +102,7 @@ impl General3 { #[allow(missing_docs)] #[inline(always)] #[allow(clippy::missing_errors_doc)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(temperature: Float, specific_humidity: Float) -> Result<(), InputError> { if !(173.0..=354.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); @@ -135,11 +122,6 @@ impl General3 { } } -generate_compute!(General3, temperature, specific_humidity); -generate_vec_compute!(General3, temperature, specific_humidity); -generate_par_vec_compute!(General3, temperature, specific_humidity); -generate_ndarray_compute!(General3, temperature, specific_humidity); -generate_par_ndarray_compute!(General3, temperature, specific_humidity); // #[cfg(test)] // mod tests { diff --git a/src/wet_bulb_potential_temperature.rs b/src/wet_bulb_potential_temperature.rs index 7e9cbac..cdbf2a6 100644 --- a/src/wet_bulb_potential_temperature.rs +++ b/src/wet_bulb_potential_temperature.rs @@ -1,16 +1,13 @@ //!Functions to calculate wet bulb potential temperature of unsaturated air in K. -use crate::compute_macros::{ - generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, - generate_par_vec_compute, generate_vec_compute, -}; + use crate::Float; use crate::{ constants::{C_P, R_D, ZERO_CELSIUS}, errors::InputError, }; -#[cfg(feature = "debug")] -use floccus_proc::logerr; + + use ndarray::{Array, Dimension, FoldWhile}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; @@ -37,7 +34,7 @@ impl DaviesJones1 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(equivalent_potential_temperature: Float) -> Result<(), InputError> { if !(257.0..=377.0).contains(&equivalent_potential_temperature) { return Err(InputError::OutOfRange(String::from( @@ -49,29 +46,24 @@ impl DaviesJones1 { } } -generate_compute!(DaviesJones1, equivalent_potential_temperature); -generate_vec_compute!(DaviesJones1, equivalent_potential_temperature); -generate_ndarray_compute!(DaviesJones1, equivalent_potential_temperature); -generate_par_vec_compute!(DaviesJones1, equivalent_potential_temperature); -generate_par_ndarray_compute!(DaviesJones1, equivalent_potential_temperature); -#[cfg(test)] -mod tests { - use crate::{ - tests_framework::{self, Argument}, - wet_bulb_potential_temperature, - }; +// #[cfg(test)] +// mod tests { +// use crate::{ +// tests_framework::{self, Argument}, +// wet_bulb_potential_temperature, +// }; - #[test] - fn davies_jones1() { - assert!(tests_framework::test_with_1arg( - &wet_bulb_potential_temperature::DaviesJones1::compute, - Argument { - name: "equivalent_potential_temperature", - def_val: 300.0, - range: [257.0, 377.0] - }, - 281.17941447108467 - )); - } -} +// #[test] +// fn davies_jones1() { +// assert!(tests_framework::test_with_1arg( +// &wet_bulb_potential_temperature::DaviesJones1::compute, +// Argument { +// name: "equivalent_potential_temperature", +// def_val: 300.0, +// range: [257.0, 377.0] +// }, +// 281.17941447108467 +// )); +// } +// } diff --git a/src/wet_bulb_temperature.rs b/src/wet_bulb_temperature.rs index c2d3ae6..833ada7 100644 --- a/src/wet_bulb_temperature.rs +++ b/src/wet_bulb_temperature.rs @@ -1,13 +1,10 @@ //!Functions to calculate wet bulb temperature of unsaturated air in K. -use crate::compute_macros::{ - generate_compute, generate_ndarray_compute, generate_par_ndarray_compute, - generate_par_vec_compute, generate_vec_compute, -}; + use crate::Float; use crate::{constants::ZERO_CELSIUS, errors::InputError}; -#[cfg(feature = "debug")] -use floccus_proc::logerr; + + use itertools::izip; use ndarray::{Array, Dimension, FoldWhile}; use rayon::iter::{ParallelBridge, ParallelIterator}; @@ -29,7 +26,7 @@ impl Stull1 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] - #[cfg_attr(feature = "debug", logerr)] + pub fn validate_inputs(temperature: Float, relative_humidity: Float) -> Result<(), InputError> { if !(253.0..=324.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); @@ -58,11 +55,6 @@ impl Stull1 { } } -generate_compute!(Stull1, temperature, relative_humidity); -generate_vec_compute!(Stull1, temperature, relative_humidity); -generate_par_vec_compute!(Stull1, temperature, relative_humidity); -generate_ndarray_compute!(Stull1, temperature, relative_humidity); -generate_par_ndarray_compute!(Stull1, temperature, relative_humidity); // #[cfg(test)] // mod tests { From cf12fb0d0015b6eb89c910b4df8938c668c99502 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:17:19 +0100 Subject: [PATCH 019/102] (WIP) comment out benchmarks --- {benches => benches-aaa}/equivalent_potential_temperature.rs | 0 {benches => benches-aaa}/mixing_ratio.rs | 0 {benches => benches-aaa}/potential_temperature.rs | 0 {benches => benches-aaa}/relative_humidity.rs | 0 {benches => benches-aaa}/results/0_3_7.txt | 0 {benches => benches-aaa}/specific_humidity.rs | 0 {benches => benches-aaa}/vapour_pressure.rs | 0 {benches => benches-aaa}/vapour_pressure_deficit.rs | 0 {benches => benches-aaa}/virtual_temperature.rs | 0 {benches => benches-aaa}/wet_bulb_potential_temperature.rs | 0 {benches => benches-aaa}/wet_bulb_temperature.rs | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename {benches => benches-aaa}/equivalent_potential_temperature.rs (100%) rename {benches => benches-aaa}/mixing_ratio.rs (100%) rename {benches => benches-aaa}/potential_temperature.rs (100%) rename {benches => benches-aaa}/relative_humidity.rs (100%) rename {benches => benches-aaa}/results/0_3_7.txt (100%) rename {benches => benches-aaa}/specific_humidity.rs (100%) rename {benches => benches-aaa}/vapour_pressure.rs (100%) rename {benches => benches-aaa}/vapour_pressure_deficit.rs (100%) rename {benches => benches-aaa}/virtual_temperature.rs (100%) rename {benches => benches-aaa}/wet_bulb_potential_temperature.rs (100%) rename {benches => benches-aaa}/wet_bulb_temperature.rs (100%) diff --git a/benches/equivalent_potential_temperature.rs b/benches-aaa/equivalent_potential_temperature.rs similarity index 100% rename from benches/equivalent_potential_temperature.rs rename to benches-aaa/equivalent_potential_temperature.rs diff --git a/benches/mixing_ratio.rs b/benches-aaa/mixing_ratio.rs similarity index 100% rename from benches/mixing_ratio.rs rename to benches-aaa/mixing_ratio.rs diff --git a/benches/potential_temperature.rs b/benches-aaa/potential_temperature.rs similarity index 100% rename from benches/potential_temperature.rs rename to benches-aaa/potential_temperature.rs diff --git a/benches/relative_humidity.rs b/benches-aaa/relative_humidity.rs similarity index 100% rename from benches/relative_humidity.rs rename to benches-aaa/relative_humidity.rs diff --git a/benches/results/0_3_7.txt b/benches-aaa/results/0_3_7.txt similarity index 100% rename from benches/results/0_3_7.txt rename to benches-aaa/results/0_3_7.txt diff --git a/benches/specific_humidity.rs b/benches-aaa/specific_humidity.rs similarity index 100% rename from benches/specific_humidity.rs rename to benches-aaa/specific_humidity.rs diff --git a/benches/vapour_pressure.rs b/benches-aaa/vapour_pressure.rs similarity index 100% rename from benches/vapour_pressure.rs rename to benches-aaa/vapour_pressure.rs diff --git a/benches/vapour_pressure_deficit.rs b/benches-aaa/vapour_pressure_deficit.rs similarity index 100% rename from benches/vapour_pressure_deficit.rs rename to benches-aaa/vapour_pressure_deficit.rs diff --git a/benches/virtual_temperature.rs b/benches-aaa/virtual_temperature.rs similarity index 100% rename from benches/virtual_temperature.rs rename to benches-aaa/virtual_temperature.rs diff --git a/benches/wet_bulb_potential_temperature.rs b/benches-aaa/wet_bulb_potential_temperature.rs similarity index 100% rename from benches/wet_bulb_potential_temperature.rs rename to benches-aaa/wet_bulb_potential_temperature.rs diff --git a/benches/wet_bulb_temperature.rs b/benches-aaa/wet_bulb_temperature.rs similarity index 100% rename from benches/wet_bulb_temperature.rs rename to benches-aaa/wet_bulb_temperature.rs From d0826b69392496a895300cd32d14341e30711748 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:17:36 +0100 Subject: [PATCH 020/102] rewrite vapour pressure --- src/tests.rs | 62 +++++++- src/vapour_pressure.rs | 327 +++++++++++++++++++---------------------- 2 files changed, 212 insertions(+), 177 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index 4839d11..5401c7a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,5 @@ use crate::errors::InputError; -use crate::formula::Formula2; +use crate::formula::{Formula1, Formula2}; use crate::quantities::ThermodynamicQuantity; use crate::Float; use float_cmp::assert_approx_eq; @@ -92,6 +92,66 @@ pub fn test_with_2args< assert_eq!(result, expected); } +pub fn test_with_1arg>( + arg1: Argument, + expected_result: Float, +) { + //the first promise of the crate is that returned value + //is calculated correctly + let result = F::compute(I1::new_si(arg1.def_val)).unwrap(); + assert_approx_eq!( + Float, + result.get_si_value(), + expected_result, + epsilon = 0.01 + ); + + // the second promise of the crate is to never return NaN or Inf + // here we check several edge cases for that + // the function can only return a finite number or InputError + // check for error implicit as Result<> ensures that if value + // is not Ok() then it is Err() + // here we don't care about error being correct + let results = vec![ + F::compute(I1::new_si(arg1.def_val)), + F::compute(I1::new_si(-9999.0)), + ]; + + for result in results { + if let Ok(result) = result { + assert!(result.get_si_value().is_finite()); + } + } + + //the third promise of the crate is to always return finite f64 + //if all inputs are within the range + //the only allowed error is InccorectArgumentsSet as it can occur + //for values within valid range + for arg1_itr in 0..=100 { + let arg1_tmp = + (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; + + let arg1_tmp = I1::new_si(arg1_tmp); + + let result = F::compute(arg1_tmp); + + match result { + Ok(r) => assert!(r.get_si_value().is_finite()), + Err(e) => assert!( + discriminant(&InputError::IncorrectArgumentSet(String::new())) == discriminant(&e) + ), + } + } + + //the fourth promise of the crate is to return an error with + //erronous variable name when input is out of range + let expected = InputError::OutOfRange(String::from(arg1.name)); + let result = F::compute(I1::new_si(arg1.range[0] - 0.1)).unwrap_err(); + assert_eq!(result, expected); + let result = F::compute(I1::new_si(arg1.range[1] + 0.1)).unwrap_err(); + assert_eq!(result, expected); +} + // #[allow(dead_code)] // pub fn test_with_1arg( // tested_function: &dyn Fn(Float) -> Result, diff --git a/src/vapour_pressure.rs b/src/vapour_pressure.rs index e0492be..ba2555e 100644 --- a/src/vapour_pressure.rs +++ b/src/vapour_pressure.rs @@ -533,11 +533,14 @@ impl Formula1 for Wexler2 { #[cfg(test)] mod tests { use crate::{ - quantities::{AtmosphericPressure, SpecificHumidity, VapourPressure}, - tests::{test_with_2args, Argument}, + quantities::{ + AtmosphericPressure, RelativeHumidity, SaturationVapourPressure, SpecificHumidity, + VapourPressure, + }, + tests::{test_with_1arg, test_with_2args, Argument}, }; - use super::Definition1; + use super::*; #[test] fn definition1() { @@ -555,177 +558,149 @@ mod tests { 3536.6680935251343, ); } + + #[test] + fn definition2() { + test_with_2args::( + Argument { + name: "saturation_vapour_pressure", + def_val: 3550.0, + range: [0.0, 50_000.0], + }, + Argument { + name: "relative_humidity", + def_val: 0.5, + range: [0.0, 2.0], + }, + 1775.0, + ); + } + + #[test] + fn buck1() { + test_with_2args::( + Argument { + name: "dewpoint", + def_val: 300.0, + range: [232.0, 324.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 3550.6603579471303, + ); + } + + #[test] + fn buck2() { + test_with_2args::( + Argument { + name: "dewpoint", + def_val: 250.0, + range: [193.0, 274.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 76.38781790372722, + ); + } + + #[test] + fn buck3() { + test_with_2args::( + Argument { + name: "dewpoint", + def_val: 300.0, + range: [253.0, 324.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 3548.5041048035896, + ); + } + + #[test] + fn buck4() { + test_with_2args::( + Argument { + name: "dewpoint", + def_val: 250.0, + range: [223.0, 274.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 76.38685471836712, + ); + } + + #[test] + fn buck3_simplified() { + test_with_1arg::( + Argument { + name: "dewpoint", + def_val: 300.0, + range: [253.0, 324.0], + }, + 3533.6421536199978, + ); + } + + #[test] + fn buck4_simplified() { + test_with_1arg::( + Argument { + name: "dewpoint", + def_val: 250.0, + range: [223.0, 274.0], + }, + 76.04197508519536, + ); + } + + #[test] + fn tetens1() { + test_with_1arg::( + Argument { + name: "dewpoint", + def_val: 300.0, + range: [273.0, 353.0], + }, + 3533.969137160892, + ); + } + + #[test] + fn wexler1() { + test_with_1arg::( + Argument { + name: "dewpoint", + def_val: 300.0, + range: [273.0, 374.0], + }, + 3535.4235919263083, + ); + } + + #[test] + fn wexler2() { + test_with_1arg::( + Argument { + name: "dewpoint", + def_val: 250.0, + range: [173.0, 274.0], + }, + 76.04351136780438, + ); + } } -// #[test] -// fn buck1() { -// assert!(tests_framework::test_with_2args( -// &vapour_pressure::buck1, -// Argument { -// name: "dewpoint", -// def_val: 300.0, -// range: [232.0, 324.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// 3550.6603579471303 -// )); -// } - -// #[test] -// fn buck2() { -// assert!(tests_framework::test_with_2args( -// &vapour_pressure::buck2, -// Argument { -// name: "dewpoint", -// def_val: 250.0, -// range: [193.0, 274.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// 76.38781790372722 -// )); -// } - -// #[test] -// fn buck3() { -// assert!(tests_framework::test_with_2args( -// &vapour_pressure::buck3, -// Argument { -// name: "dewpoint", -// def_val: 300.0, -// range: [253.0, 324.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// 3548.5041048035896 -// )); -// } - -// #[test] -// fn buck4() { -// assert!(tests_framework::test_with_2args( -// &vapour_pressure::buck4, -// Argument { -// name: "dewpoint", -// def_val: 250.0, -// range: [223.0, 274.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// 76.38685471836712 -// )); -// } - -// #[test] -// fn buck3_simplified() { -// assert!(tests_framework::test_with_1arg( -// &vapour_pressure::buck3_simplified, -// Argument { -// name: "dewpoint", -// def_val: 300.0, -// range: [253.0, 324.0] -// }, -// 3533.6421536199978 -// )); -// } - -// #[test] -// fn buck4_simplified() { -// assert!(tests_framework::test_with_1arg( -// &vapour_pressure::buck4_simplified, -// Argument { -// name: "dewpoint", -// def_val: 250.0, -// range: [223.0, 274.0] -// }, -// 76.04197508519536 -// )); -// } - -// #[test] -// fn tetens1() { -// assert!(tests_framework::test_with_1arg( -// &vapour_pressure::tetens1, -// Argument { -// name: "dewpoint", -// def_val: 300.0, -// range: [273.0, 353.0] -// }, -// 3533.969137160892 -// )); -// } - -// #[test] -// fn saturation_specific1() { -// assert!(tests_framework::test_with_2args( -// &vapour_pressure::saturation_specific1, -// Argument { -// name: "saturation_vapour_pressure", -// def_val: 3550.0, -// range: [0.0, 50_000.0] -// }, -// Argument { -// name: "relative_humidity", -// def_val: 0.5, -// range: [0.0, 2.0] -// }, -// 1775.0 -// )); -// } - -// #[test] -// fn saturation_specific2() { -// assert!(tests_framework::test_with_2args( -// &vapour_pressure::saturation_specific2, -// Argument { -// name: "vapour_pressure", -// def_val: 3000.0, -// range: [0.0, 10_000.0] -// }, -// Argument { -// name: "relative_humidity", -// def_val: 0.5, -// range: [0.00001, 2.0] -// }, -// 6000.0 -// )); -// } - -// #[test] -// fn wexler1() { -// assert!(tests_framework::test_with_1arg( -// &vapour_pressure::wexler1, -// Argument { -// name: "dewpoint", -// def_val: 300.0, -// range: [273.0, 374.0] -// }, -// 3535.4235919263083 -// )); -// } - -// #[test] -// fn wexler2() { -// assert!(tests_framework::test_with_1arg( -// &vapour_pressure::wexler2, -// Argument { -// name: "dewpoint", -// def_val: 250.0, -// range: [173.0, 274.0] -// }, -// 76.04351136780438 -// )); -// } -// } From 1914f6fbf55df332a8a935a4e924a8b7c5f361e7 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:41:53 +0100 Subject: [PATCH 021/102] rewrite mixing ration --- src/constants.rs | 7 + src/lib.rs | 2 +- src/mixing_ratio.rs | 249 ++++++++++++++++----------------- src/saturation_mixing_ratio.rs | 2 + src/tests.rs | 53 +------ 5 files changed, 140 insertions(+), 173 deletions(-) create mode 100644 src/saturation_mixing_ratio.rs diff --git a/src/constants.rs b/src/constants.rs index 0caed1d..e734974 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -4,6 +4,13 @@ use std::marker::PhantomData; use crate::Storage; +/// Gravitational acceleration of Earth +pub const G : Storage::Acceleration = Storage::Acceleration { + dimension: PhantomData, + units: PhantomData, + value: 9.806_65, +}; + /// Universal gas constant pub const R: Storage::MolarHeatCapacity = Storage::MolarHeatCapacity { dimension: PhantomData, diff --git a/src/lib.rs b/src/lib.rs index baa000f..1b9ecc6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,7 +86,7 @@ pub mod formula; pub mod quantities; // pub mod equivalent_potential_temperature; -// pub mod mixing_ratio; +pub mod mixing_ratio; // pub mod potential_temperature; // pub mod relative_humidity; // pub mod specific_humidity; diff --git a/src/mixing_ratio.rs b/src/mixing_ratio.rs index 9b0957b..8bc5ff1 100644 --- a/src/mixing_ratio.rs +++ b/src/mixing_ratio.rs @@ -1,45 +1,40 @@ -//!Functions to calculate mixing ratio of air in kg*kg^-1. -//! -//!To calculate saturation mixing ratio input dry-bulb temperature in place of dewpoint -//!or saturation vapour pressure in place of vapour pressure. - +//! Functions to calculate mixing ratio of water vapour in unsaturated air +use crate::formula::{Formula1, Formula2}; +use crate::quantities::{ + AtmosphericPressure, DewPointTemperature, MixingRatio, ThermodynamicQuantity, VapourPressure, +}; use crate::{constants::EPSILON, errors::InputError}; use crate::{vapour_pressure, Float}; use float_cmp::approx_eq; - -use itertools::izip; -use ndarray::{Array, Dimension, FoldWhile}; -use rayon::iter::{ParallelBridge, ParallelIterator}; - -///Formula for computing mixing ratio of unsaturated air from air pressure and vapour pressure +/// Formula for computing mixing ratio of unsaturated air from air pressure and vapour pressure /// -///# Errors +/// Valid `pressure` range: 100Pa - 150000Pa /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `pressure` range: 100Pa - 150000Pa\ -///Valid `vapour_pressure` range: 0Pa - 10000Pa +/// Valid `vapour_pressure` range: 0Pa - 10000Pa /// -///Returns [`InputError::IncorrectArgumentSet`] when inputs are equal, in which -///case division by 0 occurs. -pub struct General1; +/// Returns [`InputError::IncorrectArgumentSet`] when inputs are equal and division by 0 would occur. +pub struct Definition1; -impl General1 { - #[allow(missing_docs)] +impl Formula2 for Definition1 { #[inline(always)] - #[allow(clippy::missing_errors_doc)] - - pub fn validate_inputs(pressure: Float, vapour_pressure: Float) -> Result<(), InputError> { - if !(100.0..=150_000.0).contains(&pressure) { + fn validate_inputs( + pressure: AtmosphericPressure, + vapour_pressure: VapourPressure, + ) -> Result<(), InputError> { + let pressure_si = pressure.get_si_value(); + let vapour_pressure_si = vapour_pressure.get_si_value(); + + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } - if !(0.0..=50_000.0).contains(&vapour_pressure) { + if !(0.0..=50_000.0).contains(&vapour_pressure_si) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } - if approx_eq!(Float, pressure, vapour_pressure, ulps = 2) { + if approx_eq!(Float, pressure_si, vapour_pressure_si, ulps = 2) { return Err(InputError::IncorrectArgumentSet(String::from( "pressure and vapour_pressure cannot be equal", ))); @@ -48,35 +43,36 @@ impl General1 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(pressure: Float, vapour_pressure: Float) -> Float { - EPSILON * (vapour_pressure / (pressure - vapour_pressure)) + fn compute_unchecked( + pressure: AtmosphericPressure, + vapour_pressure: VapourPressure, + ) -> MixingRatio { + MixingRatio(EPSILON * (vapour_pressure.0 / (pressure.0 - vapour_pressure.0))) } } - -///Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. -///Optimised for performance. +/// Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. +/// Optimised for performance - uses [`Tetens1`]. /// -///# Errors +/// Valid `dewpoint` range: 273K - 353K /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `dewpoint` range: 273K - 353K\ -///Valid `pressure` range: 100Pa - 150000Pa +/// Valid `pressure` range: 100Pa - 150000Pa pub struct Performance1; -impl Performance1 { +impl Formula2 for Performance1 { #[inline(always)] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - - pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { - //validate inputs - if !(273.0..=353.0).contains(&dewpoint) { + fn validate_inputs( + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let dewpoint_si = dewpoint.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(273.0..=353.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } - if !(100.0..=150_000.0).contains(&pressure) { + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } @@ -84,108 +80,111 @@ impl Performance1 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(dewpoint: Float, pressure: Float) -> Float { + fn compute_unchecked( + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> MixingRatio { let vapour_pressure = vapour_pressure::Tetens1::compute_unchecked(dewpoint); - General1::compute_unchecked(pressure, vapour_pressure) + Definition1::compute_unchecked(pressure, vapour_pressure) } } - -///Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. -///Optimised for accuracy. +/// Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. +/// Optimised for accuracy - uses [`Buck1`]. /// -///# Errors +/// Valid `dewpoint` range: 232K - 324K /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `dewpoint` range: 232K - 324K\ -///Valid `pressure` range: 100Pa - 150000Pa +/// Valid `pressure` range: 100Pa - 150000Pa pub struct Accuracy1; -impl Accuracy1 { +impl Formula2 for Accuracy1 { #[inline(always)] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - - pub fn validate_inputs(dewpoint: Float, pressure: Float) -> Result<(), InputError> { - if !(232.0..=324.0).contains(&dewpoint) { + fn validate_inputs( + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let dewpoint_si = dewpoint.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(232.0..=324.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } - if !(100.0..=150_000.0).contains(&pressure) { + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } Ok(()) } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(dewpoint: Float, pressure: Float) -> Float { + fn compute_unchecked( + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> MixingRatio { let vapour_pressure = vapour_pressure::Buck1::compute_unchecked(dewpoint, pressure); - General1::compute_unchecked(pressure, vapour_pressure) + Definition1::compute_unchecked(pressure, vapour_pressure) } } -// #[cfg(test)] -// mod tests { -// use crate::{ -// mixing_ratio, -// tests_framework::{self, Argument}, -// }; - -// #[test] -// fn general1() { -// assert!(tests_framework::test_with_2args( -// &mixing_ratio::general1, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// Argument { -// name: "vapour_pressure", -// def_val: 3500.0, -// range: [0.0, 50_000.0] -// }, -// 0.022253316630823517 -// )); -// } - -// #[test] -// fn performance1() { -// assert!(tests_framework::test_with_2args( -// &mixing_ratio::performance1, -// Argument { -// name: "dewpoint", -// def_val: 300.0, -// range: [273.0, 353.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// 0.022477100514593465 -// )); -// } - -// #[test] -// fn accuracy1() { -// assert!(tests_framework::test_with_2args( -// &mixing_ratio::accuracy1, -// Argument { -// name: "dewpoint", -// def_val: 300.0, -// range: [232.0, 324.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// 0.022587116896465847 -// )); -// } -// } +#[cfg(test)] +mod tests { + use crate::{ + quantities::MixingRatio, + tests::{test_with_2args, Argument}, + }; + + use super::*; + + #[test] + fn general1() { + test_with_2args::( + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + Argument { + name: "vapour_pressure", + def_val: 3500.0, + range: [0.0, 50_000.0], + }, + 0.022253316630823517, + ); + } + + #[test] + fn performance1() { + test_with_2args::( + Argument { + name: "dewpoint", + def_val: 300.0, + range: [273.0, 353.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 0.022477100514593465, + ); + } + + #[test] + fn accuracy1() { + test_with_2args::( + Argument { + name: "dewpoint", + def_val: 300.0, + range: [232.0, 324.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 0.022587116896465847, + ); + } +} diff --git a/src/saturation_mixing_ratio.rs b/src/saturation_mixing_ratio.rs new file mode 100644 index 0000000..431801a --- /dev/null +++ b/src/saturation_mixing_ratio.rs @@ -0,0 +1,2 @@ +//! To calculate saturation mixing ratio input dry-bulb temperature in place of dewpoint +//! or saturation vapour pressure in place of vapour pressure.s \ No newline at end of file diff --git a/src/tests.rs b/src/tests.rs index 5401c7a..5932e15 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -69,9 +69,9 @@ pub fn test_with_2args< match result { Ok(r) => assert!(r.get_si_value().is_finite()), - Err(e) => assert!( - discriminant(&InputError::IncorrectArgumentSet(String::new())) - == discriminant(&e) + Err(e) => assert_eq!( + discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&e) ), } } @@ -137,8 +137,9 @@ pub fn test_with_1arg assert!(r.get_si_value().is_finite()), - Err(e) => assert!( - discriminant(&InputError::IncorrectArgumentSet(String::new())) == discriminant(&e) + Err(e) => assert_eq!( + discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&e) ), } } @@ -152,48 +153,6 @@ pub fn test_with_1arg Result, -// arg1: Argument, -// expected_result: Float, -// ) -> bool { -// let result = tested_function(arg1.def_val).unwrap(); -// assert_approx_eq!(Float, result, expected_result, epsilon = 0.01); - -// let results = vec![tested_function(0.0)]; - -// for result in results { -// if result.is_ok() { -// assert!(result.unwrap().is_finite()); -// } -// } - -// for arg1_itr in 0..=100 { -// let arg1_tmp = -// (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; - -// let result = tested_function(arg1_tmp); - -// if result.is_err() { -// assert!( -// discriminant(&InputError::IncorrectArgumentSet(String::new())) -// == discriminant(&result.unwrap_err()) -// ); -// } else { -// assert!(result.unwrap().is_finite()); -// } -// } - -// let expected = InputError::OutOfRange(String::from(arg1.name)); -// let result = tested_function(arg1.range[0] - 0.1).unwrap_err(); -// assert_eq!(result, expected); -// let result = tested_function(arg1.range[1] + 0.1).unwrap_err(); -// assert_eq!(result, expected); - -// true -// } - // #[allow(dead_code)] // pub fn test_with_3args( // tested_function: &dyn Fn(Float, Float, Float) -> Result, From 150ad65ded2031cb7ffff0a16912b3b827978685 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:48:05 +0100 Subject: [PATCH 022/102] rewrite wet bulb --- src/lib.rs | 2 +- src/wet_bulb_temperature.rs | 109 ++++++++++++++++++------------------ 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1b9ecc6..f3f11fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,7 +94,7 @@ pub mod vapour_pressure; // pub mod vapour_pressure_deficit; // pub mod virtual_temperature; // pub mod wet_bulb_potential_temperature; -// pub mod wet_bulb_temperature; +pub mod wet_bulb_temperature; #[cfg(test)] mod tests; diff --git a/src/wet_bulb_temperature.rs b/src/wet_bulb_temperature.rs index 833ada7..a981307 100644 --- a/src/wet_bulb_temperature.rs +++ b/src/wet_bulb_temperature.rs @@ -1,49 +1,53 @@ -//!Functions to calculate wet bulb temperature of unsaturated air in K. +//! Functions to calculate wet bulb temperature of unsaturated air +use uom::si::ratio::percent; +use uom::si::thermodynamic_temperature::degree_celsius; -use crate::Float; -use crate::{constants::ZERO_CELSIUS, errors::InputError}; +use crate::errors::InputError; +use crate::formula::Formula2; +use crate::quantities::{ + DryBulbTemperature, RelativeHumidity, ThermodynamicQuantity, WetBulbTemperature, +}; +use crate::Storage; - -use itertools::izip; -use ndarray::{Array, Dimension, FoldWhile}; -use rayon::iter::{ParallelBridge, ParallelIterator}; - -///Formula for computing wet bulb temperature pressure from dry bulb temperature and relative humidity. +/// Formula for computing wet bulb temperature pressure from dry bulb temperature and relative humidity. /// -///Derived by R. Stull (2011) [(doi:10.1175/JAMC-D-11-0143.1)](https://doi.org/10.1175/JAMC-D-11-0143.1) -///Created with use of gene-expression programming.\ -///Result error is within −1K to +0.65K, with mean absolute error of 0.28K +/// Derived by R. Stull (2011) [(doi:10.1175/JAMC-D-11-0143.1)](https://doi.org/10.1175/JAMC-D-11-0143.1) +/// Created with use of gene-expression programming. /// -///# Errors +/// Result error is within -1K to +0.65K, with mean absolute error of 0.28K /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `temperature` range: 253K - 324K\ -///Valid `relative_humidity` range: 0.05 - 0.99 +/// Valid `temperature` range: 253K - 324K + +/// Valid `relative_humidity` range: 0.05 - 0.99 pub struct Stull1; -impl Stull1 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] +impl Formula2 for Stull1 { #[inline(always)] - - pub fn validate_inputs(temperature: Float, relative_humidity: Float) -> Result<(), InputError> { - if !(253.0..=324.0).contains(&temperature) { + fn validate_inputs( + temperature: DryBulbTemperature, + relative_humidity: RelativeHumidity, + ) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + let relative_humidity_si = relative_humidity.get_si_value(); + + if !(253.0..=324.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(0.05..=0.99).contains(&relative_humidity) { + if !(0.05..=0.99).contains(&relative_humidity_si) { return Err(InputError::OutOfRange(String::from("relative_humidity"))); } Ok(()) } - #[allow(missing_docs)] #[inline(always)] - pub fn compute_unchecked(temperature: Float, relative_humidity: Float) -> Float { - //convert units - let temperature = temperature - ZERO_CELSIUS; - let relative_humidity = relative_humidity * 100.0; + fn compute_unchecked( + temperature: DryBulbTemperature, + relative_humidity: RelativeHumidity, + ) -> WetBulbTemperature { + let temperature = temperature.0.get::(); + let relative_humidity = relative_humidity.0.get::(); let result = (temperature * (0.151_977 * (relative_humidity + 8.313_659).sqrt()).atan()) + (temperature + relative_humidity).atan() @@ -51,33 +55,32 @@ impl Stull1 { + (0.003_918_38 * relative_humidity.powf(1.5) * (0.023_101 * relative_humidity).atan()) - 4.686_035; - result + ZERO_CELSIUS + let result = Storage::ThermodynamicTemperature::new::(result); + + WetBulbTemperature(result) } } +#[cfg(test)] +mod tests { + use crate::tests::{test_with_2args, Argument}; -// #[cfg(test)] -// mod tests { -// use crate::{ -// tests_framework::{self, Argument}, -// wet_bulb_temperature, -// }; + use super::*; -// #[test] -// fn stull1() { -// assert!(tests_framework::test_with_2args( -// &wet_bulb_temperature::stull1, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [253.0, 324.0] -// }, -// Argument { -// name: "relative_humidity", -// def_val: 0.5, -// range: [0.05, 0.99] -// }, -// 292.73867410526674 -// )); -// } -// } + #[test] + fn stull1() { + test_with_2args::( + Argument { + name: "temperature", + def_val: 300.0, + range: [253.0, 324.0], + }, + Argument { + name: "relative_humidity", + def_val: 0.5, + range: [0.05, 0.99], + }, + 292.73867410526674, + ); + } +} From bb4b4c61f1953e8e67f785ddd51515dc29ff037c Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 15:06:49 +0100 Subject: [PATCH 023/102] rewrite virtual temperature --- src/constants.rs | 17 ++- src/lib.rs | 4 +- src/tests.rs | 233 ++++++++++++++++++++--------- src/vapour_pressure.rs | 10 +- src/virtual_temperature.rs | 291 ++++++++++++++++++++----------------- 5 files changed, 344 insertions(+), 211 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index e734974..41de076 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -5,7 +5,7 @@ use std::marker::PhantomData; use crate::Storage; /// Gravitational acceleration of Earth -pub const G : Storage::Acceleration = Storage::Acceleration { +pub const G: Storage::Acceleration = Storage::Acceleration { dimension: PhantomData, units: PhantomData, value: 9.806_65, @@ -101,3 +101,18 @@ pub const R_V: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { units: PhantomData, value: R.value / M_V.value, }; + +// Internal Constants (commonly appearing in formulas to use them with oum units) + +pub(crate) const DIMLESS_ONE: Storage::Ratio = Storage::Ratio { + dimension: PhantomData, + units: PhantomData, + value: 1.0, +}; + +pub(crate) const ZERO_KELVIN: Storage::ThermodynamicTemperature = + Storage::ThermodynamicTemperature { + dimension: PhantomData, + units: PhantomData, + value: 0.0, + }; diff --git a/src/lib.rs b/src/lib.rs index f3f11fc..ac6ee1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,12 +87,14 @@ pub mod quantities; // pub mod equivalent_potential_temperature; pub mod mixing_ratio; +// pub mod saturation_mixing_ratio; // pub mod potential_temperature; // pub mod relative_humidity; // pub mod specific_humidity; pub mod vapour_pressure; +// pub mod saturation_vapour_pressure; // pub mod vapour_pressure_deficit; -// pub mod virtual_temperature; +pub mod virtual_temperature; // pub mod wet_bulb_potential_temperature; pub mod wet_bulb_temperature; diff --git a/src/tests.rs b/src/tests.rs index 5932e15..94d1bb4 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,5 @@ use crate::errors::InputError; -use crate::formula::{Formula1, Formula2}; +use crate::formula::{Formula1, Formula2, Formula3}; use crate::quantities::ThermodynamicQuantity; use crate::Float; use float_cmp::assert_approx_eq; @@ -153,71 +153,166 @@ pub fn test_with_1arg Result, -// arg1: Argument, -// arg2: Argument, -// arg3: Argument, -// expected_result: Float, -// ) -> bool { -// let result = tested_function(arg1.def_val, arg2.def_val, arg3.def_val).unwrap(); -// assert_approx_eq!(Float, result, expected_result, epsilon = 0.01); - -// let results = vec![ -// tested_function(0.0, arg2.def_val, arg3.def_val), -// tested_function(arg1.def_val, 0.0, arg3.def_val), -// tested_function(arg1.def_val, arg2.def_val, 0.0), -// tested_function(0.0, 0.0, 0.0), -// ]; - -// for result in results { -// if result.is_ok() { -// assert!(result.unwrap().is_finite()); -// } -// } - -// for arg1_itr in 0..=100 { -// for arg2_itr in 0..=100 { -// for arg3_itr in 0..=100 { -// let arg1_tmp = -// (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; -// let arg2_tmp = -// (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; -// let arg3_tmp = -// (((arg3.range[1] - arg3.range[0]) / 100.0) * arg3_itr as Float) + arg3.range[0]; - -// let result = tested_function(arg1_tmp, arg2_tmp, arg3_tmp); - -// if result.is_err() { -// assert!( -// discriminant(&InputError::IncorrectArgumentSet(String::new())) -// == discriminant(&result.unwrap_err()) -// ); -// } else { -// assert!(result.unwrap().is_finite()); -// } -// } -// } -// } - -// let expected = InputError::OutOfRange(String::from(arg1.name)); -// let result = tested_function(arg1.range[0] - 0.1, arg2.def_val, arg3.def_val).unwrap_err(); -// assert_eq!(result, expected); -// let result = tested_function(arg1.range[1] + 0.1, arg2.def_val, arg3.def_val).unwrap_err(); -// assert_eq!(result, expected); - -// let expected = InputError::OutOfRange(String::from(arg2.name)); -// let result = tested_function(arg1.def_val, arg2.range[0] - 0.1, arg3.def_val).unwrap_err(); -// assert_eq!(result, expected); -// let result = tested_function(arg1.def_val, arg2.range[1] + 0.1, arg3.def_val).unwrap_err(); -// assert_eq!(result, expected); - -// let expected = InputError::OutOfRange(String::from(arg3.name)); -// let result = tested_function(arg1.def_val, arg2.def_val, arg3.range[0] - 0.1).unwrap_err(); -// assert_eq!(result, expected); -// let result = tested_function(arg1.def_val, arg2.def_val, arg3.range[1] + 0.1).unwrap_err(); -// assert_eq!(result, expected); - -// true -// } +pub fn test_with_3args< + O: ThermodynamicQuantity, + I1: ThermodynamicQuantity, + I2: ThermodynamicQuantity, + I3: ThermodynamicQuantity, + F: Formula3, +>( + arg1: Argument, + arg2: Argument, + arg3: Argument, + expected_result: Float, +) { + //the first promise of the crate is that returned value + //is calculated correctly + let result = F::compute( + I1::new_si(arg1.def_val), + I2::new_si(arg2.def_val), + I3::new_si(arg3.def_val), + ) + .unwrap(); + assert_approx_eq!( + Float, + result.get_si_value(), + expected_result, + epsilon = 0.01 + ); + + // the second promise of the crate is to never return NaN or Inf + // here we check several edge cases for that + // the function can only return a finite number or InputError + // check for error implicit as Result<> ensures that if value + // is not Ok() then it is Err() + // here we don't care about error being correct + let results = vec![ + F::compute( + I1::new_si(arg1.def_val), + I2::new_si(arg2.def_val), + I3::new_si(arg3.def_val), + ), + F::compute( + I1::new_si(-9999.0), + I2::new_si(arg2.def_val), + I3::new_si(arg3.def_val), + ), + F::compute( + I1::new_si(arg1.def_val), + I2::new_si(-9999.0), + I3::new_si(arg3.def_val), + ), + F::compute( + I1::new_si(arg1.def_val), + I2::new_si(arg2.def_val), + I3::new_si(-9999.0), + ), + F::compute( + I1::new_si(-9999.0), + I2::new_si(-9999.0), + I3::new_si(arg3.def_val), + ), + F::compute( + I1::new_si(arg1.def_val), + I2::new_si(-9999.0), + I3::new_si(-9999.0), + ), + F::compute( + I1::new_si(-9999.0), + I2::new_si(arg2.def_val), + I3::new_si(-9999.0), + ), + F::compute( + I1::new_si(-9999.0), + I2::new_si(-9999.0), + I3::new_si(-9999.0), + ), + ]; + + for result in results { + if let Ok(result) = result { + assert!(result.get_si_value().is_finite()); + } + } + + //the third promise of the crate is to always return finite f64 + //if all inputs are within the range + //the only allowed error is InccorectArgumentsSet as it can occur + //for values within valid range + for arg1_itr in 0..=100 { + for arg2_itr in 0..=100 { + for arg3_itr in 0..=100 { + let arg1_tmp = + (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; + let arg2_tmp = + (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; + let arg3_tmp = + (((arg3.range[1] - arg3.range[0]) / 100.0) * arg3_itr as Float) + arg3.range[0]; + + let arg1_tmp = I1::new_si(arg1_tmp); + let arg2_tmp = I2::new_si(arg2_tmp); + let arg3_tmp = I3::new_si(arg3_tmp); + + let result = F::compute(arg1_tmp, arg2_tmp, arg3_tmp); + + match result { + Ok(r) => assert!(r.get_si_value().is_finite()), + Err(e) => assert_eq!( + discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&e) + ), + } + } + } + } + + //the fourth promise of the crate is to return an error with + //erronous variable name when input is out of range + let expected = InputError::OutOfRange(String::from(arg1.name)); + let result = F::compute( + I1::new_si(arg1.range[0] - 0.1), + I2::new_si(arg2.def_val), + I3::new_si(arg3.def_val), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + I1::new_si(arg1.range[1] + 0.1), + I2::new_si(arg2.def_val), + I3::new_si(arg3.def_val), + ) + .unwrap_err(); + assert_eq!(result, expected); + + let expected = InputError::OutOfRange(String::from(arg2.name)); + let result = F::compute( + I1::new_si(arg1.def_val), + I2::new_si(arg2.range[0] - 0.1), + I3::new_si(arg3.def_val), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + I1::new_si(arg1.def_val), + I2::new_si(arg2.range[1] + 0.1), + I3::new_si(arg3.def_val), + ) + .unwrap_err(); + assert_eq!(result, expected); + + let expected = InputError::OutOfRange(String::from(arg3.name)); + let result = F::compute( + I1::new_si(arg1.def_val), + I2::new_si(arg3.def_val), + I3::new_si(arg3.range[0] - 0.1), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + I1::new_si(arg1.def_val), + I2::new_si(arg3.def_val), + I3::new_si(arg3.range[1] + 0.1), + ) + .unwrap_err(); + assert_eq!(result, expected); +} diff --git a/src/vapour_pressure.rs b/src/vapour_pressure.rs index ba2555e..8435e9f 100644 --- a/src/vapour_pressure.rs +++ b/src/vapour_pressure.rs @@ -4,17 +4,17 @@ //! Formulae to calculate partial vapour pressure of the unsaturated air. +use crate::constants::DIMLESS_ONE; use crate::formula::{Formula1, Formula2}; use crate::quantities::{ AtmosphericPressure, DewPointTemperature, RelativeHumidity, SaturationVapourPressure, SpecificHumidity, ThermodynamicQuantity, VapourPressure, }; use crate::Float; -use crate::Storage::{Pressure, Ratio}; +use crate::Storage::Pressure; use crate::{constants::EPSILON, errors::InputError}; use uom::si::pressure::{hectopascal, kilopascal, pascal}; -use uom::si::ratio::ratio; use uom::si::thermodynamic_temperature::{degree_celsius, kelvin}; /// Formula for computing vapour pressure from specific humidity and pressure. @@ -55,10 +55,8 @@ impl Formula2 for Definit let specific_humidity = specific_humidity.0; let pressure = pressure.0; - let one = Ratio::new::(1.0); - - let result = - -((pressure * specific_humidity) / ((specific_humidity * (EPSILON - one)) - EPSILON)); + let result = -((pressure * specific_humidity) + / ((specific_humidity * (EPSILON - DIMLESS_ONE)) - EPSILON)); VapourPressure(result) } diff --git a/src/virtual_temperature.rs b/src/virtual_temperature.rs index 2cfea81..c1368c7 100644 --- a/src/virtual_temperature.rs +++ b/src/virtual_temperature.rs @@ -1,38 +1,38 @@ -//!Functions to calculate virtual temperature of air in K. +//! Functions to calculate virtual temperature of air //! -//!In atmospheric thermodynamics, the virtual temperature of a moist air parcel is the temperature -//!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::Float; -use crate::{constants::EPSILON, errors::InputError}; - - -use itertools::izip; -use ndarray::{Array, Dimension, FoldWhile}; -use rayon::iter::{ParallelBridge, ParallelIterator}; - -///Formula for computing virtual temperature from temperature and mixing ratio. +//! In atmospheric thermodynamics, the virtual temperature of a moist air parcel is the temperature +//! 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::{DIMLESS_ONE, EPSILON, ZERO_KELVIN}; +use crate::errors::InputError; +use crate::formula::{Formula2, Formula3}; +use crate::quantities::{ + AtmosphericPressure, DryBulbTemperature, MixingRatio, SpecificHumidity, ThermodynamicQuantity, + VapourPressure, VirtualTemperature, +}; + +/// Formula for computing virtual temperature from temperature and mixing ratio. /// -///# Errors +/// Valid `temperature` range: 173K - 373K /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `temperature` range: 173K - 373K\ -///Valid `mixing_ratio` range: 0.0000000001 - 0.5 -pub struct General1; +/// Valid `mixing_ratio` range: 0.0000000001 - 0.5 +pub struct Definition1; -impl General1 { - #[allow(missing_docs)] +impl Formula2 for Definition1 { #[inline(always)] - #[allow(clippy::missing_errors_doc)] - - pub fn validate_inputs(temperature: Float, mixing_ratio: Float) -> Result<(), InputError> { - if !(173.0..=354.0).contains(&temperature) { + fn validate_inputs( + temperature: DryBulbTemperature, + mixing_ratio: MixingRatio, + ) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + let mixing_ratio_si = mixing_ratio.get_si_value(); + + if !(173.0..=354.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(0.000_000_000_1..=0.5).contains(&mixing_ratio) { + if !(0.000_000_000_1..=0.5).contains(&mixing_ratio_si) { return Err(InputError::OutOfRange(String::from("mixing_ratio"))); } @@ -40,75 +40,91 @@ impl General1 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(temperature: Float, mixing_ratio: Float) -> Float { - temperature * ((mixing_ratio + EPSILON) / (EPSILON * (1.0 + mixing_ratio))) + fn compute_unchecked( + temperature: DryBulbTemperature, + mixing_ratio: MixingRatio, + ) -> VirtualTemperature { + let result = temperature.0 + * ((mixing_ratio.0 + EPSILON) / (EPSILON * (DIMLESS_ONE + mixing_ratio.0))); + + // this is necessary because result is TemperatureInterval + let result = ZERO_KELVIN + result; + + VirtualTemperature(result) } } - -///Formula for computing virtual temperature from air temperature, pressure and vapour pressure. +/// Formula for computing virtual temperature from air temperature, pressure and vapour pressure. +/// +/// Valid `temperature` range: 173K - 373K /// -///# Errors +/// Valid `pressure` range: 100Pa - 150000Pa /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `temperature` range: 173K - 373K\ -///Valid `pressure` range: 100Pa - 150000Pa\ -///Valid `vapour_pressure` range: 0Pa - 10000Pa -pub struct General2; - -impl General2 { - #[allow(missing_docs)] +/// Valid `vapour_pressure` range: 0Pa - 10000Pa +pub struct Definition2; + +impl Formula3 + for Definition2 +{ #[inline(always)] - #[allow(clippy::missing_errors_doc)] - - pub fn validate_inputs( - temperature: Float, - pressure: Float, - vapour_pressure: Float, + fn validate_inputs( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + vapour_pressure: VapourPressure, ) -> Result<(), InputError> { - if !(173.0..=354.0).contains(&temperature) { + let temperature_si = temperature.get_si_value(); + let pressure_si = pressure.get_si_value(); + let vapour_pressure_si = vapour_pressure.get_si_value(); + + if !(173.0..=354.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(100.0..=150_000.0).contains(&pressure) { + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } - if !(0.0..=10_000.0).contains(&vapour_pressure) { + if !(0.0..=10_000.0).contains(&vapour_pressure_si) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } Ok(()) } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { - temperature / (1.0 - ((vapour_pressure / pressure) * (1.0 - EPSILON))) + fn compute_unchecked( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + vapour_pressure: VapourPressure, + ) -> VirtualTemperature { + let result = temperature.0 + / (DIMLESS_ONE - ((vapour_pressure.0 / pressure.0) * (DIMLESS_ONE - EPSILON))); + let result = ZERO_KELVIN + result; + + VirtualTemperature(result) } } - ///Formula for computing virtual temperature from air temperature and specific humidity. /// -///# Errors +///Valid `temperature` range: 173K - 373K /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `temperature` range: 173K - 373K\ ///Valid `specific_humidity` range: 100Pa - 150000Pa -pub struct General3; +pub struct Definition3; -impl General3 { - #[allow(missing_docs)] +impl Formula2 for Definition3 { #[inline(always)] - #[allow(clippy::missing_errors_doc)] - - pub fn validate_inputs(temperature: Float, specific_humidity: Float) -> Result<(), InputError> { - if !(173.0..=354.0).contains(&temperature) { + fn validate_inputs( + temperature: DryBulbTemperature, + specific_humidity: SpecificHumidity, + ) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + let specific_humidity_si = specific_humidity.get_si_value(); + + if !(173.0..=354.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(0.000_000_001..=2.0).contains(&specific_humidity) { + if !(0.000_000_001..=2.0).contains(&specific_humidity_si) { return Err(InputError::OutOfRange(String::from("specific_humidity"))); } @@ -116,76 +132,83 @@ impl General3 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(temperature: Float, specific_humidity: Float) -> Float { - temperature * (1.0 + (specific_humidity * ((1.0 / EPSILON) - 1.0))) + fn compute_unchecked( + temperature: DryBulbTemperature, + specific_humidity: SpecificHumidity, + ) -> VirtualTemperature { + let result = temperature.0 + * (DIMLESS_ONE + (specific_humidity.0 * ((DIMLESS_ONE / EPSILON) - DIMLESS_ONE))); + let result = ZERO_KELVIN + result; + + VirtualTemperature(result) } } +#[cfg(test)] +mod tests { + use crate::tests::{test_with_2args, test_with_3args, Argument}; + + use super::*; + + #[test] + fn definition1() { + test_with_2args::( + Argument { + name: "temperature", + def_val: 300.0, + range: [173.0, 354.0], + }, + Argument { + name: "mixing_ratio", + def_val: 0.022, + range: [0.000_000_000_1, 0.5], + }, + 303.9249219815806, + ); + } + + #[test] + fn definition2() { + test_with_3args::< + VirtualTemperature, + DryBulbTemperature, + AtmosphericPressure, + VapourPressure, + Definition2, + >( + Argument { + name: "temperature", + def_val: 300.0, + range: [173.0, 354.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + Argument { + name: "vapour_pressure", + def_val: 3550.0, + range: [0.0, 10_000.0], + }, + 304.0265941965307, + ); + } -// #[cfg(test)] -// mod tests { -// use crate::{ -// tests_framework::{self, Argument}, -// virtual_temperature, -// }; - -// #[test] -// fn general1() { -// assert!(tests_framework::test_with_2args( -// &virtual_temperature::general1, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [173.0, 354.0] -// }, -// Argument { -// name: "mixing_ratio", -// def_val: 0.022, -// range: [0.000_000_000_1, 0.5] -// }, -// 303.9249219815806 -// )); -// } - -// #[test] -// fn general2() { -// assert!(tests_framework::test_with_3args( -// &virtual_temperature::general2, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [173.0, 354.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// Argument { -// name: "vapour_pressure", -// def_val: 3550.0, -// range: [0.0, 10_000.0] -// }, -// 304.0265941965307 -// )); -// } - -// #[test] -// fn general3() { -// assert!(tests_framework::test_with_2args( -// &virtual_temperature::general3, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [173.0, 354.0] -// }, -// Argument { -// name: "specific_humidity", -// def_val: 0.022, -// range: [0.000000001, 2.0] -// }, -// 304.0112702651753 -// )); -// } -// } + #[test] + fn definition3() { + test_with_2args::( + Argument { + name: "temperature", + def_val: 300.0, + range: [173.0, 354.0], + }, + Argument { + name: "specific_humidity", + def_val: 0.022, + range: [0.000000001, 2.0], + }, + 304.0112702651753, + ); + } +} From e12ca316a1ba3d9abe5c215d492060d81740dc8b Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 15:15:04 +0100 Subject: [PATCH 024/102] rewrite specific humdiity --- src/lib.rs | 2 +- src/mixing_ratio.rs | 3 ++ src/specific_humidity.rs | 109 ++++++++++++++++++++------------------- 3 files changed, 59 insertions(+), 55 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ac6ee1d..7a6b1bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,7 @@ pub mod mixing_ratio; // pub mod saturation_mixing_ratio; // pub mod potential_temperature; // pub mod relative_humidity; -// pub mod specific_humidity; +pub mod specific_humidity; pub mod vapour_pressure; // pub mod saturation_vapour_pressure; // pub mod vapour_pressure_deficit; diff --git a/src/mixing_ratio.rs b/src/mixing_ratio.rs index 8bc5ff1..14e58cc 100644 --- a/src/mixing_ratio.rs +++ b/src/mixing_ratio.rs @@ -1,4 +1,7 @@ //! Functions to calculate mixing ratio of water vapour in unsaturated air +//! +//! Mixing ratio is the ratio of the mass of a variable atmospheric constituent to the mass +//! of dry air ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Mixing_ratio)). use crate::formula::{Formula1, Formula2}; use crate::quantities::{ diff --git a/src/specific_humidity.rs b/src/specific_humidity.rs index 5a61c18..f9928d5 100644 --- a/src/specific_humidity.rs +++ b/src/specific_humidity.rs @@ -1,79 +1,80 @@ -//!Functions to calculate specific humidity of air in kg*kg^-1. +//! Functions to calculate specific humidity of air //! -//!Specific humidity (or moisture content) is the ratio of the mass -//!of water vapor to the total mass of the air parcel [Wikipedia](https://en.wikipedia.org/wiki/Humidity#Specific_humidity). +//! Specific humidity (or moisture content) is the ratio of the mass +//! of water vapor to the total mass of the air parcel [Wikipedia](https://en.wikipedia.org/wiki/Humidity#Specific_humidity). //! -//!Specific humidity is approximately equal to mixing ratio. +//! Specific humidity is approximately equal to mixing ratio. - -use crate::Float; +use crate::constants::DIMLESS_ONE; +use crate::formula::Formula2; +use crate::quantities::{ + AtmosphericPressure, SpecificHumidity, ThermodynamicQuantity, VapourPressure, +}; use crate::{constants::EPSILON, errors::InputError}; - -use itertools::izip; -use ndarray::{Array, Dimension, FoldWhile}; -use rayon::iter::{ParallelBridge, ParallelIterator}; - -///Formula for computing specific humidity from vapour pressure and pressure. -///Reverse function of [`vapour_pressure::general1`](crate::vapour_pressure::general1). -///This function is theoretical not empirical. +/// Formula for computing specific humidity from vapour pressure and pressure. +/// Reverse function of [`vapour_pressure::general1`](crate::vapour_pressure::general1). +/// This function is theoretical not empirical. /// -///Provided by [Rogers & Yau (1989)](https://www.elsevier.com/books/a-short-course-in-cloud-physics/yau/978-0-08-057094-5). +/// Provided by [Rogers & Yau (1989)](https://www.elsevier.com/books/a-short-course-in-cloud-physics/yau/978-0-08-057094-5). /// -///# Errors +/// Valid `vapour_pressure` range: 0Pa - 50000OPa /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `vapour_pressure` range: 0Pa - 50000OPa\, -///Valid `pressure` range: 100Pa - 150000Pa -pub struct General1; +/// Valid `pressure` range: 100Pa - 150000Pa +pub struct Definition1; -impl General1 { - #[allow(missing_docs)] +impl Formula2 for Definition1 { #[inline(always)] - #[allow(clippy::missing_errors_doc)] - - pub fn validate_inputs(vapour_pressure: Float, pressure: Float) -> Result<(), InputError> { - if !(0.0..=50_000.0).contains(&vapour_pressure) { + fn validate_inputs( + vapour_pressure: VapourPressure, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let vapour_pressure_si = vapour_pressure.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(0.0..=50_000.0).contains(&vapour_pressure_si) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } - if !(100.0..=150_000.0).contains(&pressure) { + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } Ok(()) } - #[allow(missing_docs)] #[inline(always)] - pub fn compute_unchecked(vapour_pressure: Float, pressure: Float) -> Float { - EPSILON * (vapour_pressure / (pressure - (vapour_pressure * (1.0 - EPSILON)))) + fn compute_unchecked( + vapour_pressure: VapourPressure, + pressure: AtmosphericPressure, + ) -> SpecificHumidity { + let result = EPSILON + * (vapour_pressure.0 / (pressure.0 - (vapour_pressure.0 * (DIMLESS_ONE - EPSILON)))); + + SpecificHumidity(result) } } +#[cfg(test)] +mod tests { + use crate::tests::{test_with_2args, Argument}; -// #[cfg(test)] -// mod tests { -// use crate::{ -// specific_humidity, -// tests_framework::{self, Argument}, -// }; + use super::*; -// #[test] -// fn general1() { -// assert!(tests_framework::test_with_2args( -// &specific_humidity::general1, -// Argument { -// name: "vapour_pressure", -// def_val: 3000.0, -// range: [0.0, 50_000.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// 0.018623845512674677 -// )); -// } -// } + #[test] + fn definition1() { + test_with_2args::( + Argument { + name: "vapour_pressure", + def_val: 3000.0, + range: [0.0, 50_000.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 0.018623845512674677, + ); + } +} From 1249f7c8e43d64e22f028d1c2fbe90460c0d0f70 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 15:24:12 +0100 Subject: [PATCH 025/102] rewrite theta w --- src/lib.rs | 2 +- src/wet_bulb_potential_temperature.rs | 87 +++++++++++++++------------ 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7a6b1bc..0daa044 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,7 +95,7 @@ pub mod vapour_pressure; // pub mod saturation_vapour_pressure; // pub mod vapour_pressure_deficit; pub mod virtual_temperature; -// pub mod wet_bulb_potential_temperature; +pub mod wet_bulb_potential_temperature; pub mod wet_bulb_temperature; #[cfg(test)] diff --git a/src/wet_bulb_potential_temperature.rs b/src/wet_bulb_potential_temperature.rs index cdbf2a6..ce08f7f 100644 --- a/src/wet_bulb_potential_temperature.rs +++ b/src/wet_bulb_potential_temperature.rs @@ -1,16 +1,18 @@ //!Functions to calculate wet bulb potential temperature of unsaturated air in K. - -use crate::Float; +use uom::si::ratio::ratio; +use uom::si::thermodynamic_temperature::{degree_celsius, kelvin}; + +use crate::formula::Formula1; +use crate::quantities::{ + EquivalentPotentialTemperature, ThermodynamicQuantity, WetBulbPotentialTemperature, +}; +use crate::Storage; use crate::{ - constants::{C_P, R_D, ZERO_CELSIUS}, + constants::{C_P, R_D}, errors::InputError, }; - -use ndarray::{Array, Dimension, FoldWhile}; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; - /// Formula for computing wet bulb potential temperature from equivalent potential temperature. /// /// Derived by R. Davies-Jones (2008) [(doi:10.1175/2007MWR2224.1)](https://doi.org/10.1175/2007MWR2224.1) @@ -21,22 +23,14 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; /// Valid `temperature` range: 257K - 377K\ pub struct DaviesJones1; -impl DaviesJones1 { - #[allow(missing_docs)] +impl Formula1 for DaviesJones1 { #[inline(always)] - pub fn compute_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); - result + ZERO_CELSIUS - } + fn validate_inputs( + equivalent_potential_temperature: EquivalentPotentialTemperature, + ) -> Result<(), InputError> { + let equivalent_potential_temperature_si = equivalent_potential_temperature.get_si_value(); - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - #[inline(always)] - - pub fn validate_inputs(equivalent_potential_temperature: Float) -> Result<(), InputError> { - if !(257.0..=377.0).contains(&equivalent_potential_temperature) { + if !(257.0..=377.0).contains(&equivalent_potential_temperature_si) { return Err(InputError::OutOfRange(String::from( "equivalent_potential_temperature", ))); @@ -44,26 +38,39 @@ impl DaviesJones1 { Ok(()) } + + #[inline(always)] + fn compute_unchecked( + equivalent_potential_temperature: EquivalentPotentialTemperature, + ) -> WetBulbPotentialTemperature { + let lambda = (C_P / R_D).get::(); + let equivalent_potential_temperature = equivalent_potential_temperature.0.get::(); + let result = 45.114 - 51.489 * (273.15 / equivalent_potential_temperature).powf(lambda); + + let result = Storage::ThermodynamicTemperature::new::(result); + + WetBulbPotentialTemperature(result) + } } +#[cfg(test)] +mod tests { + use crate::{ + quantities::{EquivalentPotentialTemperature, WetBulbPotentialTemperature}, + tests::{test_with_1arg, Argument}, + }; -// #[cfg(test)] -// mod tests { -// use crate::{ -// tests_framework::{self, Argument}, -// wet_bulb_potential_temperature, -// }; + use super::DaviesJones1; -// #[test] -// fn davies_jones1() { -// assert!(tests_framework::test_with_1arg( -// &wet_bulb_potential_temperature::DaviesJones1::compute, -// Argument { -// name: "equivalent_potential_temperature", -// def_val: 300.0, -// range: [257.0, 377.0] -// }, -// 281.17941447108467 -// )); -// } -// } + #[test] + fn davies_jones1() { + test_with_1arg::( + Argument { + name: "equivalent_potential_temperature", + def_val: 300.0, + range: [257.0, 377.0], + }, + 281.17941447108467, + ); + } +} From 04b75f7c63b0d66052ce45ba0f4766464f93ec3a Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 17:22:25 +0100 Subject: [PATCH 026/102] rewrite potential temperature --- src/lib.rs | 2 +- src/potential_temperature.rs | 155 +++++++++++++++----------- src/wet_bulb_potential_temperature.rs | 5 +- 3 files changed, 90 insertions(+), 72 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0daa044..dc565e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,7 @@ pub mod quantities; // pub mod equivalent_potential_temperature; pub mod mixing_ratio; // pub mod saturation_mixing_ratio; -// pub mod potential_temperature; +pub mod potential_temperature; // pub mod relative_humidity; pub mod specific_humidity; pub mod vapour_pressure; diff --git a/src/potential_temperature.rs b/src/potential_temperature.rs index 7f1f57f..cbab246 100644 --- a/src/potential_temperature.rs +++ b/src/potential_temperature.rs @@ -1,65 +1,73 @@ -//!Functions to calculate potential temperature of dry air in K. +//! Functions to calculate potential temperature of dry air +//! +//! The temperature that an unsaturated parcel of dry air would have if brought +//! adiabatically and reversibly from its initial state to a +//! standard pressure, p0 = 100 kPa ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Potential_temperature)). - -use crate::Float; +use crate::formula::Formula3; +use crate::quantities::{ + AtmosphericPressure, DryBulbTemperature, PotentialTemperature, ThermodynamicQuantity, + VapourPressure, +}; use crate::{ constants::{C_P, R_D}, errors::InputError, }; +use crate::{Float, Storage}; use float_cmp::approx_eq; +use uom::si::pressure::pascal; +use uom::si::ratio::ratio; +use uom::si::thermodynamic_temperature::kelvin; - -use itertools::izip; -use ndarray::{Array, Dimension, FoldWhile}; -use rayon::iter::{ParallelBridge, ParallelIterator}; - -///Formula for computing potential temperature of dry air from temperature, pressure and vapour pressure. +/// Formula for computing potential temperature of dry air from temperature, pressure and vapour pressure. +/// +/// Provided in by R. Davies-Jones (2009) [(doi:10.1175/2009MWR2774.1)](https://doi.org/10.1175/2009MWR2774.1) /// -///Provided by R. Davies-Jones (2009) [(doi:10.1175/2009MWR2774.1)](https://doi.org/10.1175/2009MWR2774.1) +/// Valid `temperature` range: 253K - 324K /// -///# Errors +/// Valid `pressure` range: 100Pa - 150000Pa /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `temperature` range: 253K - 324K\ -///Valid `pressure` range: 100Pa - 150000Pa\ -///Valid `vapour_pressure` range: 0Pa - 10000Pa +/// Valid `vapour_pressure` range: 0Pa - 10000Pa /// -///Returns [`InputError::IncorrectArgumentSet`] when `pressure` and `vapour_pressure` are equal, -///in which case division by 0 occurs. +/// Returns [`InputError::IncorrectArgumentSet`] when `pressure` and `vapour_pressure` are equal, +/// in which case division by 0 occurs. /// -///Returns [`InputError::IncorrectArgumentSet`] when `pressure` is lower than `vapour_pressure`, -///in which case floating-point exponentation of negative number occurs. -pub struct DaviesJones1; +/// Returns [`InputError::IncorrectArgumentSet`] when `pressure` is lower than `vapour_pressure`, +/// in which case floating-point exponentation of negative number occurs. +pub struct Definition1; -impl DaviesJones1 { - #[allow(missing_docs)] +impl Formula3 + for Definition1 +{ #[inline(always)] - #[allow(clippy::missing_errors_doc)] - - pub fn validate_inputs( - temperature: Float, - pressure: Float, - vapour_pressure: Float, + fn validate_inputs( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + vapour_pressure: VapourPressure, ) -> Result<(), InputError> { - if !(253.0..=324.0).contains(&temperature) { + let temperature_si = temperature.get_si_value(); + let pressure_si = pressure.get_si_value(); + let vapour_pressure_si = vapour_pressure.get_si_value(); + + if !(253.0..=324.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(100.0..=150_000.0).contains(&pressure) { + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } - if !(0.0..=10_000.0).contains(&vapour_pressure) { + if !(0.0..=10_000.0).contains(&vapour_pressure_si) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } - if approx_eq!(Float, pressure, vapour_pressure, ulps = 2) { + if approx_eq!(Float, pressure_si, vapour_pressure_si, ulps = 2) { return Err(InputError::IncorrectArgumentSet(String::from( "pressure and vapour_pressure cannot be equal", ))); } - if vapour_pressure > pressure { + if vapour_pressure_si > pressure_si { return Err(InputError::IncorrectArgumentSet(String::from( "vapour_pressure cannot be higher than pressure", ))); @@ -69,41 +77,54 @@ impl DaviesJones1 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { - let kappa = R_D / C_P; - temperature * (100_000.0 / (pressure - vapour_pressure)).powf(kappa) + fn compute_unchecked( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + vapour_pressure: VapourPressure, + ) -> PotentialTemperature { + let temperature = temperature.0.get::(); + let pressure = pressure.0.get::(); + let vapour_pressure = vapour_pressure.0.get::(); + + let kappa = (R_D / C_P).get::(); + let result = temperature * (100_000.0 / (pressure - vapour_pressure)).powf(kappa); + + let result = Storage::ThermodynamicTemperature::new::(result); + + PotentialTemperature(result) } } +#[cfg(test)] +mod tests { + use crate::tests::{test_with_3args, Argument}; -// #[cfg(test)] -// mod tests { -// use crate::{ -// potential_temperature, -// tests_framework::{self, Argument}, -// }; - -// #[test] -// fn davies_jones1() { -// assert!(tests_framework::test_with_3args( -// &potential_temperature::davies_jones1, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [253.0, 324.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// Argument { -// name: "vapour_pressure", -// def_val: 3000.0, -// range: [0.0, 10_000.0] -// }, -// 301.45136519081666 -// )); -// } -// } + use super::*; + #[test] + fn definition1() { + test_with_3args::< + PotentialTemperature, + DryBulbTemperature, + AtmosphericPressure, + VapourPressure, + Definition1, + >( + Argument { + name: "temperature", + def_val: 300.0, + range: [253.0, 324.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + Argument { + name: "vapour_pressure", + def_val: 3000.0, + range: [0.0, 10_000.0], + }, + 301.45136519081666, + ); + } +} diff --git a/src/wet_bulb_potential_temperature.rs b/src/wet_bulb_potential_temperature.rs index ce08f7f..708b1f6 100644 --- a/src/wet_bulb_potential_temperature.rs +++ b/src/wet_bulb_potential_temperature.rs @@ -17,10 +17,7 @@ use crate::{ /// /// Derived by R. Davies-Jones (2008) [(doi:10.1175/2007MWR2224.1)](https://doi.org/10.1175/2007MWR2224.1) /// -/// # Errors -/// -/// Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -/// Valid `temperature` range: 257K - 377K\ +/// Valid `temperature` range: 257K - 377K pub struct DaviesJones1; impl Formula1 for DaviesJones1 { From b3a4f55f00b0f18aa6248add152d3bc85a6fd8fd Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 17:48:42 +0100 Subject: [PATCH 027/102] add uniform test type for vapour pressure --- src/vapour_pressure.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/vapour_pressure.rs b/src/vapour_pressure.rs index 8435e9f..d3485fc 100644 --- a/src/vapour_pressure.rs +++ b/src/vapour_pressure.rs @@ -64,9 +64,9 @@ impl Formula2 for Definit /// Formula for computing vapour pressure from saturation vapour pressure and relative humidity. /// -/// Valid `saturation_vapour_pressure` range: 0Pa - 10000Pa +/// Valid `saturation_vapour_pressure` range: 0Pa - 50000Pa /// -/// Valid `relative_humidity` range: 0.0 - 1.0 +/// Valid `relative_humidity` range: 0.0 - 2.0 pub struct Definition2; impl Formula2 for Definition2 { @@ -540,9 +540,11 @@ mod tests { use super::*; + type FormulaQuantity = VapourPressure; + #[test] fn definition1() { - test_with_2args::( + test_with_2args::( Argument { name: "specific_humidity", def_val: 0.022, @@ -559,7 +561,7 @@ mod tests { #[test] fn definition2() { - test_with_2args::( + test_with_2args::( Argument { name: "saturation_vapour_pressure", def_val: 3550.0, @@ -576,7 +578,7 @@ mod tests { #[test] fn buck1() { - test_with_2args::( + test_with_2args::( Argument { name: "dewpoint", def_val: 300.0, @@ -593,7 +595,7 @@ mod tests { #[test] fn buck2() { - test_with_2args::( + test_with_2args::( Argument { name: "dewpoint", def_val: 250.0, @@ -610,7 +612,7 @@ mod tests { #[test] fn buck3() { - test_with_2args::( + test_with_2args::( Argument { name: "dewpoint", def_val: 300.0, @@ -627,7 +629,7 @@ mod tests { #[test] fn buck4() { - test_with_2args::( + test_with_2args::( Argument { name: "dewpoint", def_val: 250.0, @@ -644,7 +646,7 @@ mod tests { #[test] fn buck3_simplified() { - test_with_1arg::( + test_with_1arg::( Argument { name: "dewpoint", def_val: 300.0, @@ -656,7 +658,7 @@ mod tests { #[test] fn buck4_simplified() { - test_with_1arg::( + test_with_1arg::( Argument { name: "dewpoint", def_val: 250.0, @@ -668,7 +670,7 @@ mod tests { #[test] fn tetens1() { - test_with_1arg::( + test_with_1arg::( Argument { name: "dewpoint", def_val: 300.0, @@ -680,7 +682,7 @@ mod tests { #[test] fn wexler1() { - test_with_1arg::( + test_with_1arg::( Argument { name: "dewpoint", def_val: 300.0, @@ -692,7 +694,7 @@ mod tests { #[test] fn wexler2() { - test_with_1arg::( + test_with_1arg::( Argument { name: "dewpoint", def_val: 250.0, From dc187aca681717b313117792ee09f9b095302c2c Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:31:02 +0100 Subject: [PATCH 028/102] rewrite saturation vapour pressure --- src/lib.rs | 2 +- src/saturation_vapour_pressure.rs | 639 +++++++++++++++++++++++++++++- src/vapour_pressure.rs | 27 +- 3 files changed, 634 insertions(+), 34 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dc565e6..8c92f32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,7 +92,7 @@ pub mod potential_temperature; // pub mod relative_humidity; pub mod specific_humidity; pub mod vapour_pressure; -// pub mod saturation_vapour_pressure; +pub mod saturation_vapour_pressure; // pub mod vapour_pressure_deficit; pub mod virtual_temperature; pub mod wet_bulb_potential_temperature; diff --git a/src/saturation_vapour_pressure.rs b/src/saturation_vapour_pressure.rs index becb1c9..2e3a072 100644 --- a/src/saturation_vapour_pressure.rs +++ b/src/saturation_vapour_pressure.rs @@ -1,27 +1,45 @@ -///Formula for computing **ONLY** saturation vapour pressure from vapour pressure and relative humidity. -///For vapour pressure use [`saturation_specific1`] +//! Formulae to calculate saturation vapour pressure of the unsaturated air +//! +//! The vapor pressure of a system, at a given temperature, for which the vapor +//! of a substance is in equilibrium with a plane surface of that substance's pure +//! liquid or solid phase; that is, the vapor pressure of a system that has attained +//! saturation but not supersaturation ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Saturation_vapor_pressure)). + +use crate::errors::InputError; +use crate::formula::{Formula1, Formula2}; +use crate::quantities::{ + AtmosphericPressure, DryBulbTemperature, RelativeHumidity, SaturationVapourPressure, + ThermodynamicQuantity, VapourPressure, +}; +use crate::Float; +use crate::Storage::Pressure; + +use uom::si::pressure::{hectopascal, kilopascal, pascal}; +use uom::si::thermodynamic_temperature::{degree_celsius, kelvin}; + +type FormulaQuantity = SaturationVapourPressure; + +/// Formula for computing saturation vapour pressure from vapour pressure and relative humidity. /// -///# Errors +/// Valid `vapour_pressure` range: 0Pa - 50000Pa /// -///Returns [`InputError::OutOfRange`] when input is out of range.\ -///Valid `vapour_pressure` range: 0Pa - 10000Pa\ -///Valid `relative_humidity` range: 0.00001 - 1.0 -pub struct SaturationSpecific2; +/// Valid `relative_humidity` range: 0.00001 - 2.0 +pub struct Definition1; -impl SaturationSpecific2 { - #[allow(missing_docs)] +impl Formula2 for Definition1 { #[inline(always)] - #[allow(clippy::missing_errors_doc)] - - pub fn validate_inputs( - vapour_pressure: Float, - relative_humidity: Float, + fn validate_inputs( + vapour_pressure: VapourPressure, + relative_humidity: RelativeHumidity, ) -> Result<(), InputError> { - if !(0.00001..=2.0).contains(&relative_humidity) { + let relative_humidity_si = relative_humidity.get_si_value(); + let vapour_pressure_si = vapour_pressure.get_si_value(); + + if !(0.00001..=2.0).contains(&relative_humidity_si) { return Err(InputError::OutOfRange(String::from("relative_humidity"))); } - if !(0.0..=10_000.0).contains(&vapour_pressure) { + if !(0.0..=50_000.0).contains(&vapour_pressure_si) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } @@ -29,8 +47,591 @@ impl SaturationSpecific2 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(vapour_pressure: Float, relative_humidity: Float) -> Float { - vapour_pressure / relative_humidity + fn compute_unchecked( + vapour_pressure: VapourPressure, + relative_humidity: RelativeHumidity, + ) -> SaturationVapourPressure { + SaturationVapourPressure(vapour_pressure.0 / relative_humidity.0) + } +} + +/// Formula for saturation computing saturation vapour pressure from dewpoint temperature and pressure. +/// Should be used for air over water when accuracy is desired. +/// +/// Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). +/// +/// Valid `dewpoint temperature` range: 232K - 324K +/// +/// Valid `atmospheric pressure` range: 100Pa - 150000Pa +pub struct Buck1; + +impl Formula2 for Buck1 { + #[inline(always)] + fn validate_inputs( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(232.0..=324.0).contains(&temperature_si) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(100.0..=150_000.0).contains(&pressure_si) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + ) -> SaturationVapourPressure { + let dewpoint = temperature.0.get::(); + let pressure = pressure.0.get::(); + + let lower_a = 6.1121; + let lower_b = 18.729; + let lower_c = 257.87; + let lower_d = 227.3; + + let upper_a = 0.000_72; + let upper_b = 0.000_003_2; + let upper_c = 0.000_000_000_59; + + let lower_e = + 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))); + + let result = Pressure::new::(lower_e * lower_f); + + SaturationVapourPressure(result) + } +} + +/// Formula for computing saturation vapour pressure from dewpoint temperature and pressure. +/// Should be used for air over ice when accuracy is desired. +/// +/// Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). +/// +/// Valid `dewpoint` range: 193K - 274K +/// +/// Valid `pressure` range: 100Pa - 150000Pa +pub struct Buck2; + +impl Formula2 for Buck2 { + #[inline(always)] + fn validate_inputs( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(193.0..=274.0).contains(&temperature_si) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(100.0..=150_000.0).contains(&pressure_si) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + ) -> SaturationVapourPressure { + let dewpoint = temperature.0.get::(); + let pressure = pressure.0.get::(); + + let lower_a = 6.1115; + let lower_b = 23.036; + let lower_c = 279.82; + let lower_d = 333.7; + + let upper_a = 0.000_22; + let upper_b = 0.000_003_83; + let upper_c = 0.000_000_000_64; + + let lower_e = + 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))); + + let result = Pressure::new::(lower_e * lower_f); + + SaturationVapourPressure(result) + } +} + +/// Formula for computing saturation vapour pressure from dewpoint temperature and pressure. +/// Should be used for air over water for general use. +/// +/// Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). +/// +/// Valid `dewpoint` range: 253K - 324K +/// +/// Valid `pressure` range: 100Pa - 150000Pa +pub struct Buck3; + +impl Formula2 for Buck3 { + #[inline(always)] + fn validate_inputs( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(253.0..=324.0).contains(&temperature_si) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(100.0..=150_000.0).contains(&pressure_si) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + ) -> SaturationVapourPressure { + let dewpoint = temperature.0.get::(); + let pressure = pressure.0.get::(); + + let lower_a = 6.1121; + let lower_b = 17.502; + let lower_c = 240.97; + + let upper_a = 0.000_7; + let upper_b = 0.000_003_46; + + let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); + let lower_f = 1.0 + upper_a + (pressure * upper_b); + + let result = Pressure::new::(lower_e * lower_f); + + SaturationVapourPressure(result) + } +} + +/// Formula for computing saturation vapour pressure from dewpoint temperature. +/// Simplified version of [`buck3`]. Very popular in meteorological sources. +/// +/// Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). +/// +/// Valid `dewpoint` range: 253K - 324K +pub struct Buck3Simplified; + +impl Formula1 for Buck3Simplified { + #[inline(always)] + fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + + if !(253.0..=324.0).contains(&temperature_si) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked(temperature: DryBulbTemperature) -> SaturationVapourPressure { + let dewpoint = temperature.0.get::(); + + let lower_a = 6.1121; + let lower_b = 17.502; + let lower_c = 240.97; + + let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); + + let result = Pressure::new::(lower_e); + + SaturationVapourPressure(result) + } +} + +/// Formula for computing vapour saturation pressure from dewpoint temperature and pressure. +/// Should be used for air over ice for general use. +/// +/// Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). +/// +/// Valid `dewpoint` range: 223K - 274K +/// +/// Valid `pressure` range: 100Pa - 150000Pa +pub struct Buck4; + +impl Formula2 for Buck4 { + #[inline(always)] + fn validate_inputs( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(223.0..=274.0).contains(&temperature_si) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(100.0..=150_000.0).contains(&pressure_si) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + ) -> SaturationVapourPressure { + let dewpoint = temperature.0.get::(); + let pressure = pressure.0.get::(); + + let lower_a = 6.1115; + let lower_b = 22.452; + let lower_c = 272.55; + + let upper_a = 0.000_3; + let upper_b = 0.000_004_18; + + let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); + let lower_f = 1.0 + upper_a + (pressure * upper_b); + + let result = Pressure::new::(lower_e * lower_f); + + SaturationVapourPressure(result) + } +} + +/// Formula for computing saturation vapour pressure from dewpoint temperature. +/// Simplified version of [`buck4`], analogical to [`buck3_simplified`]. +/// +/// Derived by A. L. Buck (1981) [(doi: 10.1175/1520-0450(1981)020<1527:nefcvp>2.0.co;2)](https://doi.org/10.1175/1520-0450(1981)020%3C1527:NEFCVP%3E2.0.CO;2). +/// +/// Valid `dewpoint` range: 223K - 274K +pub struct Buck4Simplified; + +impl Formula1 for Buck4Simplified { + #[inline(always)] + fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + + if !(223.0..=274.0).contains(&temperature_si) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + Ok(()) + } + + #[inline(always)] + + fn compute_unchecked(temperature: DryBulbTemperature) -> SaturationVapourPressure { + let dewpoint = temperature.0.get::(); + + let lower_a = 6.1115; + let lower_b = 22.452; + let lower_c = 272.55; + + let lower_e = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); + + let result = Pressure::new::(lower_e); + + SaturationVapourPressure(result) + } +} + +/// Formula for computing saturation vapour pressure over water from dewpoint temperature. +/// Should be used for temperatures above 273K. +/// +/// Derived by O. Tetens (1930). +/// +/// Valid `dewpoint` range: 273K - 353K +pub struct Tetens1; + +impl Formula1 for Tetens1 { + #[inline(always)] + fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + + if !(273.0..=353.0).contains(&temperature_si) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked(temperature: DryBulbTemperature) -> SaturationVapourPressure { + let dewpoint = temperature.0.get::(); + + let lower_a = 0.61078; + let lower_b = 17.27; + let lower_c = 237.3; + + let result = lower_a * ((lower_b * dewpoint) / (dewpoint + lower_c)).exp(); + + let result = Pressure::new::(result); + + SaturationVapourPressure(result) + } +} + +/// Formula for computing saturation vapour pressure over water from dewpoint temperature. +/// Should be used when accuracy is required as it is +/// computationally expensive. +/// +/// Derived by A. Wexler (1976) [(doi: 10.6028/jres.080A.071)](https://dx.doi.org/10.6028%2Fjres.080A.071). +/// +/// Valid `dewpoint` range: 273K - 374K +pub struct Wexler1; + +impl Formula1 for Wexler1 { + #[inline(always)] + fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + + if !(273.0..=374.0).contains(&temperature_si) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked(temperature: DryBulbTemperature) -> SaturationVapourPressure { + let dewpoint = temperature.get_si_value(); + + // constants from the paper + let g: [Float; 8] = [ + -2991.2729, + -6017.0128, + 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(); + + for i in 0..=6 { + ln_p += g[i] * dewpoint.powi(i as i32 - 2); + } + + let result = Pressure::new::(ln_p.exp()); + + SaturationVapourPressure(result) + } +} + +/// Formula for computing saturation vapour over ice pressure from dewpoint temperature. +/// Should be used when accuracy is required as it is +/// computationally expensive. +/// +/// Derived by A. Wexler (1977) [(doi: 10.6028/jres.081A.003)](https://dx.doi.org/10.6028%2Fjres.081A.003). +/// +/// Valid `dewpoint` range: 173K - 274K +pub struct Wexler2; + +impl Formula1 for Wexler2 { + #[inline(always)] + fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + + if !(173.0..=274.0).contains(&temperature_si) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + Ok(()) + } + + #[inline(always)] + fn compute_unchecked(temperature: DryBulbTemperature) -> SaturationVapourPressure { + let dewpoint = temperature.0.get::(); + + // constants from the paper + let big_k: [Float; 6] = [ + -5865.3696, + 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(); + + for j in 0..=4 { + ln_p += big_k[j] * dewpoint.powi(j as i32 - 1); + } + + let result = Pressure::new::(ln_p.exp()); + + SaturationVapourPressure(result) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + quantities::{AtmosphericPressure, RelativeHumidity, VapourPressure, DryBulbTemperature}, + tests::{test_with_1arg, test_with_2args, Argument}, + }; + + use super::*; + + #[test] + fn definition1() { + test_with_2args::( + Argument { + name: "vapour_pressure", + def_val: 3000.0, + range: [0.0, 50_000.0], + }, + Argument { + name: "relative_humidity", + def_val: 0.5, + range: [0.00001, 2.0], + }, + 6000.0, + ); + } + + #[test] + fn buck1() { + test_with_2args::( + Argument { + name: "temperature", + def_val: 300.0, + range: [232.0, 324.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 3550.6603579471303, + ); + } + + #[test] + fn buck2() { + test_with_2args::( + Argument { + name: "temperature", + def_val: 250.0, + range: [193.0, 274.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 76.38781790372722, + ); + } + + #[test] + fn buck3() { + test_with_2args::( + Argument { + name: "temperature", + def_val: 300.0, + range: [253.0, 324.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 3548.5041048035896, + ); + } + + #[test] + fn buck4() { + test_with_2args::( + Argument { + name: "temperature", + def_val: 250.0, + range: [223.0, 274.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 76.38685471836712, + ); + } + + #[test] + fn buck3_simplified() { + test_with_1arg::( + Argument { + name: "temperature", + def_val: 300.0, + range: [253.0, 324.0], + }, + 3533.6421536199978, + ); + } + + #[test] + fn buck4_simplified() { + test_with_1arg::( + Argument { + name: "temperature", + def_val: 250.0, + range: [223.0, 274.0], + }, + 76.04197508519536, + ); + } + + #[test] + fn tetens1() { + test_with_1arg::( + Argument { + name: "temperature", + def_val: 300.0, + range: [273.0, 353.0], + }, + 3533.969137160892, + ); + } + + #[test] + fn wexler1() { + test_with_1arg::( + Argument { + name: "temperature", + def_val: 300.0, + range: [273.0, 374.0], + }, + 3535.4235919263083, + ); + } + + #[test] + fn wexler2() { + test_with_1arg::( + Argument { + name: "temperature", + def_val: 250.0, + range: [173.0, 274.0], + }, + 76.04351136780438, + ); } } diff --git a/src/vapour_pressure.rs b/src/vapour_pressure.rs index d3485fc..3b03e41 100644 --- a/src/vapour_pressure.rs +++ b/src/vapour_pressure.rs @@ -17,6 +17,8 @@ use crate::{constants::EPSILON, errors::InputError}; use uom::si::pressure::{hectopascal, kilopascal, pascal}; use uom::si::thermodynamic_temperature::{degree_celsius, kelvin}; +type FormulaQuantity = VapourPressure; + /// Formula for computing vapour pressure from specific humidity and pressure. /// This function is theoretical not empirical. /// @@ -27,7 +29,7 @@ use uom::si::thermodynamic_temperature::{degree_celsius, kelvin}; /// Valid `atmospheric pressure` range: 100Pa - 150000Pa pub struct Definition1; -impl Formula2 for Definition1 { +impl Formula2 for Definition1 { #[inline(always)] fn validate_inputs( specific_humidity: SpecificHumidity, @@ -69,7 +71,7 @@ impl Formula2 for Definit /// Valid `relative_humidity` range: 0.0 - 2.0 pub struct Definition2; -impl Formula2 for Definition2 { +impl Formula2 for Definition2 { #[inline(always)] fn validate_inputs( saturation_vapour_pressure: SaturationVapourPressure, @@ -112,7 +114,7 @@ impl Formula2 for De /// Valid `atmospheric pressure` range: 100Pa - 150000Pa pub struct Buck1; -impl Formula2 for Buck1 { +impl Formula2 for Buck1 { #[inline(always)] fn validate_inputs( dewpoint: DewPointTemperature, @@ -169,7 +171,7 @@ impl Formula2 for Buck /// Valid `pressure` range: 100Pa - 150000Pa pub struct Buck2; -impl Formula2 for Buck2 { +impl Formula2 for Buck2 { #[inline(always)] fn validate_inputs( dewpoint: DewPointTemperature, @@ -226,7 +228,7 @@ impl Formula2 for Buck /// Valid `pressure` range: 100Pa - 150000Pa pub struct Buck3; -impl Formula2 for Buck3 { +impl Formula2 for Buck3 { #[inline(always)] fn validate_inputs( dewpoint: DewPointTemperature, @@ -278,7 +280,7 @@ impl Formula2 for Buck /// Valid `dewpoint` range: 253K - 324K pub struct Buck3Simplified; -impl Formula1 for Buck3Simplified { +impl Formula1 for Buck3Simplified { #[inline(always)] fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { let dewpoint_si = dewpoint.get_si_value(); @@ -316,7 +318,7 @@ impl Formula1 for Buck3Simplified { /// Valid `pressure` range: 100Pa - 150000Pa pub struct Buck4; -impl Formula2 for Buck4 { +impl Formula2 for Buck4 { #[inline(always)] fn validate_inputs( dewpoint: DewPointTemperature, @@ -368,7 +370,7 @@ impl Formula2 for Buck /// Valid `dewpoint` range: 223K - 274K pub struct Buck4Simplified; -impl Formula1 for Buck4Simplified { +impl Formula1 for Buck4Simplified { #[inline(always)] fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { let dewpoint_si = dewpoint.get_si_value(); @@ -405,7 +407,7 @@ impl Formula1 for Buck4Simplified { /// Valid `dewpoint` range: 273K - 353K pub struct Tetens1; -impl Formula1 for Tetens1 { +impl Formula1 for Tetens1 { #[inline(always)] fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { let dewpoint_si = dewpoint.get_si_value(); @@ -442,7 +444,7 @@ impl Formula1 for Tetens1 { /// Valid `dewpoint` range: 273K - 374K pub struct Wexler1; -impl Formula1 for Wexler1 { +impl Formula1 for Wexler1 { #[inline(always)] fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { let dewpoint_si = dewpoint.get_si_value(); @@ -491,7 +493,7 @@ impl Formula1 for Wexler1 { /// Valid `dewpoint` range: 173K - 274K pub struct Wexler2; -impl Formula1 for Wexler2 { +impl Formula1 for Wexler2 { #[inline(always)] fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { let dewpoint_si = dewpoint.get_si_value(); @@ -533,15 +535,12 @@ mod tests { use crate::{ quantities::{ AtmosphericPressure, RelativeHumidity, SaturationVapourPressure, SpecificHumidity, - VapourPressure, }, tests::{test_with_1arg, test_with_2args, Argument}, }; use super::*; - type FormulaQuantity = VapourPressure; - #[test] fn definition1() { test_with_2args::( From 8cd68ecf9c24749e8ba458728a1d9ec01176c945 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:59:24 +0100 Subject: [PATCH 029/102] rewrite vpd --- src/lib.rs | 2 +- src/quantities.rs | 22 +++ src/tests.rs | 4 +- src/vapour_pressure_deficit.rs | 316 +++++++++++++++++---------------- 4 files changed, 190 insertions(+), 154 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8c92f32..e48aae6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ pub mod potential_temperature; pub mod specific_humidity; pub mod vapour_pressure; pub mod saturation_vapour_pressure; -// pub mod vapour_pressure_deficit; +pub mod vapour_pressure_deficit; pub mod virtual_temperature; pub mod wet_bulb_potential_temperature; pub mod wet_bulb_temperature; diff --git a/src/quantities.rs b/src/quantities.rs index fdbbec9..7a42eb4 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -40,6 +40,9 @@ pub struct VapourPressure(pub Storage::Pressure); #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct SaturationVapourPressure(pub Storage::Pressure); +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct VapourPressureDeficit(pub Storage::Pressure); + #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct MixingRatio(pub Storage::Ratio); @@ -146,6 +149,15 @@ impl SaturationVapourPressure { } } +impl VapourPressureDeficit { + pub fn new(value: Float) -> Self + where + T: uom::si::pressure::Unit + uom::si::pressure::Conversion, + { + Self(Storage::Pressure::new::(value)) + } +} + impl MixingRatio { pub fn new(value: Float) -> Self where @@ -265,6 +277,16 @@ impl ThermodynamicQuantity for SaturationVapourPressure { } } +impl ThermodynamicQuantity for VapourPressureDeficit { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} + impl ThermodynamicQuantity for MixingRatio { fn get_si_value(&self) -> Float { self.0.get::() diff --git a/src/tests.rs b/src/tests.rs index 94d1bb4..91b954f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -303,14 +303,14 @@ pub fn test_with_3args< let expected = InputError::OutOfRange(String::from(arg3.name)); let result = F::compute( I1::new_si(arg1.def_val), - I2::new_si(arg3.def_val), + I2::new_si(arg2.def_val), I3::new_si(arg3.range[0] - 0.1), ) .unwrap_err(); assert_eq!(result, expected); let result = F::compute( I1::new_si(arg1.def_val), - I2::new_si(arg3.def_val), + I2::new_si(arg2.def_val), I3::new_si(arg3.range[1] + 0.1), ) .unwrap_err(); diff --git a/src/vapour_pressure_deficit.rs b/src/vapour_pressure_deficit.rs index 80d1ae9..1fc00d8 100644 --- a/src/vapour_pressure_deficit.rs +++ b/src/vapour_pressure_deficit.rs @@ -1,41 +1,40 @@ -//!Functions to calculate vapour pressure deficit in Pa. +//! Functions to calculate vapour pressure deficit in Pa. //! -//!Vapour-pressure deficit, is the difference (deficit) between -//!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)). - +//! Vapour-pressure deficit, is the difference (deficit) between +//! 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; -use crate::{vapour_pressure, Float}; - +use crate::formula::{Formula2, Formula3}; +use crate::quantities::{ + AtmosphericPressure, DewPointTemperature, DryBulbTemperature, RelativeHumidity, + SaturationVapourPressure, ThermodynamicQuantity, VapourPressure, VapourPressureDeficit, +}; +use crate::{saturation_vapour_pressure, vapour_pressure}; -use itertools::izip; -use ndarray::{Array, Dimension, FoldWhile}; -use rayon::iter::{ParallelBridge, ParallelIterator}; +type FormulaQuantity = VapourPressureDeficit; -///Formula for computing vapour pressure deficit from vapour pressure and saturation vapour pressure +/// Formula for computing vapour pressure deficit from vapour pressure and saturation vapour pressure /// -///# Errors +/// Valid `vapour_pressure` range: 0Pa - 50000Pa /// -///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 struct General1; +/// Valid `saturation_vapour_pressure` range: 0Pa - 50000Pa +pub struct Definition1; -impl General1 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] +impl Formula2 for Definition1 { #[inline(always)] - - pub fn validate_inputs( - vapour_pressure: Float, - saturation_vapour_pressure: Float, + fn validate_inputs( + vapour_pressure: VapourPressure, + saturation_vapour_pressure: SaturationVapourPressure, ) -> Result<(), InputError> { - if !(0.0..=50_000.0).contains(&vapour_pressure) { + let vapour_pressure_si = vapour_pressure.get_si_value(); + let saturation_vapour_pressure_si = saturation_vapour_pressure.get_si_value(); + + if !(0.0..=50_000.0).contains(&vapour_pressure_si) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } - if !(0.0..=50_000.0).contains(&saturation_vapour_pressure) { + if !(0.0..=50_000.0).contains(&saturation_vapour_pressure_si) { return Err(InputError::OutOfRange(String::from( "saturation_vapour_pressure", ))); @@ -45,88 +44,97 @@ impl General1 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(vapour_pressure: Float, saturation_vapour_pressure: Float) -> Float { - saturation_vapour_pressure - vapour_pressure + fn compute_unchecked( + vapour_pressure: VapourPressure, + saturation_vapour_pressure: SaturationVapourPressure, + ) -> VapourPressureDeficit { + VapourPressureDeficit(saturation_vapour_pressure.0 - vapour_pressure.0) } } - -///Formula for computing vapour pressure deficit from temperature, dewpoint and pressure -///using [`buck3`](vapour_pressure::buck3) function for vapour pressure calculation +/// Formula for computing vapour pressure deficit from temperature, dewpoint and pressure +/// using [`buck3`](vapour_pressure::buck3) function for vapour pressure calculation /// -///# Errors +/// Valid `temperature` range: 253K - 324K /// -///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 struct General2; +/// Valid `dewpoint` range: 253K - 324K +/// +/// Valid `pressure` range: 100Pa - 150000Pa +pub struct General1; -impl General2 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] +impl Formula3 + for General1 +{ #[inline(always)] - - pub fn validate_inputs( - temperature: Float, - dewpoint: Float, - pressure: Float, + fn validate_inputs( + temperature: DryBulbTemperature, + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, ) -> Result<(), InputError> { - if !(253.0..=324.0).contains(&temperature) { + let temperature_si = temperature.get_si_value(); + let dewpoint_si = dewpoint.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(253.0..=324.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(253.0..=324.0).contains(&dewpoint) { + if !(253.0..=324.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } - if !(100.0..=150_000.0).contains(&pressure) { + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } Ok(()) } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(temperature: Float, dewpoint: Float, pressure: Float) -> Float { + fn compute_unchecked( + temperature: DryBulbTemperature, + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> VapourPressureDeficit { let vapour_pressure = vapour_pressure::Buck3::compute_unchecked(dewpoint, pressure); let saturation_vapour_pressure = - vapour_pressure::Buck3::compute_unchecked(temperature, pressure); + saturation_vapour_pressure::Buck3::compute_unchecked(temperature, pressure); - General1::compute_unchecked(vapour_pressure, saturation_vapour_pressure) + Definition1::compute_unchecked(vapour_pressure, saturation_vapour_pressure) } } - -///Formula for computing vapour pressure deficit from temperature, relative humidity and pressure -///using [`buck3`](vapour_pressure::buck3) function for vapour pressure calculation +/// Formula for computing vapour pressure deficit from temperature, relative humidity and pressure +/// using [`buck3`](vapour_pressure::buck3) function for vapour pressure calculation +/// +/// Valid `temperature` range: 253K - 319K /// -///# Errors +/// Valid `relative_humidity` range: 0.05 - 2.0 /// -///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 struct General3; - -impl General3 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] +/// Valid `pressure` range: 100Pa - 150000Pa +pub struct General2; + +impl Formula3 + for General2 +{ #[inline(always)] - - pub fn validate_inputs( - temperature: Float, - relative_humidity: Float, - pressure: Float, + fn validate_inputs( + temperature: DryBulbTemperature, + relative_humidity: RelativeHumidity, + pressure: AtmosphericPressure, ) -> Result<(), InputError> { - if !(253.0..=319.0).contains(&temperature) { + let temperature_si = temperature.get_si_value(); + let relative_humidity_si = relative_humidity.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(253.0..=319.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(0.05..=1.0).contains(&relative_humidity) { + if !(0.05..=2.0).contains(&relative_humidity_si) { return Err(InputError::OutOfRange(String::from("relative_humidity"))); } - if !(10000.0..=150_000.0).contains(&pressure) { + if !(10000.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } @@ -134,92 +142,98 @@ impl General3 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked( - temperature: Float, - relative_humidity: Float, - pressure: Float, - ) -> Float { + fn compute_unchecked( + temperature: DryBulbTemperature, + relative_humidity: RelativeHumidity, + pressure: AtmosphericPressure, + ) -> VapourPressureDeficit { let saturation_vapour_pressure = - vapour_pressure::Buck3::compute_unchecked(temperature, pressure); - let vapour_pressure = vapour_pressure::SaturationSpecific1::compute_unchecked( + saturation_vapour_pressure::Buck3::compute_unchecked(temperature, pressure); + let vapour_pressure = vapour_pressure::Definition2::compute_unchecked( saturation_vapour_pressure, relative_humidity, ); - General1::compute_unchecked(vapour_pressure, saturation_vapour_pressure) + Definition1::compute_unchecked(vapour_pressure, saturation_vapour_pressure) } } +#[cfg(test)] +mod tests { + use crate::tests::{test_with_2args, test_with_3args, Argument}; + + use super::*; + + #[test] + fn definition1() { + test_with_2args::( + Argument { + name: "vapour_pressure", + def_val: 3000.0, + range: [0.0, 50_000.0], + }, + Argument { + name: "saturation_vapour_pressure", + def_val: 3550.0, + range: [0.0, 50_000.0], + }, + 550.0, + ); + } -// #[cfg(test)] -// mod tests { -// use crate::{ -// tests_framework::{self, Argument}, -// vapour_pressure_deficit, -// }; - -// #[test] -// fn general1() { -// assert!(tests_framework::test_with_2args( -// &vapour_pressure_deficit::general1, -// Argument { -// name: "vapour_pressure", -// def_val: 3000.0, -// range: [0.0, 50_000.0] -// }, -// Argument { -// name: "saturation_vapour_pressure", -// def_val: 3550.0, -// range: [0.0, 50_000.0] -// }, -// 550.0 -// )); -// } - -// #[test] -// fn general2() { -// assert!(tests_framework::test_with_3args( -// &vapour_pressure_deficit::general2, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [253.0, 324.0] -// }, -// Argument { -// name: "dewpoint", -// def_val: 290.0, -// range: [253.0, 324.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// 1621.9415403325527 -// )); -// } - -// #[test] -// fn general3() { -// assert!(tests_framework::test_with_3args( -// &vapour_pressure_deficit::general3, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [253.0, 319.0] -// }, -// Argument { -// name: "relative_humidity", -// def_val: 0.5, -// range: [0.05, 1.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [10000.0, 150_000.0] -// }, -// 1774.2520524017948 -// )); -// } -// } + #[test] + fn general1() { + test_with_3args::< + FormulaQuantity, + DryBulbTemperature, + DewPointTemperature, + AtmosphericPressure, + General1, + >( + Argument { + name: "temperature", + def_val: 300.0, + range: [253.0, 324.0], + }, + Argument { + name: "dewpoint", + def_val: 290.0, + range: [253.0, 324.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 1621.9415403325527, + ); + } + + #[test] + fn general2() { + test_with_3args::< + FormulaQuantity, + DryBulbTemperature, + RelativeHumidity, + AtmosphericPressure, + General2, + >( + Argument { + name: "temperature", + def_val: 300.0, + range: [253.0, 319.0], + }, + Argument { + name: "relative_humidity", + def_val: 0.5, + range: [0.05, 2.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [10000.0, 150_000.0], + }, + 1774.2520524017948, + ); + } +} From 2f7b477aa4ab7c12b6c225e4649249ad374c7177 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 19:14:30 +0100 Subject: [PATCH 030/102] rewrite saturation mixing ratio --- src/lib.rs | 2 +- src/mixing_ratio.rs | 19 ++- src/quantities.rs | 23 ++++ src/relative_humidity.rs | 52 ++++---- src/saturation_mixing_ratio.rs | 209 ++++++++++++++++++++++++++++++++- src/vapour_pressure_deficit.rs | 2 +- 6 files changed, 261 insertions(+), 46 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e48aae6..05d9030 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,7 +87,7 @@ pub mod quantities; // pub mod equivalent_potential_temperature; pub mod mixing_ratio; -// pub mod saturation_mixing_ratio; +pub mod saturation_mixing_ratio; pub mod potential_temperature; // pub mod relative_humidity; pub mod specific_humidity; diff --git a/src/mixing_ratio.rs b/src/mixing_ratio.rs index 14e58cc..f7a5655 100644 --- a/src/mixing_ratio.rs +++ b/src/mixing_ratio.rs @@ -11,6 +11,8 @@ use crate::{constants::EPSILON, errors::InputError}; use crate::{vapour_pressure, Float}; use float_cmp::approx_eq; +type FormulaQuantity = MixingRatio; + /// Formula for computing mixing ratio of unsaturated air from air pressure and vapour pressure /// /// Valid `pressure` range: 100Pa - 150000Pa @@ -20,7 +22,7 @@ use float_cmp::approx_eq; /// Returns [`InputError::IncorrectArgumentSet`] when inputs are equal and division by 0 would occur. pub struct Definition1; -impl Formula2 for Definition1 { +impl Formula2 for Definition1 { #[inline(always)] fn validate_inputs( pressure: AtmosphericPressure, @@ -62,7 +64,7 @@ impl Formula2 for Definition1 /// Valid `pressure` range: 100Pa - 150000Pa pub struct Performance1; -impl Formula2 for Performance1 { +impl Formula2 for Performance1 { #[inline(always)] fn validate_inputs( dewpoint: DewPointTemperature, @@ -101,7 +103,7 @@ impl Formula2 for Perform /// Valid `pressure` range: 100Pa - 150000Pa pub struct Accuracy1; -impl Formula2 for Accuracy1 { +impl Formula2 for Accuracy1 { #[inline(always)] fn validate_inputs( dewpoint: DewPointTemperature, @@ -133,16 +135,13 @@ impl Formula2 for Accurac #[cfg(test)] mod tests { - use crate::{ - quantities::MixingRatio, - tests::{test_with_2args, Argument}, - }; + use crate::tests::{test_with_2args, Argument}; use super::*; #[test] fn general1() { - test_with_2args::( + test_with_2args::( Argument { name: "pressure", def_val: 101325.0, @@ -159,7 +158,7 @@ mod tests { #[test] fn performance1() { - test_with_2args::( + test_with_2args::( Argument { name: "dewpoint", def_val: 300.0, @@ -176,7 +175,7 @@ mod tests { #[test] fn accuracy1() { - test_with_2args::( + test_with_2args::( Argument { name: "dewpoint", def_val: 300.0, diff --git a/src/quantities.rs b/src/quantities.rs index 7a42eb4..e998b36 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -46,6 +46,9 @@ pub struct VapourPressureDeficit(pub Storage::Pressure); #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct MixingRatio(pub Storage::Ratio); +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct SaturationMixingRatio(pub Storage::Ratio); + #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct SpecificHumidity(pub Storage::Ratio); @@ -167,6 +170,15 @@ impl MixingRatio { } } +impl SaturationMixingRatio { + pub fn new(value: Float) -> Self + where + T: uom::si::ratio::Unit + uom::si::ratio::Conversion, + { + Self(Storage::Ratio::new::(value)) + } +} + impl SpecificHumidity { pub fn new(value: Float) -> Self where @@ -296,6 +308,17 @@ impl ThermodynamicQuantity for MixingRatio { Self::new::(value) } } + +impl ThermodynamicQuantity for SaturationMixingRatio { + fn get_si_value(&self) -> Float { + self.0.get::() + } + + fn new_si(value: Float) -> Self { + Self::new::(value) + } +} + impl ThermodynamicQuantity for SpecificHumidity { fn get_si_value(&self) -> Float { self.0.get::() diff --git a/src/relative_humidity.rs b/src/relative_humidity.rs index 1ce8e12..59fbb60 100644 --- a/src/relative_humidity.rs +++ b/src/relative_humidity.rs @@ -1,33 +1,26 @@ -//!Functions to calculate relative humidity in %/100 - +//! Functions to calculate relative humidity use crate::errors::InputError; +use crate::formula::Formula2; +use crate::quantities::{MixingRatio, RelativeHumidity}; use crate::{mixing_ratio, vapour_pressure, Float}; +type FormulaQuantity = RelativeHumidity; -use itertools::izip; -use ndarray::{Array, Dimension, FoldWhile}; -use rayon::iter::{ParallelBridge, ParallelIterator}; - -///Formula for computing relative humidity from mixing ratio and saturation mixing ratio. -///Can be used interchangeably with [`general2`]. +/// Formula for computing relative humidity from mixing ratio and saturation mixing ratio. +/// Can be used interchangeably with [`general2`]. /// -///By the definition of mixing ratio, this formula is mathematically equivalent of -///formula used in [`general2`]. +/// By the definition of mixing ratio, this formula is mathematically equivalent of +/// formula used in [`general2`]. /// -///# Errors +/// Valid `mixing_ratio` range: 0.00001 - 10.0 /// -///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 -pub struct General1; +/// Valid `saturation_mixing_ratio` range: 0.00001 - 10.0 +pub struct Definition1; -impl General1 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] +impl Formula2 for Definition1 { #[inline(always)] - - pub fn validate_inputs( + fn validate_inputs( mixing_ratio: Float, saturation_mixing_ratio: Float, ) -> Result<(), InputError> { @@ -45,13 +38,11 @@ impl General1 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(mixing_ratio: Float, saturation_mixing_ratio: Float) -> Float { + fn compute_unchecked(mixing_ratio: Float, saturation_mixing_ratio: Float) -> Float { mixing_ratio / saturation_mixing_ratio } } - ///Formula for computing relative humidity from vapour pressure and saturation vapour pressure. ///Can be used interchangeably with [`general1`]. /// @@ -60,13 +51,13 @@ impl General1 { ///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 struct General2; +pub struct Definition2; -impl General2 { +impl Definition2 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] - + pub fn validate_inputs( vapour_pressure: Float, saturation_vapour_pressure: Float, @@ -91,7 +82,6 @@ impl General2 { } } - ///Formula for computing relative humidity from temperature and dewpoint using [`tetens1`](vapour_pressure::tetens1) ///function for vapour pressure calculation /// @@ -106,7 +96,7 @@ impl General3 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] - + pub fn validate_inputs(temperature: Float, dewpoint: Float) -> Result<(), InputError> { if !(273.0..=353.0).contains(&temperature) { return Err(InputError::OutOfRange(String::from("temperature"))); @@ -129,7 +119,6 @@ impl General3 { } } - ///Formula for computing relative humidity from temperature, dewpoint and pressure using [`buck3`](vapour_pressure::buck3) ///function for vapour pressure calculation /// @@ -145,7 +134,7 @@ impl General4 { #[allow(missing_docs)] #[inline(always)] #[allow(clippy::missing_errors_doc)] - + pub fn validate_inputs( temperature: Float, dewpoint: Float, @@ -177,7 +166,6 @@ impl General4 { } } - ///Formula for computing relative humidity from temperature, dewpoint and pressure using [`accuracy1`](mixing_ratio::accuracy1) ///function for mixing ratio calculation /// @@ -193,7 +181,7 @@ impl General5 { #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] #[inline(always)] - + pub fn validate_inputs( temperature: Float, dewpoint: Float, diff --git a/src/saturation_mixing_ratio.rs b/src/saturation_mixing_ratio.rs index 431801a..6f83d89 100644 --- a/src/saturation_mixing_ratio.rs +++ b/src/saturation_mixing_ratio.rs @@ -1,2 +1,207 @@ -//! To calculate saturation mixing ratio input dry-bulb temperature in place of dewpoint -//! or saturation vapour pressure in place of vapour pressure.s \ No newline at end of file +//! Functions to calculate saturation mixing ratio of unsaturated air +//! +//! Saturation mixing ration is the value of the mixing ratio of saturated air at the +//! given temperature and pressure ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Saturation_mixing_ratio)). + +use crate::formula::{Formula1, Formula2}; +use crate::quantities::{ + AtmosphericPressure, DryBulbTemperature, SaturationMixingRatio, SaturationVapourPressure, + ThermodynamicQuantity, +}; +use crate::{constants::EPSILON, errors::InputError}; +use crate::{saturation_vapour_pressure, Float}; +use float_cmp::approx_eq; + +type FormulaQuantity = SaturationMixingRatio; + +/// Formula for computing mixing ratio of unsaturated air from air pressure and vapour pressure +/// +/// Valid `pressure` range: 100Pa - 150000Pa +/// +/// Valid `saturation_vapour_pressure` range: 0Pa - 10000Pa +/// +/// Returns [`InputError::IncorrectArgumentSet`] when inputs are equal and division by 0 would occur. +pub struct Definition1; + +impl Formula2 for Definition1 { + #[inline(always)] + fn validate_inputs( + pressure: AtmosphericPressure, + saturation_vapour_pressure: SaturationVapourPressure, + ) -> Result<(), InputError> { + let pressure_si = pressure.get_si_value(); + let saturation_vapour_pressure_si = saturation_vapour_pressure.get_si_value(); + + if !(100.0..=150_000.0).contains(&pressure_si) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + if !(0.0..=50_000.0).contains(&saturation_vapour_pressure_si) { + return Err(InputError::OutOfRange(String::from( + "saturation_vapour_pressure", + ))); + } + + if approx_eq!(Float, pressure_si, saturation_vapour_pressure_si, ulps = 2) { + return Err(InputError::IncorrectArgumentSet(String::from( + "pressure and saturation_vapour_pressure cannot be equal", + ))); + } + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + pressure: AtmosphericPressure, + saturation_vapour_pressure: SaturationVapourPressure, + ) -> SaturationMixingRatio { + SaturationMixingRatio( + EPSILON * (saturation_vapour_pressure.0 / (pressure.0 - saturation_vapour_pressure.0)), + ) + } +} + +/// Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. +/// Optimised for performance - uses [`Tetens1`]. +/// +/// Valid `dewpoint` range: 273K - 353K +/// +/// Valid `pressure` range: 100Pa - 150000Pa +pub struct Performance1; + +impl Formula2 for Performance1 { + #[inline(always)] + fn validate_inputs( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(273.0..=353.0).contains(&temperature_si) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(100.0..=150_000.0).contains(&pressure_si) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + ) -> SaturationMixingRatio { + let saturation_vapour_pressure = + saturation_vapour_pressure::Tetens1::compute_unchecked(temperature); + + Definition1::compute_unchecked(pressure, saturation_vapour_pressure) + } +} + +/// Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. +/// Optimised for accuracy - uses [`Buck1`]. +/// +/// Valid `dewpoint` range: 232K - 324K +/// +/// Valid `pressure` range: 100Pa - 150000Pa +pub struct Accuracy1; + +impl Formula2 for Accuracy1 { + #[inline(always)] + fn validate_inputs( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(232.0..=324.0).contains(&temperature_si) { + return Err(InputError::OutOfRange(String::from("temperature"))); + } + + if !(100.0..=150_000.0).contains(&pressure_si) { + return Err(InputError::OutOfRange(String::from("pressure"))); + } + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + ) -> SaturationMixingRatio { + let saturation_vapour_pressure = + saturation_vapour_pressure::Buck1::compute_unchecked(temperature, pressure); + + Definition1::compute_unchecked(pressure, saturation_vapour_pressure) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + quantities::DryBulbTemperature, + tests::{test_with_2args, Argument}, + }; + + use super::*; + + #[test] + fn general1() { + test_with_2args::< + FormulaQuantity, + AtmosphericPressure, + SaturationVapourPressure, + Definition1, + >( + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + Argument { + name: "saturation_vapour_pressure", + def_val: 3500.0, + range: [0.0, 50_000.0], + }, + 0.022253316630823517, + ); + } + + #[test] + fn performance1() { + test_with_2args::( + Argument { + name: "temperature", + def_val: 300.0, + range: [273.0, 353.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 0.022477100514593465, + ); + } + + #[test] + fn accuracy1() { + test_with_2args::( + Argument { + name: "temperature", + def_val: 300.0, + range: [232.0, 324.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 0.022587116896465847, + ); + } +} diff --git a/src/vapour_pressure_deficit.rs b/src/vapour_pressure_deficit.rs index 1fc00d8..b18e918 100644 --- a/src/vapour_pressure_deficit.rs +++ b/src/vapour_pressure_deficit.rs @@ -1,4 +1,4 @@ -//! Functions to calculate vapour pressure deficit in Pa. +//! Functions to calculate vapour pressure deficit //! //! Vapour-pressure deficit, is the difference (deficit) between //! the amount of moisture in the air and how much moisture the air can hold From 627a7228bf55f636c549b04208fab4c64ca1843f Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 19:51:05 +0100 Subject: [PATCH 031/102] rewrtie relative humidity --- src/lib.rs | 2 +- src/relative_humidity.rs | 427 +++++++++++++++++++++------------------ 2 files changed, 229 insertions(+), 200 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 05d9030..af10de0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,7 +89,7 @@ pub mod quantities; pub mod mixing_ratio; pub mod saturation_mixing_ratio; pub mod potential_temperature; -// pub mod relative_humidity; +pub mod relative_humidity; pub mod specific_humidity; pub mod vapour_pressure; pub mod saturation_vapour_pressure; diff --git a/src/relative_humidity.rs b/src/relative_humidity.rs index 59fbb60..54b8e0b 100644 --- a/src/relative_humidity.rs +++ b/src/relative_humidity.rs @@ -1,9 +1,12 @@ //! Functions to calculate relative humidity use crate::errors::InputError; -use crate::formula::Formula2; -use crate::quantities::{MixingRatio, RelativeHumidity}; -use crate::{mixing_ratio, vapour_pressure, Float}; +use crate::formula::{Formula1, Formula2, Formula3}; +use crate::quantities::{ + AtmosphericPressure, DewPointTemperature, DryBulbTemperature, MixingRatio, RelativeHumidity, + SaturationMixingRatio, SaturationVapourPressure, ThermodynamicQuantity, VapourPressure, +}; +use crate::{mixing_ratio, saturation_mixing_ratio, saturation_vapour_pressure, vapour_pressure}; type FormulaQuantity = RelativeHumidity; @@ -21,14 +24,17 @@ pub struct Definition1; impl Formula2 for Definition1 { #[inline(always)] fn validate_inputs( - mixing_ratio: Float, - saturation_mixing_ratio: Float, + mixing_ratio: MixingRatio, + saturation_mixing_ratio: SaturationMixingRatio, ) -> Result<(), InputError> { - if !(0.00001..=10.0).contains(&mixing_ratio) { + let mixing_ratio_si = mixing_ratio.get_si_value(); + let saturation_mixing_ratio_si = saturation_mixing_ratio.get_si_value(); + + if !(0.00001..=10.0).contains(&mixing_ratio_si) { return Err(InputError::OutOfRange(String::from("mixing_ratio"))); } - if !(0.00001..=10.0).contains(&saturation_mixing_ratio) { + if !(0.00001..=10.0).contains(&saturation_mixing_ratio_si) { return Err(InputError::OutOfRange(String::from( "saturation_mixing_ratio", ))); @@ -38,35 +44,36 @@ impl Formula2 for Definitio } #[inline(always)] - fn compute_unchecked(mixing_ratio: Float, saturation_mixing_ratio: Float) -> Float { - mixing_ratio / saturation_mixing_ratio + fn compute_unchecked( + mixing_ratio: MixingRatio, + saturation_mixing_ratio: SaturationMixingRatio, + ) -> RelativeHumidity { + RelativeHumidity(mixing_ratio.0 / saturation_mixing_ratio.0) } } -///Formula for computing relative humidity from vapour pressure and saturation vapour pressure. -///Can be used interchangeably with [`general1`]. +/// Formula for computing relative humidity from vapour pressure and saturation vapour pressure. +/// Can be used interchangeably with [`general1`]. /// -///# Errors +/// Valid `vapour_pressure` range: 0Pa - 50000Pa /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `vapour_pressure` range: 0Pa - 10000Pa -///Valid `saturation_vapour_pressure` range: 0Pa - 10000Pa +/// Valid `saturation_vapour_pressure` range: 0Pa - 50000Pa pub struct Definition2; -impl Definition2 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] +impl Formula2 for Definition2 { #[inline(always)] - - pub fn validate_inputs( - vapour_pressure: Float, - saturation_vapour_pressure: Float, + fn validate_inputs( + vapour_pressure: VapourPressure, + saturation_vapour_pressure: SaturationVapourPressure, ) -> Result<(), InputError> { - if !(0.0..=50_000.0).contains(&vapour_pressure) { + let vapour_pressure_si = vapour_pressure.get_si_value(); + let saturation_vapour_pressure_si = saturation_vapour_pressure.get_si_value(); + + if !(0.0..=50_000.0).contains(&vapour_pressure_si) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } - if !(0.1..=50_000.0).contains(&saturation_vapour_pressure) { + if !(0.1..=50_000.0).contains(&saturation_vapour_pressure_si) { return Err(InputError::OutOfRange(String::from( "saturation_vapour_pressure", ))); @@ -76,33 +83,36 @@ impl Definition2 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(vapour_pressure: Float, saturation_vapour_pressure: Float) -> Float { - vapour_pressure / saturation_vapour_pressure + fn compute_unchecked( + vapour_pressure: VapourPressure, + saturation_vapour_pressure: SaturationVapourPressure, + ) -> RelativeHumidity { + RelativeHumidity(vapour_pressure.0 / saturation_vapour_pressure.0) } } -///Formula for computing relative humidity from temperature and dewpoint using [`tetens1`](vapour_pressure::tetens1) -///function for vapour pressure calculation +/// Formula for computing relative humidity from temperature and dewpoint using [`tetens1`](vapour_pressure::tetens1) +/// function for vapour pressure calculation /// -///# Errors +/// Valid `temperature` range: 273K - 353K /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `temperature` range: 273K - 353K -///Valid `dewpoint` range: 273K - 353K -pub struct General3; +/// Valid `dewpoint` range: 273K - 353K +pub struct General1; -impl General3 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] +impl Formula2 for General1 { #[inline(always)] + fn validate_inputs( + temperature: DryBulbTemperature, + dewpoint: DewPointTemperature, + ) -> Result<(), InputError> { + let temperature_si = temperature.get_si_value(); + let dewpoint_si = dewpoint.get_si_value(); - pub fn validate_inputs(temperature: Float, dewpoint: Float) -> Result<(), InputError> { - if !(273.0..=353.0).contains(&temperature) { + if !(273.0..=353.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(273.0..=353.0).contains(&dewpoint) { + if !(273.0..=353.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } @@ -110,45 +120,50 @@ impl General3 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(temperature: Float, dewpoint: Float) -> Float { + fn compute_unchecked( + temperature: DryBulbTemperature, + dewpoint: DewPointTemperature, + ) -> RelativeHumidity { let vapour_pressure = vapour_pressure::Tetens1::compute_unchecked(dewpoint); - let saturation_vapour_pressure = vapour_pressure::Tetens1::compute_unchecked(temperature); + let saturation_vapour_pressure = + saturation_vapour_pressure::Tetens1::compute_unchecked(temperature); - General2::compute_unchecked(vapour_pressure, saturation_vapour_pressure) + Definition2::compute_unchecked(vapour_pressure, saturation_vapour_pressure) } } -///Formula for computing relative humidity from temperature, dewpoint and pressure using [`buck3`](vapour_pressure::buck3) -///function for vapour pressure calculation +/// Formula for computing relative humidity from temperature, dewpoint and pressure using [`buck3`](vapour_pressure::buck3) +/// function for vapour pressure calculation /// -///# Errors +/// Valid `temperature` range: 253K - 324K /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `temperature` range: 253K - 324K\ -///Valid `dewpoint` range: 253K - 324K\ -///Valid `pressure` range: 100Pa - 150000Pa -pub struct General4; - -impl General4 { - #[allow(missing_docs)] - #[inline(always)] - #[allow(clippy::missing_errors_doc)] +/// Valid `dewpoint` range: 253K - 324K +/// +/// Valid `pressure` range: 100Pa - 150000Pa +pub struct General2; - pub fn validate_inputs( - temperature: Float, - dewpoint: Float, - pressure: Float, +impl Formula3 + for General2 +{ + #[inline(always)] + fn validate_inputs( + temperature: DryBulbTemperature, + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, ) -> Result<(), InputError> { - if !(253.0..=324.0).contains(&temperature) { + let temperature_si = temperature.get_si_value(); + let dewpoint_si = dewpoint.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(253.0..=324.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(253.0..=324.0).contains(&dewpoint) { + if !(253.0..=324.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } - if !(100.0..=150_000.0).contains(&pressure) { + if !(100.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } @@ -156,46 +171,51 @@ impl General4 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(temperature: Float, dewpoint: Float, pressure: Float) -> Float { + fn compute_unchecked( + temperature: DryBulbTemperature, + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> RelativeHumidity { let vapour_pressure = vapour_pressure::Buck3::compute_unchecked(dewpoint, pressure); let saturation_vapour_pressure = - vapour_pressure::Buck3::compute_unchecked(temperature, pressure); + saturation_vapour_pressure::Buck3::compute_unchecked(temperature, pressure); - General2::compute_unchecked(vapour_pressure, saturation_vapour_pressure) + Definition2::compute_unchecked(vapour_pressure, saturation_vapour_pressure) } } -///Formula for computing relative humidity from temperature, dewpoint and pressure using [`accuracy1`](mixing_ratio::accuracy1) -///function for mixing ratio calculation +/// Formula for computing relative humidity from temperature, dewpoint and pressure using [`accuracy1`](mixing_ratio::accuracy1) +/// function for mixing ratio calculation /// -///# Errors +/// Valid `temperature` range: 232K - 324K /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `temperature` range: 232K - 324K\ -///Valid `dewpoint` range: 232K - 324K\ -///Valid `pressure` range: 100Pa - 150000Pa -pub struct General5; - -impl General5 { - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - #[inline(always)] +/// Valid `dewpoint` range: 232K - 324K +/// +/// Valid `pressure` range: 100Pa - 150000Pa +pub struct General3; - pub fn validate_inputs( - temperature: Float, - dewpoint: Float, - pressure: Float, +impl Formula3 + for General3 +{ + #[inline(always)] + fn validate_inputs( + temperature: DryBulbTemperature, + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, ) -> Result<(), InputError> { - if !(232.0..=314.0).contains(&temperature) { + let temperature_si = temperature.get_si_value(); + let dewpoint_si = dewpoint.get_si_value(); + let pressure_si = pressure.get_si_value(); + + if !(232.0..=314.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(232.0..=314.0).contains(&dewpoint) { + if !(232.0..=314.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } - if !(10000.0..=150_000.0).contains(&pressure) { + if !(10000.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } @@ -203,120 +223,129 @@ impl General5 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(temperature: Float, dewpoint: Float, pressure: Float) -> Float { + fn compute_unchecked( + temperature: DryBulbTemperature, + dewpoint: DewPointTemperature, + pressure: AtmosphericPressure, + ) -> FormulaQuantity { let mixing_ratio = mixing_ratio::Accuracy1::compute_unchecked(dewpoint, pressure); let saturation_mixing_ratio = - mixing_ratio::Accuracy1::compute_unchecked(temperature, pressure); + saturation_mixing_ratio::Accuracy1::compute_unchecked(temperature, pressure); - General1::compute_unchecked(mixing_ratio, saturation_mixing_ratio) + Definition1::compute_unchecked(mixing_ratio, saturation_mixing_ratio) } } -// #[cfg(test)] -// mod tests { -// use crate::{ -// relative_humidity, -// tests_framework::{self, Argument}, -// }; - -// #[test] -// fn general1() { -// assert!(tests_framework::test_with_2args( -// &relative_humidity::general1, -// Argument { -// name: "mixing_ratio", -// def_val: 0.01064, -// range: [0.00001, 10.0] -// }, -// Argument { -// name: "saturation_mixing_ratio", -// def_val: 0.01467, -// range: [0.00001, 10.0] -// }, -// 0.7252897068847989 -// )); -// } - -// #[test] -// fn general2() { -// assert!(tests_framework::test_with_2args( -// &relative_humidity::general2, -// Argument { -// name: "vapour_pressure", -// def_val: 1706.0, -// range: [0.0, 50_000.0] -// }, -// Argument { -// name: "saturation_vapour_pressure", -// def_val: 2339.0, -// range: [0.1, 50_000.0] -// }, -// 0.7293715262932877 -// )); -// } - -// #[test] -// fn general3() { -// assert!(tests_framework::test_with_2args( -// &relative_humidity::general3, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [273.0, 353.0] -// }, -// Argument { -// name: "dewpoint", -// def_val: 290.0, -// range: [273.0, 353.0] -// }, -// 0.5431069897660531 -// )); -// } - -// #[test] -// fn general4() { -// assert!(tests_framework::test_with_3args( -// &relative_humidity::general4, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [253.0, 324.0] -// }, -// Argument { -// name: "dewpoint", -// def_val: 290.0, -// range: [253.0, 324.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [100.0, 150_000.0] -// }, -// 0.5429224562155812 -// )); -// } - -// #[test] -// fn general5() { -// assert!(tests_framework::test_with_3args( -// &relative_humidity::general5, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [232.0, 314.0] -// }, -// Argument { -// name: "dewpoint", -// def_val: 290.0, -// range: [232.0, 314.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [10000.0, 150_000.0] -// }, -// 0.5338747953552858 -// )); -// } -// } +#[cfg(test)] +mod tests { + use crate::tests::{test_with_2args, test_with_3args, Argument}; + + use super::*; + + #[test] + fn definition1() { + test_with_2args::( + Argument { + name: "mixing_ratio", + def_val: 0.01064, + range: [0.00001, 10.0], + }, + Argument { + name: "saturation_mixing_ratio", + def_val: 0.01467, + range: [0.00001, 10.0], + }, + 0.7252897068847989, + ); + } + + #[test] + fn definition2() { + test_with_2args::( + Argument { + name: "vapour_pressure", + def_val: 1706.0, + range: [0.0, 50_000.0], + }, + Argument { + name: "saturation_vapour_pressure", + def_val: 2339.0, + range: [0.1, 50_000.0], + }, + 0.7293715262932877, + ); + } + + #[test] + fn general1() { + test_with_2args::( + Argument { + name: "temperature", + def_val: 300.0, + range: [273.0, 353.0], + }, + Argument { + name: "dewpoint", + def_val: 290.0, + range: [273.0, 353.0], + }, + 0.5431069897660531, + ); + } + + #[test] + fn general2() { + test_with_3args::< + FormulaQuantity, + DryBulbTemperature, + DewPointTemperature, + AtmosphericPressure, + General2, + >( + Argument { + name: "temperature", + def_val: 300.0, + range: [253.0, 324.0], + }, + Argument { + name: "dewpoint", + def_val: 290.0, + range: [253.0, 324.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [100.0, 150_000.0], + }, + 0.5429224562155812, + ); + } + + #[test] + fn general3() { + test_with_3args::< + FormulaQuantity, + DryBulbTemperature, + DewPointTemperature, + AtmosphericPressure, + General3, + >( + Argument { + name: "temperature", + def_val: 300.0, + range: [232.0, 314.0], + }, + Argument { + name: "dewpoint", + def_val: 290.0, + range: [232.0, 314.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [10000.0, 150_000.0], + }, + 0.5338747953552858, + ); + } +} From fe8616caf84f0dcc6c518cdb2a6bb11918e223ff Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 20:35:31 +0100 Subject: [PATCH 032/102] rewrite theta e --- src/constants.rs | 21 +- src/equivalent_potential_temperature.rs | 423 ++++++++++++++---------- src/lib.rs | 2 +- 3 files changed, 265 insertions(+), 181 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 41de076..6364d4d 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -81,13 +81,6 @@ pub const L_V: Storage::AvailableEnergy = Storage::AvailableEnergy { value: 2_500_800.0, }; -/// Ratio of molar masses of dry air and water vapour -pub const EPSILON: Storage::Ratio = Storage::Ratio { - dimension: PhantomData, - units: PhantomData, - value: M_V.value / M_D.value, -}; - /// Specific gas constant for dry air pub const R_D: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { dimension: PhantomData, @@ -104,6 +97,20 @@ pub const R_V: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { // Internal Constants (commonly appearing in formulas to use them with oum units) +/// Ratio of molar masses of dry air and water vapour +pub(crate) const EPSILON: Storage::Ratio = Storage::Ratio { + dimension: PhantomData, + units: PhantomData, + value: M_V.value / M_D.value, +}; + +/// Ratio of specific gas constant and specific heat capacity for dry air +pub(crate) const KAPPA: Storage::Ratio = Storage::Ratio { + dimension: PhantomData, + units: PhantomData, + value: R_D.value / C_P.value, +}; + pub(crate) const DIMLESS_ONE: Storage::Ratio = Storage::Ratio { dimension: PhantomData, units: PhantomData, diff --git a/src/equivalent_potential_temperature.rs b/src/equivalent_potential_temperature.rs index 6e64f01..a3e7ca8 100644 --- a/src/equivalent_potential_temperature.rs +++ b/src/equivalent_potential_temperature.rs @@ -1,46 +1,67 @@ -//!Functions to calculate equivalent potential temperature of air in K. - - -use crate::constants::{C_L, C_P, EPSILON, L_V, R_D, R_V}; +//! Functions to calculate equivalent potential temperature of air +//! +//! Equivalent potential eemperature is a thermodynamic quantity, with its natural logarithm proportional +//! to the entropy of moist air, that is conserved in a reversible moist +//! adiabatic process ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Equivalent_potential_temperature)). + +use uom::si::available_energy::joule_per_kilogram; +use uom::si::pressure::pascal; +use uom::si::ratio::ratio; +use uom::si::specific_heat_capacity::joule_per_kilogram_kelvin; +use uom::si::thermodynamic_temperature::kelvin; + +use crate::constants::{C_L, C_P, EPSILON, KAPPA, L_V, R_D, R_V}; use crate::errors::InputError; -use crate::{mixing_ratio, potential_temperature, relative_humidity, vapour_pressure, Float}; - -///Most accuarte formula for computing equivalent potential temperature of unsaturated air from -///temperature, pressure and vapour pressure. +use crate::formula::{Formula2, Formula3}; +use crate::quantities::{ + AtmosphericPressure, DewPointTemperature, DryBulbTemperature, EquivalentPotentialTemperature, + ThermodynamicQuantity, VapourPressure, +}; +use crate::{ + mixing_ratio, potential_temperature, relative_humidity, saturation_vapour_pressure, + vapour_pressure, +}; + +type FormulaQuantity = EquivalentPotentialTemperature; + +/// Most accuarte formula for computing equivalent potential temperature of unsaturated air from +/// temperature, pressure and vapour pressure. +/// +/// Implementation of this formula assumes no liquid or solid water in the air parcel. /// -///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 /// -///First appeared in Paluch, Ilga (1979). J. Atmos. Sci., 36, 2467-2478 +/// Provided in Emmanuel, Kerry (1994). Atmospheric Convection. Oxford University Press. /// -///Provided in Emmanuel, Kerry (1994). Atmospheric Convection. Oxford University Press. +/// Valid `temperature` range: 253K - 324K /// -///# Errors +/// Valid `pressure` range: 100Pa - 150000Pa /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `temperature` range: 253K - 324K\ -///Valid `pressure` range: 100Pa - 150000Pa\ -///Valid `vapour_pressure` range: 0Pa - 10000Pa +/// Valid `vapour_pressure` range: 0Pa - 10000Pa pub struct Paluch1; -impl Paluch1 { - #[allow(missing_docs)] +impl Formula3 + for Paluch1 +{ #[inline(always)] - #[allow(clippy::missing_errors_doc)] - - pub fn validate_inputs( - temperature: Float, - pressure: Float, - vapour_pressure: Float, + fn validate_inputs( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + vapour_pressure: VapourPressure, ) -> Result<(), InputError> { - if !(253.0..=324.0).contains(&temperature) { + let temperature_si = temperature.get_si_value(); + let pressure_si = pressure.get_si_value(); + let vapour_pressure_si = vapour_pressure.get_si_value(); + + if !(253.0..=324.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(20000.0..=150_000.0).contains(&pressure) { + if !(20000.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } - if !(0.0..=10_000.0).contains(&vapour_pressure) { + if !(0.0..=10_000.0).contains(&vapour_pressure_si) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } @@ -48,59 +69,73 @@ impl Paluch1 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { - let p0 = 100_000.0; - - let mixing_ratio = mixing_ratio::General1::compute_unchecked(pressure, vapour_pressure); + fn compute_unchecked( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + vapour_pressure: VapourPressure, + ) -> EquivalentPotentialTemperature { + let mixing_ratio = mixing_ratio::Definition1::compute_unchecked(pressure, vapour_pressure); let saturation_vapour_pressure = - vapour_pressure::Buck1::compute_unchecked(temperature, pressure); + saturation_vapour_pressure::Buck1::compute_unchecked(temperature, pressure); - let relative_humidity = relative_humidity::General2::compute_unchecked( + let relative_humidity = relative_humidity::Definition2::compute_unchecked( vapour_pressure, saturation_vapour_pressure, ); - 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() + let temperature = temperature.0.get::(); + let pressure = pressure.0.get::(); + let mixing_ratio = mixing_ratio.0.get::(); + let relative_humidity = relative_humidity.0.get::(); + + let p0 = 100_000.0; + let r_d = R_D.get::(); + let r_v = R_V.get::(); + let l_v = L_V.get::(); + let c_p = C_P.get::(); + let c_l = C_L.get::(); + + let result = 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(); + + EquivalentPotentialTemperature::new::(result) } } - -///Formula for computing equivalent potential temperature of unsaturated air from -///temperature, pressure and vapour pressure. +/// Formula for computing equivalent potential temperature of unsaturated air from +/// temperature, pressure and vapour pressure. /// -///Derived by G. H. Bryan (2008) [(doi:10.1175/2008MWR2593.1)](https://doi.org/10.1175/2008MWR2593.1) +/// Derived by G. H. Bryan (2008) [(doi:10.1175/2008MWR2593.1)](https://doi.org/10.1175/2008MWR2593.1) /// -///# Errors +/// Valid `temperature` range: 253K - 324K /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `temperature` range: 253K - 324K\ -///Valid `pressure` range: 100Pa - 150000Pa\ -///Valid `vapour_pressure` range: 0Pa - 10000Pa +/// Valid `pressure` range: 100Pa - 150000Pa +/// +/// Valid `vapour_pressure` range: 0Pa - 10000Pa pub struct Bryan1; -impl Bryan1 { - #[allow(missing_docs)] +impl Formula3 for Bryan1 { #[inline(always)] - #[allow(clippy::missing_errors_doc)] - - pub fn validate_inputs( - temperature: Float, - pressure: Float, - vapour_pressure: Float, + fn validate_inputs( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + vapour_pressure: VapourPressure, ) -> Result<(), InputError> { - if !(253.0..=324.0).contains(&temperature) { + let temperature_si = temperature.get_si_value(); + let pressure_si = pressure.get_si_value(); + let vapour_pressure_si = vapour_pressure.get_si_value(); + + if !(253.0..=324.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(20000.0..=150_000.0).contains(&pressure) { + if !(20000.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } - if !(0.0..=10_000.0).contains(&vapour_pressure) { + if !(0.0..=10_000.0).contains(&vapour_pressure_si) { return Err(InputError::OutOfRange(String::from("vapour_pressure"))); } @@ -108,65 +143,80 @@ impl Bryan1 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(temperature: Float, pressure: Float, vapour_pressure: Float) -> Float { - let kappa = R_D / C_P; - - let potential_temperature = potential_temperature::DaviesJones1::compute_unchecked( + fn compute_unchecked( + temperature: DryBulbTemperature, + pressure: AtmosphericPressure, + vapour_pressure: VapourPressure, + ) -> EquivalentPotentialTemperature { + let potential_temperature = potential_temperature::Definition1::compute_unchecked( temperature, pressure, vapour_pressure, ); let saturation_vapour_pressure = - vapour_pressure::Buck3::compute_unchecked(temperature, pressure); - let relative_humidity = relative_humidity::General2::compute_unchecked( + saturation_vapour_pressure::Buck3::compute_unchecked(temperature, pressure); + + let relative_humidity = relative_humidity::Definition2::compute_unchecked( vapour_pressure, saturation_vapour_pressure, ); - let mixing_ratio = mixing_ratio::General1::compute_unchecked(pressure, vapour_pressure); + let mixing_ratio = mixing_ratio::Definition1::compute_unchecked(pressure, vapour_pressure); + + let temperature = temperature.0.get::(); + let mixing_ratio = mixing_ratio.0.get::(); + let relative_humidity = relative_humidity.0.get::(); + let potential_temperature = potential_temperature.0.get::(); + + let kappa = KAPPA.get::(); + let l_v = L_V.get::(); + let c_p = C_P.get::(); + let epsilon = EPSILON.get::(); - potential_temperature - * relative_humidity.powf((-kappa) * (mixing_ratio / EPSILON)) - * ((L_V * mixing_ratio) / (C_P * temperature)).exp() + let result = potential_temperature + * relative_humidity.powf((-kappa) * (mixing_ratio / epsilon)) + * ((l_v * mixing_ratio) / (c_p * temperature)).exp(); + + EquivalentPotentialTemperature::new::(result) } } - -///Approximate formula for computing equivalent potential temperature of unsaturated air from -///temperature, pressure and dewpoint. +/// Approximate formula for computing equivalent potential temperature of unsaturated air from +/// temperature, pressure and dewpoint. /// -///Derived by D. Bolton (1980) -///[(doi:10.1175/1520-0493(1980)108<1046:TCOEPT>2.0.CO;2)](https://doi.org/10.1175/1520-0493(1980)108%3C1046:TCOEPT%3E2.0.CO;2) +/// Derived by D. Bolton (1980) +/// [(doi:10.1175/1520-0493(1980)108<1046:TCOEPT>2.0.CO;2)](https://doi.org/10.1175/1520-0493(1980)108%3C1046:TCOEPT%3E2.0.CO;2) /// -///# Errors +/// Valid `pressure` range: 100Pa - 150000Pa /// -///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\ -///Valid `pressure` range: 100Pa - 150000Pa\ -///Valid `temperature` range: 253K - 324K\ -///Valid `dewpoint` range: 253K - 324K +/// Valid `temperature` range: 253K - 324K +/// +/// Valid `dewpoint` range: 253K - 324K pub struct Bolton1; -impl Bolton1 { - #[allow(missing_docs)] +impl Formula3 + for Bolton1 +{ #[inline(always)] - #[allow(clippy::missing_errors_doc)] - - pub fn validate_inputs( - pressure: Float, - temperature: Float, - dewpoint: Float, + fn validate_inputs( + pressure: AtmosphericPressure, + temperature: DryBulbTemperature, + dewpoint: DewPointTemperature, ) -> Result<(), InputError> { - if !(20000.0..=150_000.0).contains(&pressure) { + let pressure_si = pressure.get_si_value(); + let temperature_si = temperature.get_si_value(); + let dewpoint_si = dewpoint.get_si_value(); + + if !(20000.0..=150_000.0).contains(&pressure_si) { return Err(InputError::OutOfRange(String::from("pressure"))); } - if !(253.0..=324.0).contains(&temperature) { + if !(253.0..=324.0).contains(&temperature_si) { return Err(InputError::OutOfRange(String::from("temperature"))); } - if !(253.0..=324.0).contains(&dewpoint) { + if !(253.0..=324.0).contains(&dewpoint_si) { return Err(InputError::OutOfRange(String::from("dewpoint"))); } @@ -174,12 +224,21 @@ impl Bolton1 { } #[inline(always)] - #[allow(missing_docs)] - pub fn compute_unchecked(pressure: Float, temperature: Float, dewpoint: Float) -> Float { - let kappa = R_D / C_P; - + fn compute_unchecked( + pressure: AtmosphericPressure, + temperature: DryBulbTemperature, + dewpoint: DewPointTemperature, + ) -> EquivalentPotentialTemperature { let vapour_pressure = vapour_pressure::Buck3::compute_unchecked(dewpoint, pressure); - let mixing_ratio = mixing_ratio::General1::compute_unchecked(pressure, vapour_pressure); + let mixing_ratio = mixing_ratio::Definition1::compute_unchecked(pressure, vapour_pressure); + + let pressure = pressure.0.get::(); + let temperature = temperature.0.get::(); + let dewpoint = dewpoint.0.get::(); + let mixing_ratio = mixing_ratio.0.get::(); + let vapour_pressure = vapour_pressure.0.get::(); + + let kappa = KAPPA.get::(); let lcl_temp = (1.0 / ((1.0 / (dewpoint - 56.0)) + ((temperature / dewpoint).ln() / 800.0))) + 56.0; @@ -188,85 +247,103 @@ impl Bolton1 { * (100_000.0 / (pressure - vapour_pressure)).powf(kappa) * (temperature / lcl_temp).powf(0.28 * mixing_ratio); - theta_dl - * (((3036.0 / lcl_temp) - 1.78) * mixing_ratio * (1.0 + 0.448 * mixing_ratio)).exp() + let result = theta_dl + * (((3036.0 / lcl_temp) - 1.78) * mixing_ratio * (1.0 + 0.448 * mixing_ratio)).exp(); + + EquivalentPotentialTemperature::new::(result) } } +#[cfg(test)] +mod tests { + use crate::{ + quantities::{AtmosphericPressure, DryBulbTemperature, VapourPressure}, + tests::{test_with_3args, Argument}, + }; + + use super::*; + + #[test] + fn paluch1() { + test_with_3args::< + FormulaQuantity, + DryBulbTemperature, + AtmosphericPressure, + VapourPressure, + Paluch1, + >( + Argument { + name: "temperature", + def_val: 300.0, + range: [253.0, 324.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [20000.0, 150_000.0], + }, + Argument { + name: "vapour_pressure", + def_val: 991.189131, + range: [0.0, 10_000.0], + }, + 315.23724970376776, + ); + } + + #[test] + fn bryan1() { + test_with_3args::< + FormulaQuantity, + DryBulbTemperature, + AtmosphericPressure, + VapourPressure, + Bryan1, + >( + Argument { + name: "temperature", + def_val: 300.0, + range: [253.0, 324.0], + }, + Argument { + name: "pressure", + def_val: 101325.0, + range: [20000.0, 150_000.0], + }, + Argument { + name: "vapour_pressure", + def_val: 991.189131, + range: [0.0, 10_000.0], + }, + 316.52762026634014, + ); + } -// #[cfg(test)] -// mod tests { -// use crate::{ -// equivalent_potential_temperature, -// tests_framework::{self, Argument}, -// }; - -// #[test] -// fn paluch1() { -// assert!(tests_framework::test_with_3args( -// &equivalent_potential_temperature::paluch1, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [253.0, 324.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [20000.0, 150_000.0] -// }, -// Argument { -// name: "vapour_pressure", -// def_val: 991.189131, -// range: [0.0, 10_000.0] -// }, -// 315.23724970376776 -// )); -// } - -// #[test] -// fn bryan1() { -// assert!(tests_framework::test_with_3args( -// &equivalent_potential_temperature::bryan1, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [253.0, 324.0] -// }, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [20000.0, 150_000.0] -// }, -// Argument { -// name: "vapour_pressure", -// def_val: 991.189131, -// range: [0.0, 10_000.0] -// }, -// 316.52762026634014 -// )); -// } - -// #[test] -// fn bolton1() { -// assert!(tests_framework::test_with_3args( -// &equivalent_potential_temperature::bolton1, -// Argument { -// name: "pressure", -// def_val: 101325.0, -// range: [20000.0, 150_000.0] -// }, -// Argument { -// name: "temperature", -// def_val: 300.0, -// range: [253.0, 324.0] -// }, -// Argument { -// name: "dewpoint", -// def_val: 280.0, -// range: [253.0, 324.0] -// }, -// 317.3855211897774 -// )); -// } -// } + #[test] + fn bolton1() { + test_with_3args::< + FormulaQuantity, + AtmosphericPressure, + DryBulbTemperature, + DewPointTemperature, + Bolton1, + >( + Argument { + name: "pressure", + def_val: 101325.0, + range: [20000.0, 150_000.0], + }, + Argument { + name: "temperature", + def_val: 300.0, + range: [253.0, 324.0], + }, + Argument { + name: "dewpoint", + def_val: 280.0, + range: [253.0, 324.0], + }, + 317.3855211897774, + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index af10de0..c6b5ced 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,7 +85,7 @@ pub mod errors; pub mod formula; pub mod quantities; -// pub mod equivalent_potential_temperature; +pub mod equivalent_potential_temperature; pub mod mixing_ratio; pub mod saturation_mixing_ratio; pub mod potential_temperature; From f6d9941912a06158c308b076b39fcb8d2457a968 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 22 Feb 2024 21:00:01 +0100 Subject: [PATCH 033/102] WIP start array and parallel --- Cargo.toml | 8 ++++---- src/formula.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5cd16f3..ac25c81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,15 +34,15 @@ criterion = "0.5.1" [features] default = [ - "array_compute", - "array_compute_parallel", + "array", + "array_parallel", "debug", "double_precision", ] debug = ["log"] double_precision = [] -array_compute = ["ndarray", "itertools"] -array_compute_parallel = ["array_compute", "ndarray/rayon", "rayon"] +array = ["ndarray", "itertools"] +array_parallel = ["array", "ndarray/rayon", "rayon"] # [[bench]] # name = "virtual_temperature" diff --git a/src/formula.rs b/src/formula.rs index cb4671c..96cf86e 100644 --- a/src/formula.rs +++ b/src/formula.rs @@ -1,6 +1,8 @@ #![allow(missing_docs)] use crate::{errors::InputError, quantities::ThermodynamicQuantity}; +use ndarray::{Array, Dimension, FoldWhile, Zip}; +use rayon::iter::{ParallelIterator, ParallelBridge, IntoParallelIterator}; pub trait Formula1 { #[allow(missing_docs)] @@ -40,6 +42,35 @@ pub trait Formula1 { Err(err) }) } + + #[cfg(feature = "array")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_vec(i1: &[I1]) -> Result, InputError> { + i1.iter().map(|&i1| Self::compute(i1)).collect() + } + + #[cfg(feature = "array")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_ndarray(i1: &Array) -> Result, InputError> { + Zip::from(i1) + .fold_while(Ok(()), |_, &a| match Self::validate_inputs(a) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(Zip::from(i1).map_collect(|&a| Self::compute_unchecked(a))) + } + + #[cfg(feature = "array")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_vec_parallel(i1: &[I1]) -> Result, InputError> { + + i1.into_par_iter().map(|&i1| Self::compute(i1)).collect() + } } pub trait Formula2 { From 2da274eed6bb3ecd694e8c39bd335fe43505b66f Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:50:22 +0100 Subject: [PATCH 034/102] implement arrays and parallel --- Cargo.toml | 14 +--- src/formula.rs | 163 ++++++++++++++++++++++++++++++++++++++++++++-- src/quantities.rs | 4 +- 3 files changed, 164 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ac25c81..367f1f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,6 @@ exclude = [".github/*"] thiserror = "1.0" float-cmp = "0.9" log = { version = "0.4", optional = true } -itertools = { version = "0.12", default-features = false, optional = true, features = [ - "use_std", -] } ndarray = { version = "0.15", default-features = false, optional = true } rayon = { version = "1.8", default-features = false, optional = true } uom = { version = "0.35", default-features = false, features = [ @@ -33,16 +30,11 @@ num-traits = { version = "0.2", default-features = false, features = ["std"] } criterion = "0.5.1" [features] -default = [ - "array", - "array_parallel", - "debug", - "double_precision", -] +default = ["array", "parallel", "debug", "double_precision"] debug = ["log"] double_precision = [] -array = ["ndarray", "itertools"] -array_parallel = ["array", "ndarray/rayon", "rayon"] +array = ["ndarray"] +parallel = ["array", "ndarray/rayon", "rayon"] # [[bench]] # name = "virtual_temperature" diff --git a/src/formula.rs b/src/formula.rs index 96cf86e..7c5d1cd 100644 --- a/src/formula.rs +++ b/src/formula.rs @@ -2,7 +2,7 @@ use crate::{errors::InputError, quantities::ThermodynamicQuantity}; use ndarray::{Array, Dimension, FoldWhile, Zip}; -use rayon::iter::{ParallelIterator, ParallelBridge, IntoParallelIterator}; +use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; pub trait Formula1 { #[allow(missing_docs)] @@ -19,12 +19,14 @@ pub trait Formula1 { #[cfg(not(feature = "debug"))] Self::validate_inputs(i1)?; #[cfg(feature = "debug")] + #[cfg(debug_assertions)] Self::validate_inputs_loggerr(i1)?; Ok(Self::compute_unchecked(i1)) } #[cfg(feature = "debug")] + #[cfg(debug_assertions)] #[inline(always)] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] @@ -55,22 +57,37 @@ pub trait Formula1 { #[allow(clippy::missing_errors_doc)] fn compute_ndarray(i1: &Array) -> Result, InputError> { Zip::from(i1) - .fold_while(Ok(()), |_, &a| match Self::validate_inputs(a) { + .fold_while(Ok(()), |_, &i1| match Self::validate_inputs(i1) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), }) .into_inner()?; - Ok(Zip::from(i1).map_collect(|&a| Self::compute_unchecked(a))) + Ok(Zip::from(i1).map_collect(|&i1| Self::compute_unchecked(i1))) } - #[cfg(feature = "array")] + #[cfg(feature = "parallel")] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] fn compute_vec_parallel(i1: &[I1]) -> Result, InputError> { - i1.into_par_iter().map(|&i1| Self::compute(i1)).collect() } + + #[cfg(feature = "parallel")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_ndarray_parallel( + i1: &Array, + ) -> Result, InputError> { + Zip::from(i1) + .fold_while(Ok(()), |_, &a| match Self::validate_inputs(a) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(Zip::from(i1).par_map_collect(|&a| Self::compute_unchecked(a))) + } } pub trait Formula2 { @@ -88,12 +105,14 @@ pub trait Formula2 Result, InputError> { + i1.iter() + .zip(i2.iter()) + .map(|(&i1, &i2)| Self::compute(i1, i2)) + .collect() + } + + #[cfg(feature = "array")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_ndarray( + i1: &Array, + i2: &Array, + ) -> Result, InputError> { + Zip::from(i1) + .and(i2) + .fold_while(Ok(()), |_, &i1, &i2| match Self::validate_inputs(i1, i2) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(Zip::from(i1) + .and(i2) + .map_collect(|&i1, &i2| Self::compute_unchecked(i1, i2))) + } + + #[cfg(feature = "parallel")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_vec_parallel(i1: &[I1], i2: &[I2]) -> Result, InputError> { + i1.into_par_iter() + .zip(i2) + .map(|(&i1, &i2)| Self::compute(i1, i2)) + .collect() + } + + #[cfg(feature = "parallel")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_ndarray_parallel( + i1: &Array, + i2: &Array, + ) -> Result, InputError> { + Zip::from(i1) + .and(i2) + .fold_while(Ok(()), |_, &i1, &i2| match Self::validate_inputs(i1, i2) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }) + .into_inner()?; + + Ok(Zip::from(i1) + .and(i2) + .par_map_collect(|&i1, &i2| Self::compute_unchecked(i1, i2))) + } } pub trait Formula3< @@ -135,12 +214,14 @@ pub trait Formula3< #[cfg(not(feature = "debug"))] Self::validate_inputs(i1, i2, i3)?; #[cfg(feature = "debug")] + #[cfg(debug_assertions)] Self::validate_inputs_loggerr(i1, i2, i3)?; Ok(Self::compute_unchecked(i1, i2, i3)) } #[cfg(feature = "debug")] + #[cfg(debug_assertions)] #[inline(always)] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] @@ -160,4 +241,76 @@ pub trait Formula3< Err(err) }) } + + #[cfg(feature = "array")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_vec(i1: &[I1], i2: &[I2], i3: &[I3]) -> Result, InputError> { + i1.iter() + .zip(i2.iter()) + .zip(i3.iter()) + .map(|((&i1, &i2), &i3)| Self::compute(i1, i2, i3)) + .collect() + } + + #[cfg(feature = "array")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_ndarray( + i1: &Array, + i2: &Array, + i3: &Array, + ) -> Result, InputError> { + Zip::from(i1) + .and(i2) + .and(i3) + .fold_while(Ok(()), |_, &i1, &i2, &i3| { + match Self::validate_inputs(i1, i2, i3) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + } + }) + .into_inner()?; + + Ok(Zip::from(i1) + .and(i2) + .and(i3) + .map_collect(|&i1, &i2, &i3| Self::compute_unchecked(i1, i2, i3))) + } + + #[cfg(feature = "parallel")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_vec_parallel(i1: &[I1], i2: &[I2], i3: &[I3]) -> Result, InputError> { + i1.into_par_iter() + .zip(i2) + .zip(i3) + .map(|((&i1, &i2), &i3)| Self::compute(i1, i2, i3)) + .collect() + } + + #[cfg(feature = "array")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_ndarray_parallel( + i1: &Array, + i2: &Array, + i3: &Array, + ) -> Result, InputError> { + Zip::from(i1) + .and(i2) + .and(i3) + .fold_while(Ok(()), |_, &i1, &i2, &i3| { + match Self::validate_inputs(i1, i2, i3) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + } + }) + .into_inner()?; + + Ok(Zip::from(i1) + .and(i2) + .and(i3) + .par_map_collect(|&i1, &i2, &i3| Self::compute_unchecked(i1, i2, i3))) + } } diff --git a/src/quantities.rs b/src/quantities.rs index e998b36..c2cd90e 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -5,7 +5,9 @@ use uom::si::{pressure::pascal, ratio::ratio, thermodynamic_temperature::kelvin} use crate::{Float, Storage}; use std::fmt::Debug; -pub trait ThermodynamicQuantity: Debug + Clone + Copy + PartialEq + PartialOrd + Default { +pub trait ThermodynamicQuantity: + Debug + Clone + Copy + PartialEq + PartialOrd + Default + Send + Sync +{ fn get_si_value(&self) -> Float; fn new_si(value: Float) -> Self; } From c66bc2ae0ecdaf21e9cf9ed4cf39a0b4efa3e9f8 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Fri, 23 Feb 2024 16:01:23 +0100 Subject: [PATCH 035/102] begin yet another refactor --- src/equivalent_potential_temperature.rs | 11 + src/lib.rs | 22 +- src/mixing_ratio.rs | 8 + src/potential_temperature.rs | 5 + src/quantities.rs | 61 +++++ src/relative_humidity.rs | 14 ++ src/tests.rs | 47 +++- src/tests/quantities_testing.rs | 122 ++++++++++ src/vapour_pressure.rs | 300 ++++++++++++------------ 9 files changed, 412 insertions(+), 178 deletions(-) create mode 100644 src/tests/quantities_testing.rs diff --git a/src/equivalent_potential_temperature.rs b/src/equivalent_potential_temperature.rs index a3e7ca8..95f8791 100644 --- a/src/equivalent_potential_temperature.rs +++ b/src/equivalent_potential_temperature.rs @@ -256,6 +256,8 @@ impl Formula3 for Acc #[cfg(test)] mod tests { + use std::marker::PhantomData; + use crate::tests::{test_with_2args, Argument}; use super::*; @@ -146,11 +148,13 @@ mod tests { name: "pressure", def_val: 101325.0, range: [100.0, 150_000.0], + _quantity: PhantomData, }, Argument { name: "vapour_pressure", def_val: 3500.0, range: [0.0, 50_000.0], + _quantity: PhantomData, }, 0.022253316630823517, ); @@ -163,11 +167,13 @@ mod tests { name: "dewpoint", def_val: 300.0, range: [273.0, 353.0], + _quantity: PhantomData, }, Argument { name: "pressure", def_val: 101325.0, range: [100.0, 150_000.0], + _quantity: PhantomData, }, 0.022477100514593465, ); @@ -180,11 +186,13 @@ mod tests { name: "dewpoint", def_val: 300.0, range: [232.0, 324.0], + _quantity: PhantomData, }, Argument { name: "pressure", def_val: 101325.0, range: [100.0, 150_000.0], + _quantity: PhantomData, }, 0.022587116896465847, ); diff --git a/src/potential_temperature.rs b/src/potential_temperature.rs index cbab246..0648736 100644 --- a/src/potential_temperature.rs +++ b/src/potential_temperature.rs @@ -97,6 +97,8 @@ impl Formula3 Float; fn new_si(value: Float) -> Self; + fn name(&self) -> &'static str; } #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] @@ -207,6 +208,10 @@ impl ThermodynamicQuantity for DryBulbTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "DryBulbTemperature" + } } impl ThermodynamicQuantity for WetBulbTemperature { fn get_si_value(&self) -> Float { @@ -216,6 +221,10 @@ impl ThermodynamicQuantity for WetBulbTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "WetBulbTemperature" + } } impl ThermodynamicQuantity for DewPointTemperature { fn get_si_value(&self) -> Float { @@ -225,6 +234,10 @@ impl ThermodynamicQuantity for DewPointTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "DewPointTemperature" + } } impl ThermodynamicQuantity for VirtualTemperature { fn get_si_value(&self) -> Float { @@ -234,6 +247,10 @@ impl ThermodynamicQuantity for VirtualTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "VirtualTemperature" + } } impl ThermodynamicQuantity for PotentialTemperature { fn get_si_value(&self) -> Float { @@ -243,6 +260,10 @@ impl ThermodynamicQuantity for PotentialTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "PotentialTemperature" + } } impl ThermodynamicQuantity for EquivalentPotentialTemperature { fn get_si_value(&self) -> Float { @@ -252,6 +273,10 @@ impl ThermodynamicQuantity for EquivalentPotentialTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "EquivalentPotentialTemperature" + } } impl ThermodynamicQuantity for WetBulbPotentialTemperature { fn get_si_value(&self) -> Float { @@ -261,6 +286,10 @@ impl ThermodynamicQuantity for WetBulbPotentialTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "WetBulbPotentialTemperature" + } } impl ThermodynamicQuantity for AtmosphericPressure { @@ -271,6 +300,10 @@ impl ThermodynamicQuantity for AtmosphericPressure { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "AtmosphericPressure" + } } impl ThermodynamicQuantity for VapourPressure { fn get_si_value(&self) -> Float { @@ -280,6 +313,10 @@ impl ThermodynamicQuantity for VapourPressure { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "VapourPressure" + } } impl ThermodynamicQuantity for SaturationVapourPressure { fn get_si_value(&self) -> Float { @@ -289,6 +326,10 @@ impl ThermodynamicQuantity for SaturationVapourPressure { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "SaturationVapourPressure" + } } impl ThermodynamicQuantity for VapourPressureDeficit { @@ -299,6 +340,10 @@ impl ThermodynamicQuantity for VapourPressureDeficit { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "VapourPressureDeficit" + } } impl ThermodynamicQuantity for MixingRatio { @@ -309,6 +354,10 @@ impl ThermodynamicQuantity for MixingRatio { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "MixingRatio" + } } impl ThermodynamicQuantity for SaturationMixingRatio { @@ -319,6 +368,10 @@ impl ThermodynamicQuantity for SaturationMixingRatio { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "SaturationMixingRatio" + } } impl ThermodynamicQuantity for SpecificHumidity { @@ -329,6 +382,10 @@ impl ThermodynamicQuantity for SpecificHumidity { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "SpecificHumidity" + } } impl ThermodynamicQuantity for RelativeHumidity { fn get_si_value(&self) -> Float { @@ -338,4 +395,8 @@ impl ThermodynamicQuantity for RelativeHumidity { fn new_si(value: Float) -> Self { Self::new::(value) } + + fn name(&self) -> &'static str { + "RelativeHumidity" + } } diff --git a/src/relative_humidity.rs b/src/relative_humidity.rs index 54b8e0b..edf359e 100644 --- a/src/relative_humidity.rs +++ b/src/relative_humidity.rs @@ -238,6 +238,8 @@ impl Formula3 { pub name: &'static str, pub def_val: Float, pub range: [Float; 2], + _quantity: PhantomData, +} + +impl Argument { + pub fn new(range: [Float; 2]) -> Self { + Self { + name: "", + def_val: 0.0, + range, + _quantity: PhantomData, + } + } } pub fn test_with_2args< O: ThermodynamicQuantity, - I1: ThermodynamicQuantity, - I2: ThermodynamicQuantity, + I1: ThermodynamicQuantity + TestingQuantity, + I2: ThermodynamicQuantity + TestingQuantity, F: Formula2, >( - arg1: Argument, - arg2: Argument, + arg1: Argument, + arg2: Argument, expected_result: Float, ) { //the first promise of the crate is that returned value @@ -92,8 +109,12 @@ pub fn test_with_2args< assert_eq!(result, expected); } -pub fn test_with_1arg>( - arg1: Argument, +pub fn test_with_1arg< + O: ThermodynamicQuantity, + I1: ThermodynamicQuantity + TestingQuantity, + F: Formula1, +>( + arg1: Argument, expected_result: Float, ) { //the first promise of the crate is that returned value @@ -155,14 +176,14 @@ pub fn test_with_1arg, >( - arg1: Argument, - arg2: Argument, - arg3: Argument, + arg1: Argument, + arg2: Argument, + arg3: Argument, expected_result: Float, ) { //the first promise of the crate is that returned value diff --git a/src/tests/quantities_testing.rs b/src/tests/quantities_testing.rs new file mode 100644 index 0000000..b18a3d6 --- /dev/null +++ b/src/tests/quantities_testing.rs @@ -0,0 +1,122 @@ +use uom::si::{ + pressure::{pascal, pound_force_per_square_inch}, + ratio::{per_mille, percent, ratio}, + thermodynamic_temperature::{degree_fahrenheit, kelvin}, +}; + +use crate::quantities::*; + +pub(crate) trait TestingQuantity: ThermodynamicQuantity { + fn default_si(&self) -> Self; + fn default_imperial(&self) -> Self; +} + +impl TestingQuantity for DryBulbTemperature { + fn default_si(&self) -> Self { + Self::new::(300.0) + } + fn default_imperial(&self) -> Self { + let value = &self.default_si().0.get::(); + + Self::new::(*value) + } +} + +impl TestingQuantity for DewPointTemperature { + fn default_si(&self) -> Self { + Self::new::(290.0) + } + fn default_imperial(&self) -> Self { + let value = &self.default_si().0.get::(); + + Self::new::(*value) + } +} + +impl TestingQuantity for EquivalentPotentialTemperature { + fn default_si(&self) -> Self { + Self::new::(300.0) + } + fn default_imperial(&self) -> Self { + let value = &self.default_si().0.get::(); + + Self::new::(*value) + } +} + +impl TestingQuantity for AtmosphericPressure { + fn default_si(&self) -> Self { + Self::new::(101325.0) + } + fn default_imperial(&self) -> Self { + let value = &self.default_si().0.get::(); + + Self::new::(*value) + } +} + +impl TestingQuantity for VapourPressure { + fn default_si(&self) -> Self { + Self::new::(1706.0) + } + fn default_imperial(&self) -> Self { + let value = &self.default_si().0.get::(); + + Self::new::(*value) + } +} + +impl TestingQuantity for SaturationVapourPressure { + fn default_si(&self) -> Self { + Self::new::(2339.0) + } + fn default_imperial(&self) -> Self { + let value = &self.default_si().0.get::(); + + Self::new::(*value) + } +} + +impl TestingQuantity for MixingRatio { + fn default_si(&self) -> Self { + Self::new::(0.01064) + } + fn default_imperial(&self) -> Self { + let value = &self.default_si().0.get::(); + + Self::new::(*value) + } +} + +impl TestingQuantity for SaturationMixingRatio { + fn default_si(&self) -> Self { + Self::new::(0.01467) + } + fn default_imperial(&self) -> Self { + let value = &self.default_si().0.get::(); + + Self::new::(*value) + } +} + +impl TestingQuantity for RelativeHumidity { + fn default_si(&self) -> Self { + Self::new::(0.5) + } + fn default_imperial(&self) -> Self { + let value = &self.default_si().0.get::(); + + Self::new::(*value) + } +} + +impl TestingQuantity for SpecificHumidity { + fn default_si(&self) -> Self { + Self::new::(0.022) + } + fn default_imperial(&self) -> Self { + let value = &self.default_si().0.get::(); + + Self::new::(*value) + } +} diff --git a/src/vapour_pressure.rs b/src/vapour_pressure.rs index 3b03e41..ca9c0ca 100644 --- a/src/vapour_pressure.rs +++ b/src/vapour_pressure.rs @@ -544,162 +544,154 @@ mod tests { #[test] fn definition1() { test_with_2args::( - Argument { - name: "specific_humidity", - def_val: 0.022, - range: [0.00001, 2.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, + Argument::new([0.00001, 2.0]), + Argument::new([100.0, 150_000.0]), 3536.6680935251343, ); } - #[test] - fn definition2() { - test_with_2args::( - Argument { - name: "saturation_vapour_pressure", - def_val: 3550.0, - range: [0.0, 50_000.0], - }, - Argument { - name: "relative_humidity", - def_val: 0.5, - range: [0.0, 2.0], - }, - 1775.0, - ); - } - - #[test] - fn buck1() { - test_with_2args::( - Argument { - name: "dewpoint", - def_val: 300.0, - range: [232.0, 324.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - 3550.6603579471303, - ); - } - - #[test] - fn buck2() { - test_with_2args::( - Argument { - name: "dewpoint", - def_val: 250.0, - range: [193.0, 274.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - 76.38781790372722, - ); - } - - #[test] - fn buck3() { - test_with_2args::( - Argument { - name: "dewpoint", - def_val: 300.0, - range: [253.0, 324.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - 3548.5041048035896, - ); - } - - #[test] - fn buck4() { - test_with_2args::( - Argument { - name: "dewpoint", - def_val: 250.0, - range: [223.0, 274.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - 76.38685471836712, - ); - } - - #[test] - fn buck3_simplified() { - test_with_1arg::( - Argument { - name: "dewpoint", - def_val: 300.0, - range: [253.0, 324.0], - }, - 3533.6421536199978, - ); - } - - #[test] - fn buck4_simplified() { - test_with_1arg::( - Argument { - name: "dewpoint", - def_val: 250.0, - range: [223.0, 274.0], - }, - 76.04197508519536, - ); - } - - #[test] - fn tetens1() { - test_with_1arg::( - Argument { - name: "dewpoint", - def_val: 300.0, - range: [273.0, 353.0], - }, - 3533.969137160892, - ); - } - - #[test] - fn wexler1() { - test_with_1arg::( - Argument { - name: "dewpoint", - def_val: 300.0, - range: [273.0, 374.0], - }, - 3535.4235919263083, - ); - } - - #[test] - fn wexler2() { - test_with_1arg::( - Argument { - name: "dewpoint", - def_val: 250.0, - range: [173.0, 274.0], - }, - 76.04351136780438, - ); - } + // #[test] + // fn definition2() { + // test_with_2args::( + // Argument { + // name: "saturation_vapour_pressure", + // def_val: 3550.0, + // range: [0.0, 50_000.0], + // }, + // Argument { + // name: "relative_humidity", + // def_val: 0.5, + // range: [0.0, 2.0], + // }, + // 1775.0, + // ); + // } + + // #[test] + // fn buck1() { + // test_with_2args::( + // Argument { + // name: "dewpoint", + // def_val: 300.0, + // range: [232.0, 324.0], + // }, + // Argument { + // name: "pressure", + // def_val: 101325.0, + // range: [100.0, 150_000.0], + // }, + // 3550.6603579471303, + // ); + // } + + // #[test] + // fn buck2() { + // test_with_2args::( + // Argument { + // name: "dewpoint", + // def_val: 250.0, + // range: [193.0, 274.0], + // }, + // Argument { + // name: "pressure", + // def_val: 101325.0, + // range: [100.0, 150_000.0], + // }, + // 76.38781790372722, + // ); + // } + + // #[test] + // fn buck3() { + // test_with_2args::( + // Argument { + // name: "dewpoint", + // def_val: 300.0, + // range: [253.0, 324.0], + // }, + // Argument { + // name: "pressure", + // def_val: 101325.0, + // range: [100.0, 150_000.0], + // }, + // 3548.5041048035896, + // ); + // } + + // #[test] + // fn buck4() { + // test_with_2args::( + // Argument { + // name: "dewpoint", + // def_val: 250.0, + // range: [223.0, 274.0], + // }, + // Argument { + // name: "pressure", + // def_val: 101325.0, + // range: [100.0, 150_000.0], + // }, + // 76.38685471836712, + // ); + // } + + // #[test] + // fn buck3_simplified() { + // test_with_1arg::( + // Argument { + // name: "dewpoint", + // def_val: 300.0, + // range: [253.0, 324.0], + // }, + // 3533.6421536199978, + // ); + // } + + // #[test] + // fn buck4_simplified() { + // test_with_1arg::( + // Argument { + // name: "dewpoint", + // def_val: 250.0, + // range: [223.0, 274.0], + // }, + // 76.04197508519536, + // ); + // } + + // #[test] + // fn tetens1() { + // test_with_1arg::( + // Argument { + // name: "dewpoint", + // def_val: 300.0, + // range: [273.0, 353.0], + // }, + // 3533.969137160892, + // ); + // } + + // #[test] + // fn wexler1() { + // test_with_1arg::( + // Argument { + // name: "dewpoint", + // def_val: 300.0, + // range: [273.0, 374.0], + // }, + // 3535.4235919263083, + // ); + // } + + // #[test] + // fn wexler2() { + // test_with_1arg::( + // Argument { + // name: "dewpoint", + // def_val: 250.0, + // range: [173.0, 274.0], + // }, + // 76.04351136780438, + // ); + // } } From c02972aebc6a37927d857b857c8207da80386655 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 Feb 2024 11:52:48 +0100 Subject: [PATCH 036/102] move floccus-proc to a common repo --- Cargo.toml | 3 + floccus-proc/Cargo.toml | 21 ++++++ floccus-proc/src/lib.rs | 146 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 floccus-proc/Cargo.toml create mode 100644 floccus-proc/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 367f1f9..2e55e2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,9 @@ num-traits = { version = "0.2", default-features = false, features = ["std"] } [dev-dependencies] criterion = "0.5.1" +[workspace] +members = ["floccus-proc"] + [features] default = ["array", "parallel", "debug", "double_precision"] debug = ["log"] diff --git a/floccus-proc/Cargo.toml b/floccus-proc/Cargo.toml new file mode 100644 index 0000000..79f864c --- /dev/null +++ b/floccus-proc/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "floccus-proc" +version = "0.3.0" +edition = "2021" +description = "Procedural macros for floccus crate" +repository = "https://github.com/ScaleWeather/floccus" +readme = "README.md" +keywords = ["macro"] +categories = ["parsing"] +license = "Apache-2.0" +exclude = [ + ".github/*", +] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +syn = { version = "2.0", features = ["full"] } +quote = "1.0" diff --git a/floccus-proc/src/lib.rs b/floccus-proc/src/lib.rs new file mode 100644 index 0000000..9f3d7e5 --- /dev/null +++ b/floccus-proc/src/lib.rs @@ -0,0 +1,146 @@ +//! Procedural macros for floccus. +//! +//! This crate contains procedural attribute macros (currently only one) used by the +//! [floccus](https://crates.io/crates/floccus). But potentially can be used in some +//! other crates, although will never be adapted to work with any other crate. +//! +//! Source code of this crate is a heavily modified copy of [log-derive](https://crates.io/crates/log-derive). +//! Check that crate for more versatile logging procedural macros. + +use quote::{quote, ToTokens}; +use syn::punctuated::Punctuated; +use syn::{ + parse_macro_input, spanned::Spanned, token, Expr, ExprAsync, ExprAwait, ExprBlock, ExprCall, + ExprClosure, ExprParen, FnArg, Ident, ItemFn, Pat, Result, ReturnType, +}; + +/// Not so simple proc macro with no attributes that logs an error +/// when function it is applied to returns `Err()`. Log message contains +/// details of function inputs and returned error. +/// +/// Internally, this macro converts the function into a closure and appends +/// `.map_err()` which passes the error untouched logging it along the way. +#[proc_macro_attribute] +pub fn logerr( + _attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let original_fn: ItemFn = parse_macro_input!(item as ItemFn); + + let input_specs = log_fn_inputs(&original_fn); + + let closure = make_closure(&original_fn); + let new_fn = + generate_function(&closure, &original_fn, input_specs).expect("Failed generating function"); + let new_fn = replace_function_headers(original_fn, new_fn); + + new_fn.into_token_stream().into() +} + +fn log_fn_inputs(func: &ItemFn) -> (String, Punctuated) { + let inputs: Vec = func + .sig + .inputs + .iter() + .cloned() + .map(|arg| match arg { + FnArg::Receiver(arg) => arg.self_token.into(), + FnArg::Typed(pat_type) => { + if let Pat::Ident(ident) = *pat_type.pat { + ident.ident + } else { + unimplemented!() + } + } + }) + .collect(); + + let items: Punctuated<_, token::Comma> = inputs.iter().cloned().collect(); + let fmt = { + let mut fmt = String::with_capacity(inputs.len() * 9); + + for input in inputs { + fmt.push_str(&input.to_string()); + fmt.push_str(": {:?}, "); + } + fmt.pop(); // Remove the extra space. + fmt.pop(); // Remove the extra comma. + fmt + }; + + (fmt, items) +} + +fn make_closure(original: &ItemFn) -> Expr { + match original.sig.asyncness { + Some(asyncness) => Expr::Await(ExprAwait { + attrs: Vec::default(), + await_token: token::Await::default(), + dot_token: token::Dot::default(), + base: Box::new(syn::Expr::Async(ExprAsync { + attrs: Vec::default(), + capture: Some(token::Move { + span: original.span(), + }), + block: *original.block.clone(), + async_token: asyncness, + })), + }), + None => Expr::Call(ExprCall { + attrs: Vec::default(), + args: Punctuated::default(), + paren_token: token::Paren::default(), + func: Box::new(syn::Expr::Paren(ExprParen { + attrs: Vec::default(), + paren_token: token::Paren::default(), + expr: Box::new(syn::Expr::Closure(ExprClosure { + attrs: Vec::default(), + asyncness: Option::default(), + movability: Option::default(), + capture: Some(token::Move { + span: original.span(), + }), + or1_token: token::Or::default(), + inputs: Punctuated::default(), + or2_token: token::Or::default(), + output: ReturnType::Default, + body: Box::new(Expr::Block(ExprBlock { + attrs: Vec::default(), + label: Option::default(), + block: *original.block.clone(), + })), + })), + })), + }), + } +} + +fn generate_function( + closure: &Expr, + original_fn: &ItemFn, + input_specs: (String, Punctuated), +) -> Result { + let (input_fmt, input_items) = input_specs; + + let fmt = original_fn.sig.ident.to_string() + "(" + &input_fmt + ") => {:?}"; + let err_expr: proc_macro2::TokenStream = quote! {log::error!(#fmt, #input_items, err)}; + + let code = { + quote! { + fn temp() { + let result = #closure; + result.map_err(|err| { #err_expr; err }) + } + } + }; + + syn::parse2(code) +} + +fn replace_function_headers(original: ItemFn, new: ItemFn) -> ItemFn { + let block = new.block; + let mut new = original; + new.block = block; + + new +} From 81a53a1b0fb859b664052b2a82ec56bb81096bcc Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 Feb 2024 11:54:26 +0100 Subject: [PATCH 037/102] start proc macros refactor --- floccus-proc/src/lib.rs | 141 +--------------------------------------- 1 file changed, 1 insertion(+), 140 deletions(-) diff --git a/floccus-proc/src/lib.rs b/floccus-proc/src/lib.rs index 9f3d7e5..4f92b83 100644 --- a/floccus-proc/src/lib.rs +++ b/floccus-proc/src/lib.rs @@ -4,143 +4,4 @@ //! [floccus](https://crates.io/crates/floccus). But potentially can be used in some //! other crates, although will never be adapted to work with any other crate. //! -//! Source code of this crate is a heavily modified copy of [log-derive](https://crates.io/crates/log-derive). -//! Check that crate for more versatile logging procedural macros. - -use quote::{quote, ToTokens}; -use syn::punctuated::Punctuated; -use syn::{ - parse_macro_input, spanned::Spanned, token, Expr, ExprAsync, ExprAwait, ExprBlock, ExprCall, - ExprClosure, ExprParen, FnArg, Ident, ItemFn, Pat, Result, ReturnType, -}; - -/// Not so simple proc macro with no attributes that logs an error -/// when function it is applied to returns `Err()`. Log message contains -/// details of function inputs and returned error. -/// -/// Internally, this macro converts the function into a closure and appends -/// `.map_err()` which passes the error untouched logging it along the way. -#[proc_macro_attribute] -pub fn logerr( - _attr: proc_macro::TokenStream, - item: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - let original_fn: ItemFn = parse_macro_input!(item as ItemFn); - - let input_specs = log_fn_inputs(&original_fn); - - let closure = make_closure(&original_fn); - let new_fn = - generate_function(&closure, &original_fn, input_specs).expect("Failed generating function"); - let new_fn = replace_function_headers(original_fn, new_fn); - - new_fn.into_token_stream().into() -} - -fn log_fn_inputs(func: &ItemFn) -> (String, Punctuated) { - let inputs: Vec = func - .sig - .inputs - .iter() - .cloned() - .map(|arg| match arg { - FnArg::Receiver(arg) => arg.self_token.into(), - FnArg::Typed(pat_type) => { - if let Pat::Ident(ident) = *pat_type.pat { - ident.ident - } else { - unimplemented!() - } - } - }) - .collect(); - - let items: Punctuated<_, token::Comma> = inputs.iter().cloned().collect(); - let fmt = { - let mut fmt = String::with_capacity(inputs.len() * 9); - - for input in inputs { - fmt.push_str(&input.to_string()); - fmt.push_str(": {:?}, "); - } - fmt.pop(); // Remove the extra space. - fmt.pop(); // Remove the extra comma. - fmt - }; - - (fmt, items) -} - -fn make_closure(original: &ItemFn) -> Expr { - match original.sig.asyncness { - Some(asyncness) => Expr::Await(ExprAwait { - attrs: Vec::default(), - await_token: token::Await::default(), - dot_token: token::Dot::default(), - base: Box::new(syn::Expr::Async(ExprAsync { - attrs: Vec::default(), - capture: Some(token::Move { - span: original.span(), - }), - block: *original.block.clone(), - async_token: asyncness, - })), - }), - None => Expr::Call(ExprCall { - attrs: Vec::default(), - args: Punctuated::default(), - paren_token: token::Paren::default(), - func: Box::new(syn::Expr::Paren(ExprParen { - attrs: Vec::default(), - paren_token: token::Paren::default(), - expr: Box::new(syn::Expr::Closure(ExprClosure { - attrs: Vec::default(), - asyncness: Option::default(), - movability: Option::default(), - capture: Some(token::Move { - span: original.span(), - }), - or1_token: token::Or::default(), - inputs: Punctuated::default(), - or2_token: token::Or::default(), - output: ReturnType::Default, - body: Box::new(Expr::Block(ExprBlock { - attrs: Vec::default(), - label: Option::default(), - block: *original.block.clone(), - })), - })), - })), - }), - } -} - -fn generate_function( - closure: &Expr, - original_fn: &ItemFn, - input_specs: (String, Punctuated), -) -> Result { - let (input_fmt, input_items) = input_specs; - - let fmt = original_fn.sig.ident.to_string() + "(" + &input_fmt + ") => {:?}"; - let err_expr: proc_macro2::TokenStream = quote! {log::error!(#fmt, #input_items, err)}; - - let code = { - quote! { - fn temp() { - let result = #closure; - result.map_err(|err| { #err_expr; err }) - } - } - }; - - syn::parse2(code) -} - -fn replace_function_headers(original: ItemFn, new: ItemFn) -> ItemFn { - let block = new.block; - let mut new = original; - new.block = block; - - new -} +//! Source code of this crate is a modified copy of [derive-name-macros](https://crates.io/crates/derive-name-macros). From fde30eee12a1c763e7ccf83eb4081debcf8854de Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 Feb 2024 13:13:29 +0100 Subject: [PATCH 038/102] actually remove proc from repo because vscode dont work --- Cargo.toml | 5 ++--- floccus-proc/Cargo.toml | 21 --------------------- floccus-proc/src/lib.rs | 7 ------- 3 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 floccus-proc/Cargo.toml delete mode 100644 floccus-proc/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 2e55e2c..8344406 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,10 @@ keywords = ["oceanography", "thermodynamics", "meteorology", "weather"] categories = ["mathematics", "science"] license = "Apache-2.0" exclude = [".github/*"] +publish = false [dependencies] +floccus-proc = { path = "../floccus-proc" } thiserror = "1.0" float-cmp = "0.9" log = { version = "0.4", optional = true } @@ -29,9 +31,6 @@ num-traits = { version = "0.2", default-features = false, features = ["std"] } [dev-dependencies] criterion = "0.5.1" -[workspace] -members = ["floccus-proc"] - [features] default = ["array", "parallel", "debug", "double_precision"] debug = ["log"] diff --git a/floccus-proc/Cargo.toml b/floccus-proc/Cargo.toml deleted file mode 100644 index 79f864c..0000000 --- a/floccus-proc/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "floccus-proc" -version = "0.3.0" -edition = "2021" -description = "Procedural macros for floccus crate" -repository = "https://github.com/ScaleWeather/floccus" -readme = "README.md" -keywords = ["macro"] -categories = ["parsing"] -license = "Apache-2.0" -exclude = [ - ".github/*", -] - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0" -syn = { version = "2.0", features = ["full"] } -quote = "1.0" diff --git a/floccus-proc/src/lib.rs b/floccus-proc/src/lib.rs deleted file mode 100644 index 4f92b83..0000000 --- a/floccus-proc/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Procedural macros for floccus. -//! -//! This crate contains procedural attribute macros (currently only one) used by the -//! [floccus](https://crates.io/crates/floccus). But potentially can be used in some -//! other crates, although will never be adapted to work with any other crate. -//! -//! Source code of this crate is a modified copy of [derive-name-macros](https://crates.io/crates/derive-name-macros). From f08f304e4cf4bceecc6d903095ef3f4c30a0d404 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 Feb 2024 14:47:08 +0100 Subject: [PATCH 039/102] refactor tests internals --- src/quantities.rs | 112 +++++++++-------------------- src/tests.rs | 117 ++++++++++++------------------ src/tests/quantities.rs | 10 +++ src/tests/quantities_testing.rs | 122 -------------------------------- src/tests/testing_traits.rs | 122 ++++++++++++++++++++++++++++++++ 5 files changed, 211 insertions(+), 272 deletions(-) create mode 100644 src/tests/quantities.rs delete mode 100644 src/tests/quantities_testing.rs create mode 100644 src/tests/testing_traits.rs diff --git a/src/quantities.rs b/src/quantities.rs index 2d3defe..f6d5190 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -1,61 +1,77 @@ #![allow(missing_docs)] +use floccus_proc::Name; use uom::si::{pressure::pascal, ratio::ratio, thermodynamic_temperature::kelvin}; -use crate::{Float, Storage}; +use crate::{errors::InputError, Float, Storage}; use std::fmt::Debug; +pub trait QuantityName { + fn type_name_as_str() -> &'static str; +} + pub trait ThermodynamicQuantity: - Debug + Clone + Copy + PartialEq + PartialOrd + Default + Send + Sync + Debug + Clone + Copy + PartialEq + PartialOrd + Default + Send + Sync + QuantityName { fn get_si_value(&self) -> Float; fn new_si(value: Float) -> Self; - fn name(&self) -> &'static str; + + fn name(&self) -> &'static str { + Self::type_name_as_str() + } + + fn check_range_si(&self, lower_bound: Float, upper_bound: Float) -> Result<(), InputError> { + if !(lower_bound..=upper_bound).contains(&self.get_si_value()) { + return Err(InputError::OutOfRange(self.name().to_string())); + } + + Ok(()) + } } -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct DryBulbTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct WetBulbTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct DewPointTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct VirtualTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct PotentialTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct EquivalentPotentialTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct WetBulbPotentialTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct AtmosphericPressure(pub Storage::Pressure); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct VapourPressure(pub Storage::Pressure); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct SaturationVapourPressure(pub Storage::Pressure); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct VapourPressureDeficit(pub Storage::Pressure); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct MixingRatio(pub Storage::Ratio); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct SaturationMixingRatio(pub Storage::Ratio); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct SpecificHumidity(pub Storage::Ratio); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct RelativeHumidity(pub Storage::Ratio); impl DryBulbTemperature { @@ -208,10 +224,6 @@ impl ThermodynamicQuantity for DryBulbTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "DryBulbTemperature" - } } impl ThermodynamicQuantity for WetBulbTemperature { fn get_si_value(&self) -> Float { @@ -221,10 +233,6 @@ impl ThermodynamicQuantity for WetBulbTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "WetBulbTemperature" - } } impl ThermodynamicQuantity for DewPointTemperature { fn get_si_value(&self) -> Float { @@ -234,10 +242,6 @@ impl ThermodynamicQuantity for DewPointTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "DewPointTemperature" - } } impl ThermodynamicQuantity for VirtualTemperature { fn get_si_value(&self) -> Float { @@ -247,10 +251,6 @@ impl ThermodynamicQuantity for VirtualTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "VirtualTemperature" - } } impl ThermodynamicQuantity for PotentialTemperature { fn get_si_value(&self) -> Float { @@ -260,10 +260,6 @@ impl ThermodynamicQuantity for PotentialTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "PotentialTemperature" - } } impl ThermodynamicQuantity for EquivalentPotentialTemperature { fn get_si_value(&self) -> Float { @@ -273,10 +269,6 @@ impl ThermodynamicQuantity for EquivalentPotentialTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "EquivalentPotentialTemperature" - } } impl ThermodynamicQuantity for WetBulbPotentialTemperature { fn get_si_value(&self) -> Float { @@ -286,10 +278,6 @@ impl ThermodynamicQuantity for WetBulbPotentialTemperature { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "WetBulbPotentialTemperature" - } } impl ThermodynamicQuantity for AtmosphericPressure { @@ -300,10 +288,6 @@ impl ThermodynamicQuantity for AtmosphericPressure { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "AtmosphericPressure" - } } impl ThermodynamicQuantity for VapourPressure { fn get_si_value(&self) -> Float { @@ -313,10 +297,6 @@ impl ThermodynamicQuantity for VapourPressure { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "VapourPressure" - } } impl ThermodynamicQuantity for SaturationVapourPressure { fn get_si_value(&self) -> Float { @@ -326,10 +306,6 @@ impl ThermodynamicQuantity for SaturationVapourPressure { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "SaturationVapourPressure" - } } impl ThermodynamicQuantity for VapourPressureDeficit { @@ -340,10 +316,6 @@ impl ThermodynamicQuantity for VapourPressureDeficit { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "VapourPressureDeficit" - } } impl ThermodynamicQuantity for MixingRatio { @@ -354,10 +326,6 @@ impl ThermodynamicQuantity for MixingRatio { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "MixingRatio" - } } impl ThermodynamicQuantity for SaturationMixingRatio { @@ -368,10 +336,6 @@ impl ThermodynamicQuantity for SaturationMixingRatio { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "SaturationMixingRatio" - } } impl ThermodynamicQuantity for SpecificHumidity { @@ -382,10 +346,6 @@ impl ThermodynamicQuantity for SpecificHumidity { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "SpecificHumidity" - } } impl ThermodynamicQuantity for RelativeHumidity { fn get_si_value(&self) -> Float { @@ -395,8 +355,4 @@ impl ThermodynamicQuantity for RelativeHumidity { fn new_si(value: Float) -> Self { Self::new::(value) } - - fn name(&self) -> &'static str { - "RelativeHumidity" - } } diff --git a/src/tests.rs b/src/tests.rs index 4d20f13..dc0ce8a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,4 +1,5 @@ -pub(crate) mod quantities_testing; +mod quantities; +pub(crate) mod testing_traits; use crate::errors::InputError; use crate::formula::{Formula1, Formula2, Formula3}; @@ -8,12 +9,10 @@ use float_cmp::assert_approx_eq; use std::marker::PhantomData; use std::mem::discriminant; -use self::quantities_testing::TestingQuantity; +use self::testing_traits::TestingQuantity; #[derive(Copy, Clone, Debug)] pub struct Argument { - pub name: &'static str, - pub def_val: Float, pub range: [Float; 2], _quantity: PhantomData, } @@ -21,12 +20,14 @@ pub struct Argument { impl Argument { pub fn new(range: [Float; 2]) -> Self { Self { - name: "", - def_val: 0.0, range, _quantity: PhantomData, } } + + pub fn quantity_name(&self) -> String { + I::type_name_as_str().to_string() + } } pub fn test_with_2args< @@ -41,7 +42,7 @@ pub fn test_with_2args< ) { //the first promise of the crate is that returned value //is calculated correctly - let result = F::compute(I1::new_si(arg1.def_val), I2::new_si(arg2.def_val)).unwrap(); + let result = F::compute(I1::default_si(), I2::default_si()).unwrap(); assert_approx_eq!( Float, result.get_si_value(), @@ -56,9 +57,9 @@ pub fn test_with_2args< // is not Ok() then it is Err() // here we don't care about error being correct let results = vec![ - F::compute(I1::new_si(arg1.def_val), I2::new_si(arg2.def_val)), - F::compute(I1::new_si(-9999.0), I2::new_si(arg2.def_val)), - F::compute(I1::new_si(arg1.def_val), I2::new_si(-9999.0)), + F::compute(I1::default_si(), I2::default_si()), + F::compute(I1::new_si(-9999.0), I2::default_si()), + F::compute(I1::default_si(), I2::new_si(-9999.0)), F::compute(I1::new_si(-9999.0), I2::new_si(-9999.0)), ]; @@ -96,16 +97,16 @@ pub fn test_with_2args< //the fourth promise of the crate is to return an error with //erronous variable name when input is out of range - let expected = InputError::OutOfRange(String::from(arg1.name)); - let result = F::compute(I1::new_si(arg1.range[0] - 0.1), I2::new_si(arg2.def_val)).unwrap_err(); + let expected = InputError::OutOfRange(arg1.quantity_name()); + let result = F::compute(I1::new_si(arg1.range[0] - 0.1), I2::default_si()).unwrap_err(); assert_eq!(result, expected); - let result = F::compute(I1::new_si(arg1.range[1] + 0.1), I2::new_si(arg2.def_val)).unwrap_err(); + let result = F::compute(I1::new_si(arg1.range[1] + 0.1), I2::default_si()).unwrap_err(); assert_eq!(result, expected); - let expected = InputError::OutOfRange(String::from(arg2.name)); - let result = F::compute(I1::new_si(arg1.def_val), I2::new_si(arg2.range[0] - 0.1)).unwrap_err(); + let expected = InputError::OutOfRange(arg2.quantity_name()); + let result = F::compute(I1::default_si(), I2::new_si(arg2.range[0] - 0.1)).unwrap_err(); assert_eq!(result, expected); - let result = F::compute(I1::new_si(arg1.def_val), I2::new_si(arg2.range[1] + 0.1)).unwrap_err(); + let result = F::compute(I1::default_si(), I2::new_si(arg2.range[1] + 0.1)).unwrap_err(); assert_eq!(result, expected); } @@ -119,7 +120,7 @@ pub fn test_with_1arg< ) { //the first promise of the crate is that returned value //is calculated correctly - let result = F::compute(I1::new_si(arg1.def_val)).unwrap(); + let result = F::compute(I1::default_si()).unwrap(); assert_approx_eq!( Float, result.get_si_value(), @@ -134,7 +135,7 @@ pub fn test_with_1arg< // is not Ok() then it is Err() // here we don't care about error being correct let results = vec![ - F::compute(I1::new_si(arg1.def_val)), + F::compute(I1::default_si()), F::compute(I1::new_si(-9999.0)), ]; @@ -167,7 +168,7 @@ pub fn test_with_1arg< //the fourth promise of the crate is to return an error with //erronous variable name when input is out of range - let expected = InputError::OutOfRange(String::from(arg1.name)); + let expected = InputError::OutOfRange(arg1.quantity_name()); let result = F::compute(I1::new_si(arg1.range[0] - 0.1)).unwrap_err(); assert_eq!(result, expected); let result = F::compute(I1::new_si(arg1.range[1] + 0.1)).unwrap_err(); @@ -189,9 +190,9 @@ pub fn test_with_3args< //the first promise of the crate is that returned value //is calculated correctly let result = F::compute( - I1::new_si(arg1.def_val), - I2::new_si(arg2.def_val), - I3::new_si(arg3.def_val), + I1::default_si(), + I2::default_si(), + I3::default_si(), ) .unwrap(); assert_approx_eq!( @@ -208,41 +209,13 @@ pub fn test_with_3args< // is not Ok() then it is Err() // here we don't care about error being correct let results = vec![ - F::compute( - I1::new_si(arg1.def_val), - I2::new_si(arg2.def_val), - I3::new_si(arg3.def_val), - ), - F::compute( - I1::new_si(-9999.0), - I2::new_si(arg2.def_val), - I3::new_si(arg3.def_val), - ), - F::compute( - I1::new_si(arg1.def_val), - I2::new_si(-9999.0), - I3::new_si(arg3.def_val), - ), - F::compute( - I1::new_si(arg1.def_val), - I2::new_si(arg2.def_val), - I3::new_si(-9999.0), - ), - F::compute( - I1::new_si(-9999.0), - I2::new_si(-9999.0), - I3::new_si(arg3.def_val), - ), - F::compute( - I1::new_si(arg1.def_val), - I2::new_si(-9999.0), - I3::new_si(-9999.0), - ), - F::compute( - I1::new_si(-9999.0), - I2::new_si(arg2.def_val), - I3::new_si(-9999.0), - ), + F::compute(I1::default_si(), I2::default_si(), I3::default_si()), + F::compute(I1::new_si(-9999.0), I2::default_si(), I3::default_si()), + F::compute(I1::default_si(), I2::new_si(-9999.0), I3::default_si()), + F::compute(I1::default_si(), I2::default_si(), I3::new_si(-9999.0)), + F::compute(I1::default_si(), I2::new_si(-9999.0), I3::default_si()), + F::compute(I1::default_si(), I2::new_si(-9999.0), I3::new_si(-9999.0)), + F::compute(I1::new_si(-9999.0), I2::default_si(), I3::new_si(-9999.0)), F::compute( I1::new_si(-9999.0), I2::new_si(-9999.0), @@ -289,49 +262,49 @@ pub fn test_with_3args< //the fourth promise of the crate is to return an error with //erronous variable name when input is out of range - let expected = InputError::OutOfRange(String::from(arg1.name)); + let expected = InputError::OutOfRange(arg1.quantity_name()); let result = F::compute( I1::new_si(arg1.range[0] - 0.1), - I2::new_si(arg2.def_val), - I3::new_si(arg3.def_val), + I2::default_si(), + I3::default_si(), ) .unwrap_err(); assert_eq!(result, expected); let result = F::compute( I1::new_si(arg1.range[1] + 0.1), - I2::new_si(arg2.def_val), - I3::new_si(arg3.def_val), + I2::default_si(), + I3::default_si(), ) .unwrap_err(); assert_eq!(result, expected); - let expected = InputError::OutOfRange(String::from(arg2.name)); + let expected = InputError::OutOfRange(arg2.quantity_name()); let result = F::compute( - I1::new_si(arg1.def_val), + I1::default_si(), I2::new_si(arg2.range[0] - 0.1), - I3::new_si(arg3.def_val), + I3::default_si(), ) .unwrap_err(); assert_eq!(result, expected); let result = F::compute( - I1::new_si(arg1.def_val), + I1::default_si(), I2::new_si(arg2.range[1] + 0.1), - I3::new_si(arg3.def_val), + I3::default_si(), ) .unwrap_err(); assert_eq!(result, expected); - let expected = InputError::OutOfRange(String::from(arg3.name)); + let expected = InputError::OutOfRange(arg3.quantity_name()); let result = F::compute( - I1::new_si(arg1.def_val), - I2::new_si(arg2.def_val), + I1::default_si(), + I2::default_si(), I3::new_si(arg3.range[0] - 0.1), ) .unwrap_err(); assert_eq!(result, expected); let result = F::compute( - I1::new_si(arg1.def_val), - I2::new_si(arg2.def_val), + I1::default_si(), + I2::default_si(), I3::new_si(arg3.range[1] + 0.1), ) .unwrap_err(); diff --git a/src/tests/quantities.rs b/src/tests/quantities.rs new file mode 100644 index 0000000..36a9692 --- /dev/null +++ b/src/tests/quantities.rs @@ -0,0 +1,10 @@ +use crate::{ + quantities::{DryBulbTemperature, ThermodynamicQuantity}, + tests::testing_traits::TestingQuantity, +}; + +#[test] +fn quantity_name() { + let quantity = DryBulbTemperature::default_si(); + assert_eq!(quantity.name(), "DryBulbTemperature"); +} diff --git a/src/tests/quantities_testing.rs b/src/tests/quantities_testing.rs deleted file mode 100644 index b18a3d6..0000000 --- a/src/tests/quantities_testing.rs +++ /dev/null @@ -1,122 +0,0 @@ -use uom::si::{ - pressure::{pascal, pound_force_per_square_inch}, - ratio::{per_mille, percent, ratio}, - thermodynamic_temperature::{degree_fahrenheit, kelvin}, -}; - -use crate::quantities::*; - -pub(crate) trait TestingQuantity: ThermodynamicQuantity { - fn default_si(&self) -> Self; - fn default_imperial(&self) -> Self; -} - -impl TestingQuantity for DryBulbTemperature { - fn default_si(&self) -> Self { - Self::new::(300.0) - } - fn default_imperial(&self) -> Self { - let value = &self.default_si().0.get::(); - - Self::new::(*value) - } -} - -impl TestingQuantity for DewPointTemperature { - fn default_si(&self) -> Self { - Self::new::(290.0) - } - fn default_imperial(&self) -> Self { - let value = &self.default_si().0.get::(); - - Self::new::(*value) - } -} - -impl TestingQuantity for EquivalentPotentialTemperature { - fn default_si(&self) -> Self { - Self::new::(300.0) - } - fn default_imperial(&self) -> Self { - let value = &self.default_si().0.get::(); - - Self::new::(*value) - } -} - -impl TestingQuantity for AtmosphericPressure { - fn default_si(&self) -> Self { - Self::new::(101325.0) - } - fn default_imperial(&self) -> Self { - let value = &self.default_si().0.get::(); - - Self::new::(*value) - } -} - -impl TestingQuantity for VapourPressure { - fn default_si(&self) -> Self { - Self::new::(1706.0) - } - fn default_imperial(&self) -> Self { - let value = &self.default_si().0.get::(); - - Self::new::(*value) - } -} - -impl TestingQuantity for SaturationVapourPressure { - fn default_si(&self) -> Self { - Self::new::(2339.0) - } - fn default_imperial(&self) -> Self { - let value = &self.default_si().0.get::(); - - Self::new::(*value) - } -} - -impl TestingQuantity for MixingRatio { - fn default_si(&self) -> Self { - Self::new::(0.01064) - } - fn default_imperial(&self) -> Self { - let value = &self.default_si().0.get::(); - - Self::new::(*value) - } -} - -impl TestingQuantity for SaturationMixingRatio { - fn default_si(&self) -> Self { - Self::new::(0.01467) - } - fn default_imperial(&self) -> Self { - let value = &self.default_si().0.get::(); - - Self::new::(*value) - } -} - -impl TestingQuantity for RelativeHumidity { - fn default_si(&self) -> Self { - Self::new::(0.5) - } - fn default_imperial(&self) -> Self { - let value = &self.default_si().0.get::(); - - Self::new::(*value) - } -} - -impl TestingQuantity for SpecificHumidity { - fn default_si(&self) -> Self { - Self::new::(0.022) - } - fn default_imperial(&self) -> Self { - let value = &self.default_si().0.get::(); - - Self::new::(*value) - } -} diff --git a/src/tests/testing_traits.rs b/src/tests/testing_traits.rs new file mode 100644 index 0000000..38ab57c --- /dev/null +++ b/src/tests/testing_traits.rs @@ -0,0 +1,122 @@ +use uom::si::{ + pressure::{pascal, pound_force_per_square_inch}, + ratio::{per_mille, percent, ratio}, + thermodynamic_temperature::{degree_fahrenheit, kelvin}, +}; + +use crate::quantities::*; + +pub(crate) trait TestingQuantity: ThermodynamicQuantity { + fn default_si() -> Self; + fn default_imperial() -> Self; +} + +impl TestingQuantity for DryBulbTemperature { + fn default_si() -> Self { + Self::new::(300.0) + } + fn default_imperial() -> Self { + let value = Self::default_si().0.get::(); + + Self::new::(value) + } +} + +impl TestingQuantity for DewPointTemperature { + fn default_si() -> Self { + Self::new::(290.0) + } + fn default_imperial() -> Self { + let value = Self::default_si().0.get::(); + + Self::new::(value) + } +} + +impl TestingQuantity for EquivalentPotentialTemperature { + fn default_si() -> Self { + Self::new::(300.0) + } + fn default_imperial() -> Self { + let value = Self::default_si().0.get::(); + + Self::new::(value) + } +} + +impl TestingQuantity for AtmosphericPressure { + fn default_si() -> Self { + Self::new::(101325.0) + } + fn default_imperial() -> Self { + let value = Self::default_si().0.get::(); + + Self::new::(value) + } +} + +impl TestingQuantity for VapourPressure { + fn default_si() -> Self { + Self::new::(1706.0) + } + fn default_imperial() -> Self { + let value = Self::default_si().0.get::(); + + Self::new::(value) + } +} + +impl TestingQuantity for SaturationVapourPressure { + fn default_si() -> Self { + Self::new::(2339.0) + } + fn default_imperial() -> Self { + let value = Self::default_si().0.get::(); + + Self::new::(value) + } +} + +impl TestingQuantity for MixingRatio { + fn default_si() -> Self { + Self::new::(0.01064) + } + fn default_imperial() -> Self { + let value = Self::default_si().0.get::(); + + Self::new::(value) + } +} + +impl TestingQuantity for SaturationMixingRatio { + fn default_si() -> Self { + Self::new::(0.01467) + } + fn default_imperial() -> Self { + let value = Self::default_si().0.get::(); + + Self::new::(value) + } +} + +impl TestingQuantity for RelativeHumidity { + fn default_si() -> Self { + Self::new::(0.5) + } + fn default_imperial() -> Self { + let value = Self::default_si().0.get::(); + + Self::new::(value) + } +} + +impl TestingQuantity for SpecificHumidity { + fn default_si() -> Self { + Self::new::(0.022) + } + fn default_imperial() -> Self { + let value = Self::default_si().0.get::(); + + Self::new::(value) + } +} From 472ab35f0b476778ed229fc57192fe12cc3d21f8 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sun, 25 Feb 2024 10:58:39 +0100 Subject: [PATCH 040/102] WIP --- src/quantities.rs | 2 + src/tests.rs | 7 +- src/tests/testing_traits.rs | 11 +- src/vapour_pressure.rs | 331 +++++++++++------------------------- 4 files changed, 111 insertions(+), 240 deletions(-) diff --git a/src/quantities.rs b/src/quantities.rs index f6d5190..17b7db8 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -20,6 +20,8 @@ pub trait ThermodynamicQuantity: Self::type_name_as_str() } + #[must_use] + #[inline(always)] fn check_range_si(&self, lower_bound: Float, upper_bound: Float) -> Result<(), InputError> { if !(lower_bound..=upper_bound).contains(&self.get_si_value()) { return Err(InputError::OutOfRange(self.name().to_string())); diff --git a/src/tests.rs b/src/tests.rs index dc0ce8a..0da13c1 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -189,12 +189,7 @@ pub fn test_with_3args< ) { //the first promise of the crate is that returned value //is calculated correctly - let result = F::compute( - I1::default_si(), - I2::default_si(), - I3::default_si(), - ) - .unwrap(); + let result = F::compute(I1::default_si(), I2::default_si(), I3::default_si()).unwrap(); assert_approx_eq!( Float, result.get_si_value(), diff --git a/src/tests/testing_traits.rs b/src/tests/testing_traits.rs index 38ab57c..f2663b4 100644 --- a/src/tests/testing_traits.rs +++ b/src/tests/testing_traits.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + use uom::si::{ pressure::{pascal, pound_force_per_square_inch}, ratio::{per_mille, percent, ratio}, @@ -6,6 +8,7 @@ use uom::si::{ use crate::quantities::*; +pub(crate) trait ReferenceAtmosphere {} pub(crate) trait TestingQuantity: ThermodynamicQuantity { fn default_si() -> Self; fn default_imperial() -> Self; @@ -57,7 +60,7 @@ impl TestingQuantity for AtmosphericPressure { impl TestingQuantity for VapourPressure { fn default_si() -> Self { - Self::new::(1706.0) + Self::new::(1920.0) } fn default_imperial() -> Self { let value = Self::default_si().0.get::(); @@ -68,7 +71,7 @@ impl TestingQuantity for VapourPressure { impl TestingQuantity for SaturationVapourPressure { fn default_si() -> Self { - Self::new::(2339.0) + Self::new::(3535.0) } fn default_imperial() -> Self { let value = Self::default_si().0.get::(); @@ -79,7 +82,7 @@ impl TestingQuantity for SaturationVapourPressure { impl TestingQuantity for MixingRatio { fn default_si() -> Self { - Self::new::(0.01064) + Self::new::(0.012) } fn default_imperial() -> Self { let value = Self::default_si().0.get::(); @@ -90,7 +93,7 @@ impl TestingQuantity for MixingRatio { impl TestingQuantity for SaturationMixingRatio { fn default_si() -> Self { - Self::new::(0.01467) + Self::new::(0.022) } fn default_imperial() -> Self { let value = Self::default_si().0.get::(); diff --git a/src/vapour_pressure.rs b/src/vapour_pressure.rs index ca9c0ca..c9e06a5 100644 --- a/src/vapour_pressure.rs +++ b/src/vapour_pressure.rs @@ -35,16 +35,8 @@ impl Formula2 for Defini specific_humidity: SpecificHumidity, pressure: AtmosphericPressure, ) -> Result<(), InputError> { - let specific_humidity_si = specific_humidity.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(0.00001..=2.0).contains(&specific_humidity_si) { - return Err(InputError::OutOfRange(String::from("specific_humidity"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + specific_humidity.check_range_si(0.00001, 2.0)?; + pressure.check_range_si(100.0, 150_000.0)?; Ok(()) } @@ -77,18 +69,8 @@ impl Formula2 for D saturation_vapour_pressure: SaturationVapourPressure, relative_humidity: RelativeHumidity, ) -> Result<(), InputError> { - let saturation_vapour_pressure_si = saturation_vapour_pressure.get_si_value(); - let relative_humidity_si = relative_humidity.get_si_value(); - - if !(0.0..=2.0).contains(&relative_humidity_si) { - return Err(InputError::OutOfRange(String::from("relative_humidity"))); - } - - if !(0.0..=50_000.0).contains(&saturation_vapour_pressure_si) { - return Err(InputError::OutOfRange(String::from( - "saturation_vapour_pressure", - ))); - } + relative_humidity.check_range_si(0.0, 2.0)?; + saturation_vapour_pressure.check_range_si(0.0, 50_000.0)?; Ok(()) } @@ -120,16 +102,8 @@ impl Formula2 for Buc dewpoint: DewPointTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { - let dewpoint_si = dewpoint.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(232.0..=324.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + dewpoint.check_range_si(232.0, 324.0)?; + pressure.check_range_si(100.0, 150_000.0)?; Ok(()) } @@ -177,16 +151,8 @@ impl Formula2 for Buc dewpoint: DewPointTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { - let dewpoint_si = dewpoint.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(193.0..=274.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + dewpoint.check_range_si(193.0, 274.0)?; + pressure.check_range_si(100.0, 150_000.0)?; Ok(()) } @@ -234,16 +200,8 @@ impl Formula2 for Buc dewpoint: DewPointTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { - let dewpoint_si = dewpoint.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(253.0..=324.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + dewpoint.check_range_si(253.0, 324.0)?; + pressure.check_range_si(100.0, 150_000.0)?; Ok(()) } @@ -283,11 +241,7 @@ pub struct Buck3Simplified; impl Formula1 for Buck3Simplified { #[inline(always)] fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { - let dewpoint_si = dewpoint.get_si_value(); - - if !(253.0..=324.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } + dewpoint.check_range_si(253.0, 324.0)?; Ok(()) } @@ -324,16 +278,8 @@ impl Formula2 for Buc dewpoint: DewPointTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { - let dewpoint_si = dewpoint.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(223.0..=274.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + dewpoint.check_range_si(223.0, 274.0)?; + pressure.check_range_si(100.0, 150_000.0)?; Ok(()) } @@ -373,11 +319,7 @@ pub struct Buck4Simplified; impl Formula1 for Buck4Simplified { #[inline(always)] fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { - let dewpoint_si = dewpoint.get_si_value(); - - if !(223.0..=274.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } + dewpoint.check_range_si(223.0, 274.0)?; Ok(()) } @@ -410,11 +352,7 @@ pub struct Tetens1; impl Formula1 for Tetens1 { #[inline(always)] fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { - let dewpoint_si = dewpoint.get_si_value(); - - if !(273.0..=353.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } + dewpoint.check_range_si(273.0, 353.0)?; Ok(()) } @@ -447,11 +385,7 @@ pub struct Wexler1; impl Formula1 for Wexler1 { #[inline(always)] fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { - let dewpoint_si = dewpoint.get_si_value(); - - if !(273.0..=374.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } + dewpoint.check_range_si(273.0, 374.0)?; Ok(()) } @@ -496,11 +430,8 @@ pub struct Wexler2; impl Formula1 for Wexler2 { #[inline(always)] fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { - let dewpoint_si = dewpoint.get_si_value(); + dewpoint.check_range_si(173.0, 274.0)?; - if !(173.0..=274.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } Ok(()) } @@ -550,148 +481,88 @@ mod tests { ); } - // #[test] - // fn definition2() { - // test_with_2args::( - // Argument { - // name: "saturation_vapour_pressure", - // def_val: 3550.0, - // range: [0.0, 50_000.0], - // }, - // Argument { - // name: "relative_humidity", - // def_val: 0.5, - // range: [0.0, 2.0], - // }, - // 1775.0, - // ); - // } - - // #[test] - // fn buck1() { - // test_with_2args::( - // Argument { - // name: "dewpoint", - // def_val: 300.0, - // range: [232.0, 324.0], - // }, - // Argument { - // name: "pressure", - // def_val: 101325.0, - // range: [100.0, 150_000.0], - // }, - // 3550.6603579471303, - // ); - // } - - // #[test] - // fn buck2() { - // test_with_2args::( - // Argument { - // name: "dewpoint", - // def_val: 250.0, - // range: [193.0, 274.0], - // }, - // Argument { - // name: "pressure", - // def_val: 101325.0, - // range: [100.0, 150_000.0], - // }, - // 76.38781790372722, - // ); - // } - - // #[test] - // fn buck3() { - // test_with_2args::( - // Argument { - // name: "dewpoint", - // def_val: 300.0, - // range: [253.0, 324.0], - // }, - // Argument { - // name: "pressure", - // def_val: 101325.0, - // range: [100.0, 150_000.0], - // }, - // 3548.5041048035896, - // ); - // } - - // #[test] - // fn buck4() { - // test_with_2args::( - // Argument { - // name: "dewpoint", - // def_val: 250.0, - // range: [223.0, 274.0], - // }, - // Argument { - // name: "pressure", - // def_val: 101325.0, - // range: [100.0, 150_000.0], - // }, - // 76.38685471836712, - // ); - // } - - // #[test] - // fn buck3_simplified() { - // test_with_1arg::( - // Argument { - // name: "dewpoint", - // def_val: 300.0, - // range: [253.0, 324.0], - // }, - // 3533.6421536199978, - // ); - // } - - // #[test] - // fn buck4_simplified() { - // test_with_1arg::( - // Argument { - // name: "dewpoint", - // def_val: 250.0, - // range: [223.0, 274.0], - // }, - // 76.04197508519536, - // ); - // } - - // #[test] - // fn tetens1() { - // test_with_1arg::( - // Argument { - // name: "dewpoint", - // def_val: 300.0, - // range: [273.0, 353.0], - // }, - // 3533.969137160892, - // ); - // } - - // #[test] - // fn wexler1() { - // test_with_1arg::( - // Argument { - // name: "dewpoint", - // def_val: 300.0, - // range: [273.0, 374.0], - // }, - // 3535.4235919263083, - // ); - // } - - // #[test] - // fn wexler2() { - // test_with_1arg::( - // Argument { - // name: "dewpoint", - // def_val: 250.0, - // range: [173.0, 274.0], - // }, - // 76.04351136780438, - // ); - // } + #[test] + fn definition2() { + test_with_2args::( + Argument::new([0.0, 50_000.0]), + Argument::new([0.0, 2.0]), + 1767.5, + ); + } + + #[test] + fn buck1() { + test_with_2args::( + Argument::new([232.0, 324.0]), + Argument::new([100.0, 150_000.0]), + 1927.0852679081806, + ); + } + + #[test] + fn buck2() { + test_with_2args::( + Argument::new([193.0, 274.0]), + Argument::new([100.0, 150_000.0]), + 76.38781790372722, + ); + } + + #[test] + fn buck3() { + test_with_2args::( + Argument::new([253.0, 324.0]), + Argument::new([100.0, 150_000.0]), + 3548.5041048035896, + ); + } + + #[test] + fn buck4() { + test_with_2args::( + Argument::new([223.0, 274.0]), + Argument::new([100.0, 150_000.0]), + 76.38685471836712, + ); + } + + #[test] + fn buck3_simplified() { + test_with_1arg::( + Argument::new([253.0, 324.0]), + 3533.6421536199978, + ); + } + + #[test] + fn buck4_simplified() { + test_with_1arg::( + Argument::new([223.0, 274.0]), + 76.04197508519536, + ); + } + + #[test] + fn tetens1() { + test_with_1arg::( + Argument::new([273.0, 353.0]), + 3533.969137160892, + ); + } + + #[test] + fn wexler1() { + test_with_1arg::( + Argument::new([273.0, 374.0]), + 3535.4235919263083, + ); + } + + #[test] + fn wexler2() { + test_with_1arg::( + Argument::new([173.0, 274.0]), + 76.04351136780438, + ); + } } From 9e71a6f981bfb0c18b624f15b8b85fb6d4008cf5 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:31:44 +0100 Subject: [PATCH 041/102] add reasonable reference values --- Cargo.toml | 3 + src/quantities.rs | 61 ----- src/tests.rs | 439 +++++++++++++++++----------------- src/tests/quantities.rs | 10 - src/tests/reference_values.rs | 31 +++ src/tests/testing_traits.rs | 177 ++++++++++---- src/vapour_pressure.rs | 158 ++++++------ 7 files changed, 466 insertions(+), 413 deletions(-) delete mode 100644 src/tests/quantities.rs create mode 100644 src/tests/reference_values.rs diff --git a/Cargo.toml b/Cargo.toml index 8344406..8d3b33f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,9 @@ uom = { version = "0.35", default-features = false, features = [ ] } num-traits = { version = "0.2", default-features = false, features = ["std"] } +[lib] +doctest = false + [dev-dependencies] criterion = "0.5.1" diff --git a/src/quantities.rs b/src/quantities.rs index 17b7db8..c08384b 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -14,7 +14,6 @@ pub trait ThermodynamicQuantity: Debug + Clone + Copy + PartialEq + PartialOrd + Default + Send + Sync + QuantityName { fn get_si_value(&self) -> Float; - fn new_si(value: Float) -> Self; fn name(&self) -> &'static str { Self::type_name_as_str() @@ -222,139 +221,79 @@ impl ThermodynamicQuantity for DryBulbTemperature { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for WetBulbTemperature { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for DewPointTemperature { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for VirtualTemperature { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for PotentialTemperature { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for EquivalentPotentialTemperature { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for WetBulbPotentialTemperature { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for AtmosphericPressure { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for VapourPressure { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for SaturationVapourPressure { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for VapourPressureDeficit { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for MixingRatio { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for SaturationMixingRatio { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for SpecificHumidity { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } impl ThermodynamicQuantity for RelativeHumidity { fn get_si_value(&self) -> Float { self.0.get::() } - - fn new_si(value: Float) -> Self { - Self::new::(value) - } } diff --git a/src/tests.rs b/src/tests.rs index 0da13c1..f51a72d 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,23 +1,22 @@ -mod quantities; pub(crate) mod testing_traits; +pub(crate) mod reference_values; use crate::errors::InputError; use crate::formula::{Formula1, Formula2, Formula3}; -use crate::quantities::ThermodynamicQuantity; use crate::Float; use float_cmp::assert_approx_eq; use std::marker::PhantomData; use std::mem::discriminant; -use self::testing_traits::TestingQuantity; +use self::testing_traits::{ReferenceAtmosphere, TestingQuantity}; #[derive(Copy, Clone, Debug)] -pub struct Argument { +pub struct Argument { pub range: [Float; 2], _quantity: PhantomData, } -impl Argument { +impl Argument { pub fn new(range: [Float; 2]) -> Self { Self { range, @@ -28,27 +27,34 @@ impl Argument { pub fn quantity_name(&self) -> String { I::type_name_as_str().to_string() } + + pub fn ref_val(&self, atm: ReferenceAtmosphere) -> I { + I::ref_val_si(atm) + } +} + +fn check_result(result: T, atm: ReferenceAtmosphere, eps: Float) { + let expected = T::ref_val_si(atm).get_si_value(); + let result = result.get_si_value(); + + assert_approx_eq!(Float, result, expected, epsilon = eps) } pub fn test_with_2args< - O: ThermodynamicQuantity, - I1: ThermodynamicQuantity + TestingQuantity, - I2: ThermodynamicQuantity + TestingQuantity, + O: TestingQuantity, + I1: TestingQuantity, + I2: TestingQuantity, F: Formula2, >( arg1: Argument, arg2: Argument, - expected_result: Float, + atm: ReferenceAtmosphere, + eps: Float, ) { //the first promise of the crate is that returned value //is calculated correctly - let result = F::compute(I1::default_si(), I2::default_si()).unwrap(); - assert_approx_eq!( - Float, - result.get_si_value(), - expected_result, - epsilon = 0.01 - ); + let result = F::compute(arg1.ref_val(atm), arg2.ref_val(atm)).unwrap(); + check_result(result, atm, eps); // the second promise of the crate is to never return NaN or Inf // here we check several edge cases for that @@ -57,9 +63,9 @@ pub fn test_with_2args< // is not Ok() then it is Err() // here we don't care about error being correct let results = vec![ - F::compute(I1::default_si(), I2::default_si()), - F::compute(I1::new_si(-9999.0), I2::default_si()), - F::compute(I1::default_si(), I2::new_si(-9999.0)), + F::compute(arg1.ref_val(atm), arg2.ref_val(atm)), + F::compute(I1::new_si(-9999.0), arg2.ref_val(atm)), + F::compute(arg1.ref_val(atm), I2::new_si(-9999.0)), F::compute(I1::new_si(-9999.0), I2::new_si(-9999.0)), ]; @@ -98,210 +104,207 @@ pub fn test_with_2args< //the fourth promise of the crate is to return an error with //erronous variable name when input is out of range let expected = InputError::OutOfRange(arg1.quantity_name()); - let result = F::compute(I1::new_si(arg1.range[0] - 0.1), I2::default_si()).unwrap_err(); + let result = F::compute(I1::new_si(arg1.range[0] - 0.1), arg2.ref_val(atm)).unwrap_err(); assert_eq!(result, expected); - let result = F::compute(I1::new_si(arg1.range[1] + 0.1), I2::default_si()).unwrap_err(); + let result = F::compute(I1::new_si(arg1.range[1] + 0.1), arg2.ref_val(atm)).unwrap_err(); assert_eq!(result, expected); let expected = InputError::OutOfRange(arg2.quantity_name()); - let result = F::compute(I1::default_si(), I2::new_si(arg2.range[0] - 0.1)).unwrap_err(); + let result = F::compute(arg1.ref_val(atm), I2::new_si(arg2.range[0] - 0.1)).unwrap_err(); assert_eq!(result, expected); - let result = F::compute(I1::default_si(), I2::new_si(arg2.range[1] + 0.1)).unwrap_err(); + let result = F::compute(arg1.ref_val(atm), I2::new_si(arg2.range[1] + 0.1)).unwrap_err(); assert_eq!(result, expected); } -pub fn test_with_1arg< - O: ThermodynamicQuantity, - I1: ThermodynamicQuantity + TestingQuantity, - F: Formula1, ->( - arg1: Argument, - expected_result: Float, -) { - //the first promise of the crate is that returned value - //is calculated correctly - let result = F::compute(I1::default_si()).unwrap(); - assert_approx_eq!( - Float, - result.get_si_value(), - expected_result, - epsilon = 0.01 - ); - - // the second promise of the crate is to never return NaN or Inf - // here we check several edge cases for that - // the function can only return a finite number or InputError - // check for error implicit as Result<> ensures that if value - // is not Ok() then it is Err() - // here we don't care about error being correct - let results = vec![ - F::compute(I1::default_si()), - F::compute(I1::new_si(-9999.0)), - ]; - - for result in results { - if let Ok(result) = result { - assert!(result.get_si_value().is_finite()); - } - } - - //the third promise of the crate is to always return finite f64 - //if all inputs are within the range - //the only allowed error is InccorectArgumentsSet as it can occur - //for values within valid range - for arg1_itr in 0..=100 { - let arg1_tmp = - (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; - - let arg1_tmp = I1::new_si(arg1_tmp); - - let result = F::compute(arg1_tmp); - - match result { - Ok(r) => assert!(r.get_si_value().is_finite()), - Err(e) => assert_eq!( - discriminant(&InputError::IncorrectArgumentSet(String::new())), - discriminant(&e) - ), - } - } - - //the fourth promise of the crate is to return an error with - //erronous variable name when input is out of range - let expected = InputError::OutOfRange(arg1.quantity_name()); - let result = F::compute(I1::new_si(arg1.range[0] - 0.1)).unwrap_err(); - assert_eq!(result, expected); - let result = F::compute(I1::new_si(arg1.range[1] + 0.1)).unwrap_err(); - assert_eq!(result, expected); -} - -pub fn test_with_3args< - O: ThermodynamicQuantity, - I1: ThermodynamicQuantity + TestingQuantity, - I2: ThermodynamicQuantity + TestingQuantity, - I3: ThermodynamicQuantity + TestingQuantity, - F: Formula3, ->( - arg1: Argument, - arg2: Argument, - arg3: Argument, - expected_result: Float, -) { - //the first promise of the crate is that returned value - //is calculated correctly - let result = F::compute(I1::default_si(), I2::default_si(), I3::default_si()).unwrap(); - assert_approx_eq!( - Float, - result.get_si_value(), - expected_result, - epsilon = 0.01 - ); - - // the second promise of the crate is to never return NaN or Inf - // here we check several edge cases for that - // the function can only return a finite number or InputError - // check for error implicit as Result<> ensures that if value - // is not Ok() then it is Err() - // here we don't care about error being correct - let results = vec![ - F::compute(I1::default_si(), I2::default_si(), I3::default_si()), - F::compute(I1::new_si(-9999.0), I2::default_si(), I3::default_si()), - F::compute(I1::default_si(), I2::new_si(-9999.0), I3::default_si()), - F::compute(I1::default_si(), I2::default_si(), I3::new_si(-9999.0)), - F::compute(I1::default_si(), I2::new_si(-9999.0), I3::default_si()), - F::compute(I1::default_si(), I2::new_si(-9999.0), I3::new_si(-9999.0)), - F::compute(I1::new_si(-9999.0), I2::default_si(), I3::new_si(-9999.0)), - F::compute( - I1::new_si(-9999.0), - I2::new_si(-9999.0), - I3::new_si(-9999.0), - ), - ]; - - for result in results { - if let Ok(result) = result { - assert!(result.get_si_value().is_finite()); - } - } - - //the third promise of the crate is to always return finite f64 - //if all inputs are within the range - //the only allowed error is InccorectArgumentsSet as it can occur - //for values within valid range - for arg1_itr in 0..=100 { - for arg2_itr in 0..=100 { - for arg3_itr in 0..=100 { - let arg1_tmp = - (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; - let arg2_tmp = - (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; - let arg3_tmp = - (((arg3.range[1] - arg3.range[0]) / 100.0) * arg3_itr as Float) + arg3.range[0]; - - let arg1_tmp = I1::new_si(arg1_tmp); - let arg2_tmp = I2::new_si(arg2_tmp); - let arg3_tmp = I3::new_si(arg3_tmp); - - let result = F::compute(arg1_tmp, arg2_tmp, arg3_tmp); - - match result { - Ok(r) => assert!(r.get_si_value().is_finite()), - Err(e) => assert_eq!( - discriminant(&InputError::IncorrectArgumentSet(String::new())), - discriminant(&e) - ), - } - } - } - } - - //the fourth promise of the crate is to return an error with - //erronous variable name when input is out of range - let expected = InputError::OutOfRange(arg1.quantity_name()); - let result = F::compute( - I1::new_si(arg1.range[0] - 0.1), - I2::default_si(), - I3::default_si(), - ) - .unwrap_err(); - assert_eq!(result, expected); - let result = F::compute( - I1::new_si(arg1.range[1] + 0.1), - I2::default_si(), - I3::default_si(), - ) - .unwrap_err(); - assert_eq!(result, expected); - - let expected = InputError::OutOfRange(arg2.quantity_name()); - let result = F::compute( - I1::default_si(), - I2::new_si(arg2.range[0] - 0.1), - I3::default_si(), - ) - .unwrap_err(); - assert_eq!(result, expected); - let result = F::compute( - I1::default_si(), - I2::new_si(arg2.range[1] + 0.1), - I3::default_si(), - ) - .unwrap_err(); - assert_eq!(result, expected); - - let expected = InputError::OutOfRange(arg3.quantity_name()); - let result = F::compute( - I1::default_si(), - I2::default_si(), - I3::new_si(arg3.range[0] - 0.1), - ) - .unwrap_err(); - assert_eq!(result, expected); - let result = F::compute( - I1::default_si(), - I2::default_si(), - I3::new_si(arg3.range[1] + 0.1), - ) - .unwrap_err(); - assert_eq!(result, expected); -} +// pub fn test_with_1arg< +// O: ThermodynamicQuantity, +// I1: ThermodynamicQuantity + TestingQuantity, +// F: Formula1, +// >( +// arg1: Argument, +// expected_result: Float, +// ) { +// //the first promise of the crate is that returned value +// //is calculated correctly +// let result = F::compute(arg1.ref_val()).unwrap(); +// assert_approx_eq!( +// Float, +// result.get_si_value(), +// expected_result, +// epsilon = 0.01 +// ); + +// // the second promise of the crate is to never return NaN or Inf +// // here we check several edge cases for that +// // the function can only return a finite number or InputError +// // check for error implicit as Result<> ensures that if value +// // is not Ok() then it is Err() +// // here we don't care about error being correct +// let results = vec![F::compute(arg1.ref_val()), F::compute(I1::new_si(-9999.0))]; + +// for result in results { +// if let Ok(result) = result { +// assert!(result.get_si_value().is_finite()); +// } +// } + +// //the third promise of the crate is to always return finite f64 +// //if all inputs are within the range +// //the only allowed error is InccorectArgumentsSet as it can occur +// //for values within valid range +// for arg1_itr in 0..=100 { +// let arg1_tmp = +// (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; + +// let arg1_tmp = I1::new_si(arg1_tmp); + +// let result = F::compute(arg1_tmp); + +// match result { +// Ok(r) => assert!(r.get_si_value().is_finite()), +// Err(e) => assert_eq!( +// discriminant(&InputError::IncorrectArgumentSet(String::new())), +// discriminant(&e) +// ), +// } +// } + +// //the fourth promise of the crate is to return an error with +// //erronous variable name when input is out of range +// let expected = InputError::OutOfRange(arg1.quantity_name()); +// let result = F::compute(I1::new_si(arg1.range[0] - 0.1)).unwrap_err(); +// assert_eq!(result, expected); +// let result = F::compute(I1::new_si(arg1.range[1] + 0.1)).unwrap_err(); +// assert_eq!(result, expected); +// } + +// pub fn test_with_3args< +// O: ThermodynamicQuantity, +// I1: ThermodynamicQuantity + TestingQuantity, +// I2: ThermodynamicQuantity + TestingQuantity, +// I3: ThermodynamicQuantity + TestingQuantity, +// F: Formula3, +// >( +// arg1: Argument, +// arg2: Argument, +// arg3: Argument, +// expected_result: Float, +// ) { +// //the first promise of the crate is that returned value +// //is calculated correctly +// let result = F::compute(I1::default_si(), I2::default_si(), I3::default_si()).unwrap(); +// assert_approx_eq!( +// Float, +// result.get_si_value(), +// expected_result, +// epsilon = 0.01 +// ); + +// // the second promise of the crate is to never return NaN or Inf +// // here we check several edge cases for that +// // the function can only return a finite number or InputError +// // check for error implicit as Result<> ensures that if value +// // is not Ok() then it is Err() +// // here we don't care about error being correct +// let results = vec![ +// F::compute(I1::default_si(), I2::default_si(), I3::default_si()), +// F::compute(I1::new_si(-9999.0), I2::default_si(), I3::default_si()), +// F::compute(I1::default_si(), I2::new_si(-9999.0), I3::default_si()), +// F::compute(I1::default_si(), I2::default_si(), I3::new_si(-9999.0)), +// F::compute(I1::default_si(), I2::new_si(-9999.0), I3::default_si()), +// F::compute(I1::default_si(), I2::new_si(-9999.0), I3::new_si(-9999.0)), +// F::compute(I1::new_si(-9999.0), I2::default_si(), I3::new_si(-9999.0)), +// F::compute( +// I1::new_si(-9999.0), +// I2::new_si(-9999.0), +// I3::new_si(-9999.0), +// ), +// ]; + +// for result in results { +// if let Ok(result) = result { +// assert!(result.get_si_value().is_finite()); +// } +// } + +// //the third promise of the crate is to always return finite f64 +// //if all inputs are within the range +// //the only allowed error is InccorectArgumentsSet as it can occur +// //for values within valid range +// for arg1_itr in 0..=100 { +// for arg2_itr in 0..=100 { +// for arg3_itr in 0..=100 { +// let arg1_tmp = +// (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; +// let arg2_tmp = +// (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; +// let arg3_tmp = +// (((arg3.range[1] - arg3.range[0]) / 100.0) * arg3_itr as Float) + arg3.range[0]; + +// let arg1_tmp = I1::new_si(arg1_tmp); +// let arg2_tmp = I2::new_si(arg2_tmp); +// let arg3_tmp = I3::new_si(arg3_tmp); + +// let result = F::compute(arg1_tmp, arg2_tmp, arg3_tmp); + +// match result { +// Ok(r) => assert!(r.get_si_value().is_finite()), +// Err(e) => assert_eq!( +// discriminant(&InputError::IncorrectArgumentSet(String::new())), +// discriminant(&e) +// ), +// } +// } +// } +// } + +// //the fourth promise of the crate is to return an error with +// //erronous variable name when input is out of range +// let expected = InputError::OutOfRange(arg1.quantity_name()); +// let result = F::compute( +// I1::new_si(arg1.range[0] - 0.1), +// I2::default_si(), +// I3::default_si(), +// ) +// .unwrap_err(); +// assert_eq!(result, expected); +// let result = F::compute( +// I1::new_si(arg1.range[1] + 0.1), +// I2::default_si(), +// I3::default_si(), +// ) +// .unwrap_err(); +// assert_eq!(result, expected); + +// let expected = InputError::OutOfRange(arg2.quantity_name()); +// let result = F::compute( +// I1::default_si(), +// I2::new_si(arg2.range[0] - 0.1), +// I3::default_si(), +// ) +// .unwrap_err(); +// assert_eq!(result, expected); +// let result = F::compute( +// I1::default_si(), +// I2::new_si(arg2.range[1] + 0.1), +// I3::default_si(), +// ) +// .unwrap_err(); +// assert_eq!(result, expected); + +// let expected = InputError::OutOfRange(arg3.quantity_name()); +// let result = F::compute( +// I1::default_si(), +// I2::default_si(), +// I3::new_si(arg3.range[0] - 0.1), +// ) +// .unwrap_err(); +// assert_eq!(result, expected); +// let result = F::compute( +// I1::default_si(), +// I2::default_si(), +// I3::new_si(arg3.range[1] + 0.1), +// ) +// .unwrap_err(); +// assert_eq!(result, expected); +// } diff --git a/src/tests/quantities.rs b/src/tests/quantities.rs deleted file mode 100644 index 36a9692..0000000 --- a/src/tests/quantities.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::{ - quantities::{DryBulbTemperature, ThermodynamicQuantity}, - tests::testing_traits::TestingQuantity, -}; - -#[test] -fn quantity_name() { - let quantity = DryBulbTemperature::default_si(); - assert_eq!(quantity.name(), "DryBulbTemperature"); -} diff --git a/src/tests/reference_values.rs b/src/tests/reference_values.rs new file mode 100644 index 0000000..095cf21 --- /dev/null +++ b/src/tests/reference_values.rs @@ -0,0 +1,31 @@ +use crate::Float; + +pub(crate) const TEMP_NORM: Float = 300.0; +pub(crate) const DWPT_NORM: Float = 290.0; +pub(crate) const PRES_NORM: Float = 100000.0; +pub(crate) const VP_NORM: Float = 1919.4253257541593; +pub(crate) const SVP_NORM: Float = 3535.4235919263083; +pub(crate) const RH_NORM: Float = 0.5429124052171476; +pub(crate) const MR_NORM: Float = 0.012172079452423202; +pub(crate) const SMR_NROM: Float = 0.022419969290542845; +pub(crate) const VPD_NORM: Float = 1615.998266172149; +pub(crate) const SH_NORM: Float = 0.012025701656390478; +pub(crate) const THETAE_NORM: Float = 331.33678499482323; +pub(crate) const THETA_NORM: Float = 301.66581400702955; +pub(crate) const THETAW_NORM: Float = 292.0717306393948; +pub(crate) const WBT_NORMAL: Float = 293.42728654340516; + +pub(crate) const TEMP_FREEZ: Float = 260.0; +pub(crate) const DWPT_FREEZ: Float = 255.0; +pub(crate) const PRES_FREEZ: Float = 100000.0; +pub(crate) const VP_FREEZ: Float = 123.17937690212507; +pub(crate) const SVP_FREEZ: Float = 195.84980045970696; +pub(crate) const RH_FREEZ: Float = 0.6289481868911442; +pub(crate) const MR_FREEZ: Float = 0.0007670962389744638; +pub(crate) const SMR_FREEZ: Float = 0.0012196493367222787; +pub(crate) const VPD_FREEZ: Float = 72.67042355758188; +pub(crate) const SH_FREEZ: Float = 0.000766508253376156; +pub(crate) const THETAE_FREEZ: Float = 261.96507880792007; +pub(crate) const THETA_FREEZ: Float = 260.0915766593588; +pub(crate) const THETAW_FREEZ: Float = 258.6611332391296; +pub(crate) const WBT_FREEZ: Float = 258.40501060754224; diff --git a/src/tests/testing_traits.rs b/src/tests/testing_traits.rs index f2663b4..bd544cd 100644 --- a/src/tests/testing_traits.rs +++ b/src/tests/testing_traits.rs @@ -1,125 +1,210 @@ -use std::marker::PhantomData; - use uom::si::{ pressure::{pascal, pound_force_per_square_inch}, ratio::{per_mille, percent, ratio}, thermodynamic_temperature::{degree_fahrenheit, kelvin}, }; -use crate::quantities::*; +use super::reference_values::*; +use crate::{quantities::*, Float}; + +#[derive(Copy, Clone, Debug)] +pub(crate) enum ReferenceAtmosphere { + Normal, + Freezing, +} -pub(crate) trait ReferenceAtmosphere {} pub(crate) trait TestingQuantity: ThermodynamicQuantity { - fn default_si() -> Self; - fn default_imperial() -> Self; + fn new_si(value: Float) -> Self; + fn imperial(&self) -> Self; + fn ref_val_si(atm: ReferenceAtmosphere) -> Self; } impl TestingQuantity for DryBulbTemperature { - fn default_si() -> Self { - Self::new::(300.0) + fn new_si(value: Float) -> Self { + Self::new::(value) } - fn default_imperial() -> Self { - let value = Self::default_si().0.get::(); + + fn imperial(&self) -> Self { + let value = self.0.get::(); Self::new::(value) } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(TEMP_NORM), + ReferenceAtmosphere::Freezing => Self::new::(TEMP_FREEZ), + } + } } impl TestingQuantity for DewPointTemperature { - fn default_si() -> Self { - Self::new::(290.0) + fn new_si(value: Float) -> Self { + Self::new::(value) } - fn default_imperial() -> Self { - let value = Self::default_si().0.get::(); + + fn imperial(&self) -> Self { + let value = self.0.get::(); Self::new::(value) } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(DWPT_NORM), + ReferenceAtmosphere::Freezing => Self::new::(DWPT_FREEZ), + } + } } impl TestingQuantity for EquivalentPotentialTemperature { - fn default_si() -> Self { - Self::new::(300.0) + fn new_si(value: Float) -> Self { + Self::new::(value) } - fn default_imperial() -> Self { - let value = Self::default_si().0.get::(); + + fn imperial(&self) -> Self { + let value = self.0.get::(); Self::new::(value) } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(THETAE_NORM), + ReferenceAtmosphere::Freezing => Self::new::(THETAE_FREEZ), + } + } } impl TestingQuantity for AtmosphericPressure { - fn default_si() -> Self { - Self::new::(101325.0) + fn new_si(value: Float) -> Self { + Self::new::(value) } - fn default_imperial() -> Self { - let value = Self::default_si().0.get::(); + + fn imperial(&self) -> Self { + let value = self.0.get::(); Self::new::(value) } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(PRES_NORM), + ReferenceAtmosphere::Freezing => Self::new::(PRES_FREEZ), + } + } } impl TestingQuantity for VapourPressure { - fn default_si() -> Self { - Self::new::(1920.0) + fn new_si(value: Float) -> Self { + Self::new::(value) } - fn default_imperial() -> Self { - let value = Self::default_si().0.get::(); + + fn imperial(&self) -> Self { + let value = self.0.get::(); Self::new::(value) } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(VP_NORM), + ReferenceAtmosphere::Freezing => Self::new::(VP_FREEZ), + } + } } impl TestingQuantity for SaturationVapourPressure { - fn default_si() -> Self { - Self::new::(3535.0) + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(SVP_NORM), + ReferenceAtmosphere::Freezing => Self::new::(SVP_FREEZ), + } + } + + fn new_si(value: Float) -> Self { + Self::new::(value) } - fn default_imperial() -> Self { - let value = Self::default_si().0.get::(); + + fn imperial(&self) -> Self { + let value = self.0.get::(); Self::new::(value) } } impl TestingQuantity for MixingRatio { - fn default_si() -> Self { - Self::new::(0.012) + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(MR_NORM), + ReferenceAtmosphere::Freezing => Self::new::(MR_FREEZ), + } } - fn default_imperial() -> Self { - let value = Self::default_si().0.get::(); + + fn new_si(value: Float) -> Self { + Self::new::(value) + } + + fn imperial(&self) -> Self { + let value = self.0.get::(); Self::new::(value) } } impl TestingQuantity for SaturationMixingRatio { - fn default_si() -> Self { - Self::new::(0.022) + fn new_si(value: Float) -> Self { + Self::new::(value) } - fn default_imperial() -> Self { - let value = Self::default_si().0.get::(); + + fn imperial(&self) -> Self { + let value = self.0.get::(); Self::new::(value) } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(SMR_NROM), + ReferenceAtmosphere::Freezing => Self::new::(SMR_FREEZ), + } + } } impl TestingQuantity for RelativeHumidity { - fn default_si() -> Self { - Self::new::(0.5) + fn new_si(value: Float) -> Self { + Self::new::(value) } - fn default_imperial() -> Self { - let value = Self::default_si().0.get::(); + + fn imperial(&self) -> Self { + let value = self.0.get::(); Self::new::(value) } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(RH_NORM), + ReferenceAtmosphere::Freezing => Self::new::(RH_FREEZ), + } + } } impl TestingQuantity for SpecificHumidity { - fn default_si() -> Self { - Self::new::(0.022) + fn new_si(value: Float) -> Self { + Self::new::(value) } - fn default_imperial() -> Self { - let value = Self::default_si().0.get::(); + + fn imperial(&self) -> Self { + let value = self.0.get::(); Self::new::(value) } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(SH_NORM), + ReferenceAtmosphere::Freezing => Self::new::(SH_FREEZ), + } + } } diff --git a/src/vapour_pressure.rs b/src/vapour_pressure.rs index c9e06a5..b761667 100644 --- a/src/vapour_pressure.rs +++ b/src/vapour_pressure.rs @@ -467,7 +467,7 @@ mod tests { quantities::{ AtmosphericPressure, RelativeHumidity, SaturationVapourPressure, SpecificHumidity, }, - tests::{test_with_1arg, test_with_2args, Argument}, + tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}, }; use super::*; @@ -477,7 +477,8 @@ mod tests { test_with_2args::( Argument::new([0.00001, 2.0]), Argument::new([100.0, 150_000.0]), - 3536.6680935251343, + ReferenceAtmosphere::Normal, + 0.00001, ); } @@ -486,83 +487,84 @@ mod tests { test_with_2args::( Argument::new([0.0, 50_000.0]), Argument::new([0.0, 2.0]), - 1767.5, + ReferenceAtmosphere::Normal, + 0.00001, ); } - #[test] - fn buck1() { - test_with_2args::( - Argument::new([232.0, 324.0]), - Argument::new([100.0, 150_000.0]), - 1927.0852679081806, - ); - } - - #[test] - fn buck2() { - test_with_2args::( - Argument::new([193.0, 274.0]), - Argument::new([100.0, 150_000.0]), - 76.38781790372722, - ); - } - - #[test] - fn buck3() { - test_with_2args::( - Argument::new([253.0, 324.0]), - Argument::new([100.0, 150_000.0]), - 3548.5041048035896, - ); - } - - #[test] - fn buck4() { - test_with_2args::( - Argument::new([223.0, 274.0]), - Argument::new([100.0, 150_000.0]), - 76.38685471836712, - ); - } - - #[test] - fn buck3_simplified() { - test_with_1arg::( - Argument::new([253.0, 324.0]), - 3533.6421536199978, - ); - } - - #[test] - fn buck4_simplified() { - test_with_1arg::( - Argument::new([223.0, 274.0]), - 76.04197508519536, - ); - } - - #[test] - fn tetens1() { - test_with_1arg::( - Argument::new([273.0, 353.0]), - 3533.969137160892, - ); - } - - #[test] - fn wexler1() { - test_with_1arg::( - Argument::new([273.0, 374.0]), - 3535.4235919263083, - ); - } - - #[test] - fn wexler2() { - test_with_1arg::( - Argument::new([173.0, 274.0]), - 76.04351136780438, - ); - } + // #[test] + // fn buck1() { + // test_with_2args::( + // Argument::new([232.0, 324.0]), + // Argument::new([100.0, 150_000.0]), + // 1927.0852679081806, + // ); + // } + + // #[test] + // fn buck2() { + // test_with_2args::( + // Argument::new([193.0, 274.0]), + // Argument::new([100.0, 150_000.0]), + // 76.38781790372722, + // ); + // } + + // #[test] + // fn buck3() { + // test_with_2args::( + // Argument::new([253.0, 324.0]), + // Argument::new([100.0, 150_000.0]), + // 3548.5041048035896, + // ); + // } + + // #[test] + // fn buck4() { + // test_with_2args::( + // Argument::new([223.0, 274.0]), + // Argument::new([100.0, 150_000.0]), + // 76.38685471836712, + // ); + // } + + // #[test] + // fn buck3_simplified() { + // test_with_1arg::( + // Argument::new([253.0, 324.0]), + // 3533.6421536199978, + // ); + // } + + // #[test] + // fn buck4_simplified() { + // test_with_1arg::( + // Argument::new([223.0, 274.0]), + // 76.04197508519536, + // ); + // } + + // #[test] + // fn tetens1() { + // test_with_1arg::( + // Argument::new([273.0, 353.0]), + // 3533.969137160892, + // ); + // } + + // #[test] + // fn wexler1() { + // test_with_1arg::( + // Argument::new([273.0, 374.0]), + // 3535.4235919263083, + // ); + // } + + // #[test] + // fn wexler2() { + // test_with_1arg::( + // Argument::new([173.0, 274.0]), + // 76.04351136780438, + // ); + // } } From 08b7bff4f5f36f522e6dd94ec3db2b9b6ae4a5d0 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:41:53 +0100 Subject: [PATCH 042/102] (hopefully last time) finish vp --- src/tests.rs | 107 +++++++++++++------------- src/vapour_pressure.rs | 165 ++++++++++++++++++++++------------------- 2 files changed, 139 insertions(+), 133 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index f51a72d..97fe3d8 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,5 @@ -pub(crate) mod testing_traits; pub(crate) mod reference_values; +pub(crate) mod testing_traits; use crate::errors::InputError; use crate::formula::{Formula1, Formula2, Formula3}; @@ -37,6 +37,8 @@ fn check_result(result: T, atm: ReferenceAtmosphere, eps: Fl let expected = T::ref_val_si(atm).get_si_value(); let result = result.get_si_value(); + dbg!(result, expected, eps); + assert_approx_eq!(Float, result, expected, epsilon = eps) } @@ -116,67 +118,62 @@ pub fn test_with_2args< assert_eq!(result, expected); } -// pub fn test_with_1arg< -// O: ThermodynamicQuantity, -// I1: ThermodynamicQuantity + TestingQuantity, -// F: Formula1, -// >( -// arg1: Argument, -// expected_result: Float, -// ) { -// //the first promise of the crate is that returned value -// //is calculated correctly -// let result = F::compute(arg1.ref_val()).unwrap(); -// assert_approx_eq!( -// Float, -// result.get_si_value(), -// expected_result, -// epsilon = 0.01 -// ); +pub fn test_with_1arg>( + arg1: Argument, + atm: ReferenceAtmosphere, + eps: Float, +) { + //the first promise of the crate is that returned value + //is calculated correctly + let result = F::compute(arg1.ref_val(atm)).unwrap(); + check_result(result, atm, eps); -// // the second promise of the crate is to never return NaN or Inf -// // here we check several edge cases for that -// // the function can only return a finite number or InputError -// // check for error implicit as Result<> ensures that if value -// // is not Ok() then it is Err() -// // here we don't care about error being correct -// let results = vec![F::compute(arg1.ref_val()), F::compute(I1::new_si(-9999.0))]; + // the second promise of the crate is to never return NaN or Inf + // here we check several edge cases for that + // the function can only return a finite number or InputError + // check for error implicit as Result<> ensures that if value + // is not Ok() then it is Err() + // here we don't care about error being correct + let results = vec![ + F::compute(arg1.ref_val(atm)), + F::compute(I1::new_si(-9999.0)), + ]; -// for result in results { -// if let Ok(result) = result { -// assert!(result.get_si_value().is_finite()); -// } -// } + for result in results { + if let Ok(result) = result { + assert!(result.get_si_value().is_finite()); + } + } -// //the third promise of the crate is to always return finite f64 -// //if all inputs are within the range -// //the only allowed error is InccorectArgumentsSet as it can occur -// //for values within valid range -// for arg1_itr in 0..=100 { -// let arg1_tmp = -// (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; + //the third promise of the crate is to always return finite f64 + //if all inputs are within the range + //the only allowed error is InccorectArgumentsSet as it can occur + //for values within valid range + for arg1_itr in 0..=100 { + let arg1_tmp = + (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; -// let arg1_tmp = I1::new_si(arg1_tmp); + let arg1_tmp = I1::new_si(arg1_tmp); -// let result = F::compute(arg1_tmp); + let result = F::compute(arg1_tmp); -// match result { -// Ok(r) => assert!(r.get_si_value().is_finite()), -// Err(e) => assert_eq!( -// discriminant(&InputError::IncorrectArgumentSet(String::new())), -// discriminant(&e) -// ), -// } -// } + match result { + Ok(r) => assert!(r.get_si_value().is_finite()), + Err(e) => assert_eq!( + discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&e) + ), + } + } -// //the fourth promise of the crate is to return an error with -// //erronous variable name when input is out of range -// let expected = InputError::OutOfRange(arg1.quantity_name()); -// let result = F::compute(I1::new_si(arg1.range[0] - 0.1)).unwrap_err(); -// assert_eq!(result, expected); -// let result = F::compute(I1::new_si(arg1.range[1] + 0.1)).unwrap_err(); -// assert_eq!(result, expected); -// } + //the fourth promise of the crate is to return an error with + //erronous variable name when input is out of range + let expected = InputError::OutOfRange(arg1.quantity_name()); + let result = F::compute(I1::new_si(arg1.range[0] - 0.1)).unwrap_err(); + assert_eq!(result, expected); + let result = F::compute(I1::new_si(arg1.range[1] + 0.1)).unwrap_err(); + assert_eq!(result, expected); +} // pub fn test_with_3args< // O: ThermodynamicQuantity, diff --git a/src/vapour_pressure.rs b/src/vapour_pressure.rs index b761667..98b602d 100644 --- a/src/vapour_pressure.rs +++ b/src/vapour_pressure.rs @@ -467,7 +467,7 @@ mod tests { quantities::{ AtmosphericPressure, RelativeHumidity, SaturationVapourPressure, SpecificHumidity, }, - tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}, + tests::{test_with_1arg, test_with_2args, testing_traits::ReferenceAtmosphere, Argument}, }; use super::*; @@ -478,7 +478,7 @@ mod tests { Argument::new([0.00001, 2.0]), Argument::new([100.0, 150_000.0]), ReferenceAtmosphere::Normal, - 0.00001, + 1e-12, ); } @@ -488,83 +488,92 @@ mod tests { Argument::new([0.0, 50_000.0]), Argument::new([0.0, 2.0]), ReferenceAtmosphere::Normal, - 0.00001, + 1e-12, ); } - // #[test] - // fn buck1() { - // test_with_2args::( - // Argument::new([232.0, 324.0]), - // Argument::new([100.0, 150_000.0]), - // 1927.0852679081806, - // ); - // } - - // #[test] - // fn buck2() { - // test_with_2args::( - // Argument::new([193.0, 274.0]), - // Argument::new([100.0, 150_000.0]), - // 76.38781790372722, - // ); - // } - - // #[test] - // fn buck3() { - // test_with_2args::( - // Argument::new([253.0, 324.0]), - // Argument::new([100.0, 150_000.0]), - // 3548.5041048035896, - // ); - // } - - // #[test] - // fn buck4() { - // test_with_2args::( - // Argument::new([223.0, 274.0]), - // Argument::new([100.0, 150_000.0]), - // 76.38685471836712, - // ); - // } - - // #[test] - // fn buck3_simplified() { - // test_with_1arg::( - // Argument::new([253.0, 324.0]), - // 3533.6421536199978, - // ); - // } - - // #[test] - // fn buck4_simplified() { - // test_with_1arg::( - // Argument::new([223.0, 274.0]), - // 76.04197508519536, - // ); - // } - - // #[test] - // fn tetens1() { - // test_with_1arg::( - // Argument::new([273.0, 353.0]), - // 3533.969137160892, - // ); - // } - - // #[test] - // fn wexler1() { - // test_with_1arg::( - // Argument::new([273.0, 374.0]), - // 3535.4235919263083, - // ); - // } - - // #[test] - // fn wexler2() { - // test_with_1arg::( - // Argument::new([173.0, 274.0]), - // 76.04351136780438, - // ); - // } + #[test] + fn buck1() { + test_with_2args::( + Argument::new([232.0, 324.0]), + Argument::new([100.0, 150_000.0]), + ReferenceAtmosphere::Normal, + 1e1, + ); + } + + #[test] + fn buck2() { + test_with_2args::( + Argument::new([193.0, 274.0]), + Argument::new([100.0, 150_000.0]), + ReferenceAtmosphere::Freezing, + 1e0, + ); + } + + #[test] + fn buck3() { + test_with_2args::( + Argument::new([253.0, 324.0]), + Argument::new([100.0, 150_000.0]), + ReferenceAtmosphere::Normal, + 1e1, + ); + } + + #[test] + fn buck4() { + test_with_2args::( + Argument::new([223.0, 274.0]), + Argument::new([100.0, 150_000.0]), + ReferenceAtmosphere::Freezing, + 1e0, + ); + } + + #[test] + fn buck3_simplified() { + test_with_1arg::( + Argument::new([253.0, 324.0]), + ReferenceAtmosphere::Normal, + 1e0, + ); + } + + #[test] + fn buck4_simplified() { + test_with_1arg::( + Argument::new([223.0, 274.0]), + ReferenceAtmosphere::Freezing, + 1e-1, + ); + } + + #[test] + fn tetens1() { + test_with_1arg::( + Argument::new([273.0, 353.0]), + ReferenceAtmosphere::Normal, + 1e0, + ); + } + + #[test] + fn wexler1() { + test_with_1arg::( + Argument::new([273.0, 374.0]), + ReferenceAtmosphere::Normal, + 1e-12, + ); + } + + #[test] + fn wexler2() { + test_with_1arg::( + Argument::new([173.0, 274.0]), + ReferenceAtmosphere::Freezing, + 1e-12, + ); + } } From 98b17fe0c4265b40c03c17d0f50557f474d35d85 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:52:11 +0100 Subject: [PATCH 043/102] vpd --- src/lib.rs | 2 +- src/tests/testing_traits.rs | 19 ++++ src/vapour_pressure_deficit.rs | 197 ++------------------------------- 3 files changed, 28 insertions(+), 190 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cd7641a..e6840b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ pub mod quantities; // pub mod specific_humidity; pub mod vapour_pressure; // pub mod saturation_vapour_pressure; -// pub mod vapour_pressure_deficit; +pub mod vapour_pressure_deficit; // pub mod virtual_temperature; // pub mod wet_bulb_potential_temperature; // pub mod wet_bulb_temperature; diff --git a/src/tests/testing_traits.rs b/src/tests/testing_traits.rs index bd544cd..6748022 100644 --- a/src/tests/testing_traits.rs +++ b/src/tests/testing_traits.rs @@ -208,3 +208,22 @@ impl TestingQuantity for SpecificHumidity { } } } + +impl TestingQuantity for VapourPressureDeficit { + fn new_si(value: Float) -> Self { + Self::new::(value) + } + + fn imperial(&self) -> Self { + let value = self.0.get::(); + + Self::new::(value) + } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(VPD_NORM), + ReferenceAtmosphere::Freezing => Self::new::(VPD_FREEZ), + } + } +} diff --git a/src/vapour_pressure_deficit.rs b/src/vapour_pressure_deficit.rs index b18e918..a6d1b3e 100644 --- a/src/vapour_pressure_deficit.rs +++ b/src/vapour_pressure_deficit.rs @@ -5,12 +5,10 @@ //! when it is saturated ([Wikipedia](https://en.wikipedia.org/wiki/Vapour-pressure_deficit)). use crate::errors::InputError; -use crate::formula::{Formula2, Formula3}; +use crate::formula::Formula2; use crate::quantities::{ - AtmosphericPressure, DewPointTemperature, DryBulbTemperature, RelativeHumidity, SaturationVapourPressure, ThermodynamicQuantity, VapourPressure, VapourPressureDeficit, }; -use crate::{saturation_vapour_pressure, vapour_pressure}; type FormulaQuantity = VapourPressureDeficit; @@ -27,18 +25,8 @@ impl Formula2 for Def vapour_pressure: VapourPressure, saturation_vapour_pressure: SaturationVapourPressure, ) -> Result<(), InputError> { - let vapour_pressure_si = vapour_pressure.get_si_value(); - let saturation_vapour_pressure_si = saturation_vapour_pressure.get_si_value(); - - if !(0.0..=50_000.0).contains(&vapour_pressure_si) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); - } - - if !(0.0..=50_000.0).contains(&saturation_vapour_pressure_si) { - return Err(InputError::OutOfRange(String::from( - "saturation_vapour_pressure", - ))); - } + vapour_pressure.check_range_si(0.0, 50_000.0)?; + saturation_vapour_pressure.check_range_si(0.0, 50_000.0)?; Ok(()) } @@ -52,188 +40,19 @@ impl Formula2 for Def } } -/// Formula for computing vapour pressure deficit from temperature, dewpoint and pressure -/// using [`buck3`](vapour_pressure::buck3) function for vapour pressure calculation -/// -/// Valid `temperature` range: 253K - 324K -/// -/// Valid `dewpoint` range: 253K - 324K -/// -/// Valid `pressure` range: 100Pa - 150000Pa -pub struct General1; - -impl Formula3 - for General1 -{ - #[inline(always)] - fn validate_inputs( - temperature: DryBulbTemperature, - dewpoint: DewPointTemperature, - pressure: AtmosphericPressure, - ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let dewpoint_si = dewpoint.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(253.0..=324.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(253.0..=324.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - Ok(()) - } - - #[inline(always)] - fn compute_unchecked( - temperature: DryBulbTemperature, - dewpoint: DewPointTemperature, - pressure: AtmosphericPressure, - ) -> VapourPressureDeficit { - let vapour_pressure = vapour_pressure::Buck3::compute_unchecked(dewpoint, pressure); - let saturation_vapour_pressure = - saturation_vapour_pressure::Buck3::compute_unchecked(temperature, pressure); - - Definition1::compute_unchecked(vapour_pressure, saturation_vapour_pressure) - } -} - -/// Formula for computing vapour pressure deficit from temperature, relative humidity and pressure -/// using [`buck3`](vapour_pressure::buck3) function for vapour pressure calculation -/// -/// Valid `temperature` range: 253K - 319K -/// -/// Valid `relative_humidity` range: 0.05 - 2.0 -/// -/// Valid `pressure` range: 100Pa - 150000Pa -pub struct General2; - -impl Formula3 - for General2 -{ - #[inline(always)] - fn validate_inputs( - temperature: DryBulbTemperature, - relative_humidity: RelativeHumidity, - pressure: AtmosphericPressure, - ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let relative_humidity_si = relative_humidity.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(253.0..=319.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(0.05..=2.0).contains(&relative_humidity_si) { - return Err(InputError::OutOfRange(String::from("relative_humidity"))); - } - - if !(10000.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - - Ok(()) - } - - #[inline(always)] - fn compute_unchecked( - temperature: DryBulbTemperature, - relative_humidity: RelativeHumidity, - pressure: AtmosphericPressure, - ) -> VapourPressureDeficit { - let saturation_vapour_pressure = - saturation_vapour_pressure::Buck3::compute_unchecked(temperature, pressure); - let vapour_pressure = vapour_pressure::Definition2::compute_unchecked( - saturation_vapour_pressure, - relative_humidity, - ); - - Definition1::compute_unchecked(vapour_pressure, saturation_vapour_pressure) - } -} - #[cfg(test)] mod tests { - use crate::tests::{test_with_2args, test_with_3args, Argument}; + use crate::tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}; use super::*; #[test] fn definition1() { test_with_2args::( - Argument { - name: "vapour_pressure", - def_val: 3000.0, - range: [0.0, 50_000.0], - }, - Argument { - name: "saturation_vapour_pressure", - def_val: 3550.0, - range: [0.0, 50_000.0], - }, - 550.0, - ); - } - - #[test] - fn general1() { - test_with_3args::< - FormulaQuantity, - DryBulbTemperature, - DewPointTemperature, - AtmosphericPressure, - General1, - >( - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0], - }, - Argument { - name: "dewpoint", - def_val: 290.0, - range: [253.0, 324.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - 1621.9415403325527, - ); - } - - #[test] - fn general2() { - test_with_3args::< - FormulaQuantity, - DryBulbTemperature, - RelativeHumidity, - AtmosphericPressure, - General2, - >( - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 319.0], - }, - Argument { - name: "relative_humidity", - def_val: 0.5, - range: [0.05, 2.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [10000.0, 150_000.0], - }, - 1774.2520524017948, + Argument::new([0.0, 50_000.0]), + Argument::new([0.0, 50_000.0]), + ReferenceAtmosphere::Normal, + 1e-12, ); } } From 544faf6d322a0911caf3b32e907a1279f7d9b2de Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:03:21 +0100 Subject: [PATCH 044/102] virtual temperature --- src/lib.rs | 2 +- src/tests.rs | 256 +++++++++++++++++----------------- src/tests/reference_values.rs | 5 +- src/tests/testing_traits.rs | 19 +++ src/virtual_temperature.rs | 110 ++++----------- 5 files changed, 181 insertions(+), 211 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e6840b2..8012392 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,7 +94,7 @@ pub mod quantities; pub mod vapour_pressure; // pub mod saturation_vapour_pressure; pub mod vapour_pressure_deficit; -// pub mod virtual_temperature; +pub mod virtual_temperature; // pub mod wet_bulb_potential_temperature; // pub mod wet_bulb_temperature; diff --git a/src/tests.rs b/src/tests.rs index 97fe3d8..6091f95 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -175,133 +175,129 @@ pub fn test_with_1arg, -// >( -// arg1: Argument, -// arg2: Argument, -// arg3: Argument, -// expected_result: Float, -// ) { -// //the first promise of the crate is that returned value -// //is calculated correctly -// let result = F::compute(I1::default_si(), I2::default_si(), I3::default_si()).unwrap(); -// assert_approx_eq!( -// Float, -// result.get_si_value(), -// expected_result, -// epsilon = 0.01 -// ); - -// // the second promise of the crate is to never return NaN or Inf -// // here we check several edge cases for that -// // the function can only return a finite number or InputError -// // check for error implicit as Result<> ensures that if value -// // is not Ok() then it is Err() -// // here we don't care about error being correct -// let results = vec![ -// F::compute(I1::default_si(), I2::default_si(), I3::default_si()), -// F::compute(I1::new_si(-9999.0), I2::default_si(), I3::default_si()), -// F::compute(I1::default_si(), I2::new_si(-9999.0), I3::default_si()), -// F::compute(I1::default_si(), I2::default_si(), I3::new_si(-9999.0)), -// F::compute(I1::default_si(), I2::new_si(-9999.0), I3::default_si()), -// F::compute(I1::default_si(), I2::new_si(-9999.0), I3::new_si(-9999.0)), -// F::compute(I1::new_si(-9999.0), I2::default_si(), I3::new_si(-9999.0)), -// F::compute( -// I1::new_si(-9999.0), -// I2::new_si(-9999.0), -// I3::new_si(-9999.0), -// ), -// ]; - -// for result in results { -// if let Ok(result) = result { -// assert!(result.get_si_value().is_finite()); -// } -// } - -// //the third promise of the crate is to always return finite f64 -// //if all inputs are within the range -// //the only allowed error is InccorectArgumentsSet as it can occur -// //for values within valid range -// for arg1_itr in 0..=100 { -// for arg2_itr in 0..=100 { -// for arg3_itr in 0..=100 { -// let arg1_tmp = -// (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; -// let arg2_tmp = -// (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; -// let arg3_tmp = -// (((arg3.range[1] - arg3.range[0]) / 100.0) * arg3_itr as Float) + arg3.range[0]; - -// let arg1_tmp = I1::new_si(arg1_tmp); -// let arg2_tmp = I2::new_si(arg2_tmp); -// let arg3_tmp = I3::new_si(arg3_tmp); - -// let result = F::compute(arg1_tmp, arg2_tmp, arg3_tmp); - -// match result { -// Ok(r) => assert!(r.get_si_value().is_finite()), -// Err(e) => assert_eq!( -// discriminant(&InputError::IncorrectArgumentSet(String::new())), -// discriminant(&e) -// ), -// } -// } -// } -// } - -// //the fourth promise of the crate is to return an error with -// //erronous variable name when input is out of range -// let expected = InputError::OutOfRange(arg1.quantity_name()); -// let result = F::compute( -// I1::new_si(arg1.range[0] - 0.1), -// I2::default_si(), -// I3::default_si(), -// ) -// .unwrap_err(); -// assert_eq!(result, expected); -// let result = F::compute( -// I1::new_si(arg1.range[1] + 0.1), -// I2::default_si(), -// I3::default_si(), -// ) -// .unwrap_err(); -// assert_eq!(result, expected); - -// let expected = InputError::OutOfRange(arg2.quantity_name()); -// let result = F::compute( -// I1::default_si(), -// I2::new_si(arg2.range[0] - 0.1), -// I3::default_si(), -// ) -// .unwrap_err(); -// assert_eq!(result, expected); -// let result = F::compute( -// I1::default_si(), -// I2::new_si(arg2.range[1] + 0.1), -// I3::default_si(), -// ) -// .unwrap_err(); -// assert_eq!(result, expected); - -// let expected = InputError::OutOfRange(arg3.quantity_name()); -// let result = F::compute( -// I1::default_si(), -// I2::default_si(), -// I3::new_si(arg3.range[0] - 0.1), -// ) -// .unwrap_err(); -// assert_eq!(result, expected); -// let result = F::compute( -// I1::default_si(), -// I2::default_si(), -// I3::new_si(arg3.range[1] + 0.1), -// ) -// .unwrap_err(); -// assert_eq!(result, expected); -// } +pub fn test_with_3args< + O: TestingQuantity, + I1: TestingQuantity, + I2: TestingQuantity, + I3: TestingQuantity, + F: Formula3, +>( + arg1: Argument, + arg2: Argument, + arg3: Argument, + atm: ReferenceAtmosphere, + eps: Float, +) { + //the first promise of the crate is that returned value + //is calculated correctly + let result = F::compute(arg1.ref_val(atm), arg2.ref_val(atm), arg3.ref_val(atm)).unwrap(); + check_result(result, atm, eps); + + // the second promise of the crate is to never return NaN or Inf + // here we check several edge cases for that + // the function can only return a finite number or InputError + // check for error implicit as Result<> ensures that if value + // is not Ok() then it is Err() + // here we don't care about error being correct + let results = vec![ + F::compute(arg1.ref_val(atm), arg2.ref_val(atm), arg3.ref_val(atm)), + F::compute(I1::new_si(-9999.0), arg2.ref_val(atm), arg3.ref_val(atm)), + F::compute(arg1.ref_val(atm), I2::new_si(-9999.0), arg3.ref_val(atm)), + F::compute(arg1.ref_val(atm), arg2.ref_val(atm), I3::new_si(-9999.0)), + F::compute(arg1.ref_val(atm), I2::new_si(-9999.0), arg3.ref_val(atm)), + F::compute(arg1.ref_val(atm), I2::new_si(-9999.0), I3::new_si(-9999.0)), + F::compute(I1::new_si(-9999.0), arg2.ref_val(atm), I3::new_si(-9999.0)), + F::compute( + I1::new_si(-9999.0), + I2::new_si(-9999.0), + I3::new_si(-9999.0), + ), + ]; + + for result in results { + if let Ok(result) = result { + assert!(result.get_si_value().is_finite()); + } + } + + //the third promise of the crate is to always return finite f64 + //if all inputs are within the range + //the only allowed error is InccorectArgumentsSet as it can occur + //for values within valid range + for arg1_itr in 0..=100 { + for arg2_itr in 0..=100 { + for arg3_itr in 0..=100 { + let arg1_tmp = + (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; + let arg2_tmp = + (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; + let arg3_tmp = + (((arg3.range[1] - arg3.range[0]) / 100.0) * arg3_itr as Float) + arg3.range[0]; + + let arg1_tmp = I1::new_si(arg1_tmp); + let arg2_tmp = I2::new_si(arg2_tmp); + let arg3_tmp = I3::new_si(arg3_tmp); + + let result = F::compute(arg1_tmp, arg2_tmp, arg3_tmp); + + match result { + Ok(r) => assert!(r.get_si_value().is_finite()), + Err(e) => assert_eq!( + discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&e) + ), + } + } + } + } + + //the fourth promise of the crate is to return an error with + //erronous variable name when input is out of range + let expected = InputError::OutOfRange(arg1.quantity_name()); + let result = F::compute( + I1::new_si(arg1.range[0] - 0.1), + arg2.ref_val(atm), + arg3.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + I1::new_si(arg1.range[1] + 0.1), + arg2.ref_val(atm), + arg3.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + + let expected = InputError::OutOfRange(arg2.quantity_name()); + let result = F::compute( + arg1.ref_val(atm), + I2::new_si(arg2.range[0] - 0.1), + arg3.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + arg1.ref_val(atm), + I2::new_si(arg2.range[1] + 0.1), + arg3.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + + let expected = InputError::OutOfRange(arg3.quantity_name()); + let result = F::compute( + arg1.ref_val(atm), + arg2.ref_val(atm), + I3::new_si(arg3.range[0] - 0.1), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + arg1.ref_val(atm), + arg2.ref_val(atm), + I3::new_si(arg3.range[1] + 0.1), + ) + .unwrap_err(); + assert_eq!(result, expected); +} diff --git a/src/tests/reference_values.rs b/src/tests/reference_values.rs index 095cf21..7a931d1 100644 --- a/src/tests/reference_values.rs +++ b/src/tests/reference_values.rs @@ -13,7 +13,9 @@ pub(crate) const SH_NORM: Float = 0.012025701656390478; pub(crate) const THETAE_NORM: Float = 331.33678499482323; pub(crate) const THETA_NORM: Float = 301.66581400702955; pub(crate) const THETAW_NORM: Float = 292.0717306393948; -pub(crate) const WBT_NORMAL: Float = 293.42728654340516; +pub(crate) const WBT_NORM: Float = 293.42728654340516; +pub(crate) const VRT_NORM: Float = 302.1926517941886; + pub(crate) const TEMP_FREEZ: Float = 260.0; pub(crate) const DWPT_FREEZ: Float = 255.0; @@ -29,3 +31,4 @@ pub(crate) const THETAE_FREEZ: Float = 261.96507880792007; pub(crate) const THETA_FREEZ: Float = 260.0915766593588; pub(crate) const THETAW_FREEZ: Float = 258.6611332391296; pub(crate) const WBT_FREEZ: Float = 258.40501060754224; +pub(crate) const VRT_FREEZ: Float = 260.12112343315795; diff --git a/src/tests/testing_traits.rs b/src/tests/testing_traits.rs index 6748022..50e7e8b 100644 --- a/src/tests/testing_traits.rs +++ b/src/tests/testing_traits.rs @@ -227,3 +227,22 @@ impl TestingQuantity for VapourPressureDeficit { } } } + +impl TestingQuantity for VirtualTemperature { + fn new_si(value: Float) -> Self { + Self::new::(value) + } + + fn imperial(&self) -> Self { + let value = self.0.get::(); + + Self::new::(value) + } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(VRT_NORM), + ReferenceAtmosphere::Freezing => Self::new::(VRT_FREEZ), + } + } +} diff --git a/src/virtual_temperature.rs b/src/virtual_temperature.rs index c1368c7..06124c2 100644 --- a/src/virtual_temperature.rs +++ b/src/virtual_temperature.rs @@ -12,6 +12,8 @@ use crate::quantities::{ VapourPressure, VirtualTemperature, }; +type FormulaQuantity = VirtualTemperature; + /// Formula for computing virtual temperature from temperature and mixing ratio. /// /// Valid `temperature` range: 173K - 373K @@ -19,22 +21,14 @@ use crate::quantities::{ /// Valid `mixing_ratio` range: 0.0000000001 - 0.5 pub struct Definition1; -impl Formula2 for Definition1 { +impl Formula2 for Definition1 { #[inline(always)] fn validate_inputs( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let mixing_ratio_si = mixing_ratio.get_si_value(); - - if !(173.0..=354.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(0.000_000_000_1..=0.5).contains(&mixing_ratio_si) { - return Err(InputError::OutOfRange(String::from("mixing_ratio"))); - } + temperature.check_range_si(173.0, 354.0)?; + mixing_ratio.check_range_si(0.000_000_000_1, 0.5)?; Ok(()) } @@ -63,7 +57,7 @@ impl Formula2 for Definitio /// Valid `vapour_pressure` range: 0Pa - 10000Pa pub struct Definition2; -impl Formula3 +impl Formula3 for Definition2 { #[inline(always)] @@ -72,21 +66,10 @@ impl Formula3 Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let pressure_si = pressure.get_si_value(); - let vapour_pressure_si = vapour_pressure.get_si_value(); - - if !(173.0..=354.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } + temperature.check_range_si(173.0, 354.0)?; + pressure.check_range_si(100.0, 150_000.0)?; + vapour_pressure.check_range_si(0.0, 10_000.0)?; - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - - if !(0.0..=10_000.0).contains(&vapour_pressure_si) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); - } Ok(()) } @@ -111,22 +94,14 @@ impl Formula3 for Definition3 { +impl Formula2 for Definition3 { #[inline(always)] fn validate_inputs( temperature: DryBulbTemperature, specific_humidity: SpecificHumidity, ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let specific_humidity_si = specific_humidity.get_si_value(); - - if !(173.0..=354.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(0.000_000_001..=2.0).contains(&specific_humidity_si) { - return Err(InputError::OutOfRange(String::from("specific_humidity"))); - } + temperature.check_range_si(173.0, 354.0)?; + specific_humidity.check_range_si(0.000_000_001, 2.0)?; Ok(()) } @@ -146,69 +121,46 @@ impl Formula2 for Defi #[cfg(test)] mod tests { - use crate::tests::{test_with_2args, test_with_3args, Argument}; + use crate::tests::{ + test_with_2args, test_with_3args, testing_traits::ReferenceAtmosphere, Argument, + }; use super::*; #[test] fn definition1() { - test_with_2args::( - Argument { - name: "temperature", - def_val: 300.0, - range: [173.0, 354.0], - }, - Argument { - name: "mixing_ratio", - def_val: 0.022, - range: [0.000_000_000_1, 0.5], - }, - 303.9249219815806, + test_with_2args::( + Argument::new([173.0, 354.0]), + Argument::new([0.000_000_000_1, 0.5]), + ReferenceAtmosphere::Normal, + 1e-12, ); } #[test] fn definition2() { test_with_3args::< - VirtualTemperature, + FormulaQuantity, DryBulbTemperature, AtmosphericPressure, VapourPressure, Definition2, >( - Argument { - name: "temperature", - def_val: 300.0, - range: [173.0, 354.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - Argument { - name: "vapour_pressure", - def_val: 3550.0, - range: [0.0, 10_000.0], - }, - 304.0265941965307, + Argument::new([173.0, 354.0]), + Argument::new([100.0, 150_000.0]), + Argument::new([0.0, 10_000.0]), + ReferenceAtmosphere::Normal, + 1e-12, ); } #[test] fn definition3() { - test_with_2args::( - Argument { - name: "temperature", - def_val: 300.0, - range: [173.0, 354.0], - }, - Argument { - name: "specific_humidity", - def_val: 0.022, - range: [0.000000001, 2.0], - }, - 304.0112702651753, + test_with_2args::( + Argument::new([173.0, 354.0]), + Argument::new([0.000000001, 2.0]), + ReferenceAtmosphere::Normal, + 1e-12, ); } } From be9f1e35be8b4d4a4b3028fa95a2f057a0d18e54 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:05:36 +0100 Subject: [PATCH 045/102] wet bulb temperature --- src/lib.rs | 2 +- src/tests/testing_traits.rs | 19 +++++++++++++++++++ src/wet_bulb_temperature.rs | 34 +++++++++++----------------------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8012392..050da90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,7 +96,7 @@ pub mod vapour_pressure; pub mod vapour_pressure_deficit; pub mod virtual_temperature; // pub mod wet_bulb_potential_temperature; -// pub mod wet_bulb_temperature; +pub mod wet_bulb_temperature; #[cfg(test)] mod tests; diff --git a/src/tests/testing_traits.rs b/src/tests/testing_traits.rs index 50e7e8b..2968cd2 100644 --- a/src/tests/testing_traits.rs +++ b/src/tests/testing_traits.rs @@ -246,3 +246,22 @@ impl TestingQuantity for VirtualTemperature { } } } + +impl TestingQuantity for WetBulbTemperature { + fn new_si(value: Float) -> Self { + Self::new::(value) + } + + fn imperial(&self) -> Self { + let value = self.0.get::(); + + Self::new::(value) + } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(WBT_NORM), + ReferenceAtmosphere::Freezing => Self::new::(WBT_FREEZ), + } + } +} diff --git a/src/wet_bulb_temperature.rs b/src/wet_bulb_temperature.rs index a981307..6032052 100644 --- a/src/wet_bulb_temperature.rs +++ b/src/wet_bulb_temperature.rs @@ -10,6 +10,8 @@ use crate::quantities::{ }; use crate::Storage; +type FormulaQuantity = WetBulbTemperature; + /// Formula for computing wet bulb temperature pressure from dry bulb temperature and relative humidity. /// /// Derived by R. Stull (2011) [(doi:10.1175/JAMC-D-11-0143.1)](https://doi.org/10.1175/JAMC-D-11-0143.1) @@ -22,22 +24,15 @@ use crate::Storage; /// Valid `relative_humidity` range: 0.05 - 0.99 pub struct Stull1; -impl Formula2 for Stull1 { +impl Formula2 for Stull1 { #[inline(always)] fn validate_inputs( temperature: DryBulbTemperature, relative_humidity: RelativeHumidity, ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let relative_humidity_si = relative_humidity.get_si_value(); - - if !(253.0..=324.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } + temperature.check_range_si(253.0, 324.0)?; + relative_humidity.check_range_si(0.05, 0.99)?; - if !(0.05..=0.99).contains(&relative_humidity_si) { - return Err(InputError::OutOfRange(String::from("relative_humidity"))); - } Ok(()) } @@ -63,24 +58,17 @@ impl Formula2 for Stul #[cfg(test)] mod tests { - use crate::tests::{test_with_2args, Argument}; + use crate::tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}; use super::*; #[test] fn stull1() { - test_with_2args::( - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0], - }, - Argument { - name: "relative_humidity", - def_val: 0.5, - range: [0.05, 0.99], - }, - 292.73867410526674, + test_with_2args::( + Argument::new([253.0, 324.0]), + Argument::new([0.05, 0.99]), + ReferenceAtmosphere::Normal, + 1e-12, ); } } From cb4d1e650afb026fd49eafdc7fae452aa1400515 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:11:30 +0100 Subject: [PATCH 046/102] relative humidity --- src/lib.rs | 2 +- src/relative_humidity.rs | 299 +++------------------------------------ 2 files changed, 17 insertions(+), 284 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 050da90..a253e23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,7 +89,7 @@ pub mod quantities; // pub mod mixing_ratio; // pub mod saturation_mixing_ratio; // pub mod potential_temperature; -// pub mod relative_humidity; +pub mod relative_humidity; // pub mod specific_humidity; pub mod vapour_pressure; // pub mod saturation_vapour_pressure; diff --git a/src/relative_humidity.rs b/src/relative_humidity.rs index edf359e..c422a81 100644 --- a/src/relative_humidity.rs +++ b/src/relative_humidity.rs @@ -1,12 +1,11 @@ //! Functions to calculate relative humidity use crate::errors::InputError; -use crate::formula::{Formula1, Formula2, Formula3}; +use crate::formula::Formula2; use crate::quantities::{ - AtmosphericPressure, DewPointTemperature, DryBulbTemperature, MixingRatio, RelativeHumidity, - SaturationMixingRatio, SaturationVapourPressure, ThermodynamicQuantity, VapourPressure, + MixingRatio, RelativeHumidity, SaturationMixingRatio, SaturationVapourPressure, + ThermodynamicQuantity, VapourPressure, }; -use crate::{mixing_ratio, saturation_mixing_ratio, saturation_vapour_pressure, vapour_pressure}; type FormulaQuantity = RelativeHumidity; @@ -27,18 +26,8 @@ impl Formula2 for Definitio mixing_ratio: MixingRatio, saturation_mixing_ratio: SaturationMixingRatio, ) -> Result<(), InputError> { - let mixing_ratio_si = mixing_ratio.get_si_value(); - let saturation_mixing_ratio_si = saturation_mixing_ratio.get_si_value(); - - if !(0.00001..=10.0).contains(&mixing_ratio_si) { - return Err(InputError::OutOfRange(String::from("mixing_ratio"))); - } - - if !(0.00001..=10.0).contains(&saturation_mixing_ratio_si) { - return Err(InputError::OutOfRange(String::from( - "saturation_mixing_ratio", - ))); - } + mixing_ratio.check_range_si(0.00001, 10.0)?; + saturation_mixing_ratio.check_range_si(0.00001, 10.0)?; Ok(()) } @@ -66,18 +55,8 @@ impl Formula2 for Def vapour_pressure: VapourPressure, saturation_vapour_pressure: SaturationVapourPressure, ) -> Result<(), InputError> { - let vapour_pressure_si = vapour_pressure.get_si_value(); - let saturation_vapour_pressure_si = saturation_vapour_pressure.get_si_value(); - - if !(0.0..=50_000.0).contains(&vapour_pressure_si) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); - } - - if !(0.1..=50_000.0).contains(&saturation_vapour_pressure_si) { - return Err(InputError::OutOfRange(String::from( - "saturation_vapour_pressure", - ))); - } + vapour_pressure.check_range_si(0.0, 50_000.0)?; + saturation_vapour_pressure.check_range_si(0.1, 50_000.0)?; Ok(()) } @@ -91,275 +70,29 @@ impl Formula2 for Def } } -/// Formula for computing relative humidity from temperature and dewpoint using [`tetens1`](vapour_pressure::tetens1) -/// function for vapour pressure calculation -/// -/// Valid `temperature` range: 273K - 353K -/// -/// Valid `dewpoint` range: 273K - 353K -pub struct General1; - -impl Formula2 for General1 { - #[inline(always)] - fn validate_inputs( - temperature: DryBulbTemperature, - dewpoint: DewPointTemperature, - ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let dewpoint_si = dewpoint.get_si_value(); - - if !(273.0..=353.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(273.0..=353.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } - - Ok(()) - } - - #[inline(always)] - fn compute_unchecked( - temperature: DryBulbTemperature, - dewpoint: DewPointTemperature, - ) -> RelativeHumidity { - let vapour_pressure = vapour_pressure::Tetens1::compute_unchecked(dewpoint); - let saturation_vapour_pressure = - saturation_vapour_pressure::Tetens1::compute_unchecked(temperature); - - Definition2::compute_unchecked(vapour_pressure, saturation_vapour_pressure) - } -} - -/// Formula for computing relative humidity from temperature, dewpoint and pressure using [`buck3`](vapour_pressure::buck3) -/// function for vapour pressure calculation -/// -/// Valid `temperature` range: 253K - 324K -/// -/// Valid `dewpoint` range: 253K - 324K -/// -/// Valid `pressure` range: 100Pa - 150000Pa -pub struct General2; - -impl Formula3 - for General2 -{ - #[inline(always)] - fn validate_inputs( - temperature: DryBulbTemperature, - dewpoint: DewPointTemperature, - pressure: AtmosphericPressure, - ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let dewpoint_si = dewpoint.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(253.0..=324.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(253.0..=324.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - - Ok(()) - } - - #[inline(always)] - fn compute_unchecked( - temperature: DryBulbTemperature, - dewpoint: DewPointTemperature, - pressure: AtmosphericPressure, - ) -> RelativeHumidity { - let vapour_pressure = vapour_pressure::Buck3::compute_unchecked(dewpoint, pressure); - let saturation_vapour_pressure = - saturation_vapour_pressure::Buck3::compute_unchecked(temperature, pressure); - - Definition2::compute_unchecked(vapour_pressure, saturation_vapour_pressure) - } -} - -/// Formula for computing relative humidity from temperature, dewpoint and pressure using [`accuracy1`](mixing_ratio::accuracy1) -/// function for mixing ratio calculation -/// -/// Valid `temperature` range: 232K - 324K -/// -/// Valid `dewpoint` range: 232K - 324K -/// -/// Valid `pressure` range: 100Pa - 150000Pa -pub struct General3; - -impl Formula3 - for General3 -{ - #[inline(always)] - fn validate_inputs( - temperature: DryBulbTemperature, - dewpoint: DewPointTemperature, - pressure: AtmosphericPressure, - ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let dewpoint_si = dewpoint.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(232.0..=314.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(232.0..=314.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } - - if !(10000.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - - Ok(()) - } - - #[inline(always)] - fn compute_unchecked( - temperature: DryBulbTemperature, - dewpoint: DewPointTemperature, - pressure: AtmosphericPressure, - ) -> FormulaQuantity { - let mixing_ratio = mixing_ratio::Accuracy1::compute_unchecked(dewpoint, pressure); - let saturation_mixing_ratio = - saturation_mixing_ratio::Accuracy1::compute_unchecked(temperature, pressure); - - Definition1::compute_unchecked(mixing_ratio, saturation_mixing_ratio) - } -} - #[cfg(test)] mod tests { - use std::marker::PhantomData; - - use crate::tests::{test_with_2args, test_with_3args, Argument}; + use crate::tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}; use super::*; #[test] fn definition1() { test_with_2args::( - Argument { - name: "mixing_ratio", - def_val: 0.01064, - range: [0.00001, 10.0], - _quantity: PhantomData, - }, - Argument { - name: "saturation_mixing_ratio", - def_val: 0.01467, - range: [0.00001, 10.0], - _quantity: PhantomData, - }, - 0.7252897068847989, + Argument::new([0.00001, 10.0]), + Argument::new([0.00001, 10.0]), + ReferenceAtmosphere::Normal, + 1e-12, ); } #[test] fn definition2() { test_with_2args::( - Argument { - name: "vapour_pressure", - def_val: 1706.0, - range: [0.0, 50_000.0], - _quantity: PhantomData, - }, - Argument { - name: "saturation_vapour_pressure", - def_val: 2339.0, - range: [0.1, 50_000.0], - _quantity: PhantomData, - }, - 0.7293715262932877, - ); - } - - #[test] - fn general1() { - test_with_2args::( - Argument { - name: "temperature", - def_val: 300.0, - range: [273.0, 353.0], - _quantity: PhantomData, - }, - Argument { - name: "dewpoint", - def_val: 290.0, - range: [273.0, 353.0], - _quantity: PhantomData, - }, - 0.5431069897660531, - ); - } - - #[test] - fn general2() { - test_with_3args::< - FormulaQuantity, - DryBulbTemperature, - DewPointTemperature, - AtmosphericPressure, - General2, - >( - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0], - _quantity: PhantomData, - }, - Argument { - name: "dewpoint", - def_val: 290.0, - range: [253.0, 324.0], - _quantity: PhantomData, - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - _quantity: PhantomData, - }, - 0.5429224562155812, - ); - } - - #[test] - fn general3() { - test_with_3args::< - FormulaQuantity, - DryBulbTemperature, - DewPointTemperature, - AtmosphericPressure, - General3, - >( - Argument { - name: "temperature", - def_val: 300.0, - range: [232.0, 314.0], - _quantity: PhantomData, - }, - Argument { - name: "dewpoint", - def_val: 290.0, - range: [232.0, 314.0], - _quantity: PhantomData, - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [10000.0, 150_000.0], - _quantity: PhantomData, - }, - 0.5338747953552858, + Argument::new([0.0, 50_000.0]), + Argument::new([0.1, 50_000.0]), + ReferenceAtmosphere::Normal, + 1e-12, ); } } From 58df217534f492aa5122da71565d6f24d2c6aa78 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:17:28 +0100 Subject: [PATCH 047/102] wet bulb potential temperature --- src/lib.rs | 2 +- src/tests/testing_traits.rs | 19 ++++++++++++++++++ src/wet_bulb_potential_temperature.rs | 29 ++++++++++----------------- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a253e23..1bf214f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,7 +95,7 @@ pub mod vapour_pressure; // pub mod saturation_vapour_pressure; pub mod vapour_pressure_deficit; pub mod virtual_temperature; -// pub mod wet_bulb_potential_temperature; +pub mod wet_bulb_potential_temperature; pub mod wet_bulb_temperature; #[cfg(test)] diff --git a/src/tests/testing_traits.rs b/src/tests/testing_traits.rs index 2968cd2..f14f8c7 100644 --- a/src/tests/testing_traits.rs +++ b/src/tests/testing_traits.rs @@ -265,3 +265,22 @@ impl TestingQuantity for WetBulbTemperature { } } } + +impl TestingQuantity for WetBulbPotentialTemperature { + fn new_si(value: Float) -> Self { + Self::new::(value) + } + + fn imperial(&self) -> Self { + let value = self.0.get::(); + + Self::new::(value) + } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(THETAW_NORM), + ReferenceAtmosphere::Freezing => Self::new::(THETAW_FREEZ), + } + } +} diff --git a/src/wet_bulb_potential_temperature.rs b/src/wet_bulb_potential_temperature.rs index 708b1f6..34100ba 100644 --- a/src/wet_bulb_potential_temperature.rs +++ b/src/wet_bulb_potential_temperature.rs @@ -13,6 +13,8 @@ use crate::{ errors::InputError, }; +type FormulaQuantity = WetBulbPotentialTemperature; + /// Formula for computing wet bulb potential temperature from equivalent potential temperature. /// /// Derived by R. Davies-Jones (2008) [(doi:10.1175/2007MWR2224.1)](https://doi.org/10.1175/2007MWR2224.1) @@ -20,18 +22,12 @@ use crate::{ /// Valid `temperature` range: 257K - 377K pub struct DaviesJones1; -impl Formula1 for DaviesJones1 { +impl Formula1 for DaviesJones1 { #[inline(always)] fn validate_inputs( equivalent_potential_temperature: EquivalentPotentialTemperature, ) -> Result<(), InputError> { - let equivalent_potential_temperature_si = equivalent_potential_temperature.get_si_value(); - - if !(257.0..=377.0).contains(&equivalent_potential_temperature_si) { - return Err(InputError::OutOfRange(String::from( - "equivalent_potential_temperature", - ))); - } + equivalent_potential_temperature.check_range_si(257.0, 377.0)?; Ok(()) } @@ -53,21 +49,18 @@ impl Formula1 for D #[cfg(test)] mod tests { use crate::{ - quantities::{EquivalentPotentialTemperature, WetBulbPotentialTemperature}, - tests::{test_with_1arg, Argument}, + quantities::EquivalentPotentialTemperature, + tests::{test_with_1arg, testing_traits::ReferenceAtmosphere, Argument}, }; - use super::DaviesJones1; + use super::*; #[test] fn davies_jones1() { - test_with_1arg::( - Argument { - name: "equivalent_potential_temperature", - def_val: 300.0, - range: [257.0, 377.0], - }, - 281.17941447108467, + test_with_1arg::( + Argument::new([257.0, 377.0]), + ReferenceAtmosphere::Normal, + 1e-12, ); } } From 47e7c011ce4270013a93486249d0c0441b3456a4 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:20:01 +0100 Subject: [PATCH 048/102] specific humidity --- src/lib.rs | 2 +- src/specific_humidity.rs | 35 +++++++++++------------------------ 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1bf214f..00df75f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,7 @@ pub mod quantities; // pub mod saturation_mixing_ratio; // pub mod potential_temperature; pub mod relative_humidity; -// pub mod specific_humidity; +pub mod specific_humidity; pub mod vapour_pressure; // pub mod saturation_vapour_pressure; pub mod vapour_pressure_deficit; diff --git a/src/specific_humidity.rs b/src/specific_humidity.rs index f9928d5..1ff4ed8 100644 --- a/src/specific_humidity.rs +++ b/src/specific_humidity.rs @@ -12,6 +12,8 @@ use crate::quantities::{ }; use crate::{constants::EPSILON, errors::InputError}; +type FormulaQuantity = SpecificHumidity; + /// Formula for computing specific humidity from vapour pressure and pressure. /// Reverse function of [`vapour_pressure::general1`](crate::vapour_pressure::general1). /// This function is theoretical not empirical. @@ -23,22 +25,14 @@ use crate::{constants::EPSILON, errors::InputError}; /// Valid `pressure` range: 100Pa - 150000Pa pub struct Definition1; -impl Formula2 for Definition1 { +impl Formula2 for Definition1 { #[inline(always)] fn validate_inputs( vapour_pressure: VapourPressure, pressure: AtmosphericPressure, ) -> Result<(), InputError> { - let vapour_pressure_si = vapour_pressure.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(0.0..=50_000.0).contains(&vapour_pressure_si) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + vapour_pressure.check_range_si(0.0, 50_000.0)?; + pressure.check_range_si(100.0, 150_000.0)?; Ok(()) } @@ -57,24 +51,17 @@ impl Formula2 for Definit #[cfg(test)] mod tests { - use crate::tests::{test_with_2args, Argument}; + use crate::tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}; use super::*; #[test] fn definition1() { - test_with_2args::( - Argument { - name: "vapour_pressure", - def_val: 3000.0, - range: [0.0, 50_000.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - 0.018623845512674677, + test_with_2args::( + Argument::new([0.0, 50_000.0]), + Argument::new([100.0, 150_000.0]), + ReferenceAtmosphere::Normal, + 1e-12, ); } } From 8ed802fc3610bbb3a78be8b23670d930fe17bd49 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:31:02 +0100 Subject: [PATCH 049/102] potential temperature --- Cargo.toml | 1 - src/lib.rs | 2 +- src/potential_temperature.rs | 79 ++++++++++++------------------------ src/tests.rs | 2 - src/tests/testing_traits.rs | 19 +++++++++ 5 files changed, 47 insertions(+), 56 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8d3b33f..02b88b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ uom = { version = "0.35", default-features = false, features = [ "f32", "f64", ] } -num-traits = { version = "0.2", default-features = false, features = ["std"] } [lib] doctest = false diff --git a/src/lib.rs b/src/lib.rs index 00df75f..3367d6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,7 @@ pub mod quantities; // pub mod equivalent_potential_temperature; // pub mod mixing_ratio; // pub mod saturation_mixing_ratio; -// pub mod potential_temperature; +pub mod potential_temperature; pub mod relative_humidity; pub mod specific_humidity; pub mod vapour_pressure; diff --git a/src/potential_temperature.rs b/src/potential_temperature.rs index 0648736..931731a 100644 --- a/src/potential_temperature.rs +++ b/src/potential_temperature.rs @@ -4,21 +4,21 @@ //! adiabatically and reversibly from its initial state to a //! standard pressure, p0 = 100 kPa ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Potential_temperature)). +use crate::constants::KAPPA; +use crate::errors::InputError; use crate::formula::Formula3; use crate::quantities::{ AtmosphericPressure, DryBulbTemperature, PotentialTemperature, ThermodynamicQuantity, VapourPressure, }; -use crate::{ - constants::{C_P, R_D}, - errors::InputError, -}; -use crate::{Float, Storage}; +use crate::Float; use float_cmp::approx_eq; use uom::si::pressure::pascal; use uom::si::ratio::ratio; use uom::si::thermodynamic_temperature::kelvin; +type FormulaQuantity = PotentialTemperature; + /// Formula for computing potential temperature of dry air from temperature, pressure and vapour pressure. /// /// Provided in by R. Davies-Jones (2009) [(doi:10.1175/2009MWR2774.1)](https://doi.org/10.1175/2009MWR2774.1) @@ -36,7 +36,7 @@ use uom::si::thermodynamic_temperature::kelvin; /// in which case floating-point exponentation of negative number occurs. pub struct Definition1; -impl Formula3 +impl Formula3 for Definition1 { #[inline(always)] @@ -45,31 +45,24 @@ impl Formula3 Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let pressure_si = pressure.get_si_value(); - let vapour_pressure_si = vapour_pressure.get_si_value(); - - if !(253.0..=324.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - - if !(0.0..=10_000.0).contains(&vapour_pressure_si) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); - } - - if approx_eq!(Float, pressure_si, vapour_pressure_si, ulps = 2) { + temperature.check_range_si(253.0, 324.0)?; + pressure.check_range_si(100.0, 150_000.0)?; + vapour_pressure.check_range_si(0.0, 10_000.0)?; + + if approx_eq!( + Float, + pressure.get_si_value(), + vapour_pressure.get_si_value(), + ulps = 2 + ) { return Err(InputError::IncorrectArgumentSet(String::from( "pressure and vapour_pressure cannot be equal", ))); } - if vapour_pressure_si > pressure_si { + if vapour_pressure.get_si_value() > pressure.get_si_value() { return Err(InputError::IncorrectArgumentSet(String::from( - "vapour_pressure cannot be higher than pressure", + "vapour_pressure cannot be greater or equal to pressure", ))); } @@ -86,50 +79,32 @@ impl Formula3(); let vapour_pressure = vapour_pressure.0.get::(); - let kappa = (R_D / C_P).get::(); + let kappa = KAPPA.get::(); let result = temperature * (100_000.0 / (pressure - vapour_pressure)).powf(kappa); - let result = Storage::ThermodynamicTemperature::new::(result); - - PotentialTemperature(result) + PotentialTemperature::new::(result) } } #[cfg(test)] mod tests { - use std::marker::PhantomData; - - use crate::tests::{test_with_3args, Argument}; + use crate::tests::{test_with_3args, testing_traits::ReferenceAtmosphere, Argument}; use super::*; #[test] fn definition1() { test_with_3args::< - PotentialTemperature, + FormulaQuantity, DryBulbTemperature, AtmosphericPressure, VapourPressure, Definition1, >( - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0], - _quantity: PhantomData, - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - _quantity: PhantomData, - }, - Argument { - name: "vapour_pressure", - def_val: 3000.0, - range: [0.0, 10_000.0], - _quantity: PhantomData, - }, - 301.45136519081666, + Argument::new([253.0, 324.0]), + Argument::new([100.0, 150_000.0]), + Argument::new([0.0, 10_000.0]), + ReferenceAtmosphere::Normal, + 1e-12, ); } } diff --git a/src/tests.rs b/src/tests.rs index 6091f95..5365d0c 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -37,8 +37,6 @@ fn check_result(result: T, atm: ReferenceAtmosphere, eps: Fl let expected = T::ref_val_si(atm).get_si_value(); let result = result.get_si_value(); - dbg!(result, expected, eps); - assert_approx_eq!(Float, result, expected, epsilon = eps) } diff --git a/src/tests/testing_traits.rs b/src/tests/testing_traits.rs index f14f8c7..1786fb6 100644 --- a/src/tests/testing_traits.rs +++ b/src/tests/testing_traits.rs @@ -284,3 +284,22 @@ impl TestingQuantity for WetBulbPotentialTemperature { } } } + +impl TestingQuantity for PotentialTemperature { + fn new_si(value: Float) -> Self { + Self::new::(value) + } + + fn imperial(&self) -> Self { + let value = self.0.get::(); + + Self::new::(value) + } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(THETA_NORM), + ReferenceAtmosphere::Freezing => Self::new::(THETA_FREEZ), + } + } +} From efb8e8de91eedc09b4c4b391245ff6bce82e213d Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 26 Feb 2024 20:09:48 +0100 Subject: [PATCH 050/102] mixing ratio and saturation mixing ratio --- src/lib.rs | 4 +- src/mixing_ratio.rs | 164 ++++----------------------------- src/potential_temperature.rs | 2 +- src/saturation_mixing_ratio.rs | 163 ++++++++------------------------ src/tests/reference_values.rs | 1 - 5 files changed, 63 insertions(+), 271 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3367d6b..f321609 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,8 +86,8 @@ pub mod formula; pub mod quantities; // pub mod equivalent_potential_temperature; -// pub mod mixing_ratio; -// pub mod saturation_mixing_ratio; +pub mod mixing_ratio; +pub mod saturation_mixing_ratio; pub mod potential_temperature; pub mod relative_humidity; pub mod specific_humidity; diff --git a/src/mixing_ratio.rs b/src/mixing_ratio.rs index 61b5cc5..5b82a33 100644 --- a/src/mixing_ratio.rs +++ b/src/mixing_ratio.rs @@ -3,12 +3,10 @@ //! Mixing ratio is the ratio of the mass of a variable atmospheric constituent to the mass //! of dry air ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Mixing_ratio)). -use crate::formula::{Formula1, Formula2}; -use crate::quantities::{ - AtmosphericPressure, DewPointTemperature, MixingRatio, ThermodynamicQuantity, VapourPressure, -}; +use crate::formula::Formula2; +use crate::quantities::{AtmosphericPressure, MixingRatio, ThermodynamicQuantity, VapourPressure}; +use crate::Float; use crate::{constants::EPSILON, errors::InputError}; -use crate::{vapour_pressure, Float}; use float_cmp::approx_eq; type FormulaQuantity = MixingRatio; @@ -28,18 +26,21 @@ impl Formula2 for Definiti pressure: AtmosphericPressure, vapour_pressure: VapourPressure, ) -> Result<(), InputError> { - let pressure_si = pressure.get_si_value(); - let vapour_pressure_si = vapour_pressure.get_si_value(); + pressure.check_range_si(100.0, 150_000.0)?; + vapour_pressure.check_range_si(0.0, 50_000.0)?; - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - - if !(0.0..=50_000.0).contains(&vapour_pressure_si) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); + if vapour_pressure.0 > pressure.0 { + return Err(InputError::OutOfRange(String::from( + "vapour_pressure cannot be greater than pressure", + ))); } - if approx_eq!(Float, pressure_si, vapour_pressure_si, ulps = 2) { + if approx_eq!( + Float, + pressure.get_si_value(), + vapour_pressure.get_si_value(), + ulps = 2 + ) { return Err(InputError::IncorrectArgumentSet(String::from( "pressure and vapour_pressure cannot be equal", ))); @@ -56,145 +57,20 @@ impl Formula2 for Definiti } } -/// Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. -/// Optimised for performance - uses [`Tetens1`]. -/// -/// Valid `dewpoint` range: 273K - 353K -/// -/// Valid `pressure` range: 100Pa - 150000Pa -pub struct Performance1; - -impl Formula2 for Performance1 { - #[inline(always)] - fn validate_inputs( - dewpoint: DewPointTemperature, - pressure: AtmosphericPressure, - ) -> Result<(), InputError> { - let dewpoint_si = dewpoint.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(273.0..=353.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - - Ok(()) - } - - #[inline(always)] - fn compute_unchecked( - dewpoint: DewPointTemperature, - pressure: AtmosphericPressure, - ) -> MixingRatio { - let vapour_pressure = vapour_pressure::Tetens1::compute_unchecked(dewpoint); - - Definition1::compute_unchecked(pressure, vapour_pressure) - } -} - -/// Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. -/// Optimised for accuracy - uses [`Buck1`]. -/// -/// Valid `dewpoint` range: 232K - 324K -/// -/// Valid `pressure` range: 100Pa - 150000Pa -pub struct Accuracy1; - -impl Formula2 for Accuracy1 { - #[inline(always)] - fn validate_inputs( - dewpoint: DewPointTemperature, - pressure: AtmosphericPressure, - ) -> Result<(), InputError> { - let dewpoint_si = dewpoint.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(232.0..=324.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - Ok(()) - } - - #[inline(always)] - fn compute_unchecked( - dewpoint: DewPointTemperature, - pressure: AtmosphericPressure, - ) -> MixingRatio { - let vapour_pressure = vapour_pressure::Buck1::compute_unchecked(dewpoint, pressure); - - Definition1::compute_unchecked(pressure, vapour_pressure) - } -} - #[cfg(test)] mod tests { - use std::marker::PhantomData; - use crate::tests::{test_with_2args, Argument}; + use crate::tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}; use super::*; #[test] fn general1() { test_with_2args::( - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - _quantity: PhantomData, - }, - Argument { - name: "vapour_pressure", - def_val: 3500.0, - range: [0.0, 50_000.0], - _quantity: PhantomData, - }, - 0.022253316630823517, - ); - } - - #[test] - fn performance1() { - test_with_2args::( - Argument { - name: "dewpoint", - def_val: 300.0, - range: [273.0, 353.0], - _quantity: PhantomData, - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - _quantity: PhantomData, - }, - 0.022477100514593465, - ); - } - - #[test] - fn accuracy1() { - test_with_2args::( - Argument { - name: "dewpoint", - def_val: 300.0, - range: [232.0, 324.0], - _quantity: PhantomData, - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - _quantity: PhantomData, - }, - 0.022587116896465847, + Argument::new([100.0, 150_000.0]), + Argument::new([0.0, 50_000.0]), + ReferenceAtmosphere::Normal, + 1e-12, ); } } diff --git a/src/potential_temperature.rs b/src/potential_temperature.rs index 931731a..31163f5 100644 --- a/src/potential_temperature.rs +++ b/src/potential_temperature.rs @@ -60,7 +60,7 @@ impl Formula3 pressure.get_si_value() { + if vapour_pressure.0 > pressure.0 { return Err(InputError::IncorrectArgumentSet(String::from( "vapour_pressure cannot be greater or equal to pressure", ))); diff --git a/src/saturation_mixing_ratio.rs b/src/saturation_mixing_ratio.rs index 6f83d89..6d59507 100644 --- a/src/saturation_mixing_ratio.rs +++ b/src/saturation_mixing_ratio.rs @@ -3,18 +3,18 @@ //! Saturation mixing ration is the value of the mixing ratio of saturated air at the //! given temperature and pressure ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Saturation_mixing_ratio)). -use crate::formula::{Formula1, Formula2}; +use crate::formula::Formula2; use crate::quantities::{ - AtmosphericPressure, DryBulbTemperature, SaturationMixingRatio, SaturationVapourPressure, - ThermodynamicQuantity, + AtmosphericPressure, MixingRatio, RelativeHumidity, SaturationMixingRatio, + SaturationVapourPressure, ThermodynamicQuantity, }; +use crate::Float; use crate::{constants::EPSILON, errors::InputError}; -use crate::{saturation_vapour_pressure, Float}; use float_cmp::approx_eq; type FormulaQuantity = SaturationMixingRatio; -/// Formula for computing mixing ratio of unsaturated air from air pressure and vapour pressure +/// Formula for computing saturation mixing ratio of unsaturated air from air pressure and vapour pressure /// /// Valid `pressure` range: 100Pa - 150000Pa /// @@ -29,20 +29,21 @@ impl Formula2 fo pressure: AtmosphericPressure, saturation_vapour_pressure: SaturationVapourPressure, ) -> Result<(), InputError> { - let pressure_si = pressure.get_si_value(); - let saturation_vapour_pressure_si = saturation_vapour_pressure.get_si_value(); + pressure.check_range_si(100.0, 150_000.0)?; + saturation_vapour_pressure.check_range_si(0.0, 50_000.0)?; - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - - if !(0.0..=50_000.0).contains(&saturation_vapour_pressure_si) { + if saturation_vapour_pressure.0 > pressure.0 { return Err(InputError::OutOfRange(String::from( - "saturation_vapour_pressure", + "saturation_vapour_pressure cannot be greater than pressure", ))); } - if approx_eq!(Float, pressure_si, saturation_vapour_pressure_si, ulps = 2) { + if approx_eq!( + Float, + pressure.get_si_value(), + saturation_vapour_pressure.get_si_value(), + ulps = 2 + ) { return Err(InputError::IncorrectArgumentSet(String::from( "pressure and saturation_vapour_pressure cannot be equal", ))); @@ -61,147 +62,63 @@ impl Formula2 fo } } -/// Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. -/// Optimised for performance - uses [`Tetens1`]. +/// Formula for computing saturation mixing ratio of unsaturated air from +/// mixing ratio and relative humidity. /// -/// Valid `dewpoint` range: 273K - 353K +/// Valid `mixing_ratio` range: 0.000_000_000_1 - 1.0 /// -/// Valid `pressure` range: 100Pa - 150000Pa -pub struct Performance1; +/// Valid `relative_humditity` range: 0.000_000_000_1 - 2.0 +pub struct Definition2; -impl Formula2 for Performance1 { +impl Formula2 for Definition2 { #[inline(always)] fn validate_inputs( - temperature: DryBulbTemperature, - pressure: AtmosphericPressure, + mixing_ratio: MixingRatio, + relative_humidity: RelativeHumidity, ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(273.0..=353.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + mixing_ratio.check_range_si(0.000_000_000_1, 1.0)?; + relative_humidity.check_range_si(0.000_000_000_1, 2.0)?; Ok(()) } #[inline(always)] fn compute_unchecked( - temperature: DryBulbTemperature, - pressure: AtmosphericPressure, + mixing_ratio: MixingRatio, + relative_humidity: RelativeHumidity, ) -> SaturationMixingRatio { - let saturation_vapour_pressure = - saturation_vapour_pressure::Tetens1::compute_unchecked(temperature); - - Definition1::compute_unchecked(pressure, saturation_vapour_pressure) - } -} - -/// Formula for computing mixing ratio of unsaturated air from dewpoint temperature and pressure. -/// Optimised for accuracy - uses [`Buck1`]. -/// -/// Valid `dewpoint` range: 232K - 324K -/// -/// Valid `pressure` range: 100Pa - 150000Pa -pub struct Accuracy1; - -impl Formula2 for Accuracy1 { - #[inline(always)] - fn validate_inputs( - temperature: DryBulbTemperature, - pressure: AtmosphericPressure, - ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(232.0..=324.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - Ok(()) - } - - #[inline(always)] - fn compute_unchecked( - temperature: DryBulbTemperature, - pressure: AtmosphericPressure, - ) -> SaturationMixingRatio { - let saturation_vapour_pressure = - saturation_vapour_pressure::Buck1::compute_unchecked(temperature, pressure); - - Definition1::compute_unchecked(pressure, saturation_vapour_pressure) + SaturationMixingRatio(mixing_ratio.0 / relative_humidity.0) } } #[cfg(test)] mod tests { - use crate::{ - quantities::DryBulbTemperature, - tests::{test_with_2args, Argument}, - }; + use crate::tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}; use super::*; #[test] - fn general1() { + fn definition1() { test_with_2args::< FormulaQuantity, AtmosphericPressure, SaturationVapourPressure, Definition1, >( - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - Argument { - name: "saturation_vapour_pressure", - def_val: 3500.0, - range: [0.0, 50_000.0], - }, - 0.022253316630823517, - ); - } - - #[test] - fn performance1() { - test_with_2args::( - Argument { - name: "temperature", - def_val: 300.0, - range: [273.0, 353.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - 0.022477100514593465, + Argument::new([100.0, 150_000.0]), + Argument::new([0.0, 50_000.0]), + ReferenceAtmosphere::Normal, + 1e-2, ); } #[test] - fn accuracy1() { - test_with_2args::( - Argument { - name: "temperature", - def_val: 300.0, - range: [232.0, 324.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - 0.022587116896465847, + fn definition2() { + test_with_2args::( + Argument::new([0.000_000_000_1, 1.0]), + Argument::new([0.000_000_000_1, 2.0]), + ReferenceAtmosphere::Normal, + 1e-12, ); } } diff --git a/src/tests/reference_values.rs b/src/tests/reference_values.rs index 7a931d1..c002a5a 100644 --- a/src/tests/reference_values.rs +++ b/src/tests/reference_values.rs @@ -16,7 +16,6 @@ pub(crate) const THETAW_NORM: Float = 292.0717306393948; pub(crate) const WBT_NORM: Float = 293.42728654340516; pub(crate) const VRT_NORM: Float = 302.1926517941886; - pub(crate) const TEMP_FREEZ: Float = 260.0; pub(crate) const DWPT_FREEZ: Float = 255.0; pub(crate) const PRES_FREEZ: Float = 100000.0; From 6fac7165c11c93fc41862cdbdc84e4456c5c9750 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 26 Feb 2024 20:18:09 +0100 Subject: [PATCH 051/102] saturation vapour pressure --- src/lib.rs | 2 +- src/saturation_vapour_pressure.rs | 213 ++++++++---------------------- 2 files changed, 53 insertions(+), 162 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f321609..af10de0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,7 +92,7 @@ pub mod potential_temperature; pub mod relative_humidity; pub mod specific_humidity; pub mod vapour_pressure; -// pub mod saturation_vapour_pressure; +pub mod saturation_vapour_pressure; pub mod vapour_pressure_deficit; pub mod virtual_temperature; pub mod wet_bulb_potential_temperature; diff --git a/src/saturation_vapour_pressure.rs b/src/saturation_vapour_pressure.rs index 2e3a072..66c1a6f 100644 --- a/src/saturation_vapour_pressure.rs +++ b/src/saturation_vapour_pressure.rs @@ -32,16 +32,8 @@ impl Formula2 for Definition1 vapour_pressure: VapourPressure, relative_humidity: RelativeHumidity, ) -> Result<(), InputError> { - let relative_humidity_si = relative_humidity.get_si_value(); - let vapour_pressure_si = vapour_pressure.get_si_value(); - - if !(0.00001..=2.0).contains(&relative_humidity_si) { - return Err(InputError::OutOfRange(String::from("relative_humidity"))); - } - - if !(0.0..=50_000.0).contains(&vapour_pressure_si) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); - } + vapour_pressure.check_range_si(0.0, 50_000.0)?; + relative_humidity.check_range_si(0.00001, 2.0)?; Ok(()) } @@ -71,16 +63,8 @@ impl Formula2 for Buck temperature: DryBulbTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(232.0..=324.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + temperature.check_range_si(232.0, 324.0)?; + pressure.check_range_si(100.0, 150_000.0)?; Ok(()) } @@ -128,16 +112,8 @@ impl Formula2 for Buck temperature: DryBulbTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(193.0..=274.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + temperature.check_range_si(193.0, 274.0)?; + pressure.check_range_si(100.0, 150_000.0)?; Ok(()) } @@ -185,16 +161,8 @@ impl Formula2 for Buck temperature: DryBulbTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(253.0..=324.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + temperature.check_range_si(253.0, 324.0)?; + pressure.check_range_si(100.0, 150_000.0)?; Ok(()) } @@ -234,11 +202,7 @@ pub struct Buck3Simplified; impl Formula1 for Buck3Simplified { #[inline(always)] fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - - if !(253.0..=324.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } + temperature.check_range_si(253.0, 324.0)?; Ok(()) } @@ -275,16 +239,8 @@ impl Formula2 for Buck temperature: DryBulbTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let pressure_si = pressure.get_si_value(); - - if !(223.0..=274.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(100.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } + temperature.check_range_si(223.0, 274.0)?; + pressure.check_range_si(100.0, 150_000.0)?; Ok(()) } @@ -324,11 +280,7 @@ pub struct Buck4Simplified; impl Formula1 for Buck4Simplified { #[inline(always)] fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - - if !(223.0..=274.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } + temperature.check_range_si(223.0, 274.0)?; Ok(()) } @@ -361,11 +313,7 @@ pub struct Tetens1; impl Formula1 for Tetens1 { #[inline(always)] fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - - if !(273.0..=353.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } + temperature.check_range_si(273.0, 353.0)?; Ok(()) } @@ -398,11 +346,7 @@ pub struct Wexler1; impl Formula1 for Wexler1 { #[inline(always)] fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - - if !(273.0..=374.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } + temperature.check_range_si(273.0, 374.0)?; Ok(()) } @@ -447,11 +391,8 @@ pub struct Wexler2; impl Formula1 for Wexler2 { #[inline(always)] fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); + temperature.check_range_si(173.0, 274.0)?; - if !(173.0..=274.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } Ok(()) } @@ -484,8 +425,8 @@ impl Formula1 for Wexler2 { #[cfg(test)] mod tests { use crate::{ - quantities::{AtmosphericPressure, RelativeHumidity, VapourPressure, DryBulbTemperature}, - tests::{test_with_1arg, test_with_2args, Argument}, + quantities::{AtmosphericPressure, DryBulbTemperature, RelativeHumidity, VapourPressure}, + tests::{test_with_1arg, test_with_2args, testing_traits::ReferenceAtmosphere, Argument}, }; use super::*; @@ -493,145 +434,95 @@ mod tests { #[test] fn definition1() { test_with_2args::( - Argument { - name: "vapour_pressure", - def_val: 3000.0, - range: [0.0, 50_000.0], - }, - Argument { - name: "relative_humidity", - def_val: 0.5, - range: [0.00001, 2.0], - }, - 6000.0, + Argument::new([0.0, 50_000.0]), + Argument::new([0.00001, 2.0]), + ReferenceAtmosphere::Normal, + 1e-12, ); } #[test] fn buck1() { test_with_2args::( - Argument { - name: "temperature", - def_val: 300.0, - range: [232.0, 324.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - 3550.6603579471303, + Argument::new([232.0, 324.0]), + Argument::new([100.0, 150_000.0]), + ReferenceAtmosphere::Normal, + 1e2, ); } #[test] fn buck2() { test_with_2args::( - Argument { - name: "temperature", - def_val: 250.0, - range: [193.0, 274.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - 76.38781790372722, + Argument::new([193.0, 274.0]), + Argument::new([100.0, 150_000.0]), + ReferenceAtmosphere::Freezing, + 1e0, ); } #[test] fn buck3() { test_with_2args::( - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - 3548.5041048035896, + Argument::new([253.0, 324.0]), + Argument::new([100.0, 150_000.0]), + ReferenceAtmosphere::Normal, + 1e2, ); } #[test] fn buck4() { test_with_2args::( - Argument { - name: "temperature", - def_val: 250.0, - range: [223.0, 274.0], - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [100.0, 150_000.0], - }, - 76.38685471836712, + Argument::new([223.0, 274.0]), + Argument::new([100.0, 150_000.0]), + ReferenceAtmosphere::Freezing, + 1e0, ); } #[test] fn buck3_simplified() { test_with_1arg::( - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0], - }, - 3533.6421536199978, + Argument::new([253.0, 324.0]), + ReferenceAtmosphere::Normal, + 1e1, ); } #[test] fn buck4_simplified() { test_with_1arg::( - Argument { - name: "temperature", - def_val: 250.0, - range: [223.0, 274.0], - }, - 76.04197508519536, + Argument::new([223.0, 274.0]), + ReferenceAtmosphere::Freezing, + 1e-1, ); } #[test] fn tetens1() { test_with_1arg::( - Argument { - name: "temperature", - def_val: 300.0, - range: [273.0, 353.0], - }, - 3533.969137160892, + Argument::new([273.0, 353.0]), + ReferenceAtmosphere::Normal, + 1e1, ); } #[test] fn wexler1() { test_with_1arg::( - Argument { - name: "temperature", - def_val: 300.0, - range: [273.0, 374.0], - }, - 3535.4235919263083, + Argument::new([273.0, 374.0]), + ReferenceAtmosphere::Normal, + 1e-12, ); } #[test] fn wexler2() { test_with_1arg::( - Argument { - name: "temperature", - def_val: 250.0, - range: [173.0, 274.0], - }, - 76.04351136780438, + Argument::new([173.0, 274.0]), + ReferenceAtmosphere::Freezing, + 1e-12, ); } } From 3199f167ae88cfe3e756d3fb34c2353c7a317fd5 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 26 Feb 2024 22:59:31 +0100 Subject: [PATCH 052/102] start theta-e --- src/equivalent_potential_temperature.rs | 338 +++++++++++------------- src/formula.rs | 83 ++++-- src/lib.rs | 2 +- 3 files changed, 227 insertions(+), 196 deletions(-) diff --git a/src/equivalent_potential_temperature.rs b/src/equivalent_potential_temperature.rs index 95f8791..b419a1d 100644 --- a/src/equivalent_potential_temperature.rs +++ b/src/equivalent_potential_temperature.rs @@ -4,6 +4,7 @@ //! to the entropy of moist air, that is conserved in a reversible moist //! adiabatic process ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Equivalent_potential_temperature)). +use float_cmp::approx_eq; use uom::si::available_energy::joule_per_kilogram; use uom::si::pressure::pascal; use uom::si::ratio::ratio; @@ -12,15 +13,12 @@ use uom::si::thermodynamic_temperature::kelvin; use crate::constants::{C_L, C_P, EPSILON, KAPPA, L_V, R_D, R_V}; use crate::errors::InputError; -use crate::formula::{Formula2, Formula3}; +use crate::formula::{Formula2, Formula4}; use crate::quantities::{ AtmosphericPressure, DewPointTemperature, DryBulbTemperature, EquivalentPotentialTemperature, - ThermodynamicQuantity, VapourPressure, -}; -use crate::{ - mixing_ratio, potential_temperature, relative_humidity, saturation_vapour_pressure, - vapour_pressure, + MixingRatio, PotentialTemperature, RelativeHumidity, ThermodynamicQuantity, VapourPressure, }; +use crate::{mixing_ratio, Float}; type FormulaQuantity = EquivalentPotentialTemperature; @@ -37,33 +35,31 @@ type FormulaQuantity = EquivalentPotentialTemperature; /// /// Valid `pressure` range: 100Pa - 150000Pa /// -/// Valid `vapour_pressure` range: 0Pa - 10000Pa +/// Valid `mixing_ratio` range: 0.000_000_1 - 2.0 +/// +/// Valid `relative_humidity` range: 0.000_000_1 - 2.0 pub struct Paluch1; -impl Formula3 - for Paluch1 +impl + Formula4< + FormulaQuantity, + DryBulbTemperature, + AtmosphericPressure, + MixingRatio, + RelativeHumidity, + > for Paluch1 { #[inline(always)] fn validate_inputs( temperature: DryBulbTemperature, pressure: AtmosphericPressure, - vapour_pressure: VapourPressure, + mixing_ratio: MixingRatio, + relative_humidity: RelativeHumidity, ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let pressure_si = pressure.get_si_value(); - let vapour_pressure_si = vapour_pressure.get_si_value(); - - if !(253.0..=324.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(20000.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - - if !(0.0..=10_000.0).contains(&vapour_pressure_si) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); - } + temperature.check_range_si(253.0, 324.0)?; + pressure.check_range_si(100.0, 150_000.0)?; + mixing_ratio.check_range_si(0.000_000_1, 2.0)?; + relative_humidity.check_range_si(0.000_000_1, 2.0)?; Ok(()) } @@ -72,17 +68,9 @@ impl Formula3 EquivalentPotentialTemperature { - let mixing_ratio = mixing_ratio::Definition1::compute_unchecked(pressure, vapour_pressure); - let saturation_vapour_pressure = - saturation_vapour_pressure::Buck1::compute_unchecked(temperature, pressure); - - let relative_humidity = relative_humidity::Definition2::compute_unchecked( - vapour_pressure, - saturation_vapour_pressure, - ); - let temperature = temperature.0.get::(); let pressure = pressure.0.get::(); let mixing_ratio = mixing_ratio.0.get::(); @@ -116,28 +104,26 @@ impl Formula3 for Bryan1 { +impl + Formula4< + FormulaQuantity, + DryBulbTemperature, + MixingRatio, + RelativeHumidity, + PotentialTemperature, + > for Bryan1 +{ #[inline(always)] fn validate_inputs( temperature: DryBulbTemperature, - pressure: AtmosphericPressure, - vapour_pressure: VapourPressure, + mixing_ratio: MixingRatio, + relative_humidity: RelativeHumidity, + potential_temperature: PotentialTemperature, ) -> Result<(), InputError> { - let temperature_si = temperature.get_si_value(); - let pressure_si = pressure.get_si_value(); - let vapour_pressure_si = vapour_pressure.get_si_value(); - - if !(253.0..=324.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(20000.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - - if !(0.0..=10_000.0).contains(&vapour_pressure_si) { - return Err(InputError::OutOfRange(String::from("vapour_pressure"))); - } + temperature.check_range_si(253.0, 324.0)?; + mixing_ratio.check_range_si(0.000_000_1, 2.0)?; + relative_humidity.check_range_si(0.000_000_1, 2.0)?; + potential_temperature.check_range_si(253.0, 324.0)?; Ok(()) } @@ -145,25 +131,10 @@ impl Formula3 EquivalentPotentialTemperature { - let potential_temperature = potential_temperature::Definition1::compute_unchecked( - temperature, - pressure, - vapour_pressure, - ); - - let saturation_vapour_pressure = - saturation_vapour_pressure::Buck3::compute_unchecked(temperature, pressure); - - let relative_humidity = relative_humidity::Definition2::compute_unchecked( - vapour_pressure, - saturation_vapour_pressure, - ); - - let mixing_ratio = mixing_ratio::Definition1::compute_unchecked(pressure, vapour_pressure); - let temperature = temperature.0.get::(); let mixing_ratio = mixing_ratio.0.get::(); let relative_humidity = relative_humidity.0.get::(); @@ -195,29 +166,36 @@ impl Formula3 - for Bolton1 +impl + Formula4< + FormulaQuantity, + AtmosphericPressure, + DryBulbTemperature, + DewPointTemperature, + VapourPressure, + > for Bolton1 { #[inline(always)] fn validate_inputs( pressure: AtmosphericPressure, temperature: DryBulbTemperature, dewpoint: DewPointTemperature, + vapour_pressure: VapourPressure, ) -> Result<(), InputError> { - let pressure_si = pressure.get_si_value(); - let temperature_si = temperature.get_si_value(); - let dewpoint_si = dewpoint.get_si_value(); - - if !(20000.0..=150_000.0).contains(&pressure_si) { - return Err(InputError::OutOfRange(String::from("pressure"))); - } - - if !(253.0..=324.0).contains(&temperature_si) { - return Err(InputError::OutOfRange(String::from("temperature"))); - } - - if !(253.0..=324.0).contains(&dewpoint_si) { - return Err(InputError::OutOfRange(String::from("dewpoint"))); + pressure.check_range_si(100.0, 150_000.0)?; + temperature.check_range_si(253.0, 324.0)?; + dewpoint.check_range_si(253.0, 324.0)?; + vapour_pressure.check_range_si(0.0, 50_000.0)?; + + if approx_eq!( + Float, + pressure.get_si_value(), + vapour_pressure.get_si_value(), + ulps = 2 + ) { + return Err(InputError::IncorrectArgumentSet( + "pressure must be greater than vapour pressure".to_string(), + )); } Ok(()) @@ -228,8 +206,8 @@ impl Formula3 EquivalentPotentialTemperature { - let vapour_pressure = vapour_pressure::Buck3::compute_unchecked(dewpoint, pressure); let mixing_ratio = mixing_ratio::Definition1::compute_unchecked(pressure, vapour_pressure); let pressure = pressure.0.get::(); @@ -265,96 +243,96 @@ mod tests { use super::*; - #[test] - fn paluch1() { - test_with_3args::< - FormulaQuantity, - DryBulbTemperature, - AtmosphericPressure, - VapourPressure, - Paluch1, - >( - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0], - _quantity: PhantomData, - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [20000.0, 150_000.0], - _quantity: PhantomData, - }, - Argument { - name: "vapour_pressure", - def_val: 991.189131, - range: [0.0, 10_000.0], - _quantity: PhantomData, - }, - 315.23724970376776, - ); - } - - #[test] - fn bryan1() { - test_with_3args::< - FormulaQuantity, - DryBulbTemperature, - AtmosphericPressure, - VapourPressure, - Bryan1, - >( - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0], - _quantity: PhantomData, - }, - Argument { - name: "pressure", - def_val: 101325.0, - range: [20000.0, 150_000.0], - _quantity: PhantomData, - }, - Argument { - name: "vapour_pressure", - def_val: 991.189131, - range: [0.0, 10_000.0], - _quantity: PhantomData, - }, - 316.52762026634014, - ); - } - - #[test] - fn bolton1() { - test_with_3args::< - FormulaQuantity, - AtmosphericPressure, - DryBulbTemperature, - DewPointTemperature, - Bolton1, - >( - Argument { - name: "pressure", - def_val: 101325.0, - range: [20000.0, 150_000.0], - _quantity: PhantomData, - }, - Argument { - name: "temperature", - def_val: 300.0, - range: [253.0, 324.0], - _quantity: PhantomData, - }, - Argument { - name: "dewpoint", - def_val: 280.0, - range: [253.0, 324.0], - _quantity: PhantomData, - }, - 317.3855211897774, - ); - } + // #[test] + // fn paluch1() { + // test_with_3args::< + // FormulaQuantity, + // DryBulbTemperature, + // AtmosphericPressure, + // VapourPressure, + // Paluch1, + // >( + // Argument { + // name: "temperature", + // def_val: 300.0, + // range: [253.0, 324.0], + // _quantity: PhantomData, + // }, + // Argument { + // name: "pressure", + // def_val: 101325.0, + // range: [20000.0, 150_000.0], + // _quantity: PhantomData, + // }, + // Argument { + // name: "vapour_pressure", + // def_val: 991.189131, + // range: [0.0, 10_000.0], + // _quantity: PhantomData, + // }, + // 315.23724970376776, + // ); + // } + + // #[test] + // fn bryan1() { + // test_with_3args::< + // FormulaQuantity, + // DryBulbTemperature, + // AtmosphericPressure, + // VapourPressure, + // Bryan1, + // >( + // Argument { + // name: "temperature", + // def_val: 300.0, + // range: [253.0, 324.0], + // _quantity: PhantomData, + // }, + // Argument { + // name: "pressure", + // def_val: 101325.0, + // range: [20000.0, 150_000.0], + // _quantity: PhantomData, + // }, + // Argument { + // name: "vapour_pressure", + // def_val: 991.189131, + // range: [0.0, 10_000.0], + // _quantity: PhantomData, + // }, + // 316.52762026634014, + // ); + // } + + // #[test] + // fn bolton1() { + // test_with_3args::< + // FormulaQuantity, + // AtmosphericPressure, + // DryBulbTemperature, + // DewPointTemperature, + // Bolton1, + // >( + // Argument { + // name: "pressure", + // def_val: 101325.0, + // range: [20000.0, 150_000.0], + // _quantity: PhantomData, + // }, + // Argument { + // name: "temperature", + // def_val: 300.0, + // range: [253.0, 324.0], + // _quantity: PhantomData, + // }, + // Argument { + // name: "dewpoint", + // def_val: 280.0, + // range: [253.0, 324.0], + // _quantity: PhantomData, + // }, + // 317.3855211897774, + // ); + // } } diff --git a/src/formula.rs b/src/formula.rs index 7c5d1cd..a82c7da 100644 --- a/src/formula.rs +++ b/src/formula.rs @@ -16,11 +16,11 @@ pub trait Formula1 { #[allow(missing_docs)] #[inline] fn compute(i1: I1) -> Result { - #[cfg(not(feature = "debug"))] - Self::validate_inputs(i1)?; - #[cfg(feature = "debug")] - #[cfg(debug_assertions)] - Self::validate_inputs_loggerr(i1)?; + if cfg!(feature = "debug") && cfg!(debug_assertions) { + Self::validate_inputs_loggerr(i1)?; + } else { + Self::validate_inputs(i1)?; + } Ok(Self::compute_unchecked(i1)) } @@ -102,11 +102,11 @@ pub trait Formula2 Result { - #[cfg(not(feature = "debug"))] - Self::validate_inputs(i1, i2)?; - #[cfg(feature = "debug")] - #[cfg(debug_assertions)] - Self::validate_inputs_loggerr(i1, i2)?; + if cfg!(feature = "debug") && cfg!(debug_assertions) { + Self::validate_inputs_loggerr(i1, i2)?; + } else { + Self::validate_inputs(i1, i2)?; + } Ok(Self::compute_unchecked(i1, i2)) } @@ -211,11 +211,11 @@ pub trait Formula3< #[allow(missing_docs)] #[inline] fn compute(i1: I1, i2: I2, i3: I3) -> Result { - #[cfg(not(feature = "debug"))] - Self::validate_inputs(i1, i2, i3)?; - #[cfg(feature = "debug")] - #[cfg(debug_assertions)] - Self::validate_inputs_loggerr(i1, i2, i3)?; + if cfg!(feature = "debug") && cfg!(debug_assertions) { + Self::validate_inputs_loggerr(i1, i2, i3)?; + } else { + Self::validate_inputs(i1, i2, i3)?; + } Ok(Self::compute_unchecked(i1, i2, i3)) } @@ -314,3 +314,56 @@ pub trait Formula3< .par_map_collect(|&i1, &i2, &i3| Self::compute_unchecked(i1, i2, i3))) } } + +pub trait Formula4< + O: ThermodynamicQuantity, + I1: ThermodynamicQuantity, + I2: ThermodynamicQuantity, + I3: ThermodynamicQuantity, + I4: ThermodynamicQuantity, +> +{ + #[allow(missing_docs)] + fn compute_unchecked(i1: I1, i2: I2, i3: I3, i4: I4) -> O; + + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn validate_inputs(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError>; + + #[allow(clippy::missing_errors_doc)] + #[allow(missing_docs)] + #[inline] + fn compute(i1: I1, i2: I2, i3: I3, i4: I4) -> Result { + if cfg!(feature = "debug") && cfg!(debug_assertions) { + Self::validate_inputs_loggerr(i1, i2, i3, i4)?; + } else { + Self::validate_inputs(i1, i2, i3, i4)?; + } + + Ok(Self::compute_unchecked(i1, i2, i3, i4)) + } + + #[cfg(feature = "debug")] + #[cfg(debug_assertions)] + #[inline(always)] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn validate_inputs_loggerr(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError> { + use std::any::type_name; + + Self::validate_inputs(i1, i2, i3, i4).or_else(|err| { + log::error!( + "Formula {} calculating {} from inputs {:?} {:?} {:?} {:?} returned error: {}", + type_name::(), + type_name::(), + i1, + i2, + i3, + i4, + err + ); + Err(err) + }) + } +} + diff --git a/src/lib.rs b/src/lib.rs index af10de0..c6b5ced 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,7 +85,7 @@ pub mod errors; pub mod formula; pub mod quantities; -// pub mod equivalent_potential_temperature; +pub mod equivalent_potential_temperature; pub mod mixing_ratio; pub mod saturation_mixing_ratio; pub mod potential_temperature; From 1f7334efae144bdbc1868b144cddcc4276c64ba9 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:20:18 +0100 Subject: [PATCH 053/102] reorganise files --- src/formulas.rs | 12 + .../equivalent_potential_temperature.rs | 24 +- src/{ => formulas}/mixing_ratio.rs | 2 +- src/{ => formulas}/potential_temperature.rs | 2 +- src/{ => formulas}/relative_humidity.rs | 2 +- src/{ => formulas}/saturation_mixing_ratio.rs | 2 +- .../saturation_vapour_pressure.rs | 2 +- src/{ => formulas}/specific_humidity.rs | 2 +- src/{ => formulas}/vapour_pressure.rs | 2 +- src/{ => formulas}/vapour_pressure_deficit.rs | 2 +- src/{ => formulas}/virtual_temperature.rs | 2 +- .../wet_bulb_potential_temperature.rs | 2 +- src/{ => formulas}/wet_bulb_temperature.rs | 2 +- src/lib.rs | 19 +- src/tests.rs | 271 +----------------- src/tests/four_arg.rs | 0 src/tests/one_arg.rs | 64 +++++ src/tests/three_arg.rs | 134 +++++++++ src/tests/two_arg.rs | 83 ++++++ src/{formula.rs => traits.rs} | 0 20 files changed, 337 insertions(+), 292 deletions(-) create mode 100644 src/formulas.rs rename src/{ => formulas}/equivalent_potential_temperature.rs (94%) rename src/{ => formulas}/mixing_ratio.rs (98%) rename src/{ => formulas}/potential_temperature.rs (99%) rename src/{ => formulas}/relative_humidity.rs (99%) rename src/{ => formulas}/saturation_mixing_ratio.rs (99%) rename src/{ => formulas}/saturation_vapour_pressure.rs (99%) rename src/{ => formulas}/specific_humidity.rs (98%) rename src/{ => formulas}/vapour_pressure.rs (99%) rename src/{ => formulas}/vapour_pressure_deficit.rs (98%) rename src/{ => formulas}/virtual_temperature.rs (99%) rename src/{ => formulas}/wet_bulb_potential_temperature.rs (98%) rename src/{ => formulas}/wet_bulb_temperature.rs (98%) create mode 100644 src/tests/four_arg.rs create mode 100644 src/tests/one_arg.rs create mode 100644 src/tests/three_arg.rs create mode 100644 src/tests/two_arg.rs rename src/{formula.rs => traits.rs} (100%) diff --git a/src/formulas.rs b/src/formulas.rs new file mode 100644 index 0000000..68c52ce --- /dev/null +++ b/src/formulas.rs @@ -0,0 +1,12 @@ +pub mod mixing_ratio; +pub mod saturation_mixing_ratio; +pub mod potential_temperature; +pub mod relative_humidity; +pub mod specific_humidity; +pub mod vapour_pressure; +pub mod saturation_vapour_pressure; +pub mod vapour_pressure_deficit; +pub mod virtual_temperature; +pub mod wet_bulb_potential_temperature; +pub mod wet_bulb_temperature; +pub mod equivalent_potential_temperature; \ No newline at end of file diff --git a/src/equivalent_potential_temperature.rs b/src/formulas/equivalent_potential_temperature.rs similarity index 94% rename from src/equivalent_potential_temperature.rs rename to src/formulas/equivalent_potential_temperature.rs index b419a1d..346094e 100644 --- a/src/equivalent_potential_temperature.rs +++ b/src/formulas/equivalent_potential_temperature.rs @@ -13,12 +13,12 @@ use uom::si::thermodynamic_temperature::kelvin; use crate::constants::{C_L, C_P, EPSILON, KAPPA, L_V, R_D, R_V}; use crate::errors::InputError; -use crate::formula::{Formula2, Formula4}; +use crate::{Formula2, Formula4}; use crate::quantities::{ AtmosphericPressure, DewPointTemperature, DryBulbTemperature, EquivalentPotentialTemperature, MixingRatio, PotentialTemperature, RelativeHumidity, ThermodynamicQuantity, VapourPressure, }; -use crate::{mixing_ratio, Float}; +use crate::{formulas::mixing_ratio, Float}; type FormulaQuantity = EquivalentPotentialTemperature; @@ -99,9 +99,11 @@ impl /// /// Valid `temperature` range: 253K - 324K /// -/// Valid `pressure` range: 100Pa - 150000Pa +/// Valid `mixing_ratio` range: 0.000_000_1 - 2.0 +/// +/// Valid `relative_humidity` range: 0.000_000_1 - 2.0 /// -/// Valid `vapour_pressure` range: 0Pa - 10000Pa +/// Valid `potential_temperature` range: 253K - 324K pub struct Bryan1; impl @@ -164,6 +166,8 @@ impl /// Valid `temperature` range: 253K - 324K /// /// Valid `dewpoint` range: 253K - 324K +/// +/// Valid `vapour_pressure` range: 0Pa - 50000Pa pub struct Bolton1; impl @@ -198,6 +202,18 @@ impl )); } + if vapour_pressure.0 > pressure.0 { + return Err(InputError::IncorrectArgumentSet( + "pressure must be greater than vapour pressure".to_string(), + )); + } + + if dewpoint.0 > temperature.0 { + return Err(InputError::IncorrectArgumentSet( + "dewpoint must be less than temperature".to_string(), + )); + } + Ok(()) } diff --git a/src/mixing_ratio.rs b/src/formulas/mixing_ratio.rs similarity index 98% rename from src/mixing_ratio.rs rename to src/formulas/mixing_ratio.rs index 5b82a33..49f7f58 100644 --- a/src/mixing_ratio.rs +++ b/src/formulas/mixing_ratio.rs @@ -3,7 +3,7 @@ //! Mixing ratio is the ratio of the mass of a variable atmospheric constituent to the mass //! of dry air ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Mixing_ratio)). -use crate::formula::Formula2; +use crate::Formula2; use crate::quantities::{AtmosphericPressure, MixingRatio, ThermodynamicQuantity, VapourPressure}; use crate::Float; use crate::{constants::EPSILON, errors::InputError}; diff --git a/src/potential_temperature.rs b/src/formulas/potential_temperature.rs similarity index 99% rename from src/potential_temperature.rs rename to src/formulas/potential_temperature.rs index 31163f5..ec71a6d 100644 --- a/src/potential_temperature.rs +++ b/src/formulas/potential_temperature.rs @@ -6,7 +6,7 @@ use crate::constants::KAPPA; use crate::errors::InputError; -use crate::formula::Formula3; +use crate::Formula3; use crate::quantities::{ AtmosphericPressure, DryBulbTemperature, PotentialTemperature, ThermodynamicQuantity, VapourPressure, diff --git a/src/relative_humidity.rs b/src/formulas/relative_humidity.rs similarity index 99% rename from src/relative_humidity.rs rename to src/formulas/relative_humidity.rs index c422a81..2d21bf1 100644 --- a/src/relative_humidity.rs +++ b/src/formulas/relative_humidity.rs @@ -1,7 +1,7 @@ //! Functions to calculate relative humidity use crate::errors::InputError; -use crate::formula::Formula2; +use crate::Formula2; use crate::quantities::{ MixingRatio, RelativeHumidity, SaturationMixingRatio, SaturationVapourPressure, ThermodynamicQuantity, VapourPressure, diff --git a/src/saturation_mixing_ratio.rs b/src/formulas/saturation_mixing_ratio.rs similarity index 99% rename from src/saturation_mixing_ratio.rs rename to src/formulas/saturation_mixing_ratio.rs index 6d59507..7b3b12a 100644 --- a/src/saturation_mixing_ratio.rs +++ b/src/formulas/saturation_mixing_ratio.rs @@ -3,7 +3,7 @@ //! Saturation mixing ration is the value of the mixing ratio of saturated air at the //! given temperature and pressure ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Saturation_mixing_ratio)). -use crate::formula::Formula2; +use crate::Formula2; use crate::quantities::{ AtmosphericPressure, MixingRatio, RelativeHumidity, SaturationMixingRatio, SaturationVapourPressure, ThermodynamicQuantity, diff --git a/src/saturation_vapour_pressure.rs b/src/formulas/saturation_vapour_pressure.rs similarity index 99% rename from src/saturation_vapour_pressure.rs rename to src/formulas/saturation_vapour_pressure.rs index 66c1a6f..7cfd25a 100644 --- a/src/saturation_vapour_pressure.rs +++ b/src/formulas/saturation_vapour_pressure.rs @@ -6,7 +6,7 @@ //! saturation but not supersaturation ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Saturation_vapor_pressure)). use crate::errors::InputError; -use crate::formula::{Formula1, Formula2}; +use crate::{Formula1, Formula2}; use crate::quantities::{ AtmosphericPressure, DryBulbTemperature, RelativeHumidity, SaturationVapourPressure, ThermodynamicQuantity, VapourPressure, diff --git a/src/specific_humidity.rs b/src/formulas/specific_humidity.rs similarity index 98% rename from src/specific_humidity.rs rename to src/formulas/specific_humidity.rs index 1ff4ed8..7a1c8c9 100644 --- a/src/specific_humidity.rs +++ b/src/formulas/specific_humidity.rs @@ -6,7 +6,7 @@ //! Specific humidity is approximately equal to mixing ratio. use crate::constants::DIMLESS_ONE; -use crate::formula::Formula2; +use crate::Formula2; use crate::quantities::{ AtmosphericPressure, SpecificHumidity, ThermodynamicQuantity, VapourPressure, }; diff --git a/src/vapour_pressure.rs b/src/formulas/vapour_pressure.rs similarity index 99% rename from src/vapour_pressure.rs rename to src/formulas/vapour_pressure.rs index 98b602d..9bb0afc 100644 --- a/src/vapour_pressure.rs +++ b/src/formulas/vapour_pressure.rs @@ -5,7 +5,7 @@ //! Formulae to calculate partial vapour pressure of the unsaturated air. use crate::constants::DIMLESS_ONE; -use crate::formula::{Formula1, Formula2}; +use crate::{Formula1, Formula2}; use crate::quantities::{ AtmosphericPressure, DewPointTemperature, RelativeHumidity, SaturationVapourPressure, SpecificHumidity, ThermodynamicQuantity, VapourPressure, diff --git a/src/vapour_pressure_deficit.rs b/src/formulas/vapour_pressure_deficit.rs similarity index 98% rename from src/vapour_pressure_deficit.rs rename to src/formulas/vapour_pressure_deficit.rs index a6d1b3e..4daeeae 100644 --- a/src/vapour_pressure_deficit.rs +++ b/src/formulas/vapour_pressure_deficit.rs @@ -5,7 +5,7 @@ //! when it is saturated ([Wikipedia](https://en.wikipedia.org/wiki/Vapour-pressure_deficit)). use crate::errors::InputError; -use crate::formula::Formula2; +use crate::Formula2; use crate::quantities::{ SaturationVapourPressure, ThermodynamicQuantity, VapourPressure, VapourPressureDeficit, }; diff --git a/src/virtual_temperature.rs b/src/formulas/virtual_temperature.rs similarity index 99% rename from src/virtual_temperature.rs rename to src/formulas/virtual_temperature.rs index 06124c2..0dcf2d6 100644 --- a/src/virtual_temperature.rs +++ b/src/formulas/virtual_temperature.rs @@ -6,7 +6,7 @@ use crate::constants::{DIMLESS_ONE, EPSILON, ZERO_KELVIN}; use crate::errors::InputError; -use crate::formula::{Formula2, Formula3}; +use crate::{Formula2, Formula3}; use crate::quantities::{ AtmosphericPressure, DryBulbTemperature, MixingRatio, SpecificHumidity, ThermodynamicQuantity, VapourPressure, VirtualTemperature, diff --git a/src/wet_bulb_potential_temperature.rs b/src/formulas/wet_bulb_potential_temperature.rs similarity index 98% rename from src/wet_bulb_potential_temperature.rs rename to src/formulas/wet_bulb_potential_temperature.rs index 34100ba..d965b24 100644 --- a/src/wet_bulb_potential_temperature.rs +++ b/src/formulas/wet_bulb_potential_temperature.rs @@ -3,7 +3,7 @@ use uom::si::ratio::ratio; use uom::si::thermodynamic_temperature::{degree_celsius, kelvin}; -use crate::formula::Formula1; +use crate::Formula1; use crate::quantities::{ EquivalentPotentialTemperature, ThermodynamicQuantity, WetBulbPotentialTemperature, }; diff --git a/src/wet_bulb_temperature.rs b/src/formulas/wet_bulb_temperature.rs similarity index 98% rename from src/wet_bulb_temperature.rs rename to src/formulas/wet_bulb_temperature.rs index 6032052..d98810c 100644 --- a/src/wet_bulb_temperature.rs +++ b/src/formulas/wet_bulb_temperature.rs @@ -4,10 +4,10 @@ use uom::si::ratio::percent; use uom::si::thermodynamic_temperature::degree_celsius; use crate::errors::InputError; -use crate::formula::Formula2; use crate::quantities::{ DryBulbTemperature, RelativeHumidity, ThermodynamicQuantity, WetBulbTemperature, }; +use crate::Formula2; use crate::Storage; type FormulaQuantity = WetBulbTemperature; diff --git a/src/lib.rs b/src/lib.rs index c6b5ced..7401931 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,22 +81,13 @@ //! information about the error. This feature potentially is not zero-cost so it is optional. pub mod constants; -pub mod errors; -pub mod formula; +mod errors; +pub mod formulas; pub mod quantities; +mod traits; -pub mod equivalent_potential_temperature; -pub mod mixing_ratio; -pub mod saturation_mixing_ratio; -pub mod potential_temperature; -pub mod relative_humidity; -pub mod specific_humidity; -pub mod vapour_pressure; -pub mod saturation_vapour_pressure; -pub mod vapour_pressure_deficit; -pub mod virtual_temperature; -pub mod wet_bulb_potential_temperature; -pub mod wet_bulb_temperature; +pub use errors::InputError; +pub use traits::{Formula1, Formula2, Formula3, Formula4}; #[cfg(test)] mod tests; diff --git a/src/tests.rs b/src/tests.rs index 5365d0c..b57f07c 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,12 +1,17 @@ +mod four_arg; +mod one_arg; pub(crate) mod reference_values; pub(crate) mod testing_traits; +mod three_arg; +mod two_arg; -use crate::errors::InputError; -use crate::formula::{Formula1, Formula2, Formula3}; use crate::Float; use float_cmp::assert_approx_eq; use std::marker::PhantomData; -use std::mem::discriminant; + +pub use self::one_arg::test_with_1arg; +pub use self::three_arg::test_with_3args; +pub use self::two_arg::test_with_2args; use self::testing_traits::{ReferenceAtmosphere, TestingQuantity}; @@ -39,263 +44,3 @@ fn check_result(result: T, atm: ReferenceAtmosphere, eps: Fl assert_approx_eq!(Float, result, expected, epsilon = eps) } - -pub fn test_with_2args< - O: TestingQuantity, - I1: TestingQuantity, - I2: TestingQuantity, - F: Formula2, ->( - arg1: Argument, - arg2: Argument, - atm: ReferenceAtmosphere, - eps: Float, -) { - //the first promise of the crate is that returned value - //is calculated correctly - let result = F::compute(arg1.ref_val(atm), arg2.ref_val(atm)).unwrap(); - check_result(result, atm, eps); - - // the second promise of the crate is to never return NaN or Inf - // here we check several edge cases for that - // the function can only return a finite number or InputError - // check for error implicit as Result<> ensures that if value - // is not Ok() then it is Err() - // here we don't care about error being correct - let results = vec![ - F::compute(arg1.ref_val(atm), arg2.ref_val(atm)), - F::compute(I1::new_si(-9999.0), arg2.ref_val(atm)), - F::compute(arg1.ref_val(atm), I2::new_si(-9999.0)), - F::compute(I1::new_si(-9999.0), I2::new_si(-9999.0)), - ]; - - for result in results { - if let Ok(result) = result { - assert!(result.get_si_value().is_finite()); - } - } - - //the third promise of the crate is to always return finite f64 - //if all inputs are within the range - //the only allowed error is InccorectArgumentsSet as it can occur - //for values within valid range - for arg1_itr in 0..=100 { - for arg2_itr in 0..=100 { - let arg1_tmp = - (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; - let arg2_tmp = - (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; - - let arg1_tmp = I1::new_si(arg1_tmp); - let arg2_tmp = I2::new_si(arg2_tmp); - - let result = F::compute(arg1_tmp, arg2_tmp); - - match result { - Ok(r) => assert!(r.get_si_value().is_finite()), - Err(e) => assert_eq!( - discriminant(&InputError::IncorrectArgumentSet(String::new())), - discriminant(&e) - ), - } - } - } - - //the fourth promise of the crate is to return an error with - //erronous variable name when input is out of range - let expected = InputError::OutOfRange(arg1.quantity_name()); - let result = F::compute(I1::new_si(arg1.range[0] - 0.1), arg2.ref_val(atm)).unwrap_err(); - assert_eq!(result, expected); - let result = F::compute(I1::new_si(arg1.range[1] + 0.1), arg2.ref_val(atm)).unwrap_err(); - assert_eq!(result, expected); - - let expected = InputError::OutOfRange(arg2.quantity_name()); - let result = F::compute(arg1.ref_val(atm), I2::new_si(arg2.range[0] - 0.1)).unwrap_err(); - assert_eq!(result, expected); - let result = F::compute(arg1.ref_val(atm), I2::new_si(arg2.range[1] + 0.1)).unwrap_err(); - assert_eq!(result, expected); -} - -pub fn test_with_1arg>( - arg1: Argument, - atm: ReferenceAtmosphere, - eps: Float, -) { - //the first promise of the crate is that returned value - //is calculated correctly - let result = F::compute(arg1.ref_val(atm)).unwrap(); - check_result(result, atm, eps); - - // the second promise of the crate is to never return NaN or Inf - // here we check several edge cases for that - // the function can only return a finite number or InputError - // check for error implicit as Result<> ensures that if value - // is not Ok() then it is Err() - // here we don't care about error being correct - let results = vec![ - F::compute(arg1.ref_val(atm)), - F::compute(I1::new_si(-9999.0)), - ]; - - for result in results { - if let Ok(result) = result { - assert!(result.get_si_value().is_finite()); - } - } - - //the third promise of the crate is to always return finite f64 - //if all inputs are within the range - //the only allowed error is InccorectArgumentsSet as it can occur - //for values within valid range - for arg1_itr in 0..=100 { - let arg1_tmp = - (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; - - let arg1_tmp = I1::new_si(arg1_tmp); - - let result = F::compute(arg1_tmp); - - match result { - Ok(r) => assert!(r.get_si_value().is_finite()), - Err(e) => assert_eq!( - discriminant(&InputError::IncorrectArgumentSet(String::new())), - discriminant(&e) - ), - } - } - - //the fourth promise of the crate is to return an error with - //erronous variable name when input is out of range - let expected = InputError::OutOfRange(arg1.quantity_name()); - let result = F::compute(I1::new_si(arg1.range[0] - 0.1)).unwrap_err(); - assert_eq!(result, expected); - let result = F::compute(I1::new_si(arg1.range[1] + 0.1)).unwrap_err(); - assert_eq!(result, expected); -} - -pub fn test_with_3args< - O: TestingQuantity, - I1: TestingQuantity, - I2: TestingQuantity, - I3: TestingQuantity, - F: Formula3, ->( - arg1: Argument, - arg2: Argument, - arg3: Argument, - atm: ReferenceAtmosphere, - eps: Float, -) { - //the first promise of the crate is that returned value - //is calculated correctly - let result = F::compute(arg1.ref_val(atm), arg2.ref_val(atm), arg3.ref_val(atm)).unwrap(); - check_result(result, atm, eps); - - // the second promise of the crate is to never return NaN or Inf - // here we check several edge cases for that - // the function can only return a finite number or InputError - // check for error implicit as Result<> ensures that if value - // is not Ok() then it is Err() - // here we don't care about error being correct - let results = vec![ - F::compute(arg1.ref_val(atm), arg2.ref_val(atm), arg3.ref_val(atm)), - F::compute(I1::new_si(-9999.0), arg2.ref_val(atm), arg3.ref_val(atm)), - F::compute(arg1.ref_val(atm), I2::new_si(-9999.0), arg3.ref_val(atm)), - F::compute(arg1.ref_val(atm), arg2.ref_val(atm), I3::new_si(-9999.0)), - F::compute(arg1.ref_val(atm), I2::new_si(-9999.0), arg3.ref_val(atm)), - F::compute(arg1.ref_val(atm), I2::new_si(-9999.0), I3::new_si(-9999.0)), - F::compute(I1::new_si(-9999.0), arg2.ref_val(atm), I3::new_si(-9999.0)), - F::compute( - I1::new_si(-9999.0), - I2::new_si(-9999.0), - I3::new_si(-9999.0), - ), - ]; - - for result in results { - if let Ok(result) = result { - assert!(result.get_si_value().is_finite()); - } - } - - //the third promise of the crate is to always return finite f64 - //if all inputs are within the range - //the only allowed error is InccorectArgumentsSet as it can occur - //for values within valid range - for arg1_itr in 0..=100 { - for arg2_itr in 0..=100 { - for arg3_itr in 0..=100 { - let arg1_tmp = - (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; - let arg2_tmp = - (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; - let arg3_tmp = - (((arg3.range[1] - arg3.range[0]) / 100.0) * arg3_itr as Float) + arg3.range[0]; - - let arg1_tmp = I1::new_si(arg1_tmp); - let arg2_tmp = I2::new_si(arg2_tmp); - let arg3_tmp = I3::new_si(arg3_tmp); - - let result = F::compute(arg1_tmp, arg2_tmp, arg3_tmp); - - match result { - Ok(r) => assert!(r.get_si_value().is_finite()), - Err(e) => assert_eq!( - discriminant(&InputError::IncorrectArgumentSet(String::new())), - discriminant(&e) - ), - } - } - } - } - - //the fourth promise of the crate is to return an error with - //erronous variable name when input is out of range - let expected = InputError::OutOfRange(arg1.quantity_name()); - let result = F::compute( - I1::new_si(arg1.range[0] - 0.1), - arg2.ref_val(atm), - arg3.ref_val(atm), - ) - .unwrap_err(); - assert_eq!(result, expected); - let result = F::compute( - I1::new_si(arg1.range[1] + 0.1), - arg2.ref_val(atm), - arg3.ref_val(atm), - ) - .unwrap_err(); - assert_eq!(result, expected); - - let expected = InputError::OutOfRange(arg2.quantity_name()); - let result = F::compute( - arg1.ref_val(atm), - I2::new_si(arg2.range[0] - 0.1), - arg3.ref_val(atm), - ) - .unwrap_err(); - assert_eq!(result, expected); - let result = F::compute( - arg1.ref_val(atm), - I2::new_si(arg2.range[1] + 0.1), - arg3.ref_val(atm), - ) - .unwrap_err(); - assert_eq!(result, expected); - - let expected = InputError::OutOfRange(arg3.quantity_name()); - let result = F::compute( - arg1.ref_val(atm), - arg2.ref_val(atm), - I3::new_si(arg3.range[0] - 0.1), - ) - .unwrap_err(); - assert_eq!(result, expected); - let result = F::compute( - arg1.ref_val(atm), - arg2.ref_val(atm), - I3::new_si(arg3.range[1] + 0.1), - ) - .unwrap_err(); - assert_eq!(result, expected); -} diff --git a/src/tests/four_arg.rs b/src/tests/four_arg.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/one_arg.rs b/src/tests/one_arg.rs new file mode 100644 index 0000000..32552b6 --- /dev/null +++ b/src/tests/one_arg.rs @@ -0,0 +1,64 @@ +use super::check_result; +use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; +use super::Argument; +use crate::errors::InputError; +use crate::Formula1; +use crate::Float; +use std::mem::discriminant; + +pub fn test_with_1arg>( + arg1: Argument, + atm: ReferenceAtmosphere, + eps: Float, +) { + //the first promise of the crate is that returned value + //is calculated correctly + let result = F::compute(arg1.ref_val(atm)).unwrap(); + check_result(result, atm, eps); + + // the second promise of the crate is to never return NaN or Inf + // here we check several edge cases for that + // the function can only return a finite number or InputError + // check for error implicit as Result<> ensures that if value + // is not Ok() then it is Err() + // here we don't care about error being correct + let results = vec![ + F::compute(arg1.ref_val(atm)), + F::compute(I1::new_si(-9999.0)), + ]; + + for result in results { + if let Ok(result) = result { + assert!(result.get_si_value().is_finite()); + } + } + + //the third promise of the crate is to always return finite f64 + //if all inputs are within the range + //the only allowed error is InccorectArgumentsSet as it can occur + //for values within valid range + for arg1_itr in 0..=100 { + let arg1_tmp = + (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; + + let arg1_tmp = I1::new_si(arg1_tmp); + + let result = F::compute(arg1_tmp); + + match result { + Ok(r) => assert!(r.get_si_value().is_finite()), + Err(e) => assert_eq!( + discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&e) + ), + } + } + + //the fourth promise of the crate is to return an error with + //erronous variable name when input is out of range + let expected = InputError::OutOfRange(arg1.quantity_name()); + let result = F::compute(I1::new_si(arg1.range[0] - 0.1)).unwrap_err(); + assert_eq!(result, expected); + let result = F::compute(I1::new_si(arg1.range[1] + 0.1)).unwrap_err(); + assert_eq!(result, expected); +} diff --git a/src/tests/three_arg.rs b/src/tests/three_arg.rs new file mode 100644 index 0000000..28d2872 --- /dev/null +++ b/src/tests/three_arg.rs @@ -0,0 +1,134 @@ +use super::check_result; +use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; +use super::Argument; +use crate::errors::InputError; +use crate::Formula3; +use crate::Float; +use std::mem::discriminant; + +pub fn test_with_3args< + O: TestingQuantity, + I1: TestingQuantity, + I2: TestingQuantity, + I3: TestingQuantity, + F: Formula3, +>( + arg1: Argument, + arg2: Argument, + arg3: Argument, + atm: ReferenceAtmosphere, + eps: Float, +) { + //the first promise of the crate is that returned value + //is calculated correctly + let result = F::compute(arg1.ref_val(atm), arg2.ref_val(atm), arg3.ref_val(atm)).unwrap(); + check_result(result, atm, eps); + + // the second promise of the crate is to never return NaN or Inf + // here we check several edge cases for that + // the function can only return a finite number or InputError + // check for error implicit as Result<> ensures that if value + // is not Ok() then it is Err() + // here we don't care about error being correct + let results = vec![ + F::compute(arg1.ref_val(atm), arg2.ref_val(atm), arg3.ref_val(atm)), + F::compute(I1::new_si(-9999.0), arg2.ref_val(atm), arg3.ref_val(atm)), + F::compute(arg1.ref_val(atm), I2::new_si(-9999.0), arg3.ref_val(atm)), + F::compute(arg1.ref_val(atm), arg2.ref_val(atm), I3::new_si(-9999.0)), + F::compute(arg1.ref_val(atm), I2::new_si(-9999.0), arg3.ref_val(atm)), + F::compute(arg1.ref_val(atm), I2::new_si(-9999.0), I3::new_si(-9999.0)), + F::compute(I1::new_si(-9999.0), arg2.ref_val(atm), I3::new_si(-9999.0)), + F::compute( + I1::new_si(-9999.0), + I2::new_si(-9999.0), + I3::new_si(-9999.0), + ), + ]; + + for result in results { + if let Ok(result) = result { + assert!(result.get_si_value().is_finite()); + } + } + + //the third promise of the crate is to always return finite f64 + //if all inputs are within the range + //the only allowed error is InccorectArgumentsSet as it can occur + //for values within valid range + for arg1_itr in 0..=100 { + for arg2_itr in 0..=100 { + for arg3_itr in 0..=100 { + let arg1_tmp = + (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; + let arg2_tmp = + (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; + let arg3_tmp = + (((arg3.range[1] - arg3.range[0]) / 100.0) * arg3_itr as Float) + arg3.range[0]; + + let arg1_tmp = I1::new_si(arg1_tmp); + let arg2_tmp = I2::new_si(arg2_tmp); + let arg3_tmp = I3::new_si(arg3_tmp); + + let result = F::compute(arg1_tmp, arg2_tmp, arg3_tmp); + + match result { + Ok(r) => assert!(r.get_si_value().is_finite()), + Err(e) => assert_eq!( + discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&e) + ), + } + } + } + } + + //the fourth promise of the crate is to return an error with + //erronous variable name when input is out of range + let expected = InputError::OutOfRange(arg1.quantity_name()); + let result = F::compute( + I1::new_si(arg1.range[0] - 0.1), + arg2.ref_val(atm), + arg3.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + I1::new_si(arg1.range[1] + 0.1), + arg2.ref_val(atm), + arg3.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + + let expected = InputError::OutOfRange(arg2.quantity_name()); + let result = F::compute( + arg1.ref_val(atm), + I2::new_si(arg2.range[0] - 0.1), + arg3.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + arg1.ref_val(atm), + I2::new_si(arg2.range[1] + 0.1), + arg3.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + + let expected = InputError::OutOfRange(arg3.quantity_name()); + let result = F::compute( + arg1.ref_val(atm), + arg2.ref_val(atm), + I3::new_si(arg3.range[0] - 0.1), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + arg1.ref_val(atm), + arg2.ref_val(atm), + I3::new_si(arg3.range[1] + 0.1), + ) + .unwrap_err(); + assert_eq!(result, expected); +} diff --git a/src/tests/two_arg.rs b/src/tests/two_arg.rs new file mode 100644 index 0000000..59e91fd --- /dev/null +++ b/src/tests/two_arg.rs @@ -0,0 +1,83 @@ +use super::check_result; +use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; +use super::Argument; +use crate::errors::InputError; +use crate::Formula2; +use crate::Float; +use std::mem::discriminant; + +pub fn test_with_2args< + O: TestingQuantity, + I1: TestingQuantity, + I2: TestingQuantity, + F: Formula2, +>( + arg1: Argument, + arg2: Argument, + atm: ReferenceAtmosphere, + eps: Float, +) { + //the first promise of the crate is that returned value + //is calculated correctly + let result = F::compute(arg1.ref_val(atm), arg2.ref_val(atm)).unwrap(); + check_result(result, atm, eps); + + // the second promise of the crate is to never return NaN or Inf + // here we check several edge cases for that + // the function can only return a finite number or InputError + // check for error implicit as Result<> ensures that if value + // is not Ok() then it is Err() + // here we don't care about error being correct + let results = vec![ + F::compute(arg1.ref_val(atm), arg2.ref_val(atm)), + F::compute(I1::new_si(-9999.0), arg2.ref_val(atm)), + F::compute(arg1.ref_val(atm), I2::new_si(-9999.0)), + F::compute(I1::new_si(-9999.0), I2::new_si(-9999.0)), + ]; + + for result in results { + if let Ok(result) = result { + assert!(result.get_si_value().is_finite()); + } + } + + //the third promise of the crate is to always return finite f64 + //if all inputs are within the range + //the only allowed error is InccorectArgumentsSet as it can occur + //for values within valid range + for arg1_itr in 0..=100 { + for arg2_itr in 0..=100 { + let arg1_tmp = + (((arg1.range[1] - arg1.range[0]) / 100.0) * arg1_itr as Float) + arg1.range[0]; + let arg2_tmp = + (((arg2.range[1] - arg2.range[0]) / 100.0) * arg2_itr as Float) + arg2.range[0]; + + let arg1_tmp = I1::new_si(arg1_tmp); + let arg2_tmp = I2::new_si(arg2_tmp); + + let result = F::compute(arg1_tmp, arg2_tmp); + + match result { + Ok(r) => assert!(r.get_si_value().is_finite()), + Err(e) => assert_eq!( + discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&e) + ), + } + } + } + + //the fourth promise of the crate is to return an error with + //erronous variable name when input is out of range + let expected = InputError::OutOfRange(arg1.quantity_name()); + let result = F::compute(I1::new_si(arg1.range[0] - 0.1), arg2.ref_val(atm)).unwrap_err(); + assert_eq!(result, expected); + let result = F::compute(I1::new_si(arg1.range[1] + 0.1), arg2.ref_val(atm)).unwrap_err(); + assert_eq!(result, expected); + + let expected = InputError::OutOfRange(arg2.quantity_name()); + let result = F::compute(arg1.ref_val(atm), I2::new_si(arg2.range[0] - 0.1)).unwrap_err(); + assert_eq!(result, expected); + let result = F::compute(arg1.ref_val(atm), I2::new_si(arg2.range[1] + 0.1)).unwrap_err(); + assert_eq!(result, expected); +} diff --git a/src/formula.rs b/src/traits.rs similarity index 100% rename from src/formula.rs rename to src/traits.rs From c8a69e34e73d812a447602b404e5dba11b1f4712 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:41:20 +0100 Subject: [PATCH 054/102] preliminary finish theta-e --- src/errors.rs | 2 +- .../equivalent_potential_temperature.rs | 165 ++++++-------- src/tests.rs | 1 + src/tests/four_arg.rs | 212 ++++++++++++++++++ src/tests/reference_values.rs | 4 +- 5 files changed, 285 insertions(+), 99 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 2f60b7a..bf9669e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -2,7 +2,7 @@ use thiserror::Error; -#[derive(Error, Debug, PartialEq, Eq)] +#[derive(Error, Debug, PartialEq, Eq, Clone)] ///Error enum returned when provided input will cause function to return erronous result ///eg. `Inf` or `NaN` diff --git a/src/formulas/equivalent_potential_temperature.rs b/src/formulas/equivalent_potential_temperature.rs index 346094e..bdddcb4 100644 --- a/src/formulas/equivalent_potential_temperature.rs +++ b/src/formulas/equivalent_potential_temperature.rs @@ -13,12 +13,12 @@ use uom::si::thermodynamic_temperature::kelvin; use crate::constants::{C_L, C_P, EPSILON, KAPPA, L_V, R_D, R_V}; use crate::errors::InputError; -use crate::{Formula2, Formula4}; use crate::quantities::{ AtmosphericPressure, DewPointTemperature, DryBulbTemperature, EquivalentPotentialTemperature, MixingRatio, PotentialTemperature, RelativeHumidity, ThermodynamicQuantity, VapourPressure, }; use crate::{formulas::mixing_ratio, Float}; +use crate::{Formula2, Formula4}; type FormulaQuantity = EquivalentPotentialTemperature; @@ -214,6 +214,15 @@ impl )); } + let mixing_ratio = mixing_ratio::Definition1::compute_unchecked(pressure, vapour_pressure); + + mixing_ratio.check_range_si(0.000_000_1, 2.0).or_else(|_| { + Err(InputError::IncorrectArgumentSet( + "pressure and vapour_pressure must give mixing_ratio less than 2 so cannot be close to each other".to_string(), + )) + } + )?; + Ok(()) } @@ -234,6 +243,8 @@ impl let kappa = KAPPA.get::(); + // technically LCL and Theta-DL should be extracted to separate functions + let lcl_temp = (1.0 / ((1.0 / (dewpoint - 56.0)) + ((temperature / dewpoint).ln() / 800.0))) + 56.0; @@ -250,105 +261,67 @@ impl #[cfg(test)] mod tests { - use std::marker::PhantomData; - use crate::{ quantities::{AtmosphericPressure, DryBulbTemperature, VapourPressure}, - tests::{test_with_3args, Argument}, + tests::{test_with_4args, testing_traits::ReferenceAtmosphere, Argument}, }; use super::*; - // #[test] - // fn paluch1() { - // test_with_3args::< - // FormulaQuantity, - // DryBulbTemperature, - // AtmosphericPressure, - // VapourPressure, - // Paluch1, - // >( - // Argument { - // name: "temperature", - // def_val: 300.0, - // range: [253.0, 324.0], - // _quantity: PhantomData, - // }, - // Argument { - // name: "pressure", - // def_val: 101325.0, - // range: [20000.0, 150_000.0], - // _quantity: PhantomData, - // }, - // Argument { - // name: "vapour_pressure", - // def_val: 991.189131, - // range: [0.0, 10_000.0], - // _quantity: PhantomData, - // }, - // 315.23724970376776, - // ); - // } - - // #[test] - // fn bryan1() { - // test_with_3args::< - // FormulaQuantity, - // DryBulbTemperature, - // AtmosphericPressure, - // VapourPressure, - // Bryan1, - // >( - // Argument { - // name: "temperature", - // def_val: 300.0, - // range: [253.0, 324.0], - // _quantity: PhantomData, - // }, - // Argument { - // name: "pressure", - // def_val: 101325.0, - // range: [20000.0, 150_000.0], - // _quantity: PhantomData, - // }, - // Argument { - // name: "vapour_pressure", - // def_val: 991.189131, - // range: [0.0, 10_000.0], - // _quantity: PhantomData, - // }, - // 316.52762026634014, - // ); - // } - - // #[test] - // fn bolton1() { - // test_with_3args::< - // FormulaQuantity, - // AtmosphericPressure, - // DryBulbTemperature, - // DewPointTemperature, - // Bolton1, - // >( - // Argument { - // name: "pressure", - // def_val: 101325.0, - // range: [20000.0, 150_000.0], - // _quantity: PhantomData, - // }, - // Argument { - // name: "temperature", - // def_val: 300.0, - // range: [253.0, 324.0], - // _quantity: PhantomData, - // }, - // Argument { - // name: "dewpoint", - // def_val: 280.0, - // range: [253.0, 324.0], - // _quantity: PhantomData, - // }, - // 317.3855211897774, - // ); - // } + #[test] + fn paluch1() { + test_with_4args::< + FormulaQuantity, + DryBulbTemperature, + AtmosphericPressure, + MixingRatio, + RelativeHumidity, + Paluch1, + >( + Argument::new([253.0, 324.0]), + Argument::new([100.0, 150_000.0]), + Argument::new([0.000_000_1, 2.0]), + Argument::new([0.000_000_1, 2.0]), + ReferenceAtmosphere::Normal, + 1e-12, + ); + } + + #[test] + fn bryan1() { + test_with_4args::< + FormulaQuantity, + DryBulbTemperature, + MixingRatio, + RelativeHumidity, + PotentialTemperature, + Bryan1, + >( + Argument::new([253.0, 324.0]), + Argument::new([0.000_000_1, 2.0]), + Argument::new([0.000_000_1, 2.0]), + Argument::new([253.0, 324.0]), + ReferenceAtmosphere::Normal, + 1e1, + ); + } + + #[test] + fn bolton1() { + test_with_4args::< + FormulaQuantity, + AtmosphericPressure, + DryBulbTemperature, + DewPointTemperature, + VapourPressure, + Bolton1, + >( + Argument::new([100.0, 150_000.0]), + Argument::new([253.0, 324.0]), + Argument::new([253.0, 324.0]), + Argument::new([0.0, 50_000.0]), + ReferenceAtmosphere::Normal, + 1e1, + ); + } } diff --git a/src/tests.rs b/src/tests.rs index b57f07c..4f8a887 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -12,6 +12,7 @@ use std::marker::PhantomData; pub use self::one_arg::test_with_1arg; pub use self::three_arg::test_with_3args; pub use self::two_arg::test_with_2args; +pub use self::four_arg::test_with_4args; use self::testing_traits::{ReferenceAtmosphere, TestingQuantity}; diff --git a/src/tests/four_arg.rs b/src/tests/four_arg.rs index e69de29..a09bffa 100644 --- a/src/tests/four_arg.rs +++ b/src/tests/four_arg.rs @@ -0,0 +1,212 @@ +use super::check_result; +use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; +use super::Argument; +use crate::errors::InputError; +use crate::Float; +use crate::Formula4; +use std::mem::discriminant; + +pub fn test_with_4args< + O: TestingQuantity, + I1: TestingQuantity, + I2: TestingQuantity, + I3: TestingQuantity, + I4: TestingQuantity, + F: Formula4, +>( + arg1: Argument, + arg2: Argument, + arg3: Argument, + arg4: Argument, + atm: ReferenceAtmosphere, + eps: Float, +) { + let result = F::compute( + arg1.ref_val(atm), + arg2.ref_val(atm), + arg3.ref_val(atm), + arg4.ref_val(atm), + ) + .unwrap(); + check_result(result, atm, eps); + + let results = vec![ + F::compute( + arg1.ref_val(atm), + arg2.ref_val(atm), + arg3.ref_val(atm), + arg4.ref_val(atm), + ), + F::compute( + I1::new_si(-9999.0), + arg2.ref_val(atm), + arg3.ref_val(atm), + arg4.ref_val(atm), + ), + F::compute( + arg1.ref_val(atm), + I2::new_si(-9999.0), + arg3.ref_val(atm), + arg4.ref_val(atm), + ), + F::compute( + arg1.ref_val(atm), + arg2.ref_val(atm), + I3::new_si(-9999.0), + arg4.ref_val(atm), + ), + F::compute( + arg1.ref_val(atm), + arg2.ref_val(atm), + arg3.ref_val(atm), + I4::new_si(-9999.0), + ), + F::compute( + arg1.ref_val(atm), + I2::new_si(-9999.0), + I3::new_si(-9999.0), + arg4.ref_val(atm), + ), + F::compute( + I1::new_si(-9999.0), + I2::new_si(-9999.0), + arg3.ref_val(atm), + arg4.ref_val(atm), + ), + F::compute( + I1::new_si(-9999.0), + arg2.ref_val(atm), + I3::new_si(-9999.0), + arg4.ref_val(atm), + ), + F::compute( + I1::new_si(-9999.0), + arg2.ref_val(atm), + arg3.ref_val(atm), + I4::new_si(-9999.0), + ), + F::compute( + I1::new_si(-9999.0), + I2::new_si(-9999.0), + I3::new_si(-9999.0), + I4::new_si(-9999.0), + ), + ]; + + for result in results { + if let Ok(result) = result { + assert!(result.get_si_value().is_finite()); + } + } + + //the third promise of the crate is to always return finite f64 + //if all inputs are within the range + //the only allowed error is InccorectArgumentsSet as it can occur + //for values within valid range + for arg1_itr in 0..=20 { + for arg2_itr in 0..=20 { + for arg3_itr in 0..=20 { + for arg4_itr in 0..=20 { + let arg1_tmp = (((arg1.range[1] - arg1.range[0]) / 20.0) * arg1_itr as Float) + + arg1.range[0]; + let arg2_tmp = (((arg2.range[1] - arg2.range[0]) / 20.0) * arg2_itr as Float) + + arg2.range[0]; + let arg3_tmp = (((arg3.range[1] - arg3.range[0]) / 20.0) * arg3_itr as Float) + + arg3.range[0]; + let arg4_tmp = (((arg4.range[1] - arg4.range[0]) / 20.0) * arg4_itr as Float) + + arg4.range[0]; + + let arg1_tmp = I1::new_si(arg1_tmp); + let arg2_tmp = I2::new_si(arg2_tmp); + let arg3_tmp = I3::new_si(arg3_tmp); + let arg4_tmp = I4::new_si(arg4_tmp); + + let result = F::compute(arg1_tmp, arg2_tmp, arg3_tmp, arg4_tmp); + + match result.clone() { + Ok(r) => assert!(r.get_si_value().is_finite()), + + Err(e) => assert_eq!( + discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&e) + ), + } + } + } + } + } + + //the fourth promise of the crate is to return an error with + //erronous variable name when input is out of range + let expected = InputError::OutOfRange(arg1.quantity_name()); + let result = F::compute( + I1::new_si(arg1.range[0] - 0.1), + arg2.ref_val(atm), + arg3.ref_val(atm), + arg4.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + I1::new_si(arg1.range[1] + 0.1), + arg2.ref_val(atm), + arg3.ref_val(atm), + arg4.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + + let expected = InputError::OutOfRange(arg2.quantity_name()); + let result = F::compute( + arg1.ref_val(atm), + I2::new_si(arg2.range[0] - 0.1), + arg3.ref_val(atm), + arg4.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + arg1.ref_val(atm), + I2::new_si(arg2.range[1] + 0.1), + arg3.ref_val(atm), + arg4.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + + let expected = InputError::OutOfRange(arg3.quantity_name()); + let result = F::compute( + arg1.ref_val(atm), + arg2.ref_val(atm), + I3::new_si(arg3.range[0] - 0.1), + arg4.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + arg1.ref_val(atm), + arg2.ref_val(atm), + I3::new_si(arg3.range[1] + 0.1), + arg4.ref_val(atm), + ) + .unwrap_err(); + assert_eq!(result, expected); + + let expected = InputError::OutOfRange(arg4.quantity_name()); + let result = F::compute( + arg1.ref_val(atm), + arg2.ref_val(atm), + arg3.ref_val(atm), + I4::new_si(arg4.range[0] - 0.1), + ) + .unwrap_err(); + assert_eq!(result, expected); + let result = F::compute( + arg1.ref_val(atm), + arg2.ref_val(atm), + arg3.ref_val(atm), + I4::new_si(arg4.range[1] + 0.1), + ) + .unwrap_err(); + assert_eq!(result, expected); +} diff --git a/src/tests/reference_values.rs b/src/tests/reference_values.rs index c002a5a..2182b9d 100644 --- a/src/tests/reference_values.rs +++ b/src/tests/reference_values.rs @@ -10,7 +10,7 @@ pub(crate) const MR_NORM: Float = 0.012172079452423202; pub(crate) const SMR_NROM: Float = 0.022419969290542845; pub(crate) const VPD_NORM: Float = 1615.998266172149; pub(crate) const SH_NORM: Float = 0.012025701656390478; -pub(crate) const THETAE_NORM: Float = 331.33678499482323; +pub(crate) const THETAE_NORM: Float = 331.329289539998; pub(crate) const THETA_NORM: Float = 301.66581400702955; pub(crate) const THETAW_NORM: Float = 292.0717306393948; pub(crate) const WBT_NORM: Float = 293.42728654340516; @@ -26,7 +26,7 @@ pub(crate) const MR_FREEZ: Float = 0.0007670962389744638; pub(crate) const SMR_FREEZ: Float = 0.0012196493367222787; pub(crate) const VPD_FREEZ: Float = 72.67042355758188; pub(crate) const SH_FREEZ: Float = 0.000766508253376156; -pub(crate) const THETAE_FREEZ: Float = 261.96507880792007; +pub(crate) const THETAE_FREEZ: Float = 261.95287841149707; pub(crate) const THETA_FREEZ: Float = 260.0915766593588; pub(crate) const THETAW_FREEZ: Float = 258.6611332391296; pub(crate) const WBT_FREEZ: Float = 258.40501060754224; From 65a164217f1568c17153fa90e74a92a2a0e6a347 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:24:27 +0100 Subject: [PATCH 055/102] refactor theta-e --- .../equivalent_potential_temperature.rs | 91 ++++++++++++++++++- 1 file changed, 86 insertions(+), 5 deletions(-) diff --git a/src/formulas/equivalent_potential_temperature.rs b/src/formulas/equivalent_potential_temperature.rs index bdddcb4..5dcd3a3 100644 --- a/src/formulas/equivalent_potential_temperature.rs +++ b/src/formulas/equivalent_potential_temperature.rs @@ -22,8 +22,8 @@ use crate::{Formula2, Formula4}; type FormulaQuantity = EquivalentPotentialTemperature; -/// Most accuarte formula for computing equivalent potential temperature of unsaturated air from -/// temperature, pressure and vapour pressure. +/// Most accurate formula for computing equivalent potential temperature of unsaturated air from +/// temperature, pressure and mixing ratio and relative humidity. /// /// Implementation of this formula assumes no liquid or solid water in the air parcel. /// @@ -93,7 +93,7 @@ impl } /// Formula for computing equivalent potential temperature of unsaturated air from -/// temperature, pressure and vapour pressure. +/// temperature, mixing ratio, relative humidity and potential temperature. /// /// Derived by G. H. Bryan (2008) [(doi:10.1175/2008MWR2593.1)](https://doi.org/10.1175/2008MWR2593.1) /// @@ -156,7 +156,7 @@ impl } /// Approximate formula for computing equivalent potential temperature of unsaturated air from -/// temperature, pressure and dewpoint. +/// temperature, pressure, dewpoint and vapour pressure. /// /// Derived by D. Bolton (1980) /// [(doi:10.1175/1520-0493(1980)108<1046:TCOEPT>2.0.CO;2)](https://doi.org/10.1175/1520-0493(1980)108%3C1046:TCOEPT%3E2.0.CO;2) @@ -243,7 +243,7 @@ impl let kappa = KAPPA.get::(); - // technically LCL and Theta-DL should be extracted to separate functions + // technically LCL and Theta-DL could be extracted to separate functions let lcl_temp = (1.0 / ((1.0 / (dewpoint - 56.0)) + ((temperature / dewpoint).ln() / 800.0))) + 56.0; @@ -259,6 +259,68 @@ impl } } +/// Approximate formula for computing equivalent potential temperature of unsaturated air from +/// temperature, dewpoint, mixing ratio and potential temperature. +/// +/// Derived by D. Bolton (1980) +/// [(doi:10.1175/1520-0493(1980)108<1046:TCOEPT>2.0.CO;2)](https://doi.org/10.1175/1520-0493(1980)108%3C1046:TCOEPT%3E2.0.CO;2) +/// +/// Valid `temperature` range: 253K - 324K +/// +/// Valid `dewpoint` range: 253K - 324K +/// +/// Valid `mixing_ratio` range: 0.000_000_1 - 2.0 +/// +/// Valid `potential_temperature` range: 253K - 324K +pub struct Bolton2; + +impl + Formula4< + EquivalentPotentialTemperature, + DryBulbTemperature, + DewPointTemperature, + MixingRatio, + PotentialTemperature, + > for Bolton2 +{ + fn compute_unchecked( + temperature: DryBulbTemperature, + dewpoint: DewPointTemperature, + mixing_ratio: MixingRatio, + potential_temperature: PotentialTemperature, + ) -> EquivalentPotentialTemperature { + let temperature = temperature.0.get::(); + let dewpoint = dewpoint.0.get::(); + let mixing_ratio = mixing_ratio.0.get::(); + let theta = potential_temperature.0.get::(); + + let lcl_temp = + (1.0 / ((1.0 / (dewpoint - 56.0)) + ((temperature / dewpoint).ln() / 800.0))) + 56.0; + + let result = theta + * (((3.376 / lcl_temp) - 0.00254) + * (1000.0 * mixing_ratio) + * (1.0 + (0.81 * mixing_ratio))) + .exp(); + + EquivalentPotentialTemperature::new::(result) + } + + fn validate_inputs( + temperature: DryBulbTemperature, + dewpoint: DewPointTemperature, + mixing_ratio: MixingRatio, + potential_temperature: PotentialTemperature, + ) -> Result<(), InputError> { + temperature.check_range_si(253.0, 324.0)?; + dewpoint.check_range_si(253.0, 324.0)?; + mixing_ratio.check_range_si(0.000_000_1, 2.0)?; + potential_temperature.check_range_si(253.0, 324.0)?; + + Ok(()) + } +} + #[cfg(test)] mod tests { use crate::{ @@ -324,4 +386,23 @@ mod tests { 1e1, ); } + + #[test] + fn bolton2() { + test_with_4args::< + FormulaQuantity, + DryBulbTemperature, + DewPointTemperature, + MixingRatio, + PotentialTemperature, + Bolton2, + >( + Argument::new([253.0, 324.0]), + Argument::new([253.0, 324.0]), + Argument::new([0.000_000_1, 2.0]), + Argument::new([253.0, 324.0]), + ReferenceAtmosphere::Normal, + 1e1, + ); + } } From 5413cda221bea3040ab085bbc4cb008453b6d240 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:32:06 +0100 Subject: [PATCH 056/102] fix tests --- src/formulas/mixing_ratio.rs | 4 ++-- src/formulas/saturation_mixing_ratio.rs | 2 +- src/formulas/wet_bulb_potential_temperature.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/formulas/mixing_ratio.rs b/src/formulas/mixing_ratio.rs index 49f7f58..95d7935 100644 --- a/src/formulas/mixing_ratio.rs +++ b/src/formulas/mixing_ratio.rs @@ -30,7 +30,7 @@ impl Formula2 for Definiti vapour_pressure.check_range_si(0.0, 50_000.0)?; if vapour_pressure.0 > pressure.0 { - return Err(InputError::OutOfRange(String::from( + return Err(InputError::IncorrectArgumentSet(String::from( "vapour_pressure cannot be greater than pressure", ))); } @@ -65,7 +65,7 @@ mod tests { use super::*; #[test] - fn general1() { + fn definition1() { test_with_2args::( Argument::new([100.0, 150_000.0]), Argument::new([0.0, 50_000.0]), diff --git a/src/formulas/saturation_mixing_ratio.rs b/src/formulas/saturation_mixing_ratio.rs index 7b3b12a..828c255 100644 --- a/src/formulas/saturation_mixing_ratio.rs +++ b/src/formulas/saturation_mixing_ratio.rs @@ -33,7 +33,7 @@ impl Formula2 fo saturation_vapour_pressure.check_range_si(0.0, 50_000.0)?; if saturation_vapour_pressure.0 > pressure.0 { - return Err(InputError::OutOfRange(String::from( + return Err(InputError::IncorrectArgumentSet(String::from( "saturation_vapour_pressure cannot be greater than pressure", ))); } diff --git a/src/formulas/wet_bulb_potential_temperature.rs b/src/formulas/wet_bulb_potential_temperature.rs index d965b24..708e989 100644 --- a/src/formulas/wet_bulb_potential_temperature.rs +++ b/src/formulas/wet_bulb_potential_temperature.rs @@ -60,7 +60,7 @@ mod tests { test_with_1arg::( Argument::new([257.0, 377.0]), ReferenceAtmosphere::Normal, - 1e-12, + 1e-2, ); } } From 61d6cdec577c582b50e92c28dcc669412e534fdd Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:39:27 +0100 Subject: [PATCH 057/102] add array functions for formula4 --- src/traits.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index a82c7da..707dad4 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -45,7 +45,6 @@ pub trait Formula1 { }) } - #[cfg(feature = "array")] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] fn compute_vec(i1: &[I1]) -> Result, InputError> { @@ -132,7 +131,6 @@ pub trait Formula2 Result, InputError> { @@ -242,7 +240,6 @@ pub trait Formula3< }) } - #[cfg(feature = "array")] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] fn compute_vec(i1: &[I1], i2: &[I2], i3: &[I3]) -> Result, InputError> { @@ -289,7 +286,7 @@ pub trait Formula3< .collect() } - #[cfg(feature = "array")] + #[cfg(feature = "parallel")] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] fn compute_ndarray_parallel( @@ -365,5 +362,90 @@ pub trait Formula4< Err(err) }) } -} + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_vec(i1: &[I1], i2: &[I2], i3: &[I3], i4: &[I4]) -> Result, InputError> { + i1.iter() + .zip(i2.iter()) + .zip(i3.iter()) + .zip(i4.iter()) + .map(|(((&i1, &i2), &i3), &i4)| Self::compute(i1, i2, i3, i4)) + .collect() + } + + #[cfg(feature = "array")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_ndarray( + i1: &Array, + i2: &Array, + i3: &Array, + i4: &Array, + ) -> Result, InputError> { + Zip::from(i1) + .and(i2) + .and(i3) + .and(i4) + .fold_while( + Ok(()), + |_, &i1, &i2, &i3, &i4| match Self::validate_inputs(i1, i2, i3, i4) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }, + ) + .into_inner()?; + + Ok(Zip::from(i1) + .and(i2) + .and(i3) + .and(i4) + .map_collect(|&i1, &i2, &i3, &i4| Self::compute_unchecked(i1, i2, i3, i4))) + } + + #[cfg(feature = "parallel")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_vec_parallel( + i1: &[I1], + i2: &[I2], + i3: &[I3], + i4: &[I4], + ) -> Result, InputError> { + i1.into_par_iter() + .zip(i2) + .zip(i3) + .zip(i4) + .map(|(((&i1, &i2), &i3), &i4)| Self::compute(i1, i2, i3, i4)) + .collect() + } + + #[cfg(feature = "parallel")] + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + fn compute_ndarray_parallel( + i1: &Array, + i2: &Array, + i3: &Array, + i4: &Array, + ) -> Result, InputError> { + Zip::from(i1) + .and(i2) + .and(i3) + .and(i4) + .fold_while( + Ok(()), + |_, &i1, &i2, &i3, &i4| match Self::validate_inputs(i1, i2, i3, i4) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + }, + ) + .into_inner()?; + + Ok(Zip::from(i1) + .and(i2) + .and(i3) + .and(i4) + .par_map_collect(|&i1, &i2, &i3, &i4| Self::compute_unchecked(i1, i2, i3, i4))) + } +} From d2d07c19724a52b2c1636f05add3bd852aa94447 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 27 Feb 2024 18:41:22 +0100 Subject: [PATCH 058/102] test array functions --- Cargo.toml | 2 +- src/tests/two_arg.rs | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 02b88b8..a9f4544 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ uom = { version = "0.35", default-features = false, features = [ doctest = false [dev-dependencies] -criterion = "0.5.1" +criterion = "0.5" [features] default = ["array", "parallel", "debug", "double_precision"] diff --git a/src/tests/two_arg.rs b/src/tests/two_arg.rs index 59e91fd..442e639 100644 --- a/src/tests/two_arg.rs +++ b/src/tests/two_arg.rs @@ -1,9 +1,11 @@ +use ndarray::Array1; + use super::check_result; use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; use super::Argument; use crate::errors::InputError; -use crate::Formula2; use crate::Float; +use crate::Formula2; use std::mem::discriminant; pub fn test_with_2args< @@ -41,10 +43,6 @@ pub fn test_with_2args< } } - //the third promise of the crate is to always return finite f64 - //if all inputs are within the range - //the only allowed error is InccorectArgumentsSet as it can occur - //for values within valid range for arg1_itr in 0..=100 { for arg2_itr in 0..=100 { let arg1_tmp = @@ -80,4 +78,31 @@ pub fn test_with_2args< assert_eq!(result, expected); let result = F::compute(arg1.ref_val(atm), I2::new_si(arg2.range[1] + 0.1)).unwrap_err(); assert_eq!(result, expected); + + let arg_vecs: (Vec<_>, Vec<_>) = (-10..=10) + .map(|i| i as Float / 1000.0) + .map(|i| { + ( + I1::new_si(arg1.ref_val(atm).get_si_value() + i), + I2::new_si(arg2.ref_val(atm).get_si_value() + i), + ) + }) + .unzip(); + + let arg_arrs = ( + Array1::from(arg_vecs.0.clone()), + Array1::from(arg_vecs.1.clone()), + ); + + let result_vec = F::compute_vec(&arg_vecs.0, &arg_vecs.1).unwrap(); + check_result(result_vec[10], atm, eps); + + let result_arr = F::compute_ndarray(&arg_arrs.0, &arg_arrs.1).unwrap(); + check_result(result_arr[10], atm, eps); + + let result_vec = F::compute_vec_parallel(&arg_vecs.0, &arg_vecs.1).unwrap(); + check_result(result_vec[10], atm, eps); + + let result_arr = F::compute_ndarray_parallel(&arg_arrs.0, &arg_arrs.1).unwrap(); + check_result(result_arr[10], atm, eps); } From 0b2f259031b101562f73e8db63619d42314cbb43 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 27 Feb 2024 19:01:47 +0100 Subject: [PATCH 059/102] test arrays and imperial --- Cargo.toml | 1 + src/tests/four_arg.rs | 76 ++++++++++++++++++++++++++++++++++++++++-- src/tests/one_arg.rs | 57 +++++++++++++++++++++++++++++-- src/tests/three_arg.rs | 72 +++++++++++++++++++++++++++++++++++++-- src/tests/two_arg.rs | 45 +++++++++++++++++++++---- 5 files changed, 237 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a9f4544..a9e0bcd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ doctest = false [dev-dependencies] criterion = "0.5" +itertools = "0.12" [features] default = ["array", "parallel", "debug", "double_precision"] diff --git a/src/tests/four_arg.rs b/src/tests/four_arg.rs index a09bffa..b8e79ad 100644 --- a/src/tests/four_arg.rs +++ b/src/tests/four_arg.rs @@ -1,3 +1,7 @@ +use float_cmp::assert_approx_eq; +use itertools::multiunzip; +use ndarray::Array1; + use super::check_result; use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; use super::Argument; @@ -21,14 +25,14 @@ pub fn test_with_4args< atm: ReferenceAtmosphere, eps: Float, ) { - let result = F::compute( + let ref_result = F::compute( arg1.ref_val(atm), arg2.ref_val(atm), arg3.ref_val(atm), arg4.ref_val(atm), ) .unwrap(); - check_result(result, atm, eps); + check_result(ref_result, atm, eps); let results = vec![ F::compute( @@ -209,4 +213,72 @@ pub fn test_with_4args< ) .unwrap_err(); assert_eq!(result, expected); + + let arg_vecs = (-10..=10).map(|i| i as Float / 1000.0).map(|i| { + ( + I1::new_si(arg1.ref_val(atm).get_si_value() + i), + I2::new_si(arg2.ref_val(atm).get_si_value() + i), + I3::new_si(arg3.ref_val(atm).get_si_value() + i), + I4::new_si(arg4.ref_val(atm).get_si_value() + i), + ) + }); + + let arg_vecs: (Vec<_>, Vec<_>, Vec<_>, Vec<_>) = multiunzip(arg_vecs); + + let arg_arrs = ( + Array1::from(arg_vecs.0.clone()), + Array1::from(arg_vecs.1.clone()), + Array1::from(arg_vecs.2.clone()), + Array1::from(arg_vecs.3.clone()), + ); + + let result_vec = F::compute_vec(&arg_vecs.0, &arg_vecs.1, &arg_vecs.2, &arg_vecs.3).unwrap(); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_vec[10].get_si_value(), + ulps = 4 + ); + + let result_arr = + F::compute_ndarray(&arg_arrs.0, &arg_arrs.1, &arg_arrs.2, &arg_arrs.3).unwrap(); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_arr[10].get_si_value(), + ulps = 4 + ); + + let result_vec = + F::compute_vec_parallel(&arg_vecs.0, &arg_vecs.1, &arg_vecs.2, &arg_vecs.3).unwrap(); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_vec[10].get_si_value(), + ulps = 4 + ); + + let result_arr = + F::compute_ndarray_parallel(&arg_arrs.0, &arg_arrs.1, &arg_arrs.2, &arg_arrs.3).unwrap(); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_arr[10].get_si_value(), + ulps = 4 + ); + + let result_imperial = F::compute( + arg1.ref_val(atm).imperial(), + arg2.ref_val(atm).imperial(), + arg3.ref_val(atm).imperial(), + arg4.ref_val(atm).imperial(), + ) + .unwrap(); + + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_imperial.get_si_value(), + epsilon = 1e-12 + ); } diff --git a/src/tests/one_arg.rs b/src/tests/one_arg.rs index 32552b6..f07e261 100644 --- a/src/tests/one_arg.rs +++ b/src/tests/one_arg.rs @@ -1,9 +1,12 @@ +use float_cmp::assert_approx_eq; +use ndarray::Array1; + use super::check_result; use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; use super::Argument; use crate::errors::InputError; -use crate::Formula1; use crate::Float; +use crate::Formula1; use std::mem::discriminant; pub fn test_with_1arg>( @@ -13,8 +16,8 @@ pub fn test_with_1arg = (-10..=10) + .map(|i| i as Float / 1000.0) + .map(|i| I1::new_si(arg1.ref_val(atm).get_si_value() + i)) + .collect(); + + let arg_arrs = Array1::from(arg_vecs.clone()); + + let result_vec = F::compute_vec(&arg_vecs).unwrap(); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_vec[10].get_si_value(), + ulps = 4 + ); + + let result_arr = F::compute_ndarray(&arg_arrs).unwrap(); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_arr[10].get_si_value(), + ulps = 4 + ); + + let result_vec = F::compute_vec_parallel(&arg_vecs).unwrap(); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_vec[10].get_si_value(), + ulps = 4 + ); + + let result_arr = F::compute_ndarray_parallel(&arg_arrs).unwrap(); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_arr[10].get_si_value(), + ulps = 4 + ); + + let result_imperial = F::compute(arg1.ref_val(atm).imperial()).unwrap(); + + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_imperial.get_si_value(), + epsilon = 1e-12 + ); } diff --git a/src/tests/three_arg.rs b/src/tests/three_arg.rs index 28d2872..1474192 100644 --- a/src/tests/three_arg.rs +++ b/src/tests/three_arg.rs @@ -1,9 +1,13 @@ +use float_cmp::assert_approx_eq; +use itertools::multiunzip; +use ndarray::Array1; + use super::check_result; use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; use super::Argument; use crate::errors::InputError; -use crate::Formula3; use crate::Float; +use crate::Formula3; use std::mem::discriminant; pub fn test_with_3args< @@ -21,8 +25,8 @@ pub fn test_with_3args< ) { //the first promise of the crate is that returned value //is calculated correctly - let result = F::compute(arg1.ref_val(atm), arg2.ref_val(atm), arg3.ref_val(atm)).unwrap(); - check_result(result, atm, eps); + let ref_result = F::compute(arg1.ref_val(atm), arg2.ref_val(atm), arg3.ref_val(atm)).unwrap(); + check_result(ref_result, atm, eps); // the second promise of the crate is to never return NaN or Inf // here we check several edge cases for that @@ -131,4 +135,66 @@ pub fn test_with_3args< ) .unwrap_err(); assert_eq!(result, expected); + + let arg_vecs = (-10..=10).map(|i| i as Float / 1000.0).map(|i| { + ( + I1::new_si(arg1.ref_val(atm).get_si_value() + i), + I2::new_si(arg2.ref_val(atm).get_si_value() + i), + I3::new_si(arg3.ref_val(atm).get_si_value() + i), + ) + }); + + let arg_vecs: (Vec<_>, Vec<_>, Vec<_>) = multiunzip(arg_vecs); + + let arg_arrs = ( + Array1::from(arg_vecs.0.clone()), + Array1::from(arg_vecs.1.clone()), + Array1::from(arg_vecs.2.clone()), + ); + + let result_vec = F::compute_vec(&arg_vecs.0, &arg_vecs.1, &arg_vecs.2).unwrap(); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_vec[10].get_si_value(), + ulps = 4 + ); + + let result_arr = F::compute_ndarray(&arg_arrs.0, &arg_arrs.1, &arg_arrs.2).unwrap(); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_arr[10].get_si_value(), + ulps = 4 + ); + + let result_vec = F::compute_vec_parallel(&arg_vecs.0, &arg_vecs.1, &arg_vecs.2).unwrap(); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_vec[10].get_si_value(), + ulps = 4 + ); + + let result_arr = F::compute_ndarray_parallel(&arg_arrs.0, &arg_arrs.1, &arg_arrs.2).unwrap(); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_arr[10].get_si_value(), + ulps = 4 + ); + + let result_imperial = F::compute( + arg1.ref_val(atm).imperial(), + arg2.ref_val(atm).imperial(), + arg3.ref_val(atm).imperial(), + ) + .unwrap(); + + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_imperial.get_si_value(), + epsilon = 1e-12 + ); } diff --git a/src/tests/two_arg.rs b/src/tests/two_arg.rs index 442e639..52412f6 100644 --- a/src/tests/two_arg.rs +++ b/src/tests/two_arg.rs @@ -1,3 +1,4 @@ +use float_cmp::assert_approx_eq; use ndarray::Array1; use super::check_result; @@ -21,8 +22,8 @@ pub fn test_with_2args< ) { //the first promise of the crate is that returned value //is calculated correctly - let result = F::compute(arg1.ref_val(atm), arg2.ref_val(atm)).unwrap(); - check_result(result, atm, eps); + let ref_result = F::compute(arg1.ref_val(atm), arg2.ref_val(atm)).unwrap(); + check_result(ref_result, atm, eps); // the second promise of the crate is to never return NaN or Inf // here we check several edge cases for that @@ -95,14 +96,46 @@ pub fn test_with_2args< ); let result_vec = F::compute_vec(&arg_vecs.0, &arg_vecs.1).unwrap(); - check_result(result_vec[10], atm, eps); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_vec[10].get_si_value(), + ulps = 4 + ); let result_arr = F::compute_ndarray(&arg_arrs.0, &arg_arrs.1).unwrap(); - check_result(result_arr[10], atm, eps); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_arr[10].get_si_value(), + ulps = 4 + ); let result_vec = F::compute_vec_parallel(&arg_vecs.0, &arg_vecs.1).unwrap(); - check_result(result_vec[10], atm, eps); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_vec[10].get_si_value(), + ulps = 4 + ); let result_arr = F::compute_ndarray_parallel(&arg_arrs.0, &arg_arrs.1).unwrap(); - check_result(result_arr[10], atm, eps); + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_arr[10].get_si_value(), + ulps = 4 + ); + + let result_imperial = F::compute( + arg1.ref_val(atm).imperial(), + arg2.ref_val(atm).imperial(), + ).unwrap(); + + assert_approx_eq!( + Float, + ref_result.get_si_value(), + result_imperial.get_si_value(), + epsilon = 1e-12 + ); } From e66c07a0711795076b550a09ec9f652da92a7e85 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Tue, 27 Feb 2024 19:38:53 +0100 Subject: [PATCH 060/102] minor docs --- src/formulas/mixing_ratio.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/formulas/mixing_ratio.rs b/src/formulas/mixing_ratio.rs index 95d7935..5830280 100644 --- a/src/formulas/mixing_ratio.rs +++ b/src/formulas/mixing_ratio.rs @@ -17,7 +17,10 @@ type FormulaQuantity = MixingRatio; /// /// Valid `vapour_pressure` range: 0Pa - 10000Pa /// -/// Returns [`InputError::IncorrectArgumentSet`] when inputs are equal and division by 0 would occur. +/// Returns [`InputError::IncorrectArgumentSet`] when: +/// +/// - inputs are equal and division by 0 would occur +/// - `vapour_pressure` is greater than `pressure` pub struct Definition1; impl Formula2 for Definition1 { From 9d81109590d601a2b4253ab5c1fb6a1943be855d Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 28 Feb 2024 08:37:04 +0100 Subject: [PATCH 061/102] switch to proc from repo --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a9e0bcd..1e41746 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ exclude = [".github/*"] publish = false [dependencies] -floccus-proc = { path = "../floccus-proc" } +floccus-proc = "0.3.0" thiserror = "1.0" float-cmp = "0.9" log = { version = "0.4", optional = true } From d990423878a03223837fb2a9f7e5016723721566 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 28 Feb 2024 08:46:38 +0100 Subject: [PATCH 062/102] readme badges update --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 958f710..a9c1017 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # floccus -[![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/basic.yml?branch=main&label=cargo%20build)](https://github.com/ScaleWeather/floccus/actions) +[![Github Repository](https://img.shields.io/badge/Github-Repository-blue?style=flat-square&logo=github&color=blue)](https://github.com/ScaleWeather/floccus) +[![Crates.io](https://img.shields.io/crates/v/floccus?style=flat-square)](https://crates.io/crates/floccus) +[![License](https://img.shields.io/github/license/ScaleWeather/floccus?style=flat-square)](https://choosealicense.com/licenses/apache-2.0/) +[![dependency status](https://deps.rs/repo/github/ScaleWeather/floccus/status.svg?style=flat-square)](https://deps.rs/repo/github/ScaleWeather/floccus) Rust crate providing formulae for air thermodynamic calculations. From 272187307fe7c3363322e3247d8e53221c4f4239 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 28 Feb 2024 09:33:21 +0100 Subject: [PATCH 063/102] test logs --- Cargo.toml | 3 ++- src/tests/four_arg.rs | 19 +++++++++++++++++++ src/tests/one_arg.rs | 14 ++++++++++++++ src/tests/three_arg.rs | 18 ++++++++++++++++++ src/tests/two_arg.rs | 20 ++++++++++++++++---- 5 files changed, 69 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1e41746..a7b096d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "floccus" -version = "0.4.0" +version = "0.4.0-alpha.1" authors = ["Jakub Lewandowski "] edition = "2021" description = "Formulae for air thermodynamic calculations" @@ -33,6 +33,7 @@ doctest = false [dev-dependencies] criterion = "0.5" itertools = "0.12" +testing_logger = "0.1" [features] default = ["array", "parallel", "debug", "double_precision"] diff --git a/src/tests/four_arg.rs b/src/tests/four_arg.rs index b8e79ad..dcd24c0 100644 --- a/src/tests/four_arg.rs +++ b/src/tests/four_arg.rs @@ -281,4 +281,23 @@ pub fn test_with_4args< result_imperial.get_si_value(), epsilon = 1e-12 ); + + testing_logger::setup(); + let _ = F::compute( + I1::new_si(-9999.0), + I2::new_si(-9999.0), + I3::new_si(-9999.0), + I4::new_si(-9999.0), + ); + + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + let body = &captured_logs[0].body; + assert!(body.contains("Formula")); + assert!(body.contains("calculating")); + assert!(body.contains("from")); + assert!(body.contains("inputs")); + assert!(body.contains("returned error:")); + assert_eq!(captured_logs[0].level, log::Level::Error); + }); } diff --git a/src/tests/one_arg.rs b/src/tests/one_arg.rs index f07e261..20d0e71 100644 --- a/src/tests/one_arg.rs +++ b/src/tests/one_arg.rs @@ -112,4 +112,18 @@ pub fn test_with_1arg Date: Wed, 28 Feb 2024 09:58:30 +0100 Subject: [PATCH 064/102] cleanup features --- Cargo.toml | 3 ++- src/tests/four_arg.rs | 11 +++++++++++ src/tests/one_arg.rs | 11 +++++++++++ src/tests/three_arg.rs | 11 +++++++++++ src/tests/two_arg.rs | 11 +++++++++++ src/traits.rs | 44 ++++++++++++++++++++++++++++-------------- 6 files changed, 75 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a7b096d..5ab025f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ uom = { version = "0.35", default-features = false, features = [ "f32", "f64", ] } +cfg-if = "1.0" [lib] doctest = false @@ -36,7 +37,7 @@ itertools = "0.12" testing_logger = "0.1" [features] -default = ["array", "parallel", "debug", "double_precision"] +default = [] debug = ["log"] double_precision = [] array = ["ndarray"] diff --git a/src/tests/four_arg.rs b/src/tests/four_arg.rs index dcd24c0..1a8d1fc 100644 --- a/src/tests/four_arg.rs +++ b/src/tests/four_arg.rs @@ -1,5 +1,6 @@ use float_cmp::assert_approx_eq; use itertools::multiunzip; +#[cfg(feature = "array")] use ndarray::Array1; use super::check_result; @@ -225,6 +226,7 @@ pub fn test_with_4args< let arg_vecs: (Vec<_>, Vec<_>, Vec<_>, Vec<_>) = multiunzip(arg_vecs); + #[cfg(feature = "array")] let arg_arrs = ( Array1::from(arg_vecs.0.clone()), Array1::from(arg_vecs.1.clone()), @@ -240,8 +242,10 @@ pub fn test_with_4args< ulps = 4 ); + #[cfg(feature = "array")] let result_arr = F::compute_ndarray(&arg_arrs.0, &arg_arrs.1, &arg_arrs.2, &arg_arrs.3).unwrap(); + #[cfg(feature = "array")] assert_approx_eq!( Float, ref_result.get_si_value(), @@ -249,8 +253,10 @@ pub fn test_with_4args< ulps = 4 ); + #[cfg(feature = "parallel")] let result_vec = F::compute_vec_parallel(&arg_vecs.0, &arg_vecs.1, &arg_vecs.2, &arg_vecs.3).unwrap(); + #[cfg(feature = "parallel")] assert_approx_eq!( Float, ref_result.get_si_value(), @@ -258,8 +264,10 @@ pub fn test_with_4args< ulps = 4 ); + #[cfg(feature = "parallel")] let result_arr = F::compute_ndarray_parallel(&arg_arrs.0, &arg_arrs.1, &arg_arrs.2, &arg_arrs.3).unwrap(); + #[cfg(feature = "parallel")] assert_approx_eq!( Float, ref_result.get_si_value(), @@ -282,7 +290,9 @@ pub fn test_with_4args< epsilon = 1e-12 ); + #[cfg(feature = "debug")] testing_logger::setup(); + #[cfg(feature = "debug")] let _ = F::compute( I1::new_si(-9999.0), I2::new_si(-9999.0), @@ -290,6 +300,7 @@ pub fn test_with_4args< I4::new_si(-9999.0), ); + #[cfg(feature = "debug")] testing_logger::validate(|captured_logs| { assert_eq!(captured_logs.len(), 1); let body = &captured_logs[0].body; diff --git a/src/tests/one_arg.rs b/src/tests/one_arg.rs index 20d0e71..c666d46 100644 --- a/src/tests/one_arg.rs +++ b/src/tests/one_arg.rs @@ -1,4 +1,5 @@ use float_cmp::assert_approx_eq; +#[cfg(feature = "array")] use ndarray::Array1; use super::check_result; @@ -70,6 +71,7 @@ pub fn test_with_1arg, Vec<_>, Vec<_>) = multiunzip(arg_vecs); + #[cfg(feature = "array")] let arg_arrs = ( Array1::from(arg_vecs.0.clone()), Array1::from(arg_vecs.1.clone()), @@ -160,7 +162,9 @@ pub fn test_with_3args< ulps = 4 ); + #[cfg(feature = "array")] let result_arr = F::compute_ndarray(&arg_arrs.0, &arg_arrs.1, &arg_arrs.2).unwrap(); + #[cfg(feature = "array")] assert_approx_eq!( Float, ref_result.get_si_value(), @@ -168,7 +172,9 @@ pub fn test_with_3args< ulps = 4 ); + #[cfg(feature = "parallel")] let result_vec = F::compute_vec_parallel(&arg_vecs.0, &arg_vecs.1, &arg_vecs.2).unwrap(); + #[cfg(feature = "parallel")] assert_approx_eq!( Float, ref_result.get_si_value(), @@ -176,7 +182,9 @@ pub fn test_with_3args< ulps = 4 ); + #[cfg(feature = "parallel")] let result_arr = F::compute_ndarray_parallel(&arg_arrs.0, &arg_arrs.1, &arg_arrs.2).unwrap(); + #[cfg(feature = "parallel")] assert_approx_eq!( Float, ref_result.get_si_value(), @@ -198,13 +206,16 @@ pub fn test_with_3args< epsilon = 1e-12 ); + #[cfg(feature = "debug")] testing_logger::setup(); + #[cfg(feature = "debug")] let _ = F::compute( I1::new_si(-9999.0), I2::new_si(-9999.0), I3::new_si(-9999.0), ); + #[cfg(feature = "debug")] testing_logger::validate(|captured_logs| { assert_eq!(captured_logs.len(), 1); let body = &captured_logs[0].body; diff --git a/src/tests/two_arg.rs b/src/tests/two_arg.rs index 49d5d1d..b9f97cf 100644 --- a/src/tests/two_arg.rs +++ b/src/tests/two_arg.rs @@ -1,4 +1,5 @@ use float_cmp::assert_approx_eq; +#[cfg(feature = "array")] use ndarray::Array1; use super::check_result; @@ -90,6 +91,7 @@ pub fn test_with_2args< }) .unzip(); + #[cfg(feature = "array")] let arg_arrs = ( Array1::from(arg_vecs.0.clone()), Array1::from(arg_vecs.1.clone()), @@ -103,7 +105,9 @@ pub fn test_with_2args< ulps = 4 ); + #[cfg(feature = "array")] let result_arr = F::compute_ndarray(&arg_arrs.0, &arg_arrs.1).unwrap(); + #[cfg(feature = "array")] assert_approx_eq!( Float, ref_result.get_si_value(), @@ -111,7 +115,9 @@ pub fn test_with_2args< ulps = 4 ); + #[cfg(feature = "parallel")] let result_vec = F::compute_vec_parallel(&arg_vecs.0, &arg_vecs.1).unwrap(); + #[cfg(feature = "parallel")] assert_approx_eq!( Float, ref_result.get_si_value(), @@ -119,7 +125,9 @@ pub fn test_with_2args< ulps = 4 ); + #[cfg(feature = "parallel")] let result_arr = F::compute_ndarray_parallel(&arg_arrs.0, &arg_arrs.1).unwrap(); + #[cfg(feature = "parallel")] assert_approx_eq!( Float, ref_result.get_si_value(), @@ -137,9 +145,12 @@ pub fn test_with_2args< epsilon = 1e-12 ); + #[cfg(feature = "debug")] testing_logger::setup(); + #[cfg(feature = "debug")] let _ = F::compute(I1::new_si(-9999.0), I2::new_si(-9999.0)); + #[cfg(feature = "debug")] testing_logger::validate(|captured_logs| { assert_eq!(captured_logs.len(), 1); let body = &captured_logs[0].body; diff --git a/src/traits.rs b/src/traits.rs index 707dad4..366bef2 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,7 +1,9 @@ #![allow(missing_docs)] use crate::{errors::InputError, quantities::ThermodynamicQuantity}; +#[cfg(feature = "array")] use ndarray::{Array, Dimension, FoldWhile, Zip}; +#[cfg(feature = "parallel")] use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; pub trait Formula1 { @@ -16,11 +18,14 @@ pub trait Formula1 { #[allow(missing_docs)] #[inline] fn compute(i1: I1) -> Result { - if cfg!(feature = "debug") && cfg!(debug_assertions) { - Self::validate_inputs_loggerr(i1)?; + cfg_if::cfg_if! { + if #[cfg(feature = "debug")] { + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + Self::validate_inputs_loggerr(i1)?;}} } else { Self::validate_inputs(i1)?; - } + }} Ok(Self::compute_unchecked(i1)) } @@ -101,11 +106,14 @@ pub trait Formula2 Result { - if cfg!(feature = "debug") && cfg!(debug_assertions) { - Self::validate_inputs_loggerr(i1, i2)?; + cfg_if::cfg_if! { + if #[cfg(feature = "debug")] { + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + Self::validate_inputs_loggerr(i1,i2)?;}} } else { - Self::validate_inputs(i1, i2)?; - } + Self::validate_inputs(i1,i2)?; + }} Ok(Self::compute_unchecked(i1, i2)) } @@ -209,11 +217,14 @@ pub trait Formula3< #[allow(missing_docs)] #[inline] fn compute(i1: I1, i2: I2, i3: I3) -> Result { - if cfg!(feature = "debug") && cfg!(debug_assertions) { - Self::validate_inputs_loggerr(i1, i2, i3)?; + cfg_if::cfg_if! { + if #[cfg(feature = "debug")] { + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + Self::validate_inputs_loggerr(i1, i2, i3)?;}} } else { - Self::validate_inputs(i1, i2, i3)?; - } + Self::validate_inputs(i1,i2,i3)?; + }} Ok(Self::compute_unchecked(i1, i2, i3)) } @@ -331,11 +342,14 @@ pub trait Formula4< #[allow(missing_docs)] #[inline] fn compute(i1: I1, i2: I2, i3: I3, i4: I4) -> Result { - if cfg!(feature = "debug") && cfg!(debug_assertions) { - Self::validate_inputs_loggerr(i1, i2, i3, i4)?; + cfg_if::cfg_if! { + if #[cfg(feature = "debug")] { + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + Self::validate_inputs_loggerr(i1,i2,i3,i4)?;}} } else { - Self::validate_inputs(i1, i2, i3, i4)?; - } + Self::validate_inputs(i1,i2,i3,i4)?; + }} Ok(Self::compute_unchecked(i1, i2, i3, i4)) } From 54546a5e0285019f8cc8f532ab6666c8eba87a78 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:02:08 +0100 Subject: [PATCH 065/102] fmt --- src/traits.rs | 64 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index 366bef2..55f74a0 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -18,14 +18,17 @@ pub trait Formula1 { #[allow(missing_docs)] #[inline] fn compute(i1: I1) -> Result { - cfg_if::cfg_if! { + cfg_if::cfg_if! { if #[cfg(feature = "debug")] { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - Self::validate_inputs_loggerr(i1)?;}} - } else { - Self::validate_inputs(i1)?; - }} + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + Self::validate_inputs_loggerr(i1)?; + } + } + } else { + Self::validate_inputs(i1)?; + } + } Ok(Self::compute_unchecked(i1)) } @@ -106,14 +109,17 @@ pub trait Formula2 Result { - cfg_if::cfg_if! { + cfg_if::cfg_if! { if #[cfg(feature = "debug")] { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - Self::validate_inputs_loggerr(i1,i2)?;}} - } else { - Self::validate_inputs(i1,i2)?; - }} + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + Self::validate_inputs_loggerr(i1,i2)?; + } + } + } else { + Self::validate_inputs(i1,i2)?; + } + } Ok(Self::compute_unchecked(i1, i2)) } @@ -217,14 +223,17 @@ pub trait Formula3< #[allow(missing_docs)] #[inline] fn compute(i1: I1, i2: I2, i3: I3) -> Result { - cfg_if::cfg_if! { + cfg_if::cfg_if! { if #[cfg(feature = "debug")] { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - Self::validate_inputs_loggerr(i1, i2, i3)?;}} - } else { - Self::validate_inputs(i1,i2,i3)?; - }} + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + Self::validate_inputs_loggerr(i1, i2, i3)?; + } + } + } else { + Self::validate_inputs(i1,i2,i3)?; + } + } Ok(Self::compute_unchecked(i1, i2, i3)) } @@ -344,12 +353,15 @@ pub trait Formula4< fn compute(i1: I1, i2: I2, i3: I3, i4: I4) -> Result { cfg_if::cfg_if! { if #[cfg(feature = "debug")] { - cfg_if::cfg_if! { + cfg_if::cfg_if! { if #[cfg(debug_assertions)] { - Self::validate_inputs_loggerr(i1,i2,i3,i4)?;}} - } else { - Self::validate_inputs(i1,i2,i3,i4)?; - }} + Self::validate_inputs_loggerr(i1,i2,i3,i4)?; + } + } + } else { + Self::validate_inputs(i1,i2,i3,i4)?; + } + } Ok(Self::compute_unchecked(i1, i2, i3, i4)) } From dda09f65a60d1d6c6e824619cf068355d160f84b Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:01:11 +0100 Subject: [PATCH 066/102] intro to solving benches --- Cargo.toml | 43 +---------- .../equivalent_potential_temperature.rs | 11 --- benches-aaa/mixing_ratio.rs | 17 ----- benches-aaa/potential_temperature.rs | 11 --- benches-aaa/relative_humidity.rs | 27 ------- benches-aaa/specific_humidity.rs | 11 --- benches-aaa/vapour_pressure.rs | 55 -------------- benches-aaa/vapour_pressure_deficit.rs | 19 ----- benches-aaa/virtual_temperature.rs | 19 ----- benches-aaa/wet_bulb_potential_temperature.rs | 11 --- benches-aaa/wet_bulb_temperature.rs | 11 --- benches/equivalent_potential_temperature.rs | 11 +++ benches/floccus_formulas.rs | 6 ++ benches/mixing_ratio.rs | 17 +++++ benches/potential_temperature.rs | 11 +++ benches/reference_values.rs | 73 +++++++++++++++++++ benches/relative_humidity.rs | 27 +++++++ {benches-aaa => benches}/results/0_3_7.txt | 0 benches/specific_humidity.rs | 11 +++ benches/vapour_pressure.rs | 54 ++++++++++++++ benches/vapour_pressure_deficit.rs | 19 +++++ benches/virtual_temperature.rs | 19 +++++ benches/wet_bulb_potential_temperature.rs | 11 +++ benches/wet_bulb_temperature.rs | 11 +++ 24 files changed, 274 insertions(+), 231 deletions(-) delete mode 100644 benches-aaa/equivalent_potential_temperature.rs delete mode 100644 benches-aaa/mixing_ratio.rs delete mode 100644 benches-aaa/potential_temperature.rs delete mode 100644 benches-aaa/relative_humidity.rs delete mode 100644 benches-aaa/specific_humidity.rs delete mode 100644 benches-aaa/vapour_pressure.rs delete mode 100644 benches-aaa/vapour_pressure_deficit.rs delete mode 100644 benches-aaa/virtual_temperature.rs delete mode 100644 benches-aaa/wet_bulb_potential_temperature.rs delete mode 100644 benches-aaa/wet_bulb_temperature.rs create mode 100644 benches/equivalent_potential_temperature.rs create mode 100644 benches/floccus_formulas.rs create mode 100644 benches/mixing_ratio.rs create mode 100644 benches/potential_temperature.rs create mode 100644 benches/reference_values.rs create mode 100644 benches/relative_humidity.rs rename {benches-aaa => benches}/results/0_3_7.txt (100%) create mode 100644 benches/specific_humidity.rs create mode 100644 benches/vapour_pressure.rs create mode 100644 benches/vapour_pressure_deficit.rs create mode 100644 benches/virtual_temperature.rs create mode 100644 benches/wet_bulb_potential_temperature.rs create mode 100644 benches/wet_bulb_temperature.rs diff --git a/Cargo.toml b/Cargo.toml index 5ab025f..e0e53b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ doctest = false criterion = "0.5" itertools = "0.12" testing_logger = "0.1" +floccus = { path = "." , features = ["double_precision"] } [features] default = [] @@ -43,42 +44,6 @@ double_precision = [] array = ["ndarray"] parallel = ["array", "ndarray/rayon", "rayon"] -# [[bench]] -# name = "virtual_temperature" -# harness = false - -# [[bench]] -# name = "vapour_pressure" -# harness = false - -# [[bench]] -# name = "vapour_pressure_deficit" -# harness = false - -# [[bench]] -# name = "mixing_ratio" -# harness = false - -# [[bench]] -# name = "wet_bulb_temperature" -# harness = false - -# [[bench]] -# name = "relative_humidity" -# harness = false - -# [[bench]] -# name = "specific_humidity" -# harness = false - -# [[bench]] -# name = "potential_temperature" -# harness = false - -# [[bench]] -# name = "equivalent_potential_temperature" -# harness = false - -# [[bench]] -# name = "wet_bulb_potential_temperature" -# harness = false +[[bench]] +name = "floccus_formulas" +harness = false diff --git a/benches-aaa/equivalent_potential_temperature.rs b/benches-aaa/equivalent_potential_temperature.rs deleted file mode 100644 index 419d3e4..0000000 --- a/benches-aaa/equivalent_potential_temperature.rs +++ /dev/null @@ -1,11 +0,0 @@ -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use floccus::equivalent_potential_temperature; - -pub fn equivalent_potential_temperature_benchmark(c: &mut Criterion) { - c.bench_function("equivalent_potential_temperature::bryan1", |b| { - b.iter(|| equivalent_potential_temperature::bryan1(black_box(300.0), black_box(101325.0), black_box(3000.0))) - }); -} - -criterion_group!(benches, equivalent_potential_temperature_benchmark); -criterion_main!(benches); \ No newline at end of file diff --git a/benches-aaa/mixing_ratio.rs b/benches-aaa/mixing_ratio.rs deleted file mode 100644 index f8f81a8..0000000 --- a/benches-aaa/mixing_ratio.rs +++ /dev/null @@ -1,17 +0,0 @@ -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use floccus::mixing_ratio; - -pub fn mixing_ratio_benchmark(c: &mut Criterion) { - c.bench_function("mixing_ratio::general1", |b| { - b.iter(|| mixing_ratio::general1(black_box(101325.0), black_box(3500.0))) - }); - c.bench_function("mixing_ratio::performance1", |b| { - b.iter(|| mixing_ratio::performance1(black_box(300.0), black_box(101325.0))) - }); - c.bench_function("mixing_ratio::accuracy1", |b| { - b.iter(|| mixing_ratio::accuracy1(black_box(300.0), black_box(101325.0))) - }); -} - -criterion_group!(benches, mixing_ratio_benchmark); -criterion_main!(benches); diff --git a/benches-aaa/potential_temperature.rs b/benches-aaa/potential_temperature.rs deleted file mode 100644 index 667ce1d..0000000 --- a/benches-aaa/potential_temperature.rs +++ /dev/null @@ -1,11 +0,0 @@ -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use floccus::potential_temperature; - -pub fn potential_temperature_benchmark(c: &mut Criterion) { - c.bench_function("potential_temperature::davies_jones1", |b| { - b.iter(|| potential_temperature::davies_jones1(black_box(300.0), black_box(101325.0), black_box(3000.0))) - }); -} - -criterion_group!(benches, potential_temperature_benchmark); -criterion_main!(benches); \ No newline at end of file diff --git a/benches-aaa/relative_humidity.rs b/benches-aaa/relative_humidity.rs deleted file mode 100644 index 5dfcee4..0000000 --- a/benches-aaa/relative_humidity.rs +++ /dev/null @@ -1,27 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use floccus::relative_humidity; - -pub fn relative_humidity_benchmark(c: &mut Criterion) { - c.bench_function("relative_humidity::general1", |b| { - b.iter(|| relative_humidity::general1(black_box(0.01064), black_box(0.01467))) - }); - - c.bench_function("relative_humidity::general2", |b| { - b.iter(|| relative_humidity::general2(black_box(1706.0), black_box(2339.0))) - }); - - c.bench_function("relative_humidity::general3", |b| { - b.iter(|| relative_humidity::general3(black_box(300.0), black_box(290.0))) - }); - - c.bench_function("relative_humidity::general4", |b| { - b.iter(|| relative_humidity::general4(black_box(300.0), black_box(290.0), black_box(101325.0))) - }); - - c.bench_function("relative_humidity::general5", |b| { - b.iter(|| relative_humidity::general5(black_box(300.0), black_box(290.0), black_box(101325.0))) - }); -} - -criterion_group!(benches, relative_humidity_benchmark); -criterion_main!(benches); diff --git a/benches-aaa/specific_humidity.rs b/benches-aaa/specific_humidity.rs deleted file mode 100644 index fd90abc..0000000 --- a/benches-aaa/specific_humidity.rs +++ /dev/null @@ -1,11 +0,0 @@ -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use floccus::specific_humidity; - -pub fn specific_humidity_benchmark(c: &mut Criterion) { - c.bench_function("specific_humidity::general1", |b| { - b.iter(|| specific_humidity::general1(black_box(3000.0), black_box(101325.0))) - }); -} - -criterion_group!(benches, specific_humidity_benchmark); -criterion_main!(benches); \ No newline at end of file diff --git a/benches-aaa/vapour_pressure.rs b/benches-aaa/vapour_pressure.rs deleted file mode 100644 index 026530f..0000000 --- a/benches-aaa/vapour_pressure.rs +++ /dev/null @@ -1,55 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use floccus::vapour_pressure; - -pub fn vapour_pressure_benchmark(c: &mut Criterion) { - c.bench_function("vapour_pressure::general1", |b| { - b.iter(|| vapour_pressure::general1(black_box(0.022), black_box(101325.0))) - }); - - c.bench_function("vapour_pressure::tetens1", |b| { - b.iter(|| vapour_pressure::tetens1(black_box(300.0))) - }); - - c.bench_function("vapour_pressure::buck1", |b| { - b.iter(|| vapour_pressure::buck1(black_box(300.0), black_box(101325.0))) - }); - - c.bench_function("vapour_pressure::buck2", |b| { - b.iter(|| vapour_pressure::buck2(black_box(250.0), black_box(101325.0))) - }); - - c.bench_function("vapour_pressure::buck3", |b| { - b.iter(|| vapour_pressure::buck3(black_box(300.0), black_box(101325.0))) - }); - - c.bench_function("vapour_pressure::buck4", |b| { - b.iter(|| vapour_pressure::buck4(black_box(250.0), black_box(101325.0))) - }); - - c.bench_function("vapour_pressure::buck3_simplified", |b| { - b.iter(|| vapour_pressure::buck3_simplified(black_box(300.0))) - }); - - c.bench_function("vapour_pressure::buck4_simplified", |b| { - b.iter(|| vapour_pressure::buck4_simplified(black_box(250.0))) - }); - - c.bench_function("vapour_pressure::saturation_specific1", |b| { - b.iter(|| vapour_pressure::saturation_specific1(black_box(3000.0), black_box(0.5))) - }); - - c.bench_function("vapour_pressure::saturation_specific2", |b| { - b.iter(|| vapour_pressure::saturation_specific2(black_box(3000.0), black_box(0.5))) - }); - - c.bench_function("vapour_pressure::wexler1", |b| { - b.iter(|| vapour_pressure::wexler1(black_box(300.0))) - }); - - c.bench_function("vapour_pressure::wexler2", |b| { - b.iter(|| vapour_pressure::wexler2(black_box(250.0))) - }); -} - -criterion_group!(benches, vapour_pressure_benchmark); -criterion_main!(benches); diff --git a/benches-aaa/vapour_pressure_deficit.rs b/benches-aaa/vapour_pressure_deficit.rs deleted file mode 100644 index e376fc4..0000000 --- a/benches-aaa/vapour_pressure_deficit.rs +++ /dev/null @@ -1,19 +0,0 @@ -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use floccus::vapour_pressure_deficit; - -pub fn virtual_temperature_benchmark(c: &mut Criterion) { - c.bench_function("vapour_pressure_deficit::general1", |b| { - b.iter(|| vapour_pressure_deficit::general1(black_box(3000.0), black_box(3550.0))) - }); - - c.bench_function("vapour_pressure_deficit::general2", |b| { - b.iter(|| vapour_pressure_deficit::general2(black_box(300.0), black_box(290.0), black_box(101325.0))) - }); - - c.bench_function("vapour_pressure_deficit::general3", |b| { - b.iter(|| vapour_pressure_deficit::general3(black_box(300.0), black_box(0.5), black_box(101325.0))) - }); -} - -criterion_group!(benches, virtual_temperature_benchmark); -criterion_main!(benches); diff --git a/benches-aaa/virtual_temperature.rs b/benches-aaa/virtual_temperature.rs deleted file mode 100644 index d476a32..0000000 --- a/benches-aaa/virtual_temperature.rs +++ /dev/null @@ -1,19 +0,0 @@ -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use floccus::virtual_temperature; - -pub fn virtual_temperature_benchmark(c: &mut Criterion) { - c.bench_function("virtual_temperature::general1", |b| { - b.iter(|| virtual_temperature::general1(black_box(300.0), black_box(0.022))) - }); - - c.bench_function("virtual_temperature::general2", |b| { - b.iter(|| virtual_temperature::general2(black_box(300.0), black_box(101325.0), black_box(3550.0))) - }); - - c.bench_function("virtual_temperature::general3", |b| { - b.iter(|| virtual_temperature::general3(black_box(300.0), black_box(0.022))) - }); -} - -criterion_group!(benches, virtual_temperature_benchmark); -criterion_main!(benches); diff --git a/benches-aaa/wet_bulb_potential_temperature.rs b/benches-aaa/wet_bulb_potential_temperature.rs deleted file mode 100644 index 104f841..0000000 --- a/benches-aaa/wet_bulb_potential_temperature.rs +++ /dev/null @@ -1,11 +0,0 @@ -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use floccus::wet_bulb_potential_temperature; - -pub fn wet_bulb_potential_temperature_benchmark(c: &mut Criterion) { - c.bench_function("wet_bulb_potential_temperature::davies_jones1", |b| { - b.iter(|| wet_bulb_potential_temperature::davies_jones1(black_box(300.0))) - }); -} - -criterion_group!(benches, wet_bulb_potential_temperature_benchmark); -criterion_main!(benches); \ No newline at end of file diff --git a/benches-aaa/wet_bulb_temperature.rs b/benches-aaa/wet_bulb_temperature.rs deleted file mode 100644 index 928e673..0000000 --- a/benches-aaa/wet_bulb_temperature.rs +++ /dev/null @@ -1,11 +0,0 @@ -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use floccus::wet_bulb_temperature; - -pub fn wet_bulb_temperature_benchmark(c: &mut Criterion) { - c.bench_function("wet_bulb_temperature::stull1", |b| { - b.iter(|| wet_bulb_temperature::stull1(black_box(300.0), black_box(0.5))) - }); -} - -criterion_group!(benches, wet_bulb_temperature_benchmark); -criterion_main!(benches); diff --git a/benches/equivalent_potential_temperature.rs b/benches/equivalent_potential_temperature.rs new file mode 100644 index 0000000..05b4591 --- /dev/null +++ b/benches/equivalent_potential_temperature.rs @@ -0,0 +1,11 @@ +// use criterion::{Criterion, black_box, criterion_group, criterion_main}; +// use floccus::equivalent_potential_temperature; + +// pub fn equivalent_potential_temperature_benchmark(c: &mut Criterion) { +// c.bench_function("equivalent_potential_temperature::bryan1", |b| { +// b.iter(|| equivalent_potential_temperature::bryan1(black_box(300.0), black_box(101325.0), black_box(3000.0))) +// }); +// } + +// criterion_group!(benches, equivalent_potential_temperature_benchmark); +// criterion_main!(benches); \ No newline at end of file diff --git a/benches/floccus_formulas.rs b/benches/floccus_formulas.rs new file mode 100644 index 0000000..7604179 --- /dev/null +++ b/benches/floccus_formulas.rs @@ -0,0 +1,6 @@ +use criterion::{criterion_group, criterion_main}; + +mod vapour_pressure; + +criterion_group!(benches, vapour_pressure::benchmark); +criterion_main!(benches); diff --git a/benches/mixing_ratio.rs b/benches/mixing_ratio.rs new file mode 100644 index 0000000..ef7fef1 --- /dev/null +++ b/benches/mixing_ratio.rs @@ -0,0 +1,17 @@ +// use criterion::{Criterion, black_box, criterion_group, criterion_main}; +// use floccus::mixing_ratio; + +// pub fn mixing_ratio_benchmark(c: &mut Criterion) { +// c.bench_function("mixing_ratio::general1", |b| { +// b.iter(|| mixing_ratio::general1(black_box(101325.0), black_box(3500.0))) +// }); +// c.bench_function("mixing_ratio::performance1", |b| { +// b.iter(|| mixing_ratio::performance1(black_box(300.0), black_box(101325.0))) +// }); +// c.bench_function("mixing_ratio::accuracy1", |b| { +// b.iter(|| mixing_ratio::accuracy1(black_box(300.0), black_box(101325.0))) +// }); +// } + +// criterion_group!(benches, mixing_ratio_benchmark); +// criterion_main!(benches); diff --git a/benches/potential_temperature.rs b/benches/potential_temperature.rs new file mode 100644 index 0000000..42881a7 --- /dev/null +++ b/benches/potential_temperature.rs @@ -0,0 +1,11 @@ +// use criterion::{Criterion, black_box, criterion_group, criterion_main}; +// use floccus::potential_temperature; + +// pub fn potential_temperature_benchmark(c: &mut Criterion) { +// c.bench_function("potential_temperature::davies_jones1", |b| { +// b.iter(|| potential_temperature::davies_jones1(black_box(300.0), black_box(101325.0), black_box(3000.0))) +// }); +// } + +// criterion_group!(benches, potential_temperature_benchmark); +// criterion_main!(benches); \ No newline at end of file diff --git a/benches/reference_values.rs b/benches/reference_values.rs new file mode 100644 index 0000000..0050d28 --- /dev/null +++ b/benches/reference_values.rs @@ -0,0 +1,73 @@ +use criterion::black_box; +use floccus::quantities::{ + AtmosphericPressure, DewPointTemperature, DryBulbTemperature, RelativeHumidity, + SaturationVapourPressure, SpecificHumidity, +}; +use uom::si::{pressure::pascal, ratio::ratio, thermodynamic_temperature::kelvin}; + +type Float = f64; + +pub(crate) const TEMP_NORM: Float = 300.0; +pub(crate) const DWPT_NORM: Float = 290.0; +pub(crate) const PRES_NORM: Float = 100000.0; +pub(crate) const VP_NORM: Float = 1919.4253257541593; +pub(crate) const SVP_NORM: Float = 3535.4235919263083; +pub(crate) const RH_NORM: Float = 0.5429124052171476; +pub(crate) const MR_NORM: Float = 0.012172079452423202; +pub(crate) const SMR_NROM: Float = 0.022419969290542845; +pub(crate) const VPD_NORM: Float = 1615.998266172149; +pub(crate) const SH_NORM: Float = 0.012025701656390478; +pub(crate) const THETAE_NORM: Float = 331.329289539998; +pub(crate) const THETA_NORM: Float = 301.66581400702955; +pub(crate) const THETAW_NORM: Float = 292.0717306393948; +pub(crate) const WBT_NORM: Float = 293.42728654340516; +pub(crate) const VRT_NORM: Float = 302.1926517941886; + +pub(crate) const TEMP_FREEZ: Float = 260.0; +pub(crate) const DWPT_FREEZ: Float = 255.0; +pub(crate) const PRES_FREEZ: Float = 100000.0; +pub(crate) const VP_FREEZ: Float = 123.17937690212507; +pub(crate) const SVP_FREEZ: Float = 195.84980045970696; +pub(crate) const RH_FREEZ: Float = 0.6289481868911442; +pub(crate) const MR_FREEZ: Float = 0.0007670962389744638; +pub(crate) const SMR_FREEZ: Float = 0.0012196493367222787; +pub(crate) const VPD_FREEZ: Float = 72.67042355758188; +pub(crate) const SH_FREEZ: Float = 0.000766508253376156; +pub(crate) const THETAE_FREEZ: Float = 261.95287841149707; +pub(crate) const THETA_FREEZ: Float = 260.0915766593588; +pub(crate) const THETAW_FREEZ: Float = 258.6611332391296; +pub(crate) const WBT_FREEZ: Float = 258.40501060754224; +pub(crate) const VRT_FREEZ: Float = 260.12112343315795; + +pub struct ReferenceValues { + pub temp: DryBulbTemperature, + pub pres: AtmosphericPressure, + pub dwpt: DewPointTemperature, + pub sphu: SpecificHumidity, + pub savp: SaturationVapourPressure, + pub rehu: RelativeHumidity, +} + +impl ReferenceValues { + pub fn normal() -> Self { + Self { + temp: black_box(DryBulbTemperature::new::(TEMP_NORM)), + pres: black_box(AtmosphericPressure::new::(PRES_NORM)), + dwpt: black_box(DewPointTemperature::new::(DWPT_NORM)), + sphu: black_box(SpecificHumidity::new::(SH_NORM)), + savp: black_box(SaturationVapourPressure::new::(SVP_NORM)), + rehu: black_box(RelativeHumidity::new::(RH_NORM)), + } + } + + pub fn freeze() -> Self { + Self { + temp: black_box(DryBulbTemperature::new::(TEMP_FREEZ)), + pres: black_box(AtmosphericPressure::new::(PRES_FREEZ)), + dwpt: black_box(DewPointTemperature::new::(DWPT_FREEZ)), + sphu: black_box(SpecificHumidity::new::(SH_FREEZ)), + savp: black_box(SaturationVapourPressure::new::(SVP_FREEZ)), + rehu: black_box(RelativeHumidity::new::(RH_FREEZ)), + } + } +} diff --git a/benches/relative_humidity.rs b/benches/relative_humidity.rs new file mode 100644 index 0000000..a800b4b --- /dev/null +++ b/benches/relative_humidity.rs @@ -0,0 +1,27 @@ +// use criterion::{black_box, criterion_group, criterion_main, Criterion}; +// use floccus::relative_humidity; + +// pub fn relative_humidity_benchmark(c: &mut Criterion) { +// c.bench_function("relative_humidity::general1", |b| { +// b.iter(|| relative_humidity::general1(black_box(0.01064), black_box(0.01467))) +// }); + +// c.bench_function("relative_humidity::general2", |b| { +// b.iter(|| relative_humidity::general2(black_box(1706.0), black_box(2339.0))) +// }); + +// c.bench_function("relative_humidity::general3", |b| { +// b.iter(|| relative_humidity::general3(black_box(300.0), black_box(290.0))) +// }); + +// c.bench_function("relative_humidity::general4", |b| { +// b.iter(|| relative_humidity::general4(black_box(300.0), black_box(290.0), black_box(101325.0))) +// }); + +// c.bench_function("relative_humidity::general5", |b| { +// b.iter(|| relative_humidity::general5(black_box(300.0), black_box(290.0), black_box(101325.0))) +// }); +// } + +// criterion_group!(benches, relative_humidity_benchmark); +// criterion_main!(benches); diff --git a/benches-aaa/results/0_3_7.txt b/benches/results/0_3_7.txt similarity index 100% rename from benches-aaa/results/0_3_7.txt rename to benches/results/0_3_7.txt diff --git a/benches/specific_humidity.rs b/benches/specific_humidity.rs new file mode 100644 index 0000000..66164f0 --- /dev/null +++ b/benches/specific_humidity.rs @@ -0,0 +1,11 @@ +// use criterion::{Criterion, black_box, criterion_group, criterion_main}; +// use floccus::specific_humidity; + +// pub fn specific_humidity_benchmark(c: &mut Criterion) { +// c.bench_function("specific_humidity::general1", |b| { +// b.iter(|| specific_humidity::general1(black_box(3000.0), black_box(101325.0))) +// }); +// } + +// criterion_group!(benches, specific_humidity_benchmark); +// criterion_main!(benches); \ No newline at end of file diff --git a/benches/vapour_pressure.rs b/benches/vapour_pressure.rs new file mode 100644 index 0000000..85fdbf1 --- /dev/null +++ b/benches/vapour_pressure.rs @@ -0,0 +1,54 @@ +use criterion::Criterion; +use floccus::{formulas::vapour_pressure, Formula1, Formula2}; + +// this is the best way to avoid code duplication I could find +include!("./reference_values.rs"); + +pub fn benchmark(c: &mut Criterion) { + let ref_norm = ReferenceValues::normal(); + let ref_freeze = ReferenceValues::freeze(); + + c.bench_function("vapour_pressure::definition1", |b| { + b.iter(|| vapour_pressure::Definition1::compute(ref_norm.sphu, ref_norm.pres)) + }); + + c.bench_function("vapour_pressure::definition2", |b| { + b.iter(|| vapour_pressure::Definition2::compute(ref_norm.savp, ref_norm.rehu)) + }); + + c.bench_function("vapour_pressure::tetens1", |b| { + b.iter(|| vapour_pressure::Tetens1::compute(ref_norm.dwpt)) + }); + + c.bench_function("vapour_pressure::buck1", |b| { + b.iter(|| vapour_pressure::Buck1::compute(ref_norm.dwpt, ref_norm.pres)) + }); + + c.bench_function("vapour_pressure::buck2", |b| { + b.iter(|| vapour_pressure::Buck2::compute(ref_freeze.dwpt, ref_freeze.pres)) + }); + + c.bench_function("vapour_pressure::buck3", |b| { + b.iter(|| vapour_pressure::Buck3::compute(ref_norm.dwpt, ref_norm.pres)) + }); + + c.bench_function("vapour_pressure::buck4", |b| { + b.iter(|| vapour_pressure::Buck4::compute(ref_freeze.dwpt, ref_freeze.pres)) + }); + + c.bench_function("vapour_pressure::buck3_simplified", |b| { + b.iter(|| vapour_pressure::Buck3Simplified::compute(ref_norm.dwpt)) + }); + + c.bench_function("vapour_pressure::buck4_simplified", |b| { + b.iter(|| vapour_pressure::Buck4Simplified::compute(ref_freeze.dwpt)) + }); + + c.bench_function("vapour_pressure::wexler1", |b| { + b.iter(|| vapour_pressure::Wexler1::compute(ref_norm.dwpt)) + }); + + c.bench_function("vapour_pressure::wexler2", |b| { + b.iter(|| vapour_pressure::Wexler2::compute(ref_freeze.dwpt)) + }); +} diff --git a/benches/vapour_pressure_deficit.rs b/benches/vapour_pressure_deficit.rs new file mode 100644 index 0000000..4aa2141 --- /dev/null +++ b/benches/vapour_pressure_deficit.rs @@ -0,0 +1,19 @@ +// use criterion::{Criterion, black_box, criterion_group, criterion_main}; +// use floccus::vapour_pressure_deficit; + +// pub fn virtual_temperature_benchmark(c: &mut Criterion) { +// c.bench_function("vapour_pressure_deficit::general1", |b| { +// b.iter(|| vapour_pressure_deficit::general1(black_box(3000.0), black_box(3550.0))) +// }); + +// c.bench_function("vapour_pressure_deficit::general2", |b| { +// b.iter(|| vapour_pressure_deficit::general2(black_box(300.0), black_box(290.0), black_box(101325.0))) +// }); + +// c.bench_function("vapour_pressure_deficit::general3", |b| { +// b.iter(|| vapour_pressure_deficit::general3(black_box(300.0), black_box(0.5), black_box(101325.0))) +// }); +// } + +// criterion_group!(benches, virtual_temperature_benchmark); +// criterion_main!(benches); diff --git a/benches/virtual_temperature.rs b/benches/virtual_temperature.rs new file mode 100644 index 0000000..57fb27f --- /dev/null +++ b/benches/virtual_temperature.rs @@ -0,0 +1,19 @@ +// use criterion::{Criterion, black_box, criterion_group, criterion_main}; +// use floccus::virtual_temperature; + +// pub fn virtual_temperature_benchmark(c: &mut Criterion) { +// c.bench_function("virtual_temperature::general1", |b| { +// b.iter(|| virtual_temperature::general1(black_box(300.0), black_box(0.022))) +// }); + +// c.bench_function("virtual_temperature::general2", |b| { +// b.iter(|| virtual_temperature::general2(black_box(300.0), black_box(101325.0), black_box(3550.0))) +// }); + +// c.bench_function("virtual_temperature::general3", |b| { +// b.iter(|| virtual_temperature::general3(black_box(300.0), black_box(0.022))) +// }); +// } + +// criterion_group!(benches, virtual_temperature_benchmark); +// criterion_main!(benches); diff --git a/benches/wet_bulb_potential_temperature.rs b/benches/wet_bulb_potential_temperature.rs new file mode 100644 index 0000000..3359b63 --- /dev/null +++ b/benches/wet_bulb_potential_temperature.rs @@ -0,0 +1,11 @@ +// use criterion::{Criterion, black_box, criterion_group, criterion_main}; +// use floccus::wet_bulb_potential_temperature; + +// pub fn wet_bulb_potential_temperature_benchmark(c: &mut Criterion) { +// c.bench_function("wet_bulb_potential_temperature::davies_jones1", |b| { +// b.iter(|| wet_bulb_potential_temperature::davies_jones1(black_box(300.0))) +// }); +// } + +// criterion_group!(benches, wet_bulb_potential_temperature_benchmark); +// criterion_main!(benches); \ No newline at end of file diff --git a/benches/wet_bulb_temperature.rs b/benches/wet_bulb_temperature.rs new file mode 100644 index 0000000..0ff2ed9 --- /dev/null +++ b/benches/wet_bulb_temperature.rs @@ -0,0 +1,11 @@ +// use criterion::{Criterion, black_box, criterion_group, criterion_main}; +// use floccus::wet_bulb_temperature; + +// pub fn wet_bulb_temperature_benchmark(c: &mut Criterion) { +// c.bench_function("wet_bulb_temperature::stull1", |b| { +// b.iter(|| wet_bulb_temperature::stull1(black_box(300.0), black_box(0.5))) +// }); +// } + +// criterion_group!(benches, wet_bulb_temperature_benchmark); +// criterion_main!(benches); From f82503d188596d46bca3464f988bed7ba2cbaea0 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:04:08 +0100 Subject: [PATCH 067/102] saturation vapour pressure benchmarks --- benches/reference_values.rs | 5 ++- benches/saturation_vapour_pressure.rs | 50 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 benches/saturation_vapour_pressure.rs diff --git a/benches/reference_values.rs b/benches/reference_values.rs index 0050d28..17e883a 100644 --- a/benches/reference_values.rs +++ b/benches/reference_values.rs @@ -1,7 +1,7 @@ use criterion::black_box; use floccus::quantities::{ AtmosphericPressure, DewPointTemperature, DryBulbTemperature, RelativeHumidity, - SaturationVapourPressure, SpecificHumidity, + SaturationVapourPressure, SpecificHumidity, VapourPressure, }; use uom::si::{pressure::pascal, ratio::ratio, thermodynamic_temperature::kelvin}; @@ -44,6 +44,7 @@ pub struct ReferenceValues { pub pres: AtmosphericPressure, pub dwpt: DewPointTemperature, pub sphu: SpecificHumidity, + pub vapr: VapourPressure, pub savp: SaturationVapourPressure, pub rehu: RelativeHumidity, } @@ -55,6 +56,7 @@ impl ReferenceValues { pres: black_box(AtmosphericPressure::new::(PRES_NORM)), dwpt: black_box(DewPointTemperature::new::(DWPT_NORM)), sphu: black_box(SpecificHumidity::new::(SH_NORM)), + vapr: black_box(VapourPressure::new::(VP_NORM)), savp: black_box(SaturationVapourPressure::new::(SVP_NORM)), rehu: black_box(RelativeHumidity::new::(RH_NORM)), } @@ -66,6 +68,7 @@ impl ReferenceValues { pres: black_box(AtmosphericPressure::new::(PRES_FREEZ)), dwpt: black_box(DewPointTemperature::new::(DWPT_FREEZ)), sphu: black_box(SpecificHumidity::new::(SH_FREEZ)), + vapr: black_box(VapourPressure::new::(VP_FREEZ)), savp: black_box(SaturationVapourPressure::new::(SVP_FREEZ)), rehu: black_box(RelativeHumidity::new::(RH_FREEZ)), } diff --git a/benches/saturation_vapour_pressure.rs b/benches/saturation_vapour_pressure.rs new file mode 100644 index 0000000..4532eab --- /dev/null +++ b/benches/saturation_vapour_pressure.rs @@ -0,0 +1,50 @@ +use criterion::Criterion; +use floccus::{formulas::saturation_vapour_pressure, Formula1, Formula2}; + +// this is the best way to avoid code duplication I could find +include!("./reference_values.rs"); + +pub fn benchmark(c: &mut Criterion) { + let ref_norm = ReferenceValues::normal(); + let ref_freeze = ReferenceValues::freeze(); + + c.bench_function("saturation_vapour_pressure::definition1", |b| { + b.iter(|| saturation_vapour_pressure::Definition1::compute(ref_norm.vapr, ref_norm.rehu)) + }); + + c.bench_function("saturation_vapour_pressure::tetens1", |b| { + b.iter(|| saturation_vapour_pressure::Tetens1::compute(ref_norm.temp)) + }); + + c.bench_function("saturation_vapour_pressure::buck1", |b| { + b.iter(|| saturation_vapour_pressure::Buck1::compute(ref_norm.temp, ref_norm.pres)) + }); + + c.bench_function("saturation_vapour_pressure::buck2", |b| { + b.iter(|| saturation_vapour_pressure::Buck2::compute(ref_freeze.temp, ref_freeze.pres)) + }); + + c.bench_function("saturation_vapour_pressure::buck3", |b| { + b.iter(|| saturation_vapour_pressure::Buck3::compute(ref_norm.temp, ref_norm.pres)) + }); + + c.bench_function("saturation_vapour_pressure::buck4", |b| { + b.iter(|| saturation_vapour_pressure::Buck4::compute(ref_freeze.temp, ref_freeze.pres)) + }); + + c.bench_function("saturation_vapour_pressure::buck3_simplified", |b| { + b.iter(|| saturation_vapour_pressure::Buck3Simplified::compute(ref_norm.temp)) + }); + + c.bench_function("saturation_vapour_pressure::buck4_simplified", |b| { + b.iter(|| saturation_vapour_pressure::Buck4Simplified::compute(ref_freeze.temp)) + }); + + c.bench_function("saturation_vapour_pressure::wexler1", |b| { + b.iter(|| saturation_vapour_pressure::Wexler1::compute(ref_norm.temp)) + }); + + c.bench_function("saturation_vapour_pressure::wexler2", |b| { + b.iter(|| saturation_vapour_pressure::Wexler2::compute(ref_freeze.temp)) + }); +} From e163ff08212af71c9908ac300a09a21a31a57245 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:07:18 +0100 Subject: [PATCH 068/102] relative humdiity benches --- benches/floccus_formulas.rs | 9 ++++++++- benches/reference_values.rs | 10 ++++++++-- benches/relative_humidity.rs | 36 +++++++++++++----------------------- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/benches/floccus_formulas.rs b/benches/floccus_formulas.rs index 7604179..5a7018e 100644 --- a/benches/floccus_formulas.rs +++ b/benches/floccus_formulas.rs @@ -1,6 +1,13 @@ use criterion::{criterion_group, criterion_main}; +mod relative_humidity; +mod saturation_vapour_pressure; mod vapour_pressure; -criterion_group!(benches, vapour_pressure::benchmark); +criterion_group!( + benches, + vapour_pressure::benchmark, + saturation_vapour_pressure::benchmark, + relative_humidity::benchmark +); criterion_main!(benches); diff --git a/benches/reference_values.rs b/benches/reference_values.rs index 17e883a..308631a 100644 --- a/benches/reference_values.rs +++ b/benches/reference_values.rs @@ -1,7 +1,7 @@ use criterion::black_box; use floccus::quantities::{ - AtmosphericPressure, DewPointTemperature, DryBulbTemperature, RelativeHumidity, - SaturationVapourPressure, SpecificHumidity, VapourPressure, + AtmosphericPressure, DewPointTemperature, DryBulbTemperature, MixingRatio, RelativeHumidity, + SaturationMixingRatio, SaturationVapourPressure, SpecificHumidity, VapourPressure, }; use uom::si::{pressure::pascal, ratio::ratio, thermodynamic_temperature::kelvin}; @@ -47,6 +47,8 @@ pub struct ReferenceValues { pub vapr: VapourPressure, pub savp: SaturationVapourPressure, pub rehu: RelativeHumidity, + pub mxrt: MixingRatio, + pub smrt: SaturationMixingRatio, } impl ReferenceValues { @@ -59,6 +61,8 @@ impl ReferenceValues { vapr: black_box(VapourPressure::new::(VP_NORM)), savp: black_box(SaturationVapourPressure::new::(SVP_NORM)), rehu: black_box(RelativeHumidity::new::(RH_NORM)), + mxrt: black_box(MixingRatio::new::(MR_NORM)), + smrt: black_box(SaturationMixingRatio::new::(SMR_NROM)), } } @@ -71,6 +75,8 @@ impl ReferenceValues { vapr: black_box(VapourPressure::new::(VP_FREEZ)), savp: black_box(SaturationVapourPressure::new::(SVP_FREEZ)), rehu: black_box(RelativeHumidity::new::(RH_FREEZ)), + mxrt: black_box(MixingRatio::new::(MR_FREEZ)), + smrt: black_box(SaturationMixingRatio::new::(SMR_FREEZ)), } } } diff --git a/benches/relative_humidity.rs b/benches/relative_humidity.rs index a800b4b..5c1b6cb 100644 --- a/benches/relative_humidity.rs +++ b/benches/relative_humidity.rs @@ -1,27 +1,17 @@ -// use criterion::{black_box, criterion_group, criterion_main, Criterion}; -// use floccus::relative_humidity; +use criterion::Criterion; +use floccus::{formulas::relative_humidity, Formula2}; -// pub fn relative_humidity_benchmark(c: &mut Criterion) { -// c.bench_function("relative_humidity::general1", |b| { -// b.iter(|| relative_humidity::general1(black_box(0.01064), black_box(0.01467))) -// }); +// this is the best way to avoid code duplication I could find +include!("./reference_values.rs"); -// c.bench_function("relative_humidity::general2", |b| { -// b.iter(|| relative_humidity::general2(black_box(1706.0), black_box(2339.0))) -// }); +pub fn benchmark(c: &mut Criterion) { + let ref_norm = ReferenceValues::normal(); -// c.bench_function("relative_humidity::general3", |b| { -// b.iter(|| relative_humidity::general3(black_box(300.0), black_box(290.0))) -// }); + c.bench_function("relative_humidity::definition1", |b| { + b.iter(|| relative_humidity::Definition1::compute(ref_norm.mxrt, ref_norm.smrt)) + }); -// c.bench_function("relative_humidity::general4", |b| { -// b.iter(|| relative_humidity::general4(black_box(300.0), black_box(290.0), black_box(101325.0))) -// }); - -// c.bench_function("relative_humidity::general5", |b| { -// b.iter(|| relative_humidity::general5(black_box(300.0), black_box(290.0), black_box(101325.0))) -// }); -// } - -// criterion_group!(benches, relative_humidity_benchmark); -// criterion_main!(benches); + c.bench_function("relative_humidity::definition2", |b| { + b.iter(|| relative_humidity::Definition2::compute(ref_norm.vapr, ref_norm.savp)) + }); +} From 10183bc69876a934b001e1429699706cccc6f99a Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:08:22 +0100 Subject: [PATCH 069/102] spec hum benches --- benches/specific_humidity.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/benches/specific_humidity.rs b/benches/specific_humidity.rs index 66164f0..93cb63f 100644 --- a/benches/specific_humidity.rs +++ b/benches/specific_humidity.rs @@ -1,11 +1,13 @@ -// use criterion::{Criterion, black_box, criterion_group, criterion_main}; -// use floccus::specific_humidity; +use criterion::Criterion; +use floccus::{formulas::specific_humidity, Formula2}; -// pub fn specific_humidity_benchmark(c: &mut Criterion) { -// c.bench_function("specific_humidity::general1", |b| { -// b.iter(|| specific_humidity::general1(black_box(3000.0), black_box(101325.0))) -// }); -// } +// this is the best way to avoid code duplication I could find +include!("./reference_values.rs"); -// criterion_group!(benches, specific_humidity_benchmark); -// criterion_main!(benches); \ No newline at end of file +pub fn benchmark(c: &mut Criterion) { + let ref_norm = ReferenceValues::normal(); + + c.bench_function("specific_humidity::definition1", |b| { + b.iter(|| specific_humidity::Definition1::compute(ref_norm.vapr, ref_norm.pres)) + }); +} From 290bc10a14f3006ec07bbd4c6be8353f00c68d6b Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:09:59 +0100 Subject: [PATCH 070/102] virtual temp benches --- benches/virtual_temperature.rs | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/benches/virtual_temperature.rs b/benches/virtual_temperature.rs index 57fb27f..4eccf13 100644 --- a/benches/virtual_temperature.rs +++ b/benches/virtual_temperature.rs @@ -1,19 +1,23 @@ -// use criterion::{Criterion, black_box, criterion_group, criterion_main}; -// use floccus::virtual_temperature; +use criterion::Criterion; +use floccus::{formulas::virtual_temperature, Formula2, Formula3}; -// pub fn virtual_temperature_benchmark(c: &mut Criterion) { -// c.bench_function("virtual_temperature::general1", |b| { -// b.iter(|| virtual_temperature::general1(black_box(300.0), black_box(0.022))) -// }); +// this is the best way to avoid code duplication I could find +include!("./reference_values.rs"); -// c.bench_function("virtual_temperature::general2", |b| { -// b.iter(|| virtual_temperature::general2(black_box(300.0), black_box(101325.0), black_box(3550.0))) -// }); +pub fn benchmark(c: &mut Criterion) { + let ref_norm = ReferenceValues::normal(); -// c.bench_function("virtual_temperature::general3", |b| { -// b.iter(|| virtual_temperature::general3(black_box(300.0), black_box(0.022))) -// }); -// } + c.bench_function("virtual_temperature::definition1", |b| { + b.iter(|| virtual_temperature::Definition1::compute(ref_norm.temp, ref_norm.mxrt)) + }); -// criterion_group!(benches, virtual_temperature_benchmark); -// criterion_main!(benches); + c.bench_function("virtual_temperature::definition2", |b| { + b.iter(|| { + virtual_temperature::Definition2::compute(ref_norm.temp, ref_norm.pres, ref_norm.vapr) + }) + }); + + c.bench_function("virtual_temperature::definition3", |b| { + b.iter(|| virtual_temperature::Definition3::compute(ref_norm.temp, ref_norm.sphu)) + }); +} From 6058d0364c045a90e1cc952dd73ff8d0fc7e8a50 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:21:05 +0100 Subject: [PATCH 071/102] all other benches --- benches/equivalent_potential_temperature.rs | 60 ++++++++++++-- benches/mixing_ratio.rs | 26 +++--- benches/potential_temperature.rs | 22 +++-- benches/reference_values.rs | 9 +- benches/results/0_3_7.txt | 92 --------------------- benches/vapour_pressure_deficit.rs | 26 +++--- benches/wet_bulb_potential_temperature.rs | 20 +++-- benches/wet_bulb_temperature.rs | 20 +++-- 8 files changed, 114 insertions(+), 161 deletions(-) delete mode 100644 benches/results/0_3_7.txt diff --git a/benches/equivalent_potential_temperature.rs b/benches/equivalent_potential_temperature.rs index 05b4591..1994f20 100644 --- a/benches/equivalent_potential_temperature.rs +++ b/benches/equivalent_potential_temperature.rs @@ -1,11 +1,53 @@ -// use criterion::{Criterion, black_box, criterion_group, criterion_main}; -// use floccus::equivalent_potential_temperature; +use criterion::Criterion; +use floccus::{formulas::equivalent_potential_temperature, Formula4}; -// pub fn equivalent_potential_temperature_benchmark(c: &mut Criterion) { -// c.bench_function("equivalent_potential_temperature::bryan1", |b| { -// b.iter(|| equivalent_potential_temperature::bryan1(black_box(300.0), black_box(101325.0), black_box(3000.0))) -// }); -// } +// this is the best way to avoid code duplication I could find +include!("./reference_values.rs"); -// criterion_group!(benches, equivalent_potential_temperature_benchmark); -// criterion_main!(benches); \ No newline at end of file +pub fn benchmark(c: &mut Criterion) { + let ref_norm = ReferenceValues::normal(); + + c.bench_function("equivalent_potential_temperature::Bolton1", |b| { + b.iter(|| { + equivalent_potential_temperature::Bolton1::compute( + ref_norm.pres, + ref_norm.temp, + ref_norm.dwpt, + ref_norm.vapr, + ) + }) + }); + + c.bench_function("equivalent_potential_temperature::Bolton2", |b| { + b.iter(|| { + equivalent_potential_temperature::Bolton2::compute( + ref_norm.temp, + ref_norm.dwpt, + ref_norm.mxrt, + ref_norm.thet, + ) + }) + }); + + c.bench_function("equivalent_potential_temperature::Bryan1", |b| { + b.iter(|| { + equivalent_potential_temperature::Bryan1::compute( + ref_norm.temp, + ref_norm.mxrt, + ref_norm.rehu, + ref_norm.thet, + ) + }) + }); + + c.bench_function("equivalent_potential_temperature::Paluch1", |b| { + b.iter(|| { + equivalent_potential_temperature::Paluch1::compute( + ref_norm.temp, + ref_norm.pres, + ref_norm.mxrt, + ref_norm.rehu, + ) + }) + }); +} diff --git a/benches/mixing_ratio.rs b/benches/mixing_ratio.rs index ef7fef1..ccc4a09 100644 --- a/benches/mixing_ratio.rs +++ b/benches/mixing_ratio.rs @@ -1,17 +1,13 @@ -// use criterion::{Criterion, black_box, criterion_group, criterion_main}; -// use floccus::mixing_ratio; +use criterion::Criterion; +use floccus::{formulas::mixing_ratio, Formula2}; -// pub fn mixing_ratio_benchmark(c: &mut Criterion) { -// c.bench_function("mixing_ratio::general1", |b| { -// b.iter(|| mixing_ratio::general1(black_box(101325.0), black_box(3500.0))) -// }); -// c.bench_function("mixing_ratio::performance1", |b| { -// b.iter(|| mixing_ratio::performance1(black_box(300.0), black_box(101325.0))) -// }); -// c.bench_function("mixing_ratio::accuracy1", |b| { -// b.iter(|| mixing_ratio::accuracy1(black_box(300.0), black_box(101325.0))) -// }); -// } +// this is the best way to avoid code duplication I could find +include!("./reference_values.rs"); -// criterion_group!(benches, mixing_ratio_benchmark); -// criterion_main!(benches); +pub fn benchmark(c: &mut Criterion) { + let ref_norm = ReferenceValues::normal(); + + c.bench_function("mixing_ratio::definition1", |b| { + b.iter(|| mixing_ratio::Definition1::compute(ref_norm.pres, ref_norm.vapr)) + }); +} diff --git a/benches/potential_temperature.rs b/benches/potential_temperature.rs index 42881a7..ef58ce1 100644 --- a/benches/potential_temperature.rs +++ b/benches/potential_temperature.rs @@ -1,11 +1,15 @@ -// use criterion::{Criterion, black_box, criterion_group, criterion_main}; -// use floccus::potential_temperature; +use criterion::Criterion; +use floccus::{formulas::potential_temperature, Formula3}; -// pub fn potential_temperature_benchmark(c: &mut Criterion) { -// c.bench_function("potential_temperature::davies_jones1", |b| { -// b.iter(|| potential_temperature::davies_jones1(black_box(300.0), black_box(101325.0), black_box(3000.0))) -// }); -// } +// this is the best way to avoid code duplication I could find +include!("./reference_values.rs"); -// criterion_group!(benches, potential_temperature_benchmark); -// criterion_main!(benches); \ No newline at end of file +pub fn benchmark(c: &mut Criterion) { + let ref_norm = ReferenceValues::normal(); + + c.bench_function("potential_temperature::definition1", |b| { + b.iter(|| { + potential_temperature::Definition1::compute(ref_norm.temp, ref_norm.pres, ref_norm.vapr) + }) + }); +} diff --git a/benches/reference_values.rs b/benches/reference_values.rs index 308631a..2c4fae0 100644 --- a/benches/reference_values.rs +++ b/benches/reference_values.rs @@ -1,7 +1,6 @@ use criterion::black_box; use floccus::quantities::{ - AtmosphericPressure, DewPointTemperature, DryBulbTemperature, MixingRatio, RelativeHumidity, - SaturationMixingRatio, SaturationVapourPressure, SpecificHumidity, VapourPressure, + AtmosphericPressure, DewPointTemperature, DryBulbTemperature, EquivalentPotentialTemperature, MixingRatio, PotentialTemperature, RelativeHumidity, SaturationMixingRatio, SaturationVapourPressure, SpecificHumidity, VapourPressure }; use uom::si::{pressure::pascal, ratio::ratio, thermodynamic_temperature::kelvin}; @@ -49,6 +48,8 @@ pub struct ReferenceValues { pub rehu: RelativeHumidity, pub mxrt: MixingRatio, pub smrt: SaturationMixingRatio, + pub thte: EquivalentPotentialTemperature, + pub thet: PotentialTemperature, } impl ReferenceValues { @@ -63,6 +64,8 @@ impl ReferenceValues { rehu: black_box(RelativeHumidity::new::(RH_NORM)), mxrt: black_box(MixingRatio::new::(MR_NORM)), smrt: black_box(SaturationMixingRatio::new::(SMR_NROM)), + thte: black_box(EquivalentPotentialTemperature::new::(THETAE_NORM)), + thet: black_box(PotentialTemperature::new::(THETA_NORM)), } } @@ -77,6 +80,8 @@ impl ReferenceValues { rehu: black_box(RelativeHumidity::new::(RH_FREEZ)), mxrt: black_box(MixingRatio::new::(MR_FREEZ)), smrt: black_box(SaturationMixingRatio::new::(SMR_FREEZ)), + thte: black_box(EquivalentPotentialTemperature::new::(THETAE_FREEZ)), + thet: black_box(PotentialTemperature::new::(THETA_FREEZ)), } } } diff --git a/benches/results/0_3_7.txt b/benches/results/0_3_7.txt deleted file mode 100644 index 525834c..0000000 --- a/benches/results/0_3_7.txt +++ /dev/null @@ -1,92 +0,0 @@ -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/benches/vapour_pressure_deficit.rs b/benches/vapour_pressure_deficit.rs index 4aa2141..209a6e0 100644 --- a/benches/vapour_pressure_deficit.rs +++ b/benches/vapour_pressure_deficit.rs @@ -1,19 +1,13 @@ -// use criterion::{Criterion, black_box, criterion_group, criterion_main}; -// use floccus::vapour_pressure_deficit; +use criterion::Criterion; +use floccus::{formulas::vapour_pressure_deficit, Formula2}; -// pub fn virtual_temperature_benchmark(c: &mut Criterion) { -// c.bench_function("vapour_pressure_deficit::general1", |b| { -// b.iter(|| vapour_pressure_deficit::general1(black_box(3000.0), black_box(3550.0))) -// }); +// this is the best way to avoid code duplication I could find +include!("./reference_values.rs"); -// c.bench_function("vapour_pressure_deficit::general2", |b| { -// b.iter(|| vapour_pressure_deficit::general2(black_box(300.0), black_box(290.0), black_box(101325.0))) -// }); +pub fn benchmark(c: &mut Criterion) { + let ref_norm = ReferenceValues::normal(); -// c.bench_function("vapour_pressure_deficit::general3", |b| { -// b.iter(|| vapour_pressure_deficit::general3(black_box(300.0), black_box(0.5), black_box(101325.0))) -// }); -// } - -// criterion_group!(benches, virtual_temperature_benchmark); -// criterion_main!(benches); + c.bench_function("vapour_pressure_deficit::definition1", |b| { + b.iter(|| vapour_pressure_deficit::Definition1::compute(ref_norm.vapr, ref_norm.savp)) + }); +} diff --git a/benches/wet_bulb_potential_temperature.rs b/benches/wet_bulb_potential_temperature.rs index 3359b63..422680c 100644 --- a/benches/wet_bulb_potential_temperature.rs +++ b/benches/wet_bulb_potential_temperature.rs @@ -1,11 +1,13 @@ -// use criterion::{Criterion, black_box, criterion_group, criterion_main}; -// use floccus::wet_bulb_potential_temperature; +use criterion::Criterion; +use floccus::{formulas::wet_bulb_potential_temperature, Formula1}; -// pub fn wet_bulb_potential_temperature_benchmark(c: &mut Criterion) { -// c.bench_function("wet_bulb_potential_temperature::davies_jones1", |b| { -// b.iter(|| wet_bulb_potential_temperature::davies_jones1(black_box(300.0))) -// }); -// } +// this is the best way to avoid code duplication I could find +include!("./reference_values.rs"); -// criterion_group!(benches, wet_bulb_potential_temperature_benchmark); -// criterion_main!(benches); \ No newline at end of file +pub fn benchmark(c: &mut Criterion) { + let ref_norm = ReferenceValues::normal(); + + c.bench_function("wet_bulb_potential_temperature::DaviesJones1", |b| { + b.iter(|| wet_bulb_potential_temperature::DaviesJones1::compute(ref_norm.thte)) + }); +} diff --git a/benches/wet_bulb_temperature.rs b/benches/wet_bulb_temperature.rs index 0ff2ed9..f521dcf 100644 --- a/benches/wet_bulb_temperature.rs +++ b/benches/wet_bulb_temperature.rs @@ -1,11 +1,13 @@ -// use criterion::{Criterion, black_box, criterion_group, criterion_main}; -// use floccus::wet_bulb_temperature; +use criterion::Criterion; +use floccus::{formulas::wet_bulb_temperature, Formula2}; -// pub fn wet_bulb_temperature_benchmark(c: &mut Criterion) { -// c.bench_function("wet_bulb_temperature::stull1", |b| { -// b.iter(|| wet_bulb_temperature::stull1(black_box(300.0), black_box(0.5))) -// }); -// } +// this is the best way to avoid code duplication I could find +include!("./reference_values.rs"); -// criterion_group!(benches, wet_bulb_temperature_benchmark); -// criterion_main!(benches); +pub fn benchmark(c: &mut Criterion) { + let ref_norm = ReferenceValues::normal(); + + c.bench_function("wet_bulb_temperature::stull1", |b| { + b.iter(|| wet_bulb_temperature::Stull1::compute(ref_norm.temp, ref_norm.rehu)) + }); +} From 561e5ed88763f98a8698feb6bca7117a931454f0 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:22:40 +0100 Subject: [PATCH 072/102] add saturation mixing ratio benches --- benches/saturation_mixing_ratio.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 benches/saturation_mixing_ratio.rs diff --git a/benches/saturation_mixing_ratio.rs b/benches/saturation_mixing_ratio.rs new file mode 100644 index 0000000..31a6a0f --- /dev/null +++ b/benches/saturation_mixing_ratio.rs @@ -0,0 +1,17 @@ +use criterion::Criterion; +use floccus::{formulas::saturation_mixing_ratio, Formula2}; + +// this is the best way to avoid code duplication I could find +include!("./reference_values.rs"); + +pub fn benchmark(c: &mut Criterion) { + let ref_norm = ReferenceValues::normal(); + + c.bench_function("saturation_mixing_ratio::definition1", |b| { + b.iter(|| saturation_mixing_ratio::Definition1::compute(ref_norm.pres, ref_norm.savp)) + }); + + c.bench_function("saturation_mixing_ratio::definition2", |b| { + b.iter(|| saturation_mixing_ratio::Definition2::compute(ref_norm.mxrt, ref_norm.rehu)) + }); +} From a7b28a057c697ebf122472108e222e12569278e7 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:24:19 +0100 Subject: [PATCH 073/102] add all benches to group --- benches/floccus_formulas.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/benches/floccus_formulas.rs b/benches/floccus_formulas.rs index 5a7018e..c619576 100644 --- a/benches/floccus_formulas.rs +++ b/benches/floccus_formulas.rs @@ -1,13 +1,31 @@ use criterion::{criterion_group, criterion_main}; +mod equivalent_potential_temperature; +mod mixing_ratio; +mod potential_temperature; mod relative_humidity; +mod saturation_mixing_ratio; mod saturation_vapour_pressure; +mod specific_humidity; mod vapour_pressure; +mod vapour_pressure_deficit; +mod virtual_temperature; +mod wet_bulb_potential_temperature; +mod wet_bulb_temperature; criterion_group!( benches, vapour_pressure::benchmark, saturation_vapour_pressure::benchmark, - relative_humidity::benchmark + relative_humidity::benchmark, + potential_temperature::benchmark, + equivalent_potential_temperature::benchmark, + wet_bulb_potential_temperature::benchmark, + wet_bulb_temperature::benchmark, + specific_humidity::benchmark, + mixing_ratio::benchmark, + virtual_temperature::benchmark, + saturation_mixing_ratio::benchmark, + vapour_pressure_deficit::benchmark ); criterion_main!(benches); From e95dae33e592a4816f38a23df1bed49062576dd7 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 6 Mar 2024 11:18:34 +0100 Subject: [PATCH 074/102] make same deps more flexible and allow publish --- Cargo.toml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e0e53b2..f943f82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ keywords = ["oceanography", "thermodynamics", "meteorology", "weather"] categories = ["mathematics", "science"] license = "Apache-2.0" exclude = [".github/*"] -publish = false [dependencies] floccus-proc = "0.3.0" @@ -18,8 +17,8 @@ thiserror = "1.0" float-cmp = "0.9" log = { version = "0.4", optional = true } ndarray = { version = "0.15", default-features = false, optional = true } -rayon = { version = "1.8", default-features = false, optional = true } -uom = { version = "0.35", default-features = false, features = [ +rayon = { version = "1", default-features = false, optional = true } +uom = { version = "0", default-features = false, features = [ "si", "std", "autoconvert", @@ -32,8 +31,8 @@ cfg-if = "1.0" doctest = false [dev-dependencies] -criterion = "0.5" -itertools = "0.12" +criterion = "0" +itertools = "0" testing_logger = "0.1" floccus = { path = "." , features = ["double_precision"] } From a236ee4559e49950cd1acfb0cdac7c98a798ceb6 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:29:01 +0100 Subject: [PATCH 075/102] improve benches naming --- benches/equivalent_potential_temperature.rs | 11 +++++---- benches/floccus_formulas.rs | 2 +- benches/mixing_ratio.rs | 5 +++- benches/potential_temperature.rs | 5 +++- benches/relative_humidity.rs | 7 ++++-- benches/saturation_mixing_ratio.rs | 8 +++++-- benches/saturation_vapour_pressure.rs | 24 +++++++++++-------- benches/specific_humidity.rs | 6 ++++- benches/vapour_pressure.rs | 26 ++++++++++++--------- benches/vapour_pressure_deficit.rs | 5 +++- benches/virtual_temperature.rs | 9 ++++--- benches/wet_bulb_potential_temperature.rs | 5 +++- benches/wet_bulb_temperature.rs | 5 +++- 13 files changed, 79 insertions(+), 39 deletions(-) diff --git a/benches/equivalent_potential_temperature.rs b/benches/equivalent_potential_temperature.rs index 1994f20..1c6411e 100644 --- a/benches/equivalent_potential_temperature.rs +++ b/benches/equivalent_potential_temperature.rs @@ -7,7 +7,9 @@ include!("./reference_values.rs"); pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); - c.bench_function("equivalent_potential_temperature::Bolton1", |b| { + let mut group = c.benchmark_group("equivalent_potential_temperature"); + + group.bench_function("equivalent_potential_temperature::Bolton1", |b| { b.iter(|| { equivalent_potential_temperature::Bolton1::compute( ref_norm.pres, @@ -18,7 +20,7 @@ pub fn benchmark(c: &mut Criterion) { }) }); - c.bench_function("equivalent_potential_temperature::Bolton2", |b| { + group.bench_function("equivalent_potential_temperature::Bolton2", |b| { b.iter(|| { equivalent_potential_temperature::Bolton2::compute( ref_norm.temp, @@ -29,7 +31,7 @@ pub fn benchmark(c: &mut Criterion) { }) }); - c.bench_function("equivalent_potential_temperature::Bryan1", |b| { + group.bench_function("equivalent_potential_temperature::Bryan1", |b| { b.iter(|| { equivalent_potential_temperature::Bryan1::compute( ref_norm.temp, @@ -40,7 +42,7 @@ pub fn benchmark(c: &mut Criterion) { }) }); - c.bench_function("equivalent_potential_temperature::Paluch1", |b| { + group.bench_function("equivalent_potential_temperature::Paluch1", |b| { b.iter(|| { equivalent_potential_temperature::Paluch1::compute( ref_norm.temp, @@ -50,4 +52,5 @@ pub fn benchmark(c: &mut Criterion) { ) }) }); + group.finish(); } diff --git a/benches/floccus_formulas.rs b/benches/floccus_formulas.rs index c619576..2531116 100644 --- a/benches/floccus_formulas.rs +++ b/benches/floccus_formulas.rs @@ -15,6 +15,7 @@ mod wet_bulb_temperature; criterion_group!( benches, + saturation_mixing_ratio::benchmark, vapour_pressure::benchmark, saturation_vapour_pressure::benchmark, relative_humidity::benchmark, @@ -25,7 +26,6 @@ criterion_group!( specific_humidity::benchmark, mixing_ratio::benchmark, virtual_temperature::benchmark, - saturation_mixing_ratio::benchmark, vapour_pressure_deficit::benchmark ); criterion_main!(benches); diff --git a/benches/mixing_ratio.rs b/benches/mixing_ratio.rs index ccc4a09..712b9ab 100644 --- a/benches/mixing_ratio.rs +++ b/benches/mixing_ratio.rs @@ -7,7 +7,10 @@ include!("./reference_values.rs"); pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); - c.bench_function("mixing_ratio::definition1", |b| { + let mut group = c.benchmark_group("mixing_ratio"); + + group.bench_function("mixing_ratio::definition1", |b| { b.iter(|| mixing_ratio::Definition1::compute(ref_norm.pres, ref_norm.vapr)) }); + group.finish(); } diff --git a/benches/potential_temperature.rs b/benches/potential_temperature.rs index ef58ce1..7452feb 100644 --- a/benches/potential_temperature.rs +++ b/benches/potential_temperature.rs @@ -7,9 +7,12 @@ include!("./reference_values.rs"); pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); - c.bench_function("potential_temperature::definition1", |b| { + let mut group = c.benchmark_group("potential_temperature"); + + group.bench_function("potential_temperature::definition1", |b| { b.iter(|| { potential_temperature::Definition1::compute(ref_norm.temp, ref_norm.pres, ref_norm.vapr) }) }); + group.finish(); } diff --git a/benches/relative_humidity.rs b/benches/relative_humidity.rs index 5c1b6cb..afe79cc 100644 --- a/benches/relative_humidity.rs +++ b/benches/relative_humidity.rs @@ -7,11 +7,14 @@ include!("./reference_values.rs"); pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); - c.bench_function("relative_humidity::definition1", |b| { + let mut group = c.benchmark_group("relative_humidity"); + + group.bench_function("relative_humidity::definition1", |b| { b.iter(|| relative_humidity::Definition1::compute(ref_norm.mxrt, ref_norm.smrt)) }); - c.bench_function("relative_humidity::definition2", |b| { + group.bench_function("relative_humidity::definition2", |b| { b.iter(|| relative_humidity::Definition2::compute(ref_norm.vapr, ref_norm.savp)) }); + group.finish(); } diff --git a/benches/saturation_mixing_ratio.rs b/benches/saturation_mixing_ratio.rs index 31a6a0f..ae77275 100644 --- a/benches/saturation_mixing_ratio.rs +++ b/benches/saturation_mixing_ratio.rs @@ -7,11 +7,15 @@ include!("./reference_values.rs"); pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); - c.bench_function("saturation_mixing_ratio::definition1", |b| { + let mut group = c.benchmark_group("saturation_mixing_ratio"); + + group.bench_function("definition1", |b| { b.iter(|| saturation_mixing_ratio::Definition1::compute(ref_norm.pres, ref_norm.savp)) }); - c.bench_function("saturation_mixing_ratio::definition2", |b| { + group.bench_function("definition2", |b| { b.iter(|| saturation_mixing_ratio::Definition2::compute(ref_norm.mxrt, ref_norm.rehu)) }); + + group.finish(); } diff --git a/benches/saturation_vapour_pressure.rs b/benches/saturation_vapour_pressure.rs index 4532eab..8279af8 100644 --- a/benches/saturation_vapour_pressure.rs +++ b/benches/saturation_vapour_pressure.rs @@ -8,43 +8,47 @@ pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); let ref_freeze = ReferenceValues::freeze(); - c.bench_function("saturation_vapour_pressure::definition1", |b| { + let mut group = c.benchmark_group("saturation_vapour_pressure"); + + group.bench_function("definition1", |b| { b.iter(|| saturation_vapour_pressure::Definition1::compute(ref_norm.vapr, ref_norm.rehu)) }); - c.bench_function("saturation_vapour_pressure::tetens1", |b| { + group.bench_function("tetens1", |b| { b.iter(|| saturation_vapour_pressure::Tetens1::compute(ref_norm.temp)) }); - c.bench_function("saturation_vapour_pressure::buck1", |b| { + group.bench_function("buck1", |b| { b.iter(|| saturation_vapour_pressure::Buck1::compute(ref_norm.temp, ref_norm.pres)) }); - c.bench_function("saturation_vapour_pressure::buck2", |b| { + group.bench_function("buck2", |b| { b.iter(|| saturation_vapour_pressure::Buck2::compute(ref_freeze.temp, ref_freeze.pres)) }); - c.bench_function("saturation_vapour_pressure::buck3", |b| { + group.bench_function("buck3", |b| { b.iter(|| saturation_vapour_pressure::Buck3::compute(ref_norm.temp, ref_norm.pres)) }); - c.bench_function("saturation_vapour_pressure::buck4", |b| { + group.bench_function("buck4", |b| { b.iter(|| saturation_vapour_pressure::Buck4::compute(ref_freeze.temp, ref_freeze.pres)) }); - c.bench_function("saturation_vapour_pressure::buck3_simplified", |b| { + group.bench_function("buck3_simplified", |b| { b.iter(|| saturation_vapour_pressure::Buck3Simplified::compute(ref_norm.temp)) }); - c.bench_function("saturation_vapour_pressure::buck4_simplified", |b| { + group.bench_function("buck4_simplified", |b| { b.iter(|| saturation_vapour_pressure::Buck4Simplified::compute(ref_freeze.temp)) }); - c.bench_function("saturation_vapour_pressure::wexler1", |b| { + group.bench_function("wexler1", |b| { b.iter(|| saturation_vapour_pressure::Wexler1::compute(ref_norm.temp)) }); - c.bench_function("saturation_vapour_pressure::wexler2", |b| { + group.bench_function("wexler2", |b| { b.iter(|| saturation_vapour_pressure::Wexler2::compute(ref_freeze.temp)) }); + + group.finish(); } diff --git a/benches/specific_humidity.rs b/benches/specific_humidity.rs index 93cb63f..177915c 100644 --- a/benches/specific_humidity.rs +++ b/benches/specific_humidity.rs @@ -7,7 +7,11 @@ include!("./reference_values.rs"); pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); - c.bench_function("specific_humidity::definition1", |b| { + let mut group = c.benchmark_group("specific_humidity"); + + group.bench_function("definition1", |b| { b.iter(|| specific_humidity::Definition1::compute(ref_norm.vapr, ref_norm.pres)) }); + + group.finish(); } diff --git a/benches/vapour_pressure.rs b/benches/vapour_pressure.rs index 85fdbf1..f86496d 100644 --- a/benches/vapour_pressure.rs +++ b/benches/vapour_pressure.rs @@ -8,47 +8,51 @@ pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); let ref_freeze = ReferenceValues::freeze(); - c.bench_function("vapour_pressure::definition1", |b| { + let mut group = c.benchmark_group("vapour_pressure"); + + + group.bench_function("vapour_pressure::definition1", |b| { b.iter(|| vapour_pressure::Definition1::compute(ref_norm.sphu, ref_norm.pres)) }); - c.bench_function("vapour_pressure::definition2", |b| { + group.bench_function("vapour_pressure::definition2", |b| { b.iter(|| vapour_pressure::Definition2::compute(ref_norm.savp, ref_norm.rehu)) }); - c.bench_function("vapour_pressure::tetens1", |b| { + group.bench_function("vapour_pressure::tetens1", |b| { b.iter(|| vapour_pressure::Tetens1::compute(ref_norm.dwpt)) }); - c.bench_function("vapour_pressure::buck1", |b| { + group.bench_function("vapour_pressure::buck1", |b| { b.iter(|| vapour_pressure::Buck1::compute(ref_norm.dwpt, ref_norm.pres)) }); - c.bench_function("vapour_pressure::buck2", |b| { + group.bench_function("vapour_pressure::buck2", |b| { b.iter(|| vapour_pressure::Buck2::compute(ref_freeze.dwpt, ref_freeze.pres)) }); - c.bench_function("vapour_pressure::buck3", |b| { + group.bench_function("vapour_pressure::buck3", |b| { b.iter(|| vapour_pressure::Buck3::compute(ref_norm.dwpt, ref_norm.pres)) }); - c.bench_function("vapour_pressure::buck4", |b| { + group.bench_function("vapour_pressure::buck4", |b| { b.iter(|| vapour_pressure::Buck4::compute(ref_freeze.dwpt, ref_freeze.pres)) }); - c.bench_function("vapour_pressure::buck3_simplified", |b| { + group.bench_function("vapour_pressure::buck3_simplified", |b| { b.iter(|| vapour_pressure::Buck3Simplified::compute(ref_norm.dwpt)) }); - c.bench_function("vapour_pressure::buck4_simplified", |b| { + group.bench_function("vapour_pressure::buck4_simplified", |b| { b.iter(|| vapour_pressure::Buck4Simplified::compute(ref_freeze.dwpt)) }); - c.bench_function("vapour_pressure::wexler1", |b| { + group.bench_function("vapour_pressure::wexler1", |b| { b.iter(|| vapour_pressure::Wexler1::compute(ref_norm.dwpt)) }); - c.bench_function("vapour_pressure::wexler2", |b| { + group.bench_function("vapour_pressure::wexler2", |b| { b.iter(|| vapour_pressure::Wexler2::compute(ref_freeze.dwpt)) }); + group.finish(); } diff --git a/benches/vapour_pressure_deficit.rs b/benches/vapour_pressure_deficit.rs index 209a6e0..99e3abc 100644 --- a/benches/vapour_pressure_deficit.rs +++ b/benches/vapour_pressure_deficit.rs @@ -7,7 +7,10 @@ include!("./reference_values.rs"); pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); - c.bench_function("vapour_pressure_deficit::definition1", |b| { + let mut group = c.benchmark_group("vapour_pressure_deficit"); + + group.bench_function("vapour_pressure_deficit::definition1", |b| { b.iter(|| vapour_pressure_deficit::Definition1::compute(ref_norm.vapr, ref_norm.savp)) }); + group.finish(); } diff --git a/benches/virtual_temperature.rs b/benches/virtual_temperature.rs index 4eccf13..4a9f83e 100644 --- a/benches/virtual_temperature.rs +++ b/benches/virtual_temperature.rs @@ -7,17 +7,20 @@ include!("./reference_values.rs"); pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); - c.bench_function("virtual_temperature::definition1", |b| { + let mut group = c.benchmark_group("virtual_temperature"); + + group.bench_function("virtual_temperature::definition1", |b| { b.iter(|| virtual_temperature::Definition1::compute(ref_norm.temp, ref_norm.mxrt)) }); - c.bench_function("virtual_temperature::definition2", |b| { + group.bench_function("virtual_temperature::definition2", |b| { b.iter(|| { virtual_temperature::Definition2::compute(ref_norm.temp, ref_norm.pres, ref_norm.vapr) }) }); - c.bench_function("virtual_temperature::definition3", |b| { + group.bench_function("virtual_temperature::definition3", |b| { b.iter(|| virtual_temperature::Definition3::compute(ref_norm.temp, ref_norm.sphu)) }); + group.finish(); } diff --git a/benches/wet_bulb_potential_temperature.rs b/benches/wet_bulb_potential_temperature.rs index 422680c..78cf2da 100644 --- a/benches/wet_bulb_potential_temperature.rs +++ b/benches/wet_bulb_potential_temperature.rs @@ -7,7 +7,10 @@ include!("./reference_values.rs"); pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); - c.bench_function("wet_bulb_potential_temperature::DaviesJones1", |b| { + let mut group = c.benchmark_group("wet_bulb_potential_temperature"); + + group.bench_function("wet_bulb_potential_temperature::DaviesJones1", |b| { b.iter(|| wet_bulb_potential_temperature::DaviesJones1::compute(ref_norm.thte)) }); + group.finish(); } diff --git a/benches/wet_bulb_temperature.rs b/benches/wet_bulb_temperature.rs index f521dcf..8a2b030 100644 --- a/benches/wet_bulb_temperature.rs +++ b/benches/wet_bulb_temperature.rs @@ -7,7 +7,10 @@ include!("./reference_values.rs"); pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); - c.bench_function("wet_bulb_temperature::stull1", |b| { + let mut group = c.benchmark_group("wet_bulb_temperature"); + + group.bench_function("wet_bulb_temperature::stull1", |b| { b.iter(|| wet_bulb_temperature::Stull1::compute(ref_norm.temp, ref_norm.rehu)) }); + group.finish(); } From 558381b223c63d682f409d08f5e57d51e8670ba8 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:36:32 +0100 Subject: [PATCH 076/102] fix bench warnings --- benches/equivalent_potential_temperature.rs | 1 + benches/floccus_formulas.rs | 2 +- benches/mixing_ratio.rs | 1 + benches/potential_temperature.rs | 1 + benches/reference_values.rs | 8 -------- benches/relative_humidity.rs | 2 ++ benches/saturation_mixing_ratio.rs | 1 + benches/saturation_vapour_pressure.rs | 1 + benches/specific_humidity.rs | 1 + benches/vapour_pressure.rs | 1 + benches/vapour_pressure_deficit.rs | 1 + benches/virtual_temperature.rs | 1 + benches/wet_bulb_potential_temperature.rs | 1 + benches/wet_bulb_temperature.rs | 1 + 14 files changed, 14 insertions(+), 9 deletions(-) diff --git a/benches/equivalent_potential_temperature.rs b/benches/equivalent_potential_temperature.rs index 1c6411e..9b848b9 100644 --- a/benches/equivalent_potential_temperature.rs +++ b/benches/equivalent_potential_temperature.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use criterion::Criterion; use floccus::{formulas::equivalent_potential_temperature, Formula4}; diff --git a/benches/floccus_formulas.rs b/benches/floccus_formulas.rs index 2531116..3f33be1 100644 --- a/benches/floccus_formulas.rs +++ b/benches/floccus_formulas.rs @@ -15,7 +15,6 @@ mod wet_bulb_temperature; criterion_group!( benches, - saturation_mixing_ratio::benchmark, vapour_pressure::benchmark, saturation_vapour_pressure::benchmark, relative_humidity::benchmark, @@ -25,6 +24,7 @@ criterion_group!( wet_bulb_temperature::benchmark, specific_humidity::benchmark, mixing_ratio::benchmark, + saturation_mixing_ratio::benchmark, virtual_temperature::benchmark, vapour_pressure_deficit::benchmark ); diff --git a/benches/mixing_ratio.rs b/benches/mixing_ratio.rs index 712b9ab..60edb60 100644 --- a/benches/mixing_ratio.rs +++ b/benches/mixing_ratio.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use criterion::Criterion; use floccus::{formulas::mixing_ratio, Formula2}; diff --git a/benches/potential_temperature.rs b/benches/potential_temperature.rs index 7452feb..37fa6f1 100644 --- a/benches/potential_temperature.rs +++ b/benches/potential_temperature.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use criterion::Criterion; use floccus::{formulas::potential_temperature, Formula3}; diff --git a/benches/reference_values.rs b/benches/reference_values.rs index 2c4fae0..e106208 100644 --- a/benches/reference_values.rs +++ b/benches/reference_values.rs @@ -14,13 +14,9 @@ pub(crate) const SVP_NORM: Float = 3535.4235919263083; pub(crate) const RH_NORM: Float = 0.5429124052171476; pub(crate) const MR_NORM: Float = 0.012172079452423202; pub(crate) const SMR_NROM: Float = 0.022419969290542845; -pub(crate) const VPD_NORM: Float = 1615.998266172149; pub(crate) const SH_NORM: Float = 0.012025701656390478; pub(crate) const THETAE_NORM: Float = 331.329289539998; pub(crate) const THETA_NORM: Float = 301.66581400702955; -pub(crate) const THETAW_NORM: Float = 292.0717306393948; -pub(crate) const WBT_NORM: Float = 293.42728654340516; -pub(crate) const VRT_NORM: Float = 302.1926517941886; pub(crate) const TEMP_FREEZ: Float = 260.0; pub(crate) const DWPT_FREEZ: Float = 255.0; @@ -30,13 +26,9 @@ pub(crate) const SVP_FREEZ: Float = 195.84980045970696; pub(crate) const RH_FREEZ: Float = 0.6289481868911442; pub(crate) const MR_FREEZ: Float = 0.0007670962389744638; pub(crate) const SMR_FREEZ: Float = 0.0012196493367222787; -pub(crate) const VPD_FREEZ: Float = 72.67042355758188; pub(crate) const SH_FREEZ: Float = 0.000766508253376156; pub(crate) const THETAE_FREEZ: Float = 261.95287841149707; pub(crate) const THETA_FREEZ: Float = 260.0915766593588; -pub(crate) const THETAW_FREEZ: Float = 258.6611332391296; -pub(crate) const WBT_FREEZ: Float = 258.40501060754224; -pub(crate) const VRT_FREEZ: Float = 260.12112343315795; pub struct ReferenceValues { pub temp: DryBulbTemperature, diff --git a/benches/relative_humidity.rs b/benches/relative_humidity.rs index afe79cc..165d6f4 100644 --- a/benches/relative_humidity.rs +++ b/benches/relative_humidity.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use criterion::Criterion; use floccus::{formulas::relative_humidity, Formula2}; diff --git a/benches/saturation_mixing_ratio.rs b/benches/saturation_mixing_ratio.rs index ae77275..14cee60 100644 --- a/benches/saturation_mixing_ratio.rs +++ b/benches/saturation_mixing_ratio.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use criterion::Criterion; use floccus::{formulas::saturation_mixing_ratio, Formula2}; diff --git a/benches/saturation_vapour_pressure.rs b/benches/saturation_vapour_pressure.rs index 8279af8..b63a2d6 100644 --- a/benches/saturation_vapour_pressure.rs +++ b/benches/saturation_vapour_pressure.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use criterion::Criterion; use floccus::{formulas::saturation_vapour_pressure, Formula1, Formula2}; diff --git a/benches/specific_humidity.rs b/benches/specific_humidity.rs index 177915c..0671d6c 100644 --- a/benches/specific_humidity.rs +++ b/benches/specific_humidity.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use criterion::Criterion; use floccus::{formulas::specific_humidity, Formula2}; diff --git a/benches/vapour_pressure.rs b/benches/vapour_pressure.rs index f86496d..4f0d95c 100644 --- a/benches/vapour_pressure.rs +++ b/benches/vapour_pressure.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use criterion::Criterion; use floccus::{formulas::vapour_pressure, Formula1, Formula2}; diff --git a/benches/vapour_pressure_deficit.rs b/benches/vapour_pressure_deficit.rs index 99e3abc..0b42b1e 100644 --- a/benches/vapour_pressure_deficit.rs +++ b/benches/vapour_pressure_deficit.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use criterion::Criterion; use floccus::{formulas::vapour_pressure_deficit, Formula2}; diff --git a/benches/virtual_temperature.rs b/benches/virtual_temperature.rs index 4a9f83e..fdcaed4 100644 --- a/benches/virtual_temperature.rs +++ b/benches/virtual_temperature.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use criterion::Criterion; use floccus::{formulas::virtual_temperature, Formula2, Formula3}; diff --git a/benches/wet_bulb_potential_temperature.rs b/benches/wet_bulb_potential_temperature.rs index 78cf2da..6dca140 100644 --- a/benches/wet_bulb_potential_temperature.rs +++ b/benches/wet_bulb_potential_temperature.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use criterion::Criterion; use floccus::{formulas::wet_bulb_potential_temperature, Formula1}; diff --git a/benches/wet_bulb_temperature.rs b/benches/wet_bulb_temperature.rs index 8a2b030..6e309b4 100644 --- a/benches/wet_bulb_temperature.rs +++ b/benches/wet_bulb_temperature.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use criterion::Criterion; use floccus::{formulas::wet_bulb_temperature, Formula2}; From db1a8654638244fbb470cf36c7124038b6904f84 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:44:15 +0100 Subject: [PATCH 077/102] correct benchmarks names --- benches/equivalent_potential_temperature.rs | 8 ++++---- benches/mixing_ratio.rs | 2 +- benches/potential_temperature.rs | 2 +- benches/relative_humidity.rs | 4 ++-- benches/vapour_pressure.rs | 22 ++++++++++----------- benches/vapour_pressure_deficit.rs | 2 +- benches/virtual_temperature.rs | 6 +++--- benches/wet_bulb_potential_temperature.rs | 2 +- benches/wet_bulb_temperature.rs | 2 +- 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/benches/equivalent_potential_temperature.rs b/benches/equivalent_potential_temperature.rs index 9b848b9..9bfba34 100644 --- a/benches/equivalent_potential_temperature.rs +++ b/benches/equivalent_potential_temperature.rs @@ -10,7 +10,7 @@ pub fn benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("equivalent_potential_temperature"); - group.bench_function("equivalent_potential_temperature::Bolton1", |b| { + group.bench_function("Bolton1", |b| { b.iter(|| { equivalent_potential_temperature::Bolton1::compute( ref_norm.pres, @@ -21,7 +21,7 @@ pub fn benchmark(c: &mut Criterion) { }) }); - group.bench_function("equivalent_potential_temperature::Bolton2", |b| { + group.bench_function("Bolton2", |b| { b.iter(|| { equivalent_potential_temperature::Bolton2::compute( ref_norm.temp, @@ -32,7 +32,7 @@ pub fn benchmark(c: &mut Criterion) { }) }); - group.bench_function("equivalent_potential_temperature::Bryan1", |b| { + group.bench_function("Bryan1", |b| { b.iter(|| { equivalent_potential_temperature::Bryan1::compute( ref_norm.temp, @@ -43,7 +43,7 @@ pub fn benchmark(c: &mut Criterion) { }) }); - group.bench_function("equivalent_potential_temperature::Paluch1", |b| { + group.bench_function("Paluch1", |b| { b.iter(|| { equivalent_potential_temperature::Paluch1::compute( ref_norm.temp, diff --git a/benches/mixing_ratio.rs b/benches/mixing_ratio.rs index 60edb60..0b287c1 100644 --- a/benches/mixing_ratio.rs +++ b/benches/mixing_ratio.rs @@ -10,7 +10,7 @@ pub fn benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("mixing_ratio"); - group.bench_function("mixing_ratio::definition1", |b| { + group.bench_function("definition1", |b| { b.iter(|| mixing_ratio::Definition1::compute(ref_norm.pres, ref_norm.vapr)) }); group.finish(); diff --git a/benches/potential_temperature.rs b/benches/potential_temperature.rs index 37fa6f1..37f128b 100644 --- a/benches/potential_temperature.rs +++ b/benches/potential_temperature.rs @@ -10,7 +10,7 @@ pub fn benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("potential_temperature"); - group.bench_function("potential_temperature::definition1", |b| { + group.bench_function("definition1", |b| { b.iter(|| { potential_temperature::Definition1::compute(ref_norm.temp, ref_norm.pres, ref_norm.vapr) }) diff --git a/benches/relative_humidity.rs b/benches/relative_humidity.rs index 165d6f4..a881ee7 100644 --- a/benches/relative_humidity.rs +++ b/benches/relative_humidity.rs @@ -11,11 +11,11 @@ pub fn benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("relative_humidity"); - group.bench_function("relative_humidity::definition1", |b| { + group.bench_function("definition1", |b| { b.iter(|| relative_humidity::Definition1::compute(ref_norm.mxrt, ref_norm.smrt)) }); - group.bench_function("relative_humidity::definition2", |b| { + group.bench_function("definition2", |b| { b.iter(|| relative_humidity::Definition2::compute(ref_norm.vapr, ref_norm.savp)) }); group.finish(); diff --git a/benches/vapour_pressure.rs b/benches/vapour_pressure.rs index 4f0d95c..6006643 100644 --- a/benches/vapour_pressure.rs +++ b/benches/vapour_pressure.rs @@ -12,47 +12,47 @@ pub fn benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("vapour_pressure"); - group.bench_function("vapour_pressure::definition1", |b| { + group.bench_function("definition1", |b| { b.iter(|| vapour_pressure::Definition1::compute(ref_norm.sphu, ref_norm.pres)) }); - group.bench_function("vapour_pressure::definition2", |b| { + group.bench_function("definition2", |b| { b.iter(|| vapour_pressure::Definition2::compute(ref_norm.savp, ref_norm.rehu)) }); - group.bench_function("vapour_pressure::tetens1", |b| { + group.bench_function("tetens1", |b| { b.iter(|| vapour_pressure::Tetens1::compute(ref_norm.dwpt)) }); - group.bench_function("vapour_pressure::buck1", |b| { + group.bench_function("buck1", |b| { b.iter(|| vapour_pressure::Buck1::compute(ref_norm.dwpt, ref_norm.pres)) }); - group.bench_function("vapour_pressure::buck2", |b| { + group.bench_function("buck2", |b| { b.iter(|| vapour_pressure::Buck2::compute(ref_freeze.dwpt, ref_freeze.pres)) }); - group.bench_function("vapour_pressure::buck3", |b| { + group.bench_function("buck3", |b| { b.iter(|| vapour_pressure::Buck3::compute(ref_norm.dwpt, ref_norm.pres)) }); - group.bench_function("vapour_pressure::buck4", |b| { + group.bench_function("buck4", |b| { b.iter(|| vapour_pressure::Buck4::compute(ref_freeze.dwpt, ref_freeze.pres)) }); - group.bench_function("vapour_pressure::buck3_simplified", |b| { + group.bench_function("buck3_simplified", |b| { b.iter(|| vapour_pressure::Buck3Simplified::compute(ref_norm.dwpt)) }); - group.bench_function("vapour_pressure::buck4_simplified", |b| { + group.bench_function("buck4_simplified", |b| { b.iter(|| vapour_pressure::Buck4Simplified::compute(ref_freeze.dwpt)) }); - group.bench_function("vapour_pressure::wexler1", |b| { + group.bench_function("wexler1", |b| { b.iter(|| vapour_pressure::Wexler1::compute(ref_norm.dwpt)) }); - group.bench_function("vapour_pressure::wexler2", |b| { + group.bench_function("wexler2", |b| { b.iter(|| vapour_pressure::Wexler2::compute(ref_freeze.dwpt)) }); group.finish(); diff --git a/benches/vapour_pressure_deficit.rs b/benches/vapour_pressure_deficit.rs index 0b42b1e..553c12c 100644 --- a/benches/vapour_pressure_deficit.rs +++ b/benches/vapour_pressure_deficit.rs @@ -10,7 +10,7 @@ pub fn benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("vapour_pressure_deficit"); - group.bench_function("vapour_pressure_deficit::definition1", |b| { + group.bench_function("definition1", |b| { b.iter(|| vapour_pressure_deficit::Definition1::compute(ref_norm.vapr, ref_norm.savp)) }); group.finish(); diff --git a/benches/virtual_temperature.rs b/benches/virtual_temperature.rs index fdcaed4..b4a2e84 100644 --- a/benches/virtual_temperature.rs +++ b/benches/virtual_temperature.rs @@ -10,17 +10,17 @@ pub fn benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("virtual_temperature"); - group.bench_function("virtual_temperature::definition1", |b| { + group.bench_function("definition1", |b| { b.iter(|| virtual_temperature::Definition1::compute(ref_norm.temp, ref_norm.mxrt)) }); - group.bench_function("virtual_temperature::definition2", |b| { + group.bench_function("definition2", |b| { b.iter(|| { virtual_temperature::Definition2::compute(ref_norm.temp, ref_norm.pres, ref_norm.vapr) }) }); - group.bench_function("virtual_temperature::definition3", |b| { + group.bench_function("definition3", |b| { b.iter(|| virtual_temperature::Definition3::compute(ref_norm.temp, ref_norm.sphu)) }); group.finish(); diff --git a/benches/wet_bulb_potential_temperature.rs b/benches/wet_bulb_potential_temperature.rs index 6dca140..792ea84 100644 --- a/benches/wet_bulb_potential_temperature.rs +++ b/benches/wet_bulb_potential_temperature.rs @@ -10,7 +10,7 @@ pub fn benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("wet_bulb_potential_temperature"); - group.bench_function("wet_bulb_potential_temperature::DaviesJones1", |b| { + group.bench_function("DaviesJones1", |b| { b.iter(|| wet_bulb_potential_temperature::DaviesJones1::compute(ref_norm.thte)) }); group.finish(); diff --git a/benches/wet_bulb_temperature.rs b/benches/wet_bulb_temperature.rs index 6e309b4..ac370cf 100644 --- a/benches/wet_bulb_temperature.rs +++ b/benches/wet_bulb_temperature.rs @@ -10,7 +10,7 @@ pub fn benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("wet_bulb_temperature"); - group.bench_function("wet_bulb_temperature::stull1", |b| { + group.bench_function("stull1", |b| { b.iter(|| wet_bulb_temperature::Stull1::compute(ref_norm.temp, ref_norm.rehu)) }); group.finish(); From afcb363e10614aa32f7bc65683547c5cb391d8e8 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 28 Mar 2024 13:35:40 +0100 Subject: [PATCH 078/102] better organise benches --- Cargo.toml | 47 ++++++++++++++++++++- benches/equivalent_potential_temperature.rs | 10 +++-- benches/floccus_formulas.rs | 31 -------------- benches/mixing_ratio.rs | 10 +++-- benches/potential_temperature.rs | 10 +++-- benches/relative_humidity.rs | 11 ++--- benches/saturation_mixing_ratio.rs | 10 +++-- benches/saturation_vapour_pressure.rs | 10 +++-- benches/specific_humidity.rs | 10 +++-- benches/{reference_values.rs => utils.rs} | 6 ++- benches/vapour_pressure.rs | 11 ++--- benches/vapour_pressure_deficit.rs | 10 +++-- benches/virtual_temperature.rs | 10 +++-- benches/wet_bulb_potential_temperature.rs | 10 +++-- benches/wet_bulb_temperature.rs | 10 +++-- 15 files changed, 122 insertions(+), 84 deletions(-) delete mode 100644 benches/floccus_formulas.rs rename benches/{reference_values.rs => utils.rs} (94%) diff --git a/Cargo.toml b/Cargo.toml index f943f82..d62328e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ doctest = false criterion = "0" itertools = "0" testing_logger = "0.1" -floccus = { path = "." , features = ["double_precision"] } +floccus = { path = ".", features = ["double_precision"] } [features] default = [] @@ -44,5 +44,48 @@ array = ["ndarray"] parallel = ["array", "ndarray/rayon", "rayon"] [[bench]] -name = "floccus_formulas" +name = "equivalent_potential_temperature" +harness = false + +[[bench]] +name = "mixing_ratio" +harness = false + +[[bench]] +name = "potential_temperature" + +[[bench]] +name = "relative_humidity" +harness = false + +[[bench]] +name = "saturation_mixing_ratio" +harness = false + +[[bench]] +name = "saturation_vapour_pressure" +harness = false + +[[bench]] +name = "specific_humidity" +harness = false + +[[bench]] +name = "vapour_pressure_deficit" +harness = false + +[[bench]] +name = "vapour_pressure" +harness = false + +[[bench]] +name = "virtual_temperature" +harness = false + +[[bench]] +name = "wet_bulb_potential_temperature" +harness = false + +[[bench]] +name = "wet_bulb_temperature" harness = false diff --git a/benches/equivalent_potential_temperature.rs b/benches/equivalent_potential_temperature.rs index 9bfba34..7eb7c34 100644 --- a/benches/equivalent_potential_temperature.rs +++ b/benches/equivalent_potential_temperature.rs @@ -1,9 +1,8 @@ -#![allow(unused)] -use criterion::Criterion; +use criterion::{criterion_group, criterion_main, Criterion}; use floccus::{formulas::equivalent_potential_temperature, Formula4}; -// this is the best way to avoid code duplication I could find -include!("./reference_values.rs"); +mod utils; +use utils::ReferenceValues; pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); @@ -55,3 +54,6 @@ pub fn benchmark(c: &mut Criterion) { }); group.finish(); } + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/benches/floccus_formulas.rs b/benches/floccus_formulas.rs deleted file mode 100644 index 3f33be1..0000000 --- a/benches/floccus_formulas.rs +++ /dev/null @@ -1,31 +0,0 @@ -use criterion::{criterion_group, criterion_main}; - -mod equivalent_potential_temperature; -mod mixing_ratio; -mod potential_temperature; -mod relative_humidity; -mod saturation_mixing_ratio; -mod saturation_vapour_pressure; -mod specific_humidity; -mod vapour_pressure; -mod vapour_pressure_deficit; -mod virtual_temperature; -mod wet_bulb_potential_temperature; -mod wet_bulb_temperature; - -criterion_group!( - benches, - vapour_pressure::benchmark, - saturation_vapour_pressure::benchmark, - relative_humidity::benchmark, - potential_temperature::benchmark, - equivalent_potential_temperature::benchmark, - wet_bulb_potential_temperature::benchmark, - wet_bulb_temperature::benchmark, - specific_humidity::benchmark, - mixing_ratio::benchmark, - saturation_mixing_ratio::benchmark, - virtual_temperature::benchmark, - vapour_pressure_deficit::benchmark -); -criterion_main!(benches); diff --git a/benches/mixing_ratio.rs b/benches/mixing_ratio.rs index 0b287c1..724dfb9 100644 --- a/benches/mixing_ratio.rs +++ b/benches/mixing_ratio.rs @@ -1,9 +1,8 @@ -#![allow(unused)] -use criterion::Criterion; +use criterion::{criterion_group, criterion_main, Criterion}; use floccus::{formulas::mixing_ratio, Formula2}; -// this is the best way to avoid code duplication I could find -include!("./reference_values.rs"); +mod utils; +use utils::ReferenceValues; pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); @@ -15,3 +14,6 @@ pub fn benchmark(c: &mut Criterion) { }); group.finish(); } + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/benches/potential_temperature.rs b/benches/potential_temperature.rs index 37f128b..1e76338 100644 --- a/benches/potential_temperature.rs +++ b/benches/potential_temperature.rs @@ -1,9 +1,8 @@ -#![allow(unused)] -use criterion::Criterion; +use criterion::{criterion_group, criterion_main, Criterion}; use floccus::{formulas::potential_temperature, Formula3}; -// this is the best way to avoid code duplication I could find -include!("./reference_values.rs"); +mod utils; +use utils::ReferenceValues; pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); @@ -17,3 +16,6 @@ pub fn benchmark(c: &mut Criterion) { }); group.finish(); } + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/benches/relative_humidity.rs b/benches/relative_humidity.rs index a881ee7..7df9c0b 100644 --- a/benches/relative_humidity.rs +++ b/benches/relative_humidity.rs @@ -1,10 +1,8 @@ -#![allow(unused)] - -use criterion::Criterion; +use criterion::{criterion_group, criterion_main, Criterion}; use floccus::{formulas::relative_humidity, Formula2}; -// this is the best way to avoid code duplication I could find -include!("./reference_values.rs"); +mod utils; +use utils::ReferenceValues; pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); @@ -20,3 +18,6 @@ pub fn benchmark(c: &mut Criterion) { }); group.finish(); } + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/benches/saturation_mixing_ratio.rs b/benches/saturation_mixing_ratio.rs index 14cee60..a9b69ce 100644 --- a/benches/saturation_mixing_ratio.rs +++ b/benches/saturation_mixing_ratio.rs @@ -1,9 +1,8 @@ -#![allow(unused)] -use criterion::Criterion; +use criterion::{criterion_group, criterion_main, Criterion}; use floccus::{formulas::saturation_mixing_ratio, Formula2}; -// this is the best way to avoid code duplication I could find -include!("./reference_values.rs"); +mod utils; +use utils::ReferenceValues; pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); @@ -20,3 +19,6 @@ pub fn benchmark(c: &mut Criterion) { group.finish(); } + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/benches/saturation_vapour_pressure.rs b/benches/saturation_vapour_pressure.rs index b63a2d6..c04eaa8 100644 --- a/benches/saturation_vapour_pressure.rs +++ b/benches/saturation_vapour_pressure.rs @@ -1,9 +1,8 @@ -#![allow(unused)] -use criterion::Criterion; +use criterion::{criterion_group, criterion_main, Criterion}; use floccus::{formulas::saturation_vapour_pressure, Formula1, Formula2}; -// this is the best way to avoid code duplication I could find -include!("./reference_values.rs"); +mod utils; +use utils::ReferenceValues; pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); @@ -53,3 +52,6 @@ pub fn benchmark(c: &mut Criterion) { group.finish(); } + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/benches/specific_humidity.rs b/benches/specific_humidity.rs index 0671d6c..2c4da88 100644 --- a/benches/specific_humidity.rs +++ b/benches/specific_humidity.rs @@ -1,9 +1,8 @@ -#![allow(unused)] -use criterion::Criterion; +use criterion::{criterion_group, criterion_main, Criterion}; use floccus::{formulas::specific_humidity, Formula2}; -// this is the best way to avoid code duplication I could find -include!("./reference_values.rs"); +mod utils; +use utils::ReferenceValues; pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); @@ -16,3 +15,6 @@ pub fn benchmark(c: &mut Criterion) { group.finish(); } + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/benches/reference_values.rs b/benches/utils.rs similarity index 94% rename from benches/reference_values.rs rename to benches/utils.rs index e106208..12bb106 100644 --- a/benches/reference_values.rs +++ b/benches/utils.rs @@ -1,6 +1,10 @@ +#![allow(unused)] + use criterion::black_box; use floccus::quantities::{ - AtmosphericPressure, DewPointTemperature, DryBulbTemperature, EquivalentPotentialTemperature, MixingRatio, PotentialTemperature, RelativeHumidity, SaturationMixingRatio, SaturationVapourPressure, SpecificHumidity, VapourPressure + AtmosphericPressure, DewPointTemperature, DryBulbTemperature, EquivalentPotentialTemperature, + MixingRatio, PotentialTemperature, RelativeHumidity, SaturationMixingRatio, + SaturationVapourPressure, SpecificHumidity, VapourPressure, }; use uom::si::{pressure::pascal, ratio::ratio, thermodynamic_temperature::kelvin}; diff --git a/benches/vapour_pressure.rs b/benches/vapour_pressure.rs index 6006643..8895d24 100644 --- a/benches/vapour_pressure.rs +++ b/benches/vapour_pressure.rs @@ -1,9 +1,8 @@ -#![allow(unused)] -use criterion::Criterion; +use criterion::{criterion_group, criterion_main, Criterion}; use floccus::{formulas::vapour_pressure, Formula1, Formula2}; -// this is the best way to avoid code duplication I could find -include!("./reference_values.rs"); +mod utils; +use utils::ReferenceValues; pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); @@ -11,7 +10,6 @@ pub fn benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("vapour_pressure"); - group.bench_function("definition1", |b| { b.iter(|| vapour_pressure::Definition1::compute(ref_norm.sphu, ref_norm.pres)) }); @@ -57,3 +55,6 @@ pub fn benchmark(c: &mut Criterion) { }); group.finish(); } + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/benches/vapour_pressure_deficit.rs b/benches/vapour_pressure_deficit.rs index 553c12c..f208536 100644 --- a/benches/vapour_pressure_deficit.rs +++ b/benches/vapour_pressure_deficit.rs @@ -1,9 +1,8 @@ -#![allow(unused)] -use criterion::Criterion; +use criterion::{criterion_group, criterion_main, Criterion}; use floccus::{formulas::vapour_pressure_deficit, Formula2}; -// this is the best way to avoid code duplication I could find -include!("./reference_values.rs"); +mod utils; +use utils::ReferenceValues; pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); @@ -15,3 +14,6 @@ pub fn benchmark(c: &mut Criterion) { }); group.finish(); } + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/benches/virtual_temperature.rs b/benches/virtual_temperature.rs index b4a2e84..ce14c04 100644 --- a/benches/virtual_temperature.rs +++ b/benches/virtual_temperature.rs @@ -1,9 +1,8 @@ -#![allow(unused)] -use criterion::Criterion; +use criterion::{criterion_group, criterion_main, Criterion}; use floccus::{formulas::virtual_temperature, Formula2, Formula3}; -// this is the best way to avoid code duplication I could find -include!("./reference_values.rs"); +mod utils; +use utils::ReferenceValues; pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); @@ -25,3 +24,6 @@ pub fn benchmark(c: &mut Criterion) { }); group.finish(); } + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/benches/wet_bulb_potential_temperature.rs b/benches/wet_bulb_potential_temperature.rs index 792ea84..b832c84 100644 --- a/benches/wet_bulb_potential_temperature.rs +++ b/benches/wet_bulb_potential_temperature.rs @@ -1,9 +1,8 @@ -#![allow(unused)] -use criterion::Criterion; +use criterion::{criterion_group, criterion_main, Criterion}; use floccus::{formulas::wet_bulb_potential_temperature, Formula1}; -// this is the best way to avoid code duplication I could find -include!("./reference_values.rs"); +mod utils; +use utils::ReferenceValues; pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); @@ -15,3 +14,6 @@ pub fn benchmark(c: &mut Criterion) { }); group.finish(); } + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/benches/wet_bulb_temperature.rs b/benches/wet_bulb_temperature.rs index ac370cf..5d3da20 100644 --- a/benches/wet_bulb_temperature.rs +++ b/benches/wet_bulb_temperature.rs @@ -1,9 +1,8 @@ -#![allow(unused)] -use criterion::Criterion; +use criterion::{criterion_group, criterion_main, Criterion}; use floccus::{formulas::wet_bulb_temperature, Formula2}; -// this is the best way to avoid code duplication I could find -include!("./reference_values.rs"); +mod utils; +use utils::ReferenceValues; pub fn benchmark(c: &mut Criterion) { let ref_norm = ReferenceValues::normal(); @@ -15,3 +14,6 @@ pub fn benchmark(c: &mut Criterion) { }); group.finish(); } + +criterion_group!(benches, benchmark); +criterion_main!(benches); From 63c66b7c74aa714790e1c1209fb46738ad5407d4 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 28 Mar 2024 13:51:48 +0100 Subject: [PATCH 079/102] add accessers for quantities + refactor --- src/quantities.rs | 228 +-------------------------------- src/quantities/accessors.rs | 143 +++++++++++++++++++++ src/quantities/constructors.rs | 143 +++++++++++++++++++++ src/quantities/trait_impls.rs | 83 ++++++++++++ 4 files changed, 373 insertions(+), 224 deletions(-) create mode 100644 src/quantities/accessors.rs create mode 100644 src/quantities/constructors.rs create mode 100644 src/quantities/trait_impls.rs diff --git a/src/quantities.rs b/src/quantities.rs index c08384b..e5122a5 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -1,7 +1,10 @@ #![allow(missing_docs)] +mod constructors; +mod accessors; +mod trait_impls; + use floccus_proc::Name; -use uom::si::{pressure::pascal, ratio::ratio, thermodynamic_temperature::kelvin}; use crate::{errors::InputError, Float, Storage}; use std::fmt::Debug; @@ -74,226 +77,3 @@ pub struct SpecificHumidity(pub Storage::Ratio); #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct RelativeHumidity(pub Storage::Ratio); - -impl DryBulbTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl WetBulbTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl DewPointTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl VirtualTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl PotentialTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl EquivalentPotentialTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl WetBulbPotentialTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl AtmosphericPressure { - pub fn new(value: Float) -> Self - where - T: uom::si::pressure::Unit + uom::si::pressure::Conversion, - { - Self(Storage::Pressure::new::(value)) - } -} - -impl VapourPressure { - pub fn new(value: Float) -> Self - where - T: uom::si::pressure::Unit + uom::si::pressure::Conversion, - { - Self(Storage::Pressure::new::(value)) - } -} - -impl SaturationVapourPressure { - pub fn new(value: Float) -> Self - where - T: uom::si::pressure::Unit + uom::si::pressure::Conversion, - { - Self(Storage::Pressure::new::(value)) - } -} - -impl VapourPressureDeficit { - pub fn new(value: Float) -> Self - where - T: uom::si::pressure::Unit + uom::si::pressure::Conversion, - { - Self(Storage::Pressure::new::(value)) - } -} - -impl MixingRatio { - pub fn new(value: Float) -> Self - where - T: uom::si::ratio::Unit + uom::si::ratio::Conversion, - { - Self(Storage::Ratio::new::(value)) - } -} - -impl SaturationMixingRatio { - pub fn new(value: Float) -> Self - where - T: uom::si::ratio::Unit + uom::si::ratio::Conversion, - { - Self(Storage::Ratio::new::(value)) - } -} - -impl SpecificHumidity { - pub fn new(value: Float) -> Self - where - T: uom::si::ratio::Unit + uom::si::ratio::Conversion, - { - Self(Storage::Ratio::new::(value)) - } -} - -impl RelativeHumidity { - pub fn new(value: Float) -> Self - where - T: uom::si::ratio::Unit + uom::si::ratio::Conversion, - { - Self(Storage::Ratio::new::(value)) - } -} - -impl ThermodynamicQuantity for DryBulbTemperature { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} -impl ThermodynamicQuantity for WetBulbTemperature { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} -impl ThermodynamicQuantity for DewPointTemperature { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} -impl ThermodynamicQuantity for VirtualTemperature { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} -impl ThermodynamicQuantity for PotentialTemperature { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} -impl ThermodynamicQuantity for EquivalentPotentialTemperature { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} -impl ThermodynamicQuantity for WetBulbPotentialTemperature { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} - -impl ThermodynamicQuantity for AtmosphericPressure { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} -impl ThermodynamicQuantity for VapourPressure { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} -impl ThermodynamicQuantity for SaturationVapourPressure { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} - -impl ThermodynamicQuantity for VapourPressureDeficit { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} - -impl ThermodynamicQuantity for MixingRatio { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} - -impl ThermodynamicQuantity for SaturationMixingRatio { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} - -impl ThermodynamicQuantity for SpecificHumidity { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} -impl ThermodynamicQuantity for RelativeHumidity { - fn get_si_value(&self) -> Float { - self.0.get::() - } -} diff --git a/src/quantities/accessors.rs b/src/quantities/accessors.rs new file mode 100644 index 0000000..92c6d58 --- /dev/null +++ b/src/quantities/accessors.rs @@ -0,0 +1,143 @@ +use super::*; + +impl DryBulbTemperature { + pub fn get(&self) -> Float + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + self.0.get::() + } +} + +impl WetBulbTemperature { + pub fn get(&self) -> Float + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + self.0.get::() + } +} + +impl DewPointTemperature { + pub fn get(&self) -> Float + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + self.0.get::() + } +} + +impl VirtualTemperature { + pub fn get(&self) -> Float + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + self.0.get::() + } +} + +impl PotentialTemperature { + pub fn get(&self) -> Float + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + self.0.get::() + } +} + +impl EquivalentPotentialTemperature { + pub fn get(&self) -> Float + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + self.0.get::() + } +} + +impl WetBulbPotentialTemperature { + pub fn get(&self) -> Float + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + self.0.get::() + } +} + +impl AtmosphericPressure { + pub fn get(&self) -> Float + where + T: uom::si::pressure::Unit + uom::si::pressure::Conversion, + { + self.0.get::() + } +} + +impl VapourPressure { + pub fn get(&self) -> Float + where + T: uom::si::pressure::Unit + uom::si::pressure::Conversion, + { + self.0.get::() + } +} + +impl SaturationVapourPressure { + pub fn get(&self) -> Float + where + T: uom::si::pressure::Unit + uom::si::pressure::Conversion, + { + self.0.get::() + } +} + +impl VapourPressureDeficit { + pub fn get(&self) -> Float + where + T: uom::si::pressure::Unit + uom::si::pressure::Conversion, + { + self.0.get::() + } +} + +impl MixingRatio { + pub fn get(&self) -> Float + where + T: uom::si::ratio::Unit + uom::si::ratio::Conversion, + { + self.0.get::() + } +} + +impl SaturationMixingRatio { + pub fn get(&self) -> Float + where + T: uom::si::ratio::Unit + uom::si::ratio::Conversion, + { + self.0.get::() + } +} + +impl SpecificHumidity { + pub fn get(&self) -> Float + where + T: uom::si::ratio::Unit + uom::si::ratio::Conversion, + { + self.0.get::() + } +} + +impl RelativeHumidity { + pub fn get(&self) -> Float + where + T: uom::si::ratio::Unit + uom::si::ratio::Conversion, + { + self.0.get::() + } +} diff --git a/src/quantities/constructors.rs b/src/quantities/constructors.rs new file mode 100644 index 0000000..806d6b9 --- /dev/null +++ b/src/quantities/constructors.rs @@ -0,0 +1,143 @@ +use super::*; + +impl DryBulbTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl WetBulbTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl DewPointTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl VirtualTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl PotentialTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl EquivalentPotentialTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl WetBulbPotentialTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl AtmosphericPressure { + pub fn new(value: Float) -> Self + where + T: uom::si::pressure::Unit + uom::si::pressure::Conversion, + { + Self(Storage::Pressure::new::(value)) + } +} + +impl VapourPressure { + pub fn new(value: Float) -> Self + where + T: uom::si::pressure::Unit + uom::si::pressure::Conversion, + { + Self(Storage::Pressure::new::(value)) + } +} + +impl SaturationVapourPressure { + pub fn new(value: Float) -> Self + where + T: uom::si::pressure::Unit + uom::si::pressure::Conversion, + { + Self(Storage::Pressure::new::(value)) + } +} + +impl VapourPressureDeficit { + pub fn new(value: Float) -> Self + where + T: uom::si::pressure::Unit + uom::si::pressure::Conversion, + { + Self(Storage::Pressure::new::(value)) + } +} + +impl MixingRatio { + pub fn new(value: Float) -> Self + where + T: uom::si::ratio::Unit + uom::si::ratio::Conversion, + { + Self(Storage::Ratio::new::(value)) + } +} + +impl SaturationMixingRatio { + pub fn new(value: Float) -> Self + where + T: uom::si::ratio::Unit + uom::si::ratio::Conversion, + { + Self(Storage::Ratio::new::(value)) + } +} + +impl SpecificHumidity { + pub fn new(value: Float) -> Self + where + T: uom::si::ratio::Unit + uom::si::ratio::Conversion, + { + Self(Storage::Ratio::new::(value)) + } +} + +impl RelativeHumidity { + pub fn new(value: Float) -> Self + where + T: uom::si::ratio::Unit + uom::si::ratio::Conversion, + { + Self(Storage::Ratio::new::(value)) + } +} diff --git a/src/quantities/trait_impls.rs b/src/quantities/trait_impls.rs new file mode 100644 index 0000000..780d06b --- /dev/null +++ b/src/quantities/trait_impls.rs @@ -0,0 +1,83 @@ +use super::*; +use uom::si::{pressure::pascal, ratio::ratio, thermodynamic_temperature::kelvin}; + +impl ThermodynamicQuantity for DryBulbTemperature { + fn get_si_value(&self) -> Float { + self.get::() + } +} +impl ThermodynamicQuantity for WetBulbTemperature { + fn get_si_value(&self) -> Float { + self.get::() + } +} +impl ThermodynamicQuantity for DewPointTemperature { + fn get_si_value(&self) -> Float { + self.get::() + } +} +impl ThermodynamicQuantity for VirtualTemperature { + fn get_si_value(&self) -> Float { + self.get::() + } +} +impl ThermodynamicQuantity for PotentialTemperature { + fn get_si_value(&self) -> Float { + self.get::() + } +} +impl ThermodynamicQuantity for EquivalentPotentialTemperature { + fn get_si_value(&self) -> Float { + self.get::() + } +} +impl ThermodynamicQuantity for WetBulbPotentialTemperature { + fn get_si_value(&self) -> Float { + self.get::() + } +} + +impl ThermodynamicQuantity for AtmosphericPressure { + fn get_si_value(&self) -> Float { + self.get::() + } +} +impl ThermodynamicQuantity for VapourPressure { + fn get_si_value(&self) -> Float { + self.get::() + } +} +impl ThermodynamicQuantity for SaturationVapourPressure { + fn get_si_value(&self) -> Float { + self.get::() + } +} + +impl ThermodynamicQuantity for VapourPressureDeficit { + fn get_si_value(&self) -> Float { + self.get::() + } +} + +impl ThermodynamicQuantity for MixingRatio { + fn get_si_value(&self) -> Float { + self.get::() + } +} + +impl ThermodynamicQuantity for SaturationMixingRatio { + fn get_si_value(&self) -> Float { + self.get::() + } +} + +impl ThermodynamicQuantity for SpecificHumidity { + fn get_si_value(&self) -> Float { + self.get::() + } +} +impl ThermodynamicQuantity for RelativeHumidity { + fn get_si_value(&self) -> Float { + self.get::() + } +} From a5ca57a2c70ab054aec6ddfc3f11271e9af50d9c Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:16:52 +0100 Subject: [PATCH 080/102] refactor traits for cleaner API --- benches/equivalent_potential_temperature.rs | 2 +- benches/mixing_ratio.rs | 2 +- benches/potential_temperature.rs | 2 +- benches/relative_humidity.rs | 2 +- benches/saturation_mixing_ratio.rs | 2 +- benches/saturation_vapour_pressure.rs | 2 +- benches/specific_humidity.rs | 2 +- benches/vapour_pressure.rs | 2 +- benches/vapour_pressure_deficit.rs | 2 +- benches/virtual_temperature.rs | 2 +- benches/wet_bulb_potential_temperature.rs | 2 +- benches/wet_bulb_temperature.rs | 2 +- src/errors.rs | 8 ++-- src/formulas.rs | 10 ++-- .../equivalent_potential_temperature.rs | 8 ++-- src/formulas/mixing_ratio.rs | 4 +- src/formulas/potential_temperature.rs | 5 +- src/formulas/relative_humidity.rs | 6 +-- src/formulas/saturation_mixing_ratio.rs | 6 +-- src/formulas/saturation_vapour_pressure.rs | 6 +-- src/formulas/specific_humidity.rs | 6 +-- src/{ => formulas}/traits.rs | 0 src/formulas/vapour_pressure.rs | 6 +-- src/formulas/vapour_pressure_deficit.rs | 4 +- src/formulas/virtual_temperature.rs | 4 +- .../wet_bulb_potential_temperature.rs | 4 +- src/formulas/wet_bulb_temperature.rs | 4 +- src/lib.rs | 2 - src/quantities.rs | 13 ++++-- src/quantities/trait_impls.rs | 46 +++++++++++++------ src/tests/four_arg.rs | 2 +- src/tests/one_arg.rs | 2 +- src/tests/testing_traits.rs | 2 +- src/tests/three_arg.rs | 2 +- src/tests/two_arg.rs | 2 +- 35 files changed, 99 insertions(+), 77 deletions(-) rename src/{ => formulas}/traits.rs (100%) diff --git a/benches/equivalent_potential_temperature.rs b/benches/equivalent_potential_temperature.rs index 7eb7c34..b53bd4c 100644 --- a/benches/equivalent_potential_temperature.rs +++ b/benches/equivalent_potential_temperature.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use floccus::{formulas::equivalent_potential_temperature, Formula4}; +use floccus::{formulas::equivalent_potential_temperature, formulas::Formula4}; mod utils; use utils::ReferenceValues; diff --git a/benches/mixing_ratio.rs b/benches/mixing_ratio.rs index 724dfb9..3a6cfbd 100644 --- a/benches/mixing_ratio.rs +++ b/benches/mixing_ratio.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use floccus::{formulas::mixing_ratio, Formula2}; +use floccus::{formulas::mixing_ratio, formulas::Formula2}; mod utils; use utils::ReferenceValues; diff --git a/benches/potential_temperature.rs b/benches/potential_temperature.rs index 1e76338..4b08b91 100644 --- a/benches/potential_temperature.rs +++ b/benches/potential_temperature.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use floccus::{formulas::potential_temperature, Formula3}; +use floccus::{formulas::potential_temperature, formulas::Formula3}; mod utils; use utils::ReferenceValues; diff --git a/benches/relative_humidity.rs b/benches/relative_humidity.rs index 7df9c0b..5cee30a 100644 --- a/benches/relative_humidity.rs +++ b/benches/relative_humidity.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use floccus::{formulas::relative_humidity, Formula2}; +use floccus::{formulas::relative_humidity, formulas::Formula2}; mod utils; use utils::ReferenceValues; diff --git a/benches/saturation_mixing_ratio.rs b/benches/saturation_mixing_ratio.rs index a9b69ce..2eacc58 100644 --- a/benches/saturation_mixing_ratio.rs +++ b/benches/saturation_mixing_ratio.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use floccus::{formulas::saturation_mixing_ratio, Formula2}; +use floccus::{formulas::saturation_mixing_ratio, formulas::Formula2}; mod utils; use utils::ReferenceValues; diff --git a/benches/saturation_vapour_pressure.rs b/benches/saturation_vapour_pressure.rs index c04eaa8..f0c29a6 100644 --- a/benches/saturation_vapour_pressure.rs +++ b/benches/saturation_vapour_pressure.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use floccus::{formulas::saturation_vapour_pressure, Formula1, Formula2}; +use floccus::{formulas::saturation_vapour_pressure, formulas::{Formula1, Formula2}}; mod utils; use utils::ReferenceValues; diff --git a/benches/specific_humidity.rs b/benches/specific_humidity.rs index 2c4da88..78815e7 100644 --- a/benches/specific_humidity.rs +++ b/benches/specific_humidity.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use floccus::{formulas::specific_humidity, Formula2}; +use floccus::{formulas::specific_humidity, formulas::Formula2}; mod utils; use utils::ReferenceValues; diff --git a/benches/vapour_pressure.rs b/benches/vapour_pressure.rs index 8895d24..297913a 100644 --- a/benches/vapour_pressure.rs +++ b/benches/vapour_pressure.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use floccus::{formulas::vapour_pressure, Formula1, Formula2}; +use floccus::{formulas::vapour_pressure, formulas::{Formula1, Formula2}}; mod utils; use utils::ReferenceValues; diff --git a/benches/vapour_pressure_deficit.rs b/benches/vapour_pressure_deficit.rs index f208536..681fbf8 100644 --- a/benches/vapour_pressure_deficit.rs +++ b/benches/vapour_pressure_deficit.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use floccus::{formulas::vapour_pressure_deficit, Formula2}; +use floccus::{formulas::vapour_pressure_deficit, formulas::Formula2}; mod utils; use utils::ReferenceValues; diff --git a/benches/virtual_temperature.rs b/benches/virtual_temperature.rs index ce14c04..01ce84d 100644 --- a/benches/virtual_temperature.rs +++ b/benches/virtual_temperature.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use floccus::{formulas::virtual_temperature, Formula2, Formula3}; +use floccus::{formulas::virtual_temperature, formulas::{Formula2, Formula3}}; mod utils; use utils::ReferenceValues; diff --git a/benches/wet_bulb_potential_temperature.rs b/benches/wet_bulb_potential_temperature.rs index b832c84..38016e3 100644 --- a/benches/wet_bulb_potential_temperature.rs +++ b/benches/wet_bulb_potential_temperature.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use floccus::{formulas::wet_bulb_potential_temperature, Formula1}; +use floccus::{formulas::wet_bulb_potential_temperature, formulas::Formula1}; mod utils; use utils::ReferenceValues; diff --git a/benches/wet_bulb_temperature.rs b/benches/wet_bulb_temperature.rs index 5d3da20..3df907a 100644 --- a/benches/wet_bulb_temperature.rs +++ b/benches/wet_bulb_temperature.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use floccus::{formulas::wet_bulb_temperature, Formula2}; +use floccus::{formulas::wet_bulb_temperature, formulas::Formula2}; mod utils; use utils::ReferenceValues; diff --git a/src/errors.rs b/src/errors.rs index bf9669e..1eecbe4 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -46,8 +46,10 @@ pub enum InputError { /// ///This error should be handled on case-to-case basis, as it can be returned by functions ///for different reasons. Check the documentation of function that you use to learn more - ///about when this error can appear. - #[error("Provided arguments result in erronous output. - Check documentation of the function and change one of arguments. Details: {0}")] + ///about when this error can appear. + #[error( + "Provided arguments result in erronous output. + Check documentation of the function and change one of arguments. Details: {0}" + )] IncorrectArgumentSet(String), } diff --git a/src/formulas.rs b/src/formulas.rs index 68c52ce..f59cb49 100644 --- a/src/formulas.rs +++ b/src/formulas.rs @@ -1,12 +1,16 @@ +pub mod equivalent_potential_temperature; pub mod mixing_ratio; -pub mod saturation_mixing_ratio; pub mod potential_temperature; pub mod relative_humidity; +pub mod saturation_mixing_ratio; +pub mod saturation_vapour_pressure; pub mod specific_humidity; pub mod vapour_pressure; -pub mod saturation_vapour_pressure; pub mod vapour_pressure_deficit; pub mod virtual_temperature; pub mod wet_bulb_potential_temperature; pub mod wet_bulb_temperature; -pub mod equivalent_potential_temperature; \ No newline at end of file + +mod traits; + +pub use traits::{Formula1, Formula2, Formula3, Formula4}; diff --git a/src/formulas/equivalent_potential_temperature.rs b/src/formulas/equivalent_potential_temperature.rs index 5dcd3a3..5d5f1ce 100644 --- a/src/formulas/equivalent_potential_temperature.rs +++ b/src/formulas/equivalent_potential_temperature.rs @@ -15,10 +15,12 @@ use crate::constants::{C_L, C_P, EPSILON, KAPPA, L_V, R_D, R_V}; use crate::errors::InputError; use crate::quantities::{ AtmosphericPressure, DewPointTemperature, DryBulbTemperature, EquivalentPotentialTemperature, - MixingRatio, PotentialTemperature, RelativeHumidity, ThermodynamicQuantity, VapourPressure, + MixingRatio, PotentialTemperature, QuantityHelpers, RelativeHumidity, VapourPressure, +}; +use crate::{ + formulas::{mixing_ratio, Formula2, Formula4}, + Float, }; -use crate::{formulas::mixing_ratio, Float}; -use crate::{Formula2, Formula4}; type FormulaQuantity = EquivalentPotentialTemperature; diff --git a/src/formulas/mixing_ratio.rs b/src/formulas/mixing_ratio.rs index 5830280..3bb620c 100644 --- a/src/formulas/mixing_ratio.rs +++ b/src/formulas/mixing_ratio.rs @@ -3,8 +3,8 @@ //! Mixing ratio is the ratio of the mass of a variable atmospheric constituent to the mass //! of dry air ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Mixing_ratio)). -use crate::Formula2; -use crate::quantities::{AtmosphericPressure, MixingRatio, ThermodynamicQuantity, VapourPressure}; +use crate::formulas::Formula2; +use crate::quantities::{AtmosphericPressure, MixingRatio, QuantityHelpers, VapourPressure}; use crate::Float; use crate::{constants::EPSILON, errors::InputError}; use float_cmp::approx_eq; diff --git a/src/formulas/potential_temperature.rs b/src/formulas/potential_temperature.rs index ec71a6d..6720238 100644 --- a/src/formulas/potential_temperature.rs +++ b/src/formulas/potential_temperature.rs @@ -6,10 +6,9 @@ use crate::constants::KAPPA; use crate::errors::InputError; -use crate::Formula3; +use crate::formulas::Formula3; use crate::quantities::{ - AtmosphericPressure, DryBulbTemperature, PotentialTemperature, ThermodynamicQuantity, - VapourPressure, + AtmosphericPressure, DryBulbTemperature, PotentialTemperature, QuantityHelpers, VapourPressure, }; use crate::Float; use float_cmp::approx_eq; diff --git a/src/formulas/relative_humidity.rs b/src/formulas/relative_humidity.rs index 2d21bf1..182f129 100644 --- a/src/formulas/relative_humidity.rs +++ b/src/formulas/relative_humidity.rs @@ -1,10 +1,10 @@ //! Functions to calculate relative humidity use crate::errors::InputError; -use crate::Formula2; +use crate::formulas::Formula2; use crate::quantities::{ - MixingRatio, RelativeHumidity, SaturationMixingRatio, SaturationVapourPressure, - ThermodynamicQuantity, VapourPressure, + MixingRatio, QuantityHelpers, RelativeHumidity, SaturationMixingRatio, + SaturationVapourPressure, VapourPressure, }; type FormulaQuantity = RelativeHumidity; diff --git a/src/formulas/saturation_mixing_ratio.rs b/src/formulas/saturation_mixing_ratio.rs index 828c255..71aea7e 100644 --- a/src/formulas/saturation_mixing_ratio.rs +++ b/src/formulas/saturation_mixing_ratio.rs @@ -3,10 +3,10 @@ //! Saturation mixing ration is the value of the mixing ratio of saturated air at the //! given temperature and pressure ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Saturation_mixing_ratio)). -use crate::Formula2; +use crate::formulas::Formula2; use crate::quantities::{ - AtmosphericPressure, MixingRatio, RelativeHumidity, SaturationMixingRatio, - SaturationVapourPressure, ThermodynamicQuantity, + AtmosphericPressure, MixingRatio, QuantityHelpers, RelativeHumidity, SaturationMixingRatio, + SaturationVapourPressure, }; use crate::Float; use crate::{constants::EPSILON, errors::InputError}; diff --git a/src/formulas/saturation_vapour_pressure.rs b/src/formulas/saturation_vapour_pressure.rs index 7cfd25a..511af5f 100644 --- a/src/formulas/saturation_vapour_pressure.rs +++ b/src/formulas/saturation_vapour_pressure.rs @@ -6,10 +6,10 @@ //! saturation but not supersaturation ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Saturation_vapor_pressure)). use crate::errors::InputError; -use crate::{Formula1, Formula2}; +use crate::formulas::{Formula1, Formula2}; use crate::quantities::{ - AtmosphericPressure, DryBulbTemperature, RelativeHumidity, SaturationVapourPressure, - ThermodynamicQuantity, VapourPressure, + AtmosphericPressure, DryBulbTemperature, QuantityHelpers, RelativeHumidity, + SaturationVapourPressure, VapourPressure, }; use crate::Float; use crate::Storage::Pressure; diff --git a/src/formulas/specific_humidity.rs b/src/formulas/specific_humidity.rs index 7a1c8c9..589446c 100644 --- a/src/formulas/specific_humidity.rs +++ b/src/formulas/specific_humidity.rs @@ -6,10 +6,8 @@ //! Specific humidity is approximately equal to mixing ratio. use crate::constants::DIMLESS_ONE; -use crate::Formula2; -use crate::quantities::{ - AtmosphericPressure, SpecificHumidity, ThermodynamicQuantity, VapourPressure, -}; +use crate::formulas::Formula2; +use crate::quantities::{AtmosphericPressure, QuantityHelpers, SpecificHumidity, VapourPressure}; use crate::{constants::EPSILON, errors::InputError}; type FormulaQuantity = SpecificHumidity; diff --git a/src/traits.rs b/src/formulas/traits.rs similarity index 100% rename from src/traits.rs rename to src/formulas/traits.rs diff --git a/src/formulas/vapour_pressure.rs b/src/formulas/vapour_pressure.rs index 9bb0afc..b70259a 100644 --- a/src/formulas/vapour_pressure.rs +++ b/src/formulas/vapour_pressure.rs @@ -5,10 +5,10 @@ //! Formulae to calculate partial vapour pressure of the unsaturated air. use crate::constants::DIMLESS_ONE; -use crate::{Formula1, Formula2}; +use crate::formulas::{Formula1, Formula2}; use crate::quantities::{ - AtmosphericPressure, DewPointTemperature, RelativeHumidity, SaturationVapourPressure, - SpecificHumidity, ThermodynamicQuantity, VapourPressure, + AtmosphericPressure, DewPointTemperature, QuantityHelpers, RelativeHumidity, + SaturationVapourPressure, SpecificHumidity, VapourPressure, }; use crate::Float; use crate::Storage::Pressure; diff --git a/src/formulas/vapour_pressure_deficit.rs b/src/formulas/vapour_pressure_deficit.rs index 4daeeae..cc46c87 100644 --- a/src/formulas/vapour_pressure_deficit.rs +++ b/src/formulas/vapour_pressure_deficit.rs @@ -5,9 +5,9 @@ //! when it is saturated ([Wikipedia](https://en.wikipedia.org/wiki/Vapour-pressure_deficit)). use crate::errors::InputError; -use crate::Formula2; +use crate::formulas::Formula2; use crate::quantities::{ - SaturationVapourPressure, ThermodynamicQuantity, VapourPressure, VapourPressureDeficit, + QuantityHelpers, SaturationVapourPressure, VapourPressure, VapourPressureDeficit, }; type FormulaQuantity = VapourPressureDeficit; diff --git a/src/formulas/virtual_temperature.rs b/src/formulas/virtual_temperature.rs index 0dcf2d6..fee78fa 100644 --- a/src/formulas/virtual_temperature.rs +++ b/src/formulas/virtual_temperature.rs @@ -6,9 +6,9 @@ use crate::constants::{DIMLESS_ONE, EPSILON, ZERO_KELVIN}; use crate::errors::InputError; -use crate::{Formula2, Formula3}; +use crate::formulas::{Formula2, Formula3}; use crate::quantities::{ - AtmosphericPressure, DryBulbTemperature, MixingRatio, SpecificHumidity, ThermodynamicQuantity, + AtmosphericPressure, DryBulbTemperature, MixingRatio, QuantityHelpers, SpecificHumidity, VapourPressure, VirtualTemperature, }; diff --git a/src/formulas/wet_bulb_potential_temperature.rs b/src/formulas/wet_bulb_potential_temperature.rs index 708e989..48e4fde 100644 --- a/src/formulas/wet_bulb_potential_temperature.rs +++ b/src/formulas/wet_bulb_potential_temperature.rs @@ -3,9 +3,9 @@ use uom::si::ratio::ratio; use uom::si::thermodynamic_temperature::{degree_celsius, kelvin}; -use crate::Formula1; +use crate::formulas::Formula1; use crate::quantities::{ - EquivalentPotentialTemperature, ThermodynamicQuantity, WetBulbPotentialTemperature, + EquivalentPotentialTemperature, QuantityHelpers, WetBulbPotentialTemperature, }; use crate::Storage; use crate::{ diff --git a/src/formulas/wet_bulb_temperature.rs b/src/formulas/wet_bulb_temperature.rs index d98810c..a051c44 100644 --- a/src/formulas/wet_bulb_temperature.rs +++ b/src/formulas/wet_bulb_temperature.rs @@ -4,10 +4,10 @@ use uom::si::ratio::percent; use uom::si::thermodynamic_temperature::degree_celsius; use crate::errors::InputError; +use crate::formulas::Formula2; use crate::quantities::{ - DryBulbTemperature, RelativeHumidity, ThermodynamicQuantity, WetBulbTemperature, + DryBulbTemperature, QuantityHelpers, RelativeHumidity, WetBulbTemperature, }; -use crate::Formula2; use crate::Storage; type FormulaQuantity = WetBulbTemperature; diff --git a/src/lib.rs b/src/lib.rs index 7401931..24ec52b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,10 +84,8 @@ pub mod constants; mod errors; pub mod formulas; pub mod quantities; -mod traits; pub use errors::InputError; -pub use traits::{Formula1, Formula2, Formula3, Formula4}; #[cfg(test)] mod tests; diff --git a/src/quantities.rs b/src/quantities.rs index e5122a5..84d748f 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -1,7 +1,7 @@ #![allow(missing_docs)] -mod constructors; mod accessors; +mod constructors; mod trait_impls; use floccus_proc::Name; @@ -9,13 +9,16 @@ use floccus_proc::Name; use crate::{errors::InputError, Float, Storage}; use std::fmt::Debug; -pub trait QuantityName { +pub trait ThermodynamicQuantity: + Debug + Clone + Copy + PartialEq + PartialOrd + Default + Send + Sync +{ +} + +pub(crate) trait QuantityName { fn type_name_as_str() -> &'static str; } -pub trait ThermodynamicQuantity: - Debug + Clone + Copy + PartialEq + PartialOrd + Default + Send + Sync + QuantityName -{ +pub(crate) trait QuantityHelpers: QuantityName + ThermodynamicQuantity { fn get_si_value(&self) -> Float; fn name(&self) -> &'static str { diff --git a/src/quantities/trait_impls.rs b/src/quantities/trait_impls.rs index 780d06b..96b5e59 100644 --- a/src/quantities/trait_impls.rs +++ b/src/quantities/trait_impls.rs @@ -1,82 +1,98 @@ use super::*; use uom::si::{pressure::pascal, ratio::ratio, thermodynamic_temperature::kelvin}; -impl ThermodynamicQuantity for DryBulbTemperature { +impl ThermodynamicQuantity for DryBulbTemperature {} +impl ThermodynamicQuantity for WetBulbTemperature {} +impl ThermodynamicQuantity for DewPointTemperature {} +impl ThermodynamicQuantity for VirtualTemperature {} +impl ThermodynamicQuantity for PotentialTemperature {} +impl ThermodynamicQuantity for EquivalentPotentialTemperature {} +impl ThermodynamicQuantity for WetBulbPotentialTemperature {} +impl ThermodynamicQuantity for AtmosphericPressure {} +impl ThermodynamicQuantity for VapourPressure {} +impl ThermodynamicQuantity for SaturationVapourPressure {} +impl ThermodynamicQuantity for VapourPressureDeficit {} +impl ThermodynamicQuantity for MixingRatio {} +impl ThermodynamicQuantity for SaturationMixingRatio {} +impl ThermodynamicQuantity for SpecificHumidity {} +impl ThermodynamicQuantity for RelativeHumidity {} + +impl QuantityHelpers for DryBulbTemperature { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for WetBulbTemperature { +impl QuantityHelpers for WetBulbTemperature { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for DewPointTemperature { +impl QuantityHelpers for DewPointTemperature { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for VirtualTemperature { +impl QuantityHelpers for VirtualTemperature { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for PotentialTemperature { +impl QuantityHelpers for PotentialTemperature { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for EquivalentPotentialTemperature { +impl QuantityHelpers for EquivalentPotentialTemperature { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for WetBulbPotentialTemperature { +impl QuantityHelpers for WetBulbPotentialTemperature { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for AtmosphericPressure { +impl QuantityHelpers for AtmosphericPressure { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for VapourPressure { +impl QuantityHelpers for VapourPressure { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for SaturationVapourPressure { +impl QuantityHelpers for SaturationVapourPressure { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for VapourPressureDeficit { +impl QuantityHelpers for VapourPressureDeficit { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for MixingRatio { +impl QuantityHelpers for MixingRatio { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for SaturationMixingRatio { +impl QuantityHelpers for SaturationMixingRatio { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for SpecificHumidity { +impl QuantityHelpers for SpecificHumidity { fn get_si_value(&self) -> Float { self.get::() } } -impl ThermodynamicQuantity for RelativeHumidity { +impl QuantityHelpers for RelativeHumidity { fn get_si_value(&self) -> Float { self.get::() } diff --git a/src/tests/four_arg.rs b/src/tests/four_arg.rs index 1a8d1fc..523b31a 100644 --- a/src/tests/four_arg.rs +++ b/src/tests/four_arg.rs @@ -7,8 +7,8 @@ use super::check_result; use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; use super::Argument; use crate::errors::InputError; +use crate::formulas::Formula4; use crate::Float; -use crate::Formula4; use std::mem::discriminant; pub fn test_with_4args< diff --git a/src/tests/one_arg.rs b/src/tests/one_arg.rs index c666d46..beffdc6 100644 --- a/src/tests/one_arg.rs +++ b/src/tests/one_arg.rs @@ -6,8 +6,8 @@ use super::check_result; use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; use super::Argument; use crate::errors::InputError; +use crate::formulas::Formula1; use crate::Float; -use crate::Formula1; use std::mem::discriminant; pub fn test_with_1arg>( diff --git a/src/tests/testing_traits.rs b/src/tests/testing_traits.rs index 1786fb6..4606f66 100644 --- a/src/tests/testing_traits.rs +++ b/src/tests/testing_traits.rs @@ -13,7 +13,7 @@ pub(crate) enum ReferenceAtmosphere { Freezing, } -pub(crate) trait TestingQuantity: ThermodynamicQuantity { +pub(crate) trait TestingQuantity: QuantityHelpers { fn new_si(value: Float) -> Self; fn imperial(&self) -> Self; fn ref_val_si(atm: ReferenceAtmosphere) -> Self; diff --git a/src/tests/three_arg.rs b/src/tests/three_arg.rs index b7f1625..fd9240b 100644 --- a/src/tests/three_arg.rs +++ b/src/tests/three_arg.rs @@ -7,8 +7,8 @@ use super::check_result; use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; use super::Argument; use crate::errors::InputError; +use crate::formulas::Formula3; use crate::Float; -use crate::Formula3; use std::mem::discriminant; pub fn test_with_3args< diff --git a/src/tests/two_arg.rs b/src/tests/two_arg.rs index b9f97cf..6c5fc2d 100644 --- a/src/tests/two_arg.rs +++ b/src/tests/two_arg.rs @@ -6,8 +6,8 @@ use super::check_result; use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; use super::Argument; use crate::errors::InputError; +use crate::formulas::Formula2; use crate::Float; -use crate::Formula2; use std::mem::discriminant; pub fn test_with_2args< From 0bf02a5d500498cb61bdf2fdbf6d70280c060bf2 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:19:38 +0100 Subject: [PATCH 081/102] publish new alpha --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d62328e..c65689a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "floccus" -version = "0.4.0-alpha.1" +version = "0.4.0-alpha.2" authors = ["Jakub Lewandowski "] edition = "2021" description = "Formulae for air thermodynamic calculations" From 75b4f504f5081ea9906621d6db8ff4ad4a84de66 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:36:11 +0200 Subject: [PATCH 082/102] remove implicit dependencies --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c65689a..c55e1de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "floccus" -version = "0.4.0-alpha.2" +version = "0.4.0-alpha.3" authors = ["Jakub Lewandowski "] edition = "2021" description = "Formulae for air thermodynamic calculations" @@ -38,10 +38,10 @@ floccus = { path = ".", features = ["double_precision"] } [features] default = [] -debug = ["log"] +debug = ["dep:log"] double_precision = [] -array = ["ndarray"] -parallel = ["array", "ndarray/rayon", "rayon"] +array = ["dep:ndarray"] +parallel = ["array", "ndarray/rayon", "dep:rayon"] [[bench]] name = "equivalent_potential_temperature" From cb0b800dd617580e33a6f99c055b1371919aaf1c Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:37:17 +0200 Subject: [PATCH 083/102] simplify OutOfRange api --- Cargo.toml | 2 +- src/errors.rs | 2 +- src/quantities.rs | 2 +- src/tests.rs | 16 ++++++++++++---- src/tests/four_arg.rs | 21 +++++++++------------ src/tests/one_arg.rs | 6 +++--- src/tests/three_arg.rs | 16 +++++++--------- src/tests/two_arg.rs | 11 +++++------ 8 files changed, 39 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c55e1de..e045e8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "floccus" -version = "0.4.0-alpha.3" +version = "0.4.0-alpha.4" authors = ["Jakub Lewandowski "] edition = "2021" description = "Formulae for air thermodynamic calculations" diff --git a/src/errors.rs b/src/errors.rs index 1eecbe4..b7b0a0e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -28,7 +28,7 @@ pub enum InputError { /// ///If you find that in your use case input ranges are too narrow you should first look for a more relevant formula. ///If such formula does not exist do not hesitate to create an issue in Github repository. - OutOfRange(String), + OutOfRange(&'static str), ///Error returned when provided set of arguments will result in invalid output. ///Contains detailed information about the error. diff --git a/src/quantities.rs b/src/quantities.rs index 84d748f..2aa7001 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -29,7 +29,7 @@ pub(crate) trait QuantityHelpers: QuantityName + ThermodynamicQuantity { #[inline(always)] fn check_range_si(&self, lower_bound: Float, upper_bound: Float) -> Result<(), InputError> { if !(lower_bound..=upper_bound).contains(&self.get_si_value()) { - return Err(InputError::OutOfRange(self.name().to_string())); + return Err(InputError::OutOfRange(self.name())); } Ok(()) diff --git a/src/tests.rs b/src/tests.rs index 4f8a887..80f0fe1 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -5,14 +5,14 @@ pub(crate) mod testing_traits; mod three_arg; mod two_arg; -use crate::Float; +use crate::{Float, InputError}; use float_cmp::assert_approx_eq; use std::marker::PhantomData; +pub use self::four_arg::test_with_4args; pub use self::one_arg::test_with_1arg; pub use self::three_arg::test_with_3args; pub use self::two_arg::test_with_2args; -pub use self::four_arg::test_with_4args; use self::testing_traits::{ReferenceAtmosphere, TestingQuantity}; @@ -30,8 +30,8 @@ impl Argument { } } - pub fn quantity_name(&self) -> String { - I::type_name_as_str().to_string() + pub fn quantity_name(&self) -> &str { + I::type_name_as_str() } pub fn ref_val(&self, atm: ReferenceAtmosphere) -> I { @@ -45,3 +45,11 @@ fn check_result(result: T, atm: ReferenceAtmosphere, eps: Fl assert_approx_eq!(Float, result, expected, epsilon = eps) } + +pub fn check_range_error(result: InputError, expected_name: &str) { + if let InputError::OutOfRange(name) = result { + assert_eq!(name, expected_name) + } else { + panic!("wrong error type") + } +} diff --git a/src/tests/four_arg.rs b/src/tests/four_arg.rs index 523b31a..95629b9 100644 --- a/src/tests/four_arg.rs +++ b/src/tests/four_arg.rs @@ -8,6 +8,7 @@ use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; use super::Argument; use crate::errors::InputError; use crate::formulas::Formula4; +use crate::tests::check_range_error; use crate::Float; use std::mem::discriminant; @@ -143,7 +144,6 @@ pub fn test_with_4args< //the fourth promise of the crate is to return an error with //erronous variable name when input is out of range - let expected = InputError::OutOfRange(arg1.quantity_name()); let result = F::compute( I1::new_si(arg1.range[0] - 0.1), arg2.ref_val(atm), @@ -151,7 +151,7 @@ pub fn test_with_4args< arg4.ref_val(atm), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg1.quantity_name()); let result = F::compute( I1::new_si(arg1.range[1] + 0.1), arg2.ref_val(atm), @@ -159,9 +159,8 @@ pub fn test_with_4args< arg4.ref_val(atm), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg1.quantity_name()); - let expected = InputError::OutOfRange(arg2.quantity_name()); let result = F::compute( arg1.ref_val(atm), I2::new_si(arg2.range[0] - 0.1), @@ -169,7 +168,7 @@ pub fn test_with_4args< arg4.ref_val(atm), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg2.quantity_name()); let result = F::compute( arg1.ref_val(atm), I2::new_si(arg2.range[1] + 0.1), @@ -177,9 +176,8 @@ pub fn test_with_4args< arg4.ref_val(atm), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg2.quantity_name()); - let expected = InputError::OutOfRange(arg3.quantity_name()); let result = F::compute( arg1.ref_val(atm), arg2.ref_val(atm), @@ -187,7 +185,7 @@ pub fn test_with_4args< arg4.ref_val(atm), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg3.quantity_name()); let result = F::compute( arg1.ref_val(atm), arg2.ref_val(atm), @@ -195,9 +193,8 @@ pub fn test_with_4args< arg4.ref_val(atm), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg3.quantity_name()); - let expected = InputError::OutOfRange(arg4.quantity_name()); let result = F::compute( arg1.ref_val(atm), arg2.ref_val(atm), @@ -205,7 +202,7 @@ pub fn test_with_4args< I4::new_si(arg4.range[0] - 0.1), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg4.quantity_name()); let result = F::compute( arg1.ref_val(atm), arg2.ref_val(atm), @@ -213,7 +210,7 @@ pub fn test_with_4args< I4::new_si(arg4.range[1] + 0.1), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg4.quantity_name()); let arg_vecs = (-10..=10).map(|i| i as Float / 1000.0).map(|i| { ( diff --git a/src/tests/one_arg.rs b/src/tests/one_arg.rs index beffdc6..36908a3 100644 --- a/src/tests/one_arg.rs +++ b/src/tests/one_arg.rs @@ -7,6 +7,7 @@ use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; use super::Argument; use crate::errors::InputError; use crate::formulas::Formula1; +use crate::tests::check_range_error; use crate::Float; use std::mem::discriminant; @@ -60,11 +61,10 @@ pub fn test_with_1arg = (-10..=10) .map(|i| i as Float / 1000.0) diff --git a/src/tests/three_arg.rs b/src/tests/three_arg.rs index fd9240b..37cf177 100644 --- a/src/tests/three_arg.rs +++ b/src/tests/three_arg.rs @@ -8,6 +8,7 @@ use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; use super::Argument; use crate::errors::InputError; use crate::formulas::Formula3; +use crate::tests::check_range_error; use crate::Float; use std::mem::discriminant; @@ -89,53 +90,50 @@ pub fn test_with_3args< //the fourth promise of the crate is to return an error with //erronous variable name when input is out of range - let expected = InputError::OutOfRange(arg1.quantity_name()); let result = F::compute( I1::new_si(arg1.range[0] - 0.1), arg2.ref_val(atm), arg3.ref_val(atm), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg1.quantity_name()); let result = F::compute( I1::new_si(arg1.range[1] + 0.1), arg2.ref_val(atm), arg3.ref_val(atm), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg1.quantity_name()); - let expected = InputError::OutOfRange(arg2.quantity_name()); let result = F::compute( arg1.ref_val(atm), I2::new_si(arg2.range[0] - 0.1), arg3.ref_val(atm), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg2.quantity_name()); let result = F::compute( arg1.ref_val(atm), I2::new_si(arg2.range[1] + 0.1), arg3.ref_val(atm), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg2.quantity_name()); - let expected = InputError::OutOfRange(arg3.quantity_name()); let result = F::compute( arg1.ref_val(atm), arg2.ref_val(atm), I3::new_si(arg3.range[0] - 0.1), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg3.quantity_name()); let result = F::compute( arg1.ref_val(atm), arg2.ref_val(atm), I3::new_si(arg3.range[1] + 0.1), ) .unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg3.quantity_name()); let arg_vecs = (-10..=10).map(|i| i as Float / 1000.0).map(|i| { ( diff --git a/src/tests/two_arg.rs b/src/tests/two_arg.rs index 6c5fc2d..5fecb9c 100644 --- a/src/tests/two_arg.rs +++ b/src/tests/two_arg.rs @@ -7,6 +7,7 @@ use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; use super::Argument; use crate::errors::InputError; use crate::formulas::Formula2; +use crate::tests::check_range_error; use crate::Float; use std::mem::discriminant; @@ -69,17 +70,15 @@ pub fn test_with_2args< //the fourth promise of the crate is to return an error with //erronous variable name when input is out of range - let expected = InputError::OutOfRange(arg1.quantity_name()); let result = F::compute(I1::new_si(arg1.range[0] - 0.1), arg2.ref_val(atm)).unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg1.quantity_name()); let result = F::compute(I1::new_si(arg1.range[1] + 0.1), arg2.ref_val(atm)).unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg1.quantity_name()); - let expected = InputError::OutOfRange(arg2.quantity_name()); let result = F::compute(arg1.ref_val(atm), I2::new_si(arg2.range[0] - 0.1)).unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg2.quantity_name()); let result = F::compute(arg1.ref_val(atm), I2::new_si(arg2.range[1] + 0.1)).unwrap_err(); - assert_eq!(result, expected); + check_range_error(result, arg2.quantity_name()); let arg_vecs: (Vec<_>, Vec<_>) = (-10..=10) .map(|i| i as Float / 1000.0) From 4e8b93d6f639aaae10a7775b28b11fd76a9b9875 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:38:15 +0200 Subject: [PATCH 084/102] correct deps versions --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e045e8f..65d04e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,9 @@ floccus-proc = "0.3.0" thiserror = "1.0" float-cmp = "0.9" log = { version = "0.4", optional = true } -ndarray = { version = "0.15", default-features = false, optional = true } -rayon = { version = "1", default-features = false, optional = true } -uom = { version = "0", default-features = false, features = [ +ndarray = { version = "0.16", default-features = false, optional = true } +rayon = { version = "1.10", default-features = false, optional = true } +uom = { version = "0.36", default-features = false, features = [ "si", "std", "autoconvert", @@ -31,8 +31,8 @@ cfg-if = "1.0" doctest = false [dev-dependencies] -criterion = "0" -itertools = "0" +criterion = "0.5" +itertools = "0.13" testing_logger = "0.1" floccus = { path = ".", features = ["double_precision"] } From 86eae17142ae43f7d9e9d1d776d4aff126b43da1 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 17 Aug 2024 16:55:03 +0200 Subject: [PATCH 085/102] relax APIs for computations with ndarray input --- Cargo.toml | 2 +- src/formulas/traits.rs | 172 ++++++++++++++++++++++++++++++++--------- 2 files changed, 138 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 65d04e0..ec176a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "floccus" -version = "0.4.0-alpha.4" +version = "0.4.0-alpha.5" authors = ["Jakub Lewandowski "] edition = "2021" description = "Formulae for air thermodynamic calculations" diff --git a/src/formulas/traits.rs b/src/formulas/traits.rs index 55f74a0..cefe8e2 100644 --- a/src/formulas/traits.rs +++ b/src/formulas/traits.rs @@ -2,7 +2,7 @@ use crate::{errors::InputError, quantities::ThermodynamicQuantity}; #[cfg(feature = "array")] -use ndarray::{Array, Dimension, FoldWhile, Zip}; +use ndarray::{Array, ArrayView, Dimension, FoldWhile, Zip}; #[cfg(feature = "parallel")] use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; @@ -62,7 +62,14 @@ pub trait Formula1 { #[cfg(feature = "array")] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - fn compute_ndarray(i1: &Array) -> Result, InputError> { + fn compute_ndarray<'a, D: Dimension + Copy, A: Into>>( + i1: A, + ) -> Result, InputError> + where + I1: 'a, + { + let i1: ArrayView = i1.into(); + Zip::from(i1) .fold_while(Ok(()), |_, &i1| match Self::validate_inputs(i1) { Ok(_) => FoldWhile::Continue(Ok(())), @@ -83,9 +90,14 @@ pub trait Formula1 { #[cfg(feature = "parallel")] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - fn compute_ndarray_parallel( - i1: &Array, - ) -> Result, InputError> { + fn compute_ndarray_parallel<'a, D: Dimension + Copy, A: Into>>( + i1: A, + ) -> Result, InputError> + where + I1: 'a, + { + let i1: ArrayView = i1.into(); + Zip::from(i1) .fold_while(Ok(()), |_, &a| match Self::validate_inputs(a) { Ok(_) => FoldWhile::Continue(Ok(())), @@ -157,10 +169,22 @@ pub trait Formula2( - i1: &Array, - i2: &Array, - ) -> Result, InputError> { + fn compute_ndarray< + 'a, + D: Dimension + Copy, + A1: Into>, + A2: Into>, + >( + i1: A1, + i2: A2, + ) -> Result, InputError> + where + I1: 'a, + I2: 'a, + { + let i1: ArrayView = i1.into(); + let i2: ArrayView = i2.into(); + Zip::from(i1) .and(i2) .fold_while(Ok(()), |_, &i1, &i2| match Self::validate_inputs(i1, i2) { @@ -187,10 +211,22 @@ pub trait Formula2( - i1: &Array, - i2: &Array, - ) -> Result, InputError> { + fn compute_ndarray_parallel< + 'a, + D: Dimension + Copy, + A1: Into>, + A2: Into>, + >( + i1: A1, + i2: A2, + ) -> Result, InputError> + where + I1: 'a, + I2: 'a, + { + let i1: ArrayView = i1.into(); + let i2: ArrayView = i2.into(); + Zip::from(i1) .and(i2) .fold_while(Ok(()), |_, &i1, &i2| match Self::validate_inputs(i1, i2) { @@ -273,11 +309,26 @@ pub trait Formula3< #[cfg(feature = "array")] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - fn compute_ndarray( - i1: &Array, - i2: &Array, - i3: &Array, - ) -> Result, InputError> { + fn compute_ndarray< + 'a, + D: Dimension + Copy, + A1: Into>, + A2: Into>, + A3: Into>, + >( + i1: A1, + i2: A2, + i3: A3, + ) -> Result, InputError> + where + I1: 'a, + I2: 'a, + I3: 'a, + { + let i1: ArrayView = i1.into(); + let i2: ArrayView = i2.into(); + let i3: ArrayView = i3.into(); + Zip::from(i1) .and(i2) .and(i3) @@ -309,11 +360,26 @@ pub trait Formula3< #[cfg(feature = "parallel")] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - fn compute_ndarray_parallel( - i1: &Array, - i2: &Array, - i3: &Array, - ) -> Result, InputError> { + fn compute_ndarray_parallel< + 'a, + D: Dimension + Copy, + A1: Into>, + A2: Into>, + A3: Into>, + >( + i1: A1, + i2: A2, + i3: A3, + ) -> Result, InputError> + where + I1: 'a, + I2: 'a, + I3: 'a, + { + let i1: ArrayView = i1.into(); + let i2: ArrayView = i2.into(); + let i3: ArrayView = i3.into(); + Zip::from(i1) .and(i2) .and(i3) @@ -403,12 +469,30 @@ pub trait Formula4< #[cfg(feature = "array")] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - fn compute_ndarray( - i1: &Array, - i2: &Array, - i3: &Array, - i4: &Array, - ) -> Result, InputError> { + fn compute_ndarray< + 'a, + D: Dimension + Copy, + A1: Into>, + A2: Into>, + A3: Into>, + A4: Into>, + >( + i1: A1, + i2: A2, + i3: A3, + i4: A4, + ) -> Result, InputError> + where + I1: 'a, + I2: 'a, + I3: 'a, + I4: 'a, + { + let i1: ArrayView = i1.into(); + let i2: ArrayView = i2.into(); + let i3: ArrayView = i3.into(); + let i4: ArrayView = i4.into(); + Zip::from(i1) .and(i2) .and(i3) @@ -449,12 +533,30 @@ pub trait Formula4< #[cfg(feature = "parallel")] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - fn compute_ndarray_parallel( - i1: &Array, - i2: &Array, - i3: &Array, - i4: &Array, - ) -> Result, InputError> { + fn compute_ndarray_parallel< + 'a, + D: Dimension + Copy, + A1: Into>, + A2: Into>, + A3: Into>, + A4: Into>, + >( + i1: A1, + i2: A2, + i3: A3, + i4: A4, + ) -> Result, InputError> + where + I1: 'a, + I2: 'a, + I3: 'a, + I4: 'a, + { + let i1: ArrayView = i1.into(); + let i2: ArrayView = i2.into(); + let i3: ArrayView = i3.into(); + let i4: ArrayView = i4.into(); + Zip::from(i1) .and(i2) .and(i3) From 8455130ad12732adafe8e5abaa439bab6b5c8d61 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 17 Aug 2024 18:58:25 +0200 Subject: [PATCH 086/102] debug log errors in all compute functions --- Cargo.toml | 3 +- src/formulas/traits.rs | 160 ++++++++++++++++++++--------------------- 2 files changed, 78 insertions(+), 85 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ec176a4..bb304dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "floccus" -version = "0.4.0-alpha.5" +version = "0.4.0-alpha.6" authors = ["Jakub Lewandowski "] edition = "2021" description = "Formulae for air thermodynamic calculations" @@ -25,7 +25,6 @@ uom = { version = "0.36", default-features = false, features = [ "f32", "f64", ] } -cfg-if = "1.0" [lib] doctest = false diff --git a/src/formulas/traits.rs b/src/formulas/traits.rs index cefe8e2..232fbf6 100644 --- a/src/formulas/traits.rs +++ b/src/formulas/traits.rs @@ -14,31 +14,20 @@ pub trait Formula1 { #[allow(clippy::missing_errors_doc)] fn validate_inputs(i1: I1) -> Result<(), InputError>; - #[allow(clippy::missing_errors_doc)] #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] #[inline] - fn compute(i1: I1) -> Result { - cfg_if::cfg_if! { - if #[cfg(feature = "debug")] { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - Self::validate_inputs_loggerr(i1)?; - } - } - } else { - Self::validate_inputs(i1)?; - } - } - - Ok(Self::compute_unchecked(i1)) + #[cfg(not(feature = "debug"))] + fn validate_inputs_internal(i1: I1) -> Result<(), InputError> { + Self::validate_inputs(i1) } #[cfg(feature = "debug")] #[cfg(debug_assertions)] - #[inline(always)] + #[inline] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - fn validate_inputs_loggerr(i1: I1) -> Result<(), InputError> { + fn validate_inputs_internal(i1: I1) -> Result<(), InputError> { use std::any::type_name; Self::validate_inputs(i1).or_else(|err| { @@ -53,6 +42,14 @@ pub trait Formula1 { }) } + #[allow(clippy::missing_errors_doc)] + #[allow(missing_docs)] + #[inline] + fn compute(i1: I1) -> Result { + Self::validate_inputs_internal(i1)?; + Ok(Self::compute_unchecked(i1)) + } + #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] fn compute_vec(i1: &[I1]) -> Result, InputError> { @@ -71,7 +68,7 @@ pub trait Formula1 { let i1: ArrayView = i1.into(); Zip::from(i1) - .fold_while(Ok(()), |_, &i1| match Self::validate_inputs(i1) { + .fold_while(Ok(()), |_, &i1| match Self::validate_inputs_internal(i1) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), }) @@ -99,7 +96,7 @@ pub trait Formula1 { let i1: ArrayView = i1.into(); Zip::from(i1) - .fold_while(Ok(()), |_, &a| match Self::validate_inputs(a) { + .fold_while(Ok(()), |_, &a| match Self::validate_inputs_internal(a) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), }) @@ -117,31 +114,20 @@ pub trait Formula2 Result<(), InputError>; - #[allow(clippy::missing_errors_doc)] #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] #[inline] - fn compute(i1: I1, i2: I2) -> Result { - cfg_if::cfg_if! { - if #[cfg(feature = "debug")] { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - Self::validate_inputs_loggerr(i1,i2)?; - } - } - } else { - Self::validate_inputs(i1,i2)?; - } - } - - Ok(Self::compute_unchecked(i1, i2)) + #[cfg(not(feature = "debug"))] + fn validate_inputs_internal(i1: I1, i2: I2) -> Result<(), InputError> { + Self::validate_inputs(i1, i2) } #[cfg(feature = "debug")] #[cfg(debug_assertions)] - #[inline(always)] + #[inline] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - fn validate_inputs_loggerr(i1: I1, i2: I2) -> Result<(), InputError> { + fn validate_inputs_internal(i1: I1, i2: I2) -> Result<(), InputError> { use std::any::type_name; Self::validate_inputs(i1, i2).or_else(|err| { @@ -157,6 +143,14 @@ pub trait Formula2 Result { + Self::validate_inputs_internal(i1, i2)?; + Ok(Self::compute_unchecked(i1, i2)) + } + #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] fn compute_vec(i1: &[I1], i2: &[I2]) -> Result, InputError> { @@ -187,9 +181,11 @@ pub trait Formula2 FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), + .fold_while(Ok(()), |_, &i1, &i2| { + match Self::validate_inputs_internal(i1, i2) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + } }) .into_inner()?; @@ -229,9 +225,11 @@ pub trait Formula2 FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), + .fold_while(Ok(()), |_, &i1, &i2| { + match Self::validate_inputs_internal(i1, i2) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), + } }) .into_inner()?; @@ -255,31 +253,20 @@ pub trait Formula3< #[allow(clippy::missing_errors_doc)] fn validate_inputs(i1: I1, i2: I2, i3: I3) -> Result<(), InputError>; - #[allow(clippy::missing_errors_doc)] #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] #[inline] - fn compute(i1: I1, i2: I2, i3: I3) -> Result { - cfg_if::cfg_if! { - if #[cfg(feature = "debug")] { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - Self::validate_inputs_loggerr(i1, i2, i3)?; - } - } - } else { - Self::validate_inputs(i1,i2,i3)?; - } - } - - Ok(Self::compute_unchecked(i1, i2, i3)) + #[cfg(not(feature = "debug"))] + fn validate_inputs_internal(i1: I1, i2: I2, i3: I3) -> Result<(), InputError> { + Self::validate_inputs(i1, i2, i3) } #[cfg(feature = "debug")] #[cfg(debug_assertions)] - #[inline(always)] + #[inline] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - fn validate_inputs_loggerr(i1: I1, i2: I2, i3: I3) -> Result<(), InputError> { + fn validate_inputs_internal(i1: I1, i2: I2, i3: I3) -> Result<(), InputError> { use std::any::type_name; Self::validate_inputs(i1, i2, i3).or_else(|err| { @@ -296,6 +283,14 @@ pub trait Formula3< }) } + #[allow(clippy::missing_errors_doc)] + #[allow(missing_docs)] + #[inline] + fn compute(i1: I1, i2: I2, i3: I3) -> Result { + Self::validate_inputs_internal(i1, i2, i3)?; + Ok(Self::compute_unchecked(i1, i2, i3)) + } + #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] fn compute_vec(i1: &[I1], i2: &[I2], i3: &[I3]) -> Result, InputError> { @@ -332,12 +327,13 @@ pub trait Formula3< Zip::from(i1) .and(i2) .and(i3) - .fold_while(Ok(()), |_, &i1, &i2, &i3| { - match Self::validate_inputs(i1, i2, i3) { + .fold_while( + Ok(()), + |_, &i1, &i2, &i3| match Self::validate_inputs_internal(i1, i2, i3) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), - } - }) + }, + ) .into_inner()?; Ok(Zip::from(i1) @@ -383,12 +379,13 @@ pub trait Formula3< Zip::from(i1) .and(i2) .and(i3) - .fold_while(Ok(()), |_, &i1, &i2, &i3| { - match Self::validate_inputs(i1, i2, i3) { + .fold_while( + Ok(()), + |_, &i1, &i2, &i3| match Self::validate_inputs_internal(i1, i2, i3) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), - } - }) + }, + ) .into_inner()?; Ok(Zip::from(i1) @@ -413,31 +410,28 @@ pub trait Formula4< #[allow(clippy::missing_errors_doc)] fn validate_inputs(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError>; + #[allow(missing_docs)] + #[allow(clippy::missing_errors_doc)] + #[inline] + #[cfg(not(feature = "debug"))] + fn validate_inputs_internal(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError> { + Self::validate_inputs(i1, i2, i3, i4) + } + #[allow(clippy::missing_errors_doc)] #[allow(missing_docs)] #[inline] fn compute(i1: I1, i2: I2, i3: I3, i4: I4) -> Result { - cfg_if::cfg_if! { - if #[cfg(feature = "debug")] { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - Self::validate_inputs_loggerr(i1,i2,i3,i4)?; - } - } - } else { - Self::validate_inputs(i1,i2,i3,i4)?; - } - } - + Self::validate_inputs_internal(i1, i2, i3, i4)?; Ok(Self::compute_unchecked(i1, i2, i3, i4)) } #[cfg(feature = "debug")] #[cfg(debug_assertions)] - #[inline(always)] + #[inline] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - fn validate_inputs_loggerr(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError> { + fn validate_inputs_internal(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError> { use std::any::type_name; Self::validate_inputs(i1, i2, i3, i4).or_else(|err| { @@ -499,7 +493,7 @@ pub trait Formula4< .and(i4) .fold_while( Ok(()), - |_, &i1, &i2, &i3, &i4| match Self::validate_inputs(i1, i2, i3, i4) { + |_, &i1, &i2, &i3, &i4| match Self::validate_inputs_internal(i1, i2, i3, i4) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), }, @@ -563,7 +557,7 @@ pub trait Formula4< .and(i4) .fold_while( Ok(()), - |_, &i1, &i2, &i3, &i4| match Self::validate_inputs(i1, i2, i3, i4) { + |_, &i1, &i2, &i3, &i4| match Self::validate_inputs_internal(i1, i2, i3, i4) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), }, From 8b5a5be6de07db560c67cb1363e7208d8cda4ab7 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 17 Aug 2024 19:16:02 +0200 Subject: [PATCH 087/102] make CI checks more exhaustive --- .github/workflows/basic.yml | 39 +++++++++++++++++++++++++++++++------ src/lib.rs | 1 + 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 2f69821..e7b699c 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -24,15 +24,42 @@ jobs: 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 --no-default-features cargo test --features "double_precision" cargo test --features "debug" + cargo test --features "array" + cargo test --features "parallel" + cargo test --features "double_precision, debug" + cargo test --features "double_precision, array" + cargo test --features "double_precision, parallel" + cargo test --features "debug, array" + cargo test --features "debug, parallel" + cargo test --features "array, parallel" + cargo test --features "double_precision, debug, array" + cargo test --features "double_precision, debug, parallel" + cargo test --features "double_precision, array, parallel" + cargo test --features "debug, array, parallel" + cargo test --all-features cargo clean + - name: Check release builds + run: | + cargo build --release + cargo build --release --no-default-features + cargo build --release --features "double_precision" + cargo build --release --features "debug" + cargo build --release --features "array" + cargo build --release --features "parallel" + cargo build --release --features "double_precision, debug" + cargo build --release --features "double_precision, array" + cargo build --release --features "double_precision, parallel" + cargo build --release --features "debug, array" + cargo build --release --features "debug, parallel" + cargo build --release --features "array, parallel" + cargo build --release --features "double_precision, debug, array" + cargo build --release --features "double_precision, debug, parallel" + cargo build --release --features "double_precision, array, parallel" + cargo build --release --features "debug, array, parallel" + cargo build --release --all-features diff --git a/src/lib.rs b/src/lib.rs index 24ec52b..6ccfc52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ #![warn(clippy::cargo)] #![allow(clippy::excessive_precision)] #![allow(clippy::must_use_candidate)] +#![allow(missing_docs)] // TODO: Remove it //! Crate providing formulae for air thermodynamic calculations. //! From d6699bf4a3b7b81037a13e409cca14a8cc316279 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 17 Aug 2024 19:26:21 +0200 Subject: [PATCH 088/102] fix debug log compile condition --- Cargo.toml | 2 +- src/formulas/traits.rs | 28 ++++++++++++---------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bb304dc..081c6b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "floccus" -version = "0.4.0-alpha.6" +version = "0.4.0-alpha.7" authors = ["Jakub Lewandowski "] edition = "2021" description = "Formulae for air thermodynamic calculations" diff --git a/src/formulas/traits.rs b/src/formulas/traits.rs index 232fbf6..6bed55d 100644 --- a/src/formulas/traits.rs +++ b/src/formulas/traits.rs @@ -14,17 +14,16 @@ pub trait Formula1 { #[allow(clippy::missing_errors_doc)] fn validate_inputs(i1: I1) -> Result<(), InputError>; + #[inline] + #[cfg(any(not(debug_assertions), not(feature = "debug")))] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - #[inline] - #[cfg(not(feature = "debug"))] fn validate_inputs_internal(i1: I1) -> Result<(), InputError> { Self::validate_inputs(i1) } - #[cfg(feature = "debug")] - #[cfg(debug_assertions)] #[inline] + #[cfg(all(debug_assertions, feature = "debug"))] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] fn validate_inputs_internal(i1: I1) -> Result<(), InputError> { @@ -114,17 +113,16 @@ pub trait Formula2 Result<(), InputError>; + #[inline] + #[cfg(any(not(debug_assertions), not(feature = "debug")))] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - #[inline] - #[cfg(not(feature = "debug"))] fn validate_inputs_internal(i1: I1, i2: I2) -> Result<(), InputError> { Self::validate_inputs(i1, i2) } - #[cfg(feature = "debug")] - #[cfg(debug_assertions)] #[inline] + #[cfg(all(debug_assertions, feature = "debug"))] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] fn validate_inputs_internal(i1: I1, i2: I2) -> Result<(), InputError> { @@ -253,17 +251,16 @@ pub trait Formula3< #[allow(clippy::missing_errors_doc)] fn validate_inputs(i1: I1, i2: I2, i3: I3) -> Result<(), InputError>; + #[inline] + #[cfg(any(not(debug_assertions), not(feature = "debug")))] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - #[inline] - #[cfg(not(feature = "debug"))] fn validate_inputs_internal(i1: I1, i2: I2, i3: I3) -> Result<(), InputError> { Self::validate_inputs(i1, i2, i3) } - #[cfg(feature = "debug")] - #[cfg(debug_assertions)] #[inline] + #[cfg(all(debug_assertions, feature = "debug"))] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] fn validate_inputs_internal(i1: I1, i2: I2, i3: I3) -> Result<(), InputError> { @@ -410,10 +407,10 @@ pub trait Formula4< #[allow(clippy::missing_errors_doc)] fn validate_inputs(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError>; + #[inline] + #[cfg(any(not(debug_assertions), not(feature = "debug")))] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] - #[inline] - #[cfg(not(feature = "debug"))] fn validate_inputs_internal(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError> { Self::validate_inputs(i1, i2, i3, i4) } @@ -426,9 +423,8 @@ pub trait Formula4< Ok(Self::compute_unchecked(i1, i2, i3, i4)) } - #[cfg(feature = "debug")] - #[cfg(debug_assertions)] #[inline] + #[cfg(all(debug_assertions, feature = "debug"))] #[allow(missing_docs)] #[allow(clippy::missing_errors_doc)] fn validate_inputs_internal(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError> { From 3056ba696953de8359af79c43c7a9ad90c611755 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 19 Aug 2024 11:28:37 +0200 Subject: [PATCH 089/102] add equivalent potential temperatures, clear up some constants --- Cargo.toml | 2 +- src/constants.rs | 17 ++- src/formulas.rs | 2 + .../adiabatic_equivalent_temperature.rs | 126 ++++++++++++++++++ .../equivalent_potential_temperature.rs | 7 +- .../isobaric_equivalent_temperature.rs | 66 +++++++++ src/formulas/potential_temperature.rs | 5 +- .../wet_bulb_potential_temperature.rs | 8 +- src/quantities.rs | 6 + src/quantities/accessors.rs | 20 +++ src/quantities/constructors.rs | 20 +++ src/quantities/trait_impls.rs | 12 ++ src/tests/reference_values.rs | 6 + src/tests/testing_traits.rs | 38 ++++++ 14 files changed, 323 insertions(+), 12 deletions(-) create mode 100644 src/formulas/adiabatic_equivalent_temperature.rs create mode 100644 src/formulas/isobaric_equivalent_temperature.rs diff --git a/Cargo.toml b/Cargo.toml index 081c6b1..f660c1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "floccus" -version = "0.4.0-alpha.7" +version = "0.4.0-alpha.8" authors = ["Jakub Lewandowski "] edition = "2021" description = "Formulae for air thermodynamic calculations" diff --git a/src/constants.rs b/src/constants.rs index 6364d4d..09722ad 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -95,7 +95,14 @@ pub const R_V: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { value: R.value / M_V.value, }; -// Internal Constants (commonly appearing in formulas to use them with oum units) +// Internal Constants (commonly appearing in formulas to use them with uom units) + +/// Reference pressure level of 1000hPa. Used in adiabatic processes +pub(crate) const REF_PRES: Storage::Pressure = Storage::Pressure { + dimension: PhantomData, + units: PhantomData, + value: 100_000., +}; /// Ratio of molar masses of dry air and water vapour pub(crate) const EPSILON: Storage::Ratio = Storage::Ratio { @@ -111,12 +118,20 @@ pub(crate) const KAPPA: Storage::Ratio = Storage::Ratio { value: R_D.value / C_P.value, }; +/// Inverse of KAPPA +pub(crate) const LAMBDA: Storage::Ratio = Storage::Ratio { + dimension: PhantomData, + units: PhantomData, + value: C_P.value / R_D.value, +}; + pub(crate) const DIMLESS_ONE: Storage::Ratio = Storage::Ratio { dimension: PhantomData, units: PhantomData, value: 1.0, }; +/// Useful to convert TemperatureInterval into ThermodynamicTemperature pub(crate) const ZERO_KELVIN: Storage::ThermodynamicTemperature = Storage::ThermodynamicTemperature { dimension: PhantomData, diff --git a/src/formulas.rs b/src/formulas.rs index f59cb49..d76aa30 100644 --- a/src/formulas.rs +++ b/src/formulas.rs @@ -12,5 +12,7 @@ pub mod wet_bulb_potential_temperature; pub mod wet_bulb_temperature; mod traits; +pub mod isobaric_equivalent_temperature; +pub mod adiabatic_equivalent_temperature; pub use traits::{Formula1, Formula2, Formula3, Formula4}; diff --git a/src/formulas/adiabatic_equivalent_temperature.rs b/src/formulas/adiabatic_equivalent_temperature.rs new file mode 100644 index 0000000..7775569 --- /dev/null +++ b/src/formulas/adiabatic_equivalent_temperature.rs @@ -0,0 +1,126 @@ +//! Functions to calculate adiabatic potential temperature of unsaturated air +//! +//! Adiabatic equivalent temperature (also known as pseudoequivalent temperature) is a temperature that +//! an air parcel would have after undergoing the following process: dry-adiabatic expansion until saturated; +//! pseudoadiabatic expansion until all moisture is precipitated out; dry- adiabatic compression to the initial +//! pressure. This is the equivalent temperature as read from a thermodynamic chart and is always greater than +//! the isobaric equivalent temperature. +//! ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Equivalent_temperature)). + +use uom::si::pressure::pascal; +use uom::si::ratio::ratio; +use uom::si::thermodynamic_temperature::kelvin; + +use crate::constants::{C_P, KAPPA, L_V, REF_PRES, ZERO_KELVIN}; +use crate::errors::InputError; +use crate::formulas::Formula2; +use crate::quantities::{ + AdiabaticEquivalentTemperature, AtmosphericPressure, DryBulbTemperature, + EquivalentPotentialTemperature, MixingRatio, QuantityHelpers, +}; + +type FormulaQuantity = AdiabaticEquivalentTemperature; + +/// Formula for computing adiabatic equivalent temperature from dry-bulb temperature and mixing ratio +/// +/// Valid `temperature` range: 253K - 324K +/// +/// Valid `mixing_ratio` range: 0.000_000_1 - 2.0 +pub struct Definition1; + +impl Formula2 for Definition1 { + #[inline(always)] + fn validate_inputs( + temperature: DryBulbTemperature, + mixing_ratio: MixingRatio, + ) -> Result<(), InputError> { + temperature.check_range_si(253.0, 324.0)?; + mixing_ratio.check_range_si(0.000_000_1, 2.0)?; + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + temperature: DryBulbTemperature, + mixing_ratio: MixingRatio, + ) -> FormulaQuantity { + let temp_ae = temperature.0 * ((L_V * mixing_ratio.0) / (C_P * temperature.0)).exp(); + + AdiabaticEquivalentTemperature(temp_ae + ZERO_KELVIN) + } +} + +/// Formula for computing adiabatic equivalent temperature from +/// atmospheric pressure and equivalent potential temperature. +/// +/// Cited in [Davies-Jones (2008)](https://doi.org/10.1175/2007MWR2224.1) +/// +/// Valid `equivalent_potential_temperature` range: 173K - 373K +/// +/// Valid `pressure` range: 100Pa - 150000Pa +pub struct Definition2; + +impl Formula2 + for Definition2 +{ + #[inline(always)] + fn validate_inputs( + equivalent_potential_temperature: EquivalentPotentialTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + equivalent_potential_temperature.check_range_si(173., 373.)?; + pressure.check_range_si(100., 150_000.)?; + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + equivalent_potential_temperature: EquivalentPotentialTemperature, + pressure: AtmosphericPressure, + ) -> FormulaQuantity { + let p = pressure.get::(); + let p0 = REF_PRES.get::(); + let kappa = KAPPA.get::(); + let theta_e = equivalent_potential_temperature.get::(); + + let pi = (p / p0).powf(kappa); + let temp_ae = theta_e * pi; + + AdiabaticEquivalentTemperature::new::(temp_ae) + } +} + +#[cfg(test)] +mod tests { + + use crate::tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}; + + use super::*; + + #[test] + fn definition1() { + test_with_2args::( + Argument::new([253., 324.]), + Argument::new([0.000_000_1, 2.0]), + ReferenceAtmosphere::Normal, + 1e-12, + ); + } + + #[test] + fn definition2() { + test_with_2args::< + FormulaQuantity, + EquivalentPotentialTemperature, + AtmosphericPressure, + Definition2, + >( + Argument::new([173., 373.]), + Argument::new([100., 150_000.]), + ReferenceAtmosphere::Normal, + 1., + ); + } +} diff --git a/src/formulas/equivalent_potential_temperature.rs b/src/formulas/equivalent_potential_temperature.rs index 5d5f1ce..0b25a29 100644 --- a/src/formulas/equivalent_potential_temperature.rs +++ b/src/formulas/equivalent_potential_temperature.rs @@ -11,7 +11,7 @@ use uom::si::ratio::ratio; use uom::si::specific_heat_capacity::joule_per_kilogram_kelvin; use uom::si::thermodynamic_temperature::kelvin; -use crate::constants::{C_L, C_P, EPSILON, KAPPA, L_V, R_D, R_V}; +use crate::constants::{C_L, C_P, EPSILON, KAPPA, L_V, REF_PRES, R_D, R_V}; use crate::errors::InputError; use crate::quantities::{ AtmosphericPressure, DewPointTemperature, DryBulbTemperature, EquivalentPotentialTemperature, @@ -78,7 +78,7 @@ impl let mixing_ratio = mixing_ratio.0.get::(); let relative_humidity = relative_humidity.0.get::(); - let p0 = 100_000.0; + let p0 = REF_PRES.get::(); let r_d = R_D.get::(); let r_v = R_V.get::(); let l_v = L_V.get::(); @@ -242,6 +242,7 @@ impl let dewpoint = dewpoint.0.get::(); let mixing_ratio = mixing_ratio.0.get::(); let vapour_pressure = vapour_pressure.0.get::(); + let p0 = REF_PRES.get::(); let kappa = KAPPA.get::(); @@ -251,7 +252,7 @@ impl (1.0 / ((1.0 / (dewpoint - 56.0)) + ((temperature / dewpoint).ln() / 800.0))) + 56.0; let theta_dl = temperature - * (100_000.0 / (pressure - vapour_pressure)).powf(kappa) + * (p0 / (pressure - vapour_pressure)).powf(kappa) * (temperature / lcl_temp).powf(0.28 * mixing_ratio); let result = theta_dl diff --git a/src/formulas/isobaric_equivalent_temperature.rs b/src/formulas/isobaric_equivalent_temperature.rs new file mode 100644 index 0000000..9ed4c0c --- /dev/null +++ b/src/formulas/isobaric_equivalent_temperature.rs @@ -0,0 +1,66 @@ +//! Functions to calculate isobaric potential temperature of unsaturated air +//! +//! +//! Isobaric equivalent temperature is a temperature that an air parcel would have if all water +//! vapor were condensed at constant pressure and the enthalpy released from the vapor used to heat the air. +//! This process is physically impossible in the atmosphere +//! ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Equivalent_temperature)). + +use crate::constants::{C_P, DIMLESS_ONE, L_V, ZERO_KELVIN}; +use crate::errors::InputError; +use crate::formulas::Formula2; +use crate::quantities::{ + DryBulbTemperature, IsobaricEquivalentTemperature, MixingRatio, QuantityHelpers, +}; + +type FormulaQuantity = IsobaricEquivalentTemperature; + +/// Formula for computing isobaric equivalent temperature from dry-bulb temperature and mixing ratio +/// +/// Valid `temperature` range: 253K - 324K +/// +/// Valid `mixing_ratio` range: 0.000_000_1 - 2.0 +/// +pub struct Definition1; + +impl Formula2 for Definition1 { + #[inline(always)] + fn validate_inputs( + temperature: DryBulbTemperature, + mixing_ratio: MixingRatio, + ) -> Result<(), InputError> { + temperature.check_range_si(253.0, 324.0)?; + mixing_ratio.check_range_si(0.000_000_1, 2.0)?; + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + temperature: DryBulbTemperature, + mixing_ratio: MixingRatio, + ) -> FormulaQuantity { + let temp_ie = + temperature.0 * (DIMLESS_ONE + ((L_V * mixing_ratio.0) / (C_P * temperature.0))); + + IsobaricEquivalentTemperature(ZERO_KELVIN + temp_ie) + } +} + +#[cfg(test)] +mod tests { + + use crate::tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}; + + use super::*; + + #[test] + fn definition1() { + test_with_2args::( + Argument::new([253., 324.]), + Argument::new([0.000_000_1, 2.0]), + ReferenceAtmosphere::Normal, + 1e-12, + ); + } +} diff --git a/src/formulas/potential_temperature.rs b/src/formulas/potential_temperature.rs index 6720238..0c8d705 100644 --- a/src/formulas/potential_temperature.rs +++ b/src/formulas/potential_temperature.rs @@ -4,7 +4,7 @@ //! adiabatically and reversibly from its initial state to a //! standard pressure, p0 = 100 kPa ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Potential_temperature)). -use crate::constants::KAPPA; +use crate::constants::{KAPPA, REF_PRES}; use crate::errors::InputError; use crate::formulas::Formula3; use crate::quantities::{ @@ -77,9 +77,10 @@ impl Formula3(); let pressure = pressure.0.get::(); let vapour_pressure = vapour_pressure.0.get::(); + let p0 = REF_PRES.get::(); let kappa = KAPPA.get::(); - let result = temperature * (100_000.0 / (pressure - vapour_pressure)).powf(kappa); + let result = temperature * (p0 / (pressure - vapour_pressure)).powf(kappa); PotentialTemperature::new::(result) } diff --git a/src/formulas/wet_bulb_potential_temperature.rs b/src/formulas/wet_bulb_potential_temperature.rs index 48e4fde..94fb9d1 100644 --- a/src/formulas/wet_bulb_potential_temperature.rs +++ b/src/formulas/wet_bulb_potential_temperature.rs @@ -3,15 +3,13 @@ use uom::si::ratio::ratio; use uom::si::thermodynamic_temperature::{degree_celsius, kelvin}; +use crate::constants::LAMBDA; +use crate::errors::InputError; use crate::formulas::Formula1; use crate::quantities::{ EquivalentPotentialTemperature, QuantityHelpers, WetBulbPotentialTemperature, }; use crate::Storage; -use crate::{ - constants::{C_P, R_D}, - errors::InputError, -}; type FormulaQuantity = WetBulbPotentialTemperature; @@ -36,7 +34,7 @@ impl Formula1 for DaviesJones1 fn compute_unchecked( equivalent_potential_temperature: EquivalentPotentialTemperature, ) -> WetBulbPotentialTemperature { - let lambda = (C_P / R_D).get::(); + let lambda = LAMBDA.get::(); let equivalent_potential_temperature = equivalent_potential_temperature.0.get::(); let result = 45.114 - 51.489 * (273.15 / equivalent_potential_temperature).powf(lambda); diff --git a/src/quantities.rs b/src/quantities.rs index 2aa7001..a7f72ee 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -48,6 +48,12 @@ pub struct DewPointTemperature(pub Storage::ThermodynamicTemperature); #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct VirtualTemperature(pub Storage::ThermodynamicTemperature); +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +pub struct IsobaricEquivalentTemperature(pub Storage::ThermodynamicTemperature); + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +pub struct AdiabaticEquivalentTemperature(pub Storage::ThermodynamicTemperature); + #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] pub struct PotentialTemperature(pub Storage::ThermodynamicTemperature); diff --git a/src/quantities/accessors.rs b/src/quantities/accessors.rs index 92c6d58..21af2d0 100644 --- a/src/quantities/accessors.rs +++ b/src/quantities/accessors.rs @@ -50,6 +50,26 @@ impl PotentialTemperature { } } +impl IsobaricEquivalentTemperature { + pub fn get(&self) -> Float + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + self.0.get::() + } +} + +impl AdiabaticEquivalentTemperature { + pub fn get(&self) -> Float + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + self.0.get::() + } +} + impl EquivalentPotentialTemperature { pub fn get(&self) -> Float where diff --git a/src/quantities/constructors.rs b/src/quantities/constructors.rs index 806d6b9..1d12ebe 100644 --- a/src/quantities/constructors.rs +++ b/src/quantities/constructors.rs @@ -50,6 +50,26 @@ impl PotentialTemperature { } } +impl IsobaricEquivalentTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + +impl AdiabaticEquivalentTemperature { + pub fn new(value: Float) -> Self + where + T: uom::si::thermodynamic_temperature::Unit + + uom::si::thermodynamic_temperature::Conversion, + { + Self(Storage::ThermodynamicTemperature::new::(value)) + } +} + impl EquivalentPotentialTemperature { pub fn new(value: Float) -> Self where diff --git a/src/quantities/trait_impls.rs b/src/quantities/trait_impls.rs index 96b5e59..c4f8d25 100644 --- a/src/quantities/trait_impls.rs +++ b/src/quantities/trait_impls.rs @@ -5,6 +5,8 @@ impl ThermodynamicQuantity for DryBulbTemperature {} impl ThermodynamicQuantity for WetBulbTemperature {} impl ThermodynamicQuantity for DewPointTemperature {} impl ThermodynamicQuantity for VirtualTemperature {} +impl ThermodynamicQuantity for IsobaricEquivalentTemperature {} +impl ThermodynamicQuantity for AdiabaticEquivalentTemperature {} impl ThermodynamicQuantity for PotentialTemperature {} impl ThermodynamicQuantity for EquivalentPotentialTemperature {} impl ThermodynamicQuantity for WetBulbPotentialTemperature {} @@ -42,6 +44,16 @@ impl QuantityHelpers for PotentialTemperature { self.get::() } } +impl QuantityHelpers for IsobaricEquivalentTemperature { + fn get_si_value(&self) -> Float { + self.get::() + } +} +impl QuantityHelpers for AdiabaticEquivalentTemperature { + fn get_si_value(&self) -> Float { + self.get::() + } +} impl QuantityHelpers for EquivalentPotentialTemperature { fn get_si_value(&self) -> Float { self.get::() diff --git a/src/tests/reference_values.rs b/src/tests/reference_values.rs index 2182b9d..7f1a790 100644 --- a/src/tests/reference_values.rs +++ b/src/tests/reference_values.rs @@ -1,3 +1,5 @@ +use std::f64::NAN; + use crate::Float; pub(crate) const TEMP_NORM: Float = 300.0; @@ -15,6 +17,8 @@ pub(crate) const THETA_NORM: Float = 301.66581400702955; pub(crate) const THETAW_NORM: Float = 292.0717306393948; pub(crate) const WBT_NORM: Float = 293.42728654340516; pub(crate) const VRT_NORM: Float = 302.1926517941886; +pub(crate) const TIE_NORM: Float = 330.29726646682764; +pub(crate) const TAE_NORM: Float = 331.8799684989062; pub(crate) const TEMP_FREEZ: Float = 260.0; pub(crate) const DWPT_FREEZ: Float = 255.0; @@ -31,3 +35,5 @@ pub(crate) const THETA_FREEZ: Float = 260.0915766593588; pub(crate) const THETAW_FREEZ: Float = 258.6611332391296; pub(crate) const WBT_FREEZ: Float = 258.40501060754224; pub(crate) const VRT_FREEZ: Float = 260.12112343315795; +pub(crate) const TIE_FREEZ: Float = NAN; +pub(crate) const TAE_FREEZ: Float = NAN; diff --git a/src/tests/testing_traits.rs b/src/tests/testing_traits.rs index 4606f66..225748d 100644 --- a/src/tests/testing_traits.rs +++ b/src/tests/testing_traits.rs @@ -303,3 +303,41 @@ impl TestingQuantity for PotentialTemperature { } } } + +impl TestingQuantity for IsobaricEquivalentTemperature { + fn new_si(value: Float) -> Self { + Self::new::(value) + } + + fn imperial(&self) -> Self { + let value = self.0.get::(); + + Self::new::(value) + } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(TIE_NORM), + ReferenceAtmosphere::Freezing => Self::new::(TIE_FREEZ), + } + } +} + +impl TestingQuantity for AdiabaticEquivalentTemperature { + fn new_si(value: Float) -> Self { + Self::new::(value) + } + + fn imperial(&self) -> Self { + let value = self.0.get::(); + + Self::new::(value) + } + + fn ref_val_si(atm: ReferenceAtmosphere) -> Self { + match atm { + ReferenceAtmosphere::Normal => Self::new::(TAE_NORM), + ReferenceAtmosphere::Freezing => Self::new::(TAE_FREEZ), + } + } +} From cc94149876cd321d29e51f4d5fa1093878ba40b7 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 19 Aug 2024 12:08:36 +0200 Subject: [PATCH 090/102] add theta_w with wider range --- .../wet_bulb_potential_temperature.rs | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/src/formulas/wet_bulb_potential_temperature.rs b/src/formulas/wet_bulb_potential_temperature.rs index 94fb9d1..7e96150 100644 --- a/src/formulas/wet_bulb_potential_temperature.rs +++ b/src/formulas/wet_bulb_potential_temperature.rs @@ -15,9 +15,11 @@ type FormulaQuantity = WetBulbPotentialTemperature; /// Formula for computing wet bulb potential temperature from equivalent potential temperature. /// -/// Derived by R. Davies-Jones (2008) [(doi:10.1175/2007MWR2224.1)](https://doi.org/10.1175/2007MWR2224.1) +/// Derived by R. Davies-Jones (2008) [(doi:10.1175/2007MWR2224.1)](https://doi.org/10.1175/2007MWR2224.1). +/// This formula is a part of three formuale covering a wide range of theta_e inputs. +/// The other two formulas have not been (yet) implemented. /// -/// Valid `temperature` range: 257K - 377K +/// Valid `equivalent_potential_temperature` range: 257K - 377K pub struct DaviesJones1; impl Formula1 for DaviesJones1 { @@ -44,6 +46,50 @@ impl Formula1 for DaviesJones1 } } +/// Formula for computing wet bulb potential temperature from equivalent potential temperature. +/// +/// Derived by R. Davies-Jones (2008) [(doi:10.1175/2007MWR2224.1)](https://doi.org/10.1175/2007MWR2224.1). +/// This is a very accurate rational-function approximation of [DaviesJones1] and two other (unimplemented) +/// formulas. +/// +/// Valid `equivalent_potential_temperature` range: 174K - 377K +pub struct DaviesJones2; + +impl Formula1 for DaviesJones2 { + #[inline(always)] + fn validate_inputs( + equivalent_potential_temperature: EquivalentPotentialTemperature, + ) -> Result<(), InputError> { + equivalent_potential_temperature.check_range_si(174.0, 377.0)?; + + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + equivalent_potential_temperature: EquivalentPotentialTemperature, + ) -> WetBulbPotentialTemperature { + let theta_e = equivalent_potential_temperature.get::(); + + let x = theta_e / 273.15; + let a0 = 7.101574; + let a1 = -20.68208; + let a2 = 16.11182; + let a3 = 2.574631; + let a4 = -5.205688; + let b1 = -3.552497; + let b2 = 3.781782; + let b3 = -0.6899655; + let b4 = -0.5929340; + + let exponent = (a0 + a1 * x + a2 * x.powi(2) + a3 * x.powi(3) + a4 * x.powi(4)) + / (1. + b1 * x + b2 * x.powi(2) + b3 * x.powi(3) + b4 * x.powi(4)); + let theta_w = theta_e - 273.15 - exponent.exp(); + + WetBulbPotentialTemperature::new::(theta_w) + } +} + #[cfg(test)] mod tests { use crate::{ @@ -61,4 +107,13 @@ mod tests { 1e-2, ); } + + #[test] + fn davies_jones2() { + test_with_1arg::( + Argument::new([174.0, 377.0]), + ReferenceAtmosphere::Normal, + 1e-1, + ); + } } From 91bbe8162848c6100a5edf214d483c1660110a2d Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 19 Aug 2024 12:43:05 +0200 Subject: [PATCH 091/102] generate inputs for vector/array tests as a percentage of reference value --- src/tests/four_arg.rs | 8 ++++---- src/tests/one_arg.rs | 2 +- src/tests/three_arg.rs | 6 +++--- src/tests/two_arg.rs | 6 ++++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/tests/four_arg.rs b/src/tests/four_arg.rs index 95629b9..6b78640 100644 --- a/src/tests/four_arg.rs +++ b/src/tests/four_arg.rs @@ -214,10 +214,10 @@ pub fn test_with_4args< let arg_vecs = (-10..=10).map(|i| i as Float / 1000.0).map(|i| { ( - I1::new_si(arg1.ref_val(atm).get_si_value() + i), - I2::new_si(arg2.ref_val(atm).get_si_value() + i), - I3::new_si(arg3.ref_val(atm).get_si_value() + i), - I4::new_si(arg4.ref_val(atm).get_si_value() + i), + I1::new_si((1.0 + i) * arg1.ref_val(atm).get_si_value()), + I2::new_si((1.0 + i) * arg2.ref_val(atm).get_si_value()), + I3::new_si((1.0 + i) * arg3.ref_val(atm).get_si_value()), + I4::new_si((1.0 + i) * arg4.ref_val(atm).get_si_value()), ) }); diff --git a/src/tests/one_arg.rs b/src/tests/one_arg.rs index 36908a3..ad47ca1 100644 --- a/src/tests/one_arg.rs +++ b/src/tests/one_arg.rs @@ -68,7 +68,7 @@ pub fn test_with_1arg = (-10..=10) .map(|i| i as Float / 1000.0) - .map(|i| I1::new_si(arg1.ref_val(atm).get_si_value() + i)) + .map(|i| I1::new_si((1.0 + i) * arg1.ref_val(atm).get_si_value())) .collect(); #[cfg(feature = "array")] diff --git a/src/tests/three_arg.rs b/src/tests/three_arg.rs index 37cf177..80b42b7 100644 --- a/src/tests/three_arg.rs +++ b/src/tests/three_arg.rs @@ -137,9 +137,9 @@ pub fn test_with_3args< let arg_vecs = (-10..=10).map(|i| i as Float / 1000.0).map(|i| { ( - I1::new_si(arg1.ref_val(atm).get_si_value() + i), - I2::new_si(arg2.ref_val(atm).get_si_value() + i), - I3::new_si(arg3.ref_val(atm).get_si_value() + i), + I1::new_si((1.0 + i) * arg1.ref_val(atm).get_si_value()), + I2::new_si((1.0 + i) * arg2.ref_val(atm).get_si_value()), + I3::new_si((1.0 + i) * arg3.ref_val(atm).get_si_value()), ) }); diff --git a/src/tests/two_arg.rs b/src/tests/two_arg.rs index 5fecb9c..d9b9edc 100644 --- a/src/tests/two_arg.rs +++ b/src/tests/two_arg.rs @@ -84,12 +84,14 @@ pub fn test_with_2args< .map(|i| i as Float / 1000.0) .map(|i| { ( - I1::new_si(arg1.ref_val(atm).get_si_value() + i), - I2::new_si(arg2.ref_val(atm).get_si_value() + i), + I1::new_si((1.0 + i) * arg1.ref_val(atm).get_si_value()), + I2::new_si((1.0 + i) * arg2.ref_val(atm).get_si_value()), ) }) .unzip(); + dbg!(&arg_vecs); + #[cfg(feature = "array")] let arg_arrs = ( Array1::from(arg_vecs.0.clone()), From a5ca7f84d5424c8b06d089e4f7adccec7246d8e9 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:04:51 +0200 Subject: [PATCH 092/102] add more extensive formula for wbt --- Cargo.toml | 1 + .../adiabatic_equivalent_temperature.rs | 29 ++- src/formulas/wet_bulb_temperature.rs | 177 +++++++++++++++++- src/tests/reference_values.rs | 6 +- 4 files changed, 201 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f660c1b..0f8a170 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ doctest = false [dev-dependencies] criterion = "0.5" itertools = "0.13" +log = "0.4" testing_logger = "0.1" floccus = { path = ".", features = ["double_precision"] } diff --git a/src/formulas/adiabatic_equivalent_temperature.rs b/src/formulas/adiabatic_equivalent_temperature.rs index 7775569..034f0f6 100644 --- a/src/formulas/adiabatic_equivalent_temperature.rs +++ b/src/formulas/adiabatic_equivalent_temperature.rs @@ -100,7 +100,7 @@ mod tests { use super::*; #[test] - fn definition1() { + fn definition1_norm() { test_with_2args::( Argument::new([253., 324.]), Argument::new([0.000_000_1, 2.0]), @@ -110,7 +110,7 @@ mod tests { } #[test] - fn definition2() { + fn definition2_norm() { test_with_2args::< FormulaQuantity, EquivalentPotentialTemperature, @@ -123,4 +123,29 @@ mod tests { 1., ); } + + #[test] + fn definition1_freez() { + test_with_2args::( + Argument::new([253., 324.]), + Argument::new([0.000_000_1, 2.0]), + ReferenceAtmosphere::Freezing, + 1e-12, + ); + } + + #[test] + fn definition2_freez() { + test_with_2args::< + FormulaQuantity, + EquivalentPotentialTemperature, + AtmosphericPressure, + Definition2, + >( + Argument::new([173., 373.]), + Argument::new([100., 150_000.]), + ReferenceAtmosphere::Freezing, + 1e-1, + ); + } } diff --git a/src/formulas/wet_bulb_temperature.rs b/src/formulas/wet_bulb_temperature.rs index a051c44..51438dc 100644 --- a/src/formulas/wet_bulb_temperature.rs +++ b/src/formulas/wet_bulb_temperature.rs @@ -1,18 +1,20 @@ //! Functions to calculate wet bulb temperature of unsaturated air -use uom::si::ratio::percent; -use uom::si::thermodynamic_temperature::degree_celsius; +use uom::si::pressure::pascal; +use uom::si::ratio::{percent, ratio}; +use uom::si::thermodynamic_temperature::{degree_celsius, kelvin}; +use crate::constants::{EPSILON, KAPPA, LAMBDA, REF_PRES}; use crate::errors::InputError; use crate::formulas::Formula2; use crate::quantities::{ - DryBulbTemperature, QuantityHelpers, RelativeHumidity, WetBulbTemperature, + AdiabaticEquivalentTemperature, AtmosphericPressure, DryBulbTemperature, QuantityHelpers, + RelativeHumidity, WetBulbTemperature, }; -use crate::Storage; type FormulaQuantity = WetBulbTemperature; -/// Formula for computing wet bulb temperature pressure from dry bulb temperature and relative humidity. +/// Formula for computing wet bulb temperature from dry bulb temperature and relative humidity. /// /// Derived by R. Stull (2011) [(doi:10.1175/JAMC-D-11-0143.1)](https://doi.org/10.1175/JAMC-D-11-0143.1) /// Created with use of gene-expression programming. @@ -50,14 +52,101 @@ impl Formula2 for Stull1 + (0.003_918_38 * relative_humidity.powf(1.5) * (0.023_101 * relative_humidity).atan()) - 4.686_035; - let result = Storage::ThermodynamicTemperature::new::(result); + WetBulbTemperature::new::(result) + } +} - WetBulbTemperature(result) +/// Formula for computing wet bulb temperature pressure from adiabatic equivalent temperature, +/// saturation mixing ratio and pressure. +/// +/// Derived by R. Davies-Jones (2008) [(doi:10.1175/2007MWR2224.1)](https://doi.org/10.1175/2007MWR2224.1). +/// +/// Note that this formula conditionally selects equation used for computation. +/// Thus it has discontinuities along the input range and has some performance overhead. +/// +/// Valid `equivalent_temperature` range: 174K - 377K +/// +/// Valid `pressure` range: 100Pa - 150000Pa +pub struct DaviesJones1; + +impl Formula2 + for DaviesJones1 +{ + #[inline(always)] + fn validate_inputs( + equivalent_temperature: AdiabaticEquivalentTemperature, + pressure: AtmosphericPressure, + ) -> Result<(), InputError> { + equivalent_temperature.check_range_si(174., 377.)?; + pressure.check_range_si(100., 150_000.)?; + Ok(()) + } + + #[inline(always)] + fn compute_unchecked( + equivalent_temperature: AdiabaticEquivalentTemperature, + pressure: AtmosphericPressure, + ) -> WetBulbTemperature { + let lambda = LAMBDA.get::(); + let kappa = KAPPA.get::(); + let t_e = equivalent_temperature.get::(); + let p = pressure.get::(); + let p0 = REF_PRES.get::(); + let pi = (p / p0).powf(kappa); + + let indicator = (273.15 / t_e).powf(lambda); + let d_pi = (0.1859 * (pi / p0) + 0.6512).powi(-1); + + let k1_pi = || -38.5 * pi.powi(2) + 137.81 * pi - 53.737; + let k2_pi = || -4.392 * pi.powi(2) + 56.831 * pi - 0.384; + + let eq_4_9 = || k1_pi() - k2_pi() * indicator; + let eq_4_10 = || (k1_pi() - 1.21) - (k2_pi() - 1.21) * indicator; + let eq_4_11 = || { + (k1_pi() - 2.66) - (k2_pi() - 1.21) * indicator + 0.58 * (273.15 / t_e).powf(-lambda) + }; + + let eq_4_8 = || { + let eps = EPSILON.get::(); + let big_a = 2675.0; + let a = 17.67; + let b = 243.5; + let e_s = 6.112 * (a * (t_e - 273.15) / (t_e - 273.15 + b)).exp(); //effectively Tetens + let r_s = (eps * e_s) / ((p / 100.0) - e_s); + let d_ln_es_d = a * b / (t_e - 273.15 + b).powi(2); + + t_e - 273.15 - ((big_a * r_s) / (1.0 + (big_a * r_s * d_ln_es_d))) + }; + + let t_w; + + if indicator > d_pi { + t_w = eq_4_8(); + #[cfg(test)] + log::info!("eq 4.8"); + } else if indicator >= 1.0 && indicator <= d_pi { + t_w = eq_4_9(); + #[cfg(test)] + log::info!("eq 4.9"); + } else if indicator < 1.0 && indicator >= 0.4 { + t_w = eq_4_10(); + #[cfg(test)] + log::info!("eq 4.10"); + } else { + t_w = eq_4_11(); + #[cfg(test)] + log::info!("eq 4.11"); + } + + WetBulbTemperature::new::(t_w) } } #[cfg(test)] mod tests { + use float_cmp::assert_approx_eq; + use uom::si::pressure::hectopascal; + use crate::tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}; use super::*; @@ -68,7 +157,81 @@ mod tests { Argument::new([253.0, 324.0]), Argument::new([0.05, 0.99]), ReferenceAtmosphere::Normal, + 1e1, + ); + } + + #[test] + fn daviesjones1_norm() { + test_with_2args::< + FormulaQuantity, + AdiabaticEquivalentTemperature, + AtmosphericPressure, + DaviesJones1, + >( + Argument::new([174.0, 377.0]), + Argument::new([100., 150_000.]), + ReferenceAtmosphere::Normal, + 1e-12, + ); + } + + #[test] + fn daviesjones1_freez() { + test_with_2args::< + FormulaQuantity, + AdiabaticEquivalentTemperature, + AtmosphericPressure, + DaviesJones1, + >( + Argument::new([174.0, 377.0]), + Argument::new([100., 150_000.]), + ReferenceAtmosphere::Freezing, 1e-12, ); } + + #[test] + fn daviesjones1_manual() { + testing_logger::setup(); + // eq 4.8 + let pressure = AtmosphericPressure::new::(500.0); + let t_e = AdiabaticEquivalentTemperature::new::(240.); + let wbt = DaviesJones1::compute_unchecked(t_e, pressure); + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + assert_eq!(captured_logs[0].body, "eq 4.8") + }); + assert_approx_eq!(f64, 238.88007446399956, wbt.get_si_value()); + + // eq 4.9 + let pressure = AtmosphericPressure::new::(850.0); + let t_e = AdiabaticEquivalentTemperature::new::(260.); + let wbt = DaviesJones1::compute_unchecked(t_e, pressure); + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + assert_eq!(captured_logs[0].body, "eq 4.9") + }); + assert_approx_eq!(f64, 256.61913438141625, wbt.get_si_value()); + + // eq 4.10 + let pressure = AtmosphericPressure::new::(1000.0); + let t_e = AdiabaticEquivalentTemperature::new::(330.); + let wbt = DaviesJones1::compute_unchecked(t_e, pressure); + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + assert_eq!(captured_logs[0].body, "eq 4.10") + }); + assert_approx_eq!(f64, 291.2797584152462, wbt.get_si_value()); + + // eq 4.11 + let pressure = AtmosphericPressure::new::(1000.0); + let t_e = AdiabaticEquivalentTemperature::new::(370.); + let wbt = DaviesJones1::compute_unchecked(t_e, pressure); + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + assert_eq!(captured_logs[0].body, "eq 4.11") + }); + assert_approx_eq!(f64, 300.1638142668842, wbt.get_si_value()); + } } diff --git a/src/tests/reference_values.rs b/src/tests/reference_values.rs index 7f1a790..b8284dc 100644 --- a/src/tests/reference_values.rs +++ b/src/tests/reference_values.rs @@ -15,7 +15,7 @@ pub(crate) const SH_NORM: Float = 0.012025701656390478; pub(crate) const THETAE_NORM: Float = 331.329289539998; pub(crate) const THETA_NORM: Float = 301.66581400702955; pub(crate) const THETAW_NORM: Float = 292.0717306393948; -pub(crate) const WBT_NORM: Float = 293.42728654340516; +pub(crate) const WBT_NORM: Float = 291.79619238702986; pub(crate) const VRT_NORM: Float = 302.1926517941886; pub(crate) const TIE_NORM: Float = 330.29726646682764; pub(crate) const TAE_NORM: Float = 331.8799684989062; @@ -33,7 +33,7 @@ pub(crate) const SH_FREEZ: Float = 0.000766508253376156; pub(crate) const THETAE_FREEZ: Float = 261.95287841149707; pub(crate) const THETA_FREEZ: Float = 260.0915766593588; pub(crate) const THETAW_FREEZ: Float = 258.6611332391296; -pub(crate) const WBT_FREEZ: Float = 258.40501060754224; +pub(crate) const WBT_FREEZ: Float = 258.4257262913491; pub(crate) const VRT_FREEZ: Float = 260.12112343315795; pub(crate) const TIE_FREEZ: Float = NAN; -pub(crate) const TAE_FREEZ: Float = NAN; +pub(crate) const TAE_FREEZ: Float = 261.9163911760276; From 90eb2eb8852c0612d236deaf0a7f82461874a298 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 May 2025 14:24:29 +0200 Subject: [PATCH 093/102] corrected information about source of theta_e equation --- benches/equivalent_potential_temperature.rs | 2 +- src/formulas/equivalent_potential_temperature.rs | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/benches/equivalent_potential_temperature.rs b/benches/equivalent_potential_temperature.rs index b53bd4c..97d740b 100644 --- a/benches/equivalent_potential_temperature.rs +++ b/benches/equivalent_potential_temperature.rs @@ -44,7 +44,7 @@ pub fn benchmark(c: &mut Criterion) { group.bench_function("Paluch1", |b| { b.iter(|| { - equivalent_potential_temperature::Paluch1::compute( + equivalent_potential_temperature::Kerry1::compute( ref_norm.temp, ref_norm.pres, ref_norm.mxrt, diff --git a/src/formulas/equivalent_potential_temperature.rs b/src/formulas/equivalent_potential_temperature.rs index 0b25a29..e5a9a13 100644 --- a/src/formulas/equivalent_potential_temperature.rs +++ b/src/formulas/equivalent_potential_temperature.rs @@ -28,10 +28,9 @@ type FormulaQuantity = EquivalentPotentialTemperature; /// temperature, pressure and mixing ratio and relative humidity. /// /// 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. +/// +/// Appears in Emmanuel, Kerry (1994). Atmospheric Convection. Oxford University Press. +/// as equation 4.5.11 /// /// Valid `temperature` range: 253K - 324K /// @@ -40,7 +39,7 @@ type FormulaQuantity = EquivalentPotentialTemperature; /// Valid `mixing_ratio` range: 0.000_000_1 - 2.0 /// /// Valid `relative_humidity` range: 0.000_000_1 - 2.0 -pub struct Paluch1; +pub struct Kerry1; impl Formula4< @@ -49,7 +48,7 @@ impl AtmosphericPressure, MixingRatio, RelativeHumidity, - > for Paluch1 + > for Kerry1 { #[inline(always)] fn validate_inputs( @@ -341,7 +340,7 @@ mod tests { AtmosphericPressure, MixingRatio, RelativeHumidity, - Paluch1, + Kerry1, >( Argument::new([253.0, 324.0]), Argument::new([100.0, 150_000.0]), From d48032e73f9bdfa131a2b50101e96e195b197c3d Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 May 2025 14:26:14 +0200 Subject: [PATCH 094/102] updated dependecies and rust edition --- Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0f8a170..a95f3aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "floccus" -version = "0.4.0-alpha.8" +version = "0.4.0-alpha.9" authors = ["Jakub Lewandowski "] -edition = "2021" +edition = "2024" description = "Formulae for air thermodynamic calculations" repository = "https://github.com/ScaleWeather/floccus" readme = "README.md" @@ -13,12 +13,12 @@ exclude = [".github/*"] [dependencies] floccus-proc = "0.3.0" -thiserror = "1.0" -float-cmp = "0.9" +thiserror = "2.0" +float-cmp = "0.10" log = { version = "0.4", optional = true } ndarray = { version = "0.16", default-features = false, optional = true } rayon = { version = "1.10", default-features = false, optional = true } -uom = { version = "0.36", default-features = false, features = [ +uom = { version = "0.37", default-features = false, features = [ "si", "std", "autoconvert", @@ -30,8 +30,8 @@ uom = { version = "0.36", default-features = false, features = [ doctest = false [dev-dependencies] -criterion = "0.5" -itertools = "0.13" +criterion = "0.6" +itertools = "0.14" log = "0.4" testing_logger = "0.1" floccus = { path = ".", features = ["double_precision"] } From 330cd32540a05422c9f32cd0b4a44f036dfe9a79 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 May 2025 14:40:37 +0200 Subject: [PATCH 095/102] IncorrectArgumentSet error argument does not have to be String There's nothing dynamic in the error generation so there's no point hindering the performance --- src/errors.rs | 2 +- src/formulas/equivalent_potential_temperature.rs | 8 ++++---- src/formulas/mixing_ratio.rs | 12 ++++++------ src/formulas/potential_temperature.rs | 8 ++++---- src/formulas/saturation_mixing_ratio.rs | 12 ++++++------ src/tests/four_arg.rs | 2 +- src/tests/one_arg.rs | 2 +- src/tests/three_arg.rs | 2 +- src/tests/two_arg.rs | 6 +++--- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index b7b0a0e..1973b9d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -51,5 +51,5 @@ pub enum InputError { "Provided arguments result in erronous output. Check documentation of the function and change one of arguments. Details: {0}" )] - IncorrectArgumentSet(String), + IncorrectArgumentSet(&'static str), } diff --git a/src/formulas/equivalent_potential_temperature.rs b/src/formulas/equivalent_potential_temperature.rs index e5a9a13..f60b77a 100644 --- a/src/formulas/equivalent_potential_temperature.rs +++ b/src/formulas/equivalent_potential_temperature.rs @@ -199,19 +199,19 @@ impl ulps = 2 ) { return Err(InputError::IncorrectArgumentSet( - "pressure must be greater than vapour pressure".to_string(), + "pressure must be greater than vapour pressure", )); } if vapour_pressure.0 > pressure.0 { return Err(InputError::IncorrectArgumentSet( - "pressure must be greater than vapour pressure".to_string(), + "pressure must be greater than vapour pressure", )); } if dewpoint.0 > temperature.0 { return Err(InputError::IncorrectArgumentSet( - "dewpoint must be less than temperature".to_string(), + "dewpoint must be less than temperature" )); } @@ -219,7 +219,7 @@ impl mixing_ratio.check_range_si(0.000_000_1, 2.0).or_else(|_| { Err(InputError::IncorrectArgumentSet( - "pressure and vapour_pressure must give mixing_ratio less than 2 so cannot be close to each other".to_string(), + "pressure and vapour_pressure must give mixing_ratio less than 2 so cannot be close to each other", )) } )?; diff --git a/src/formulas/mixing_ratio.rs b/src/formulas/mixing_ratio.rs index 3bb620c..29cb179 100644 --- a/src/formulas/mixing_ratio.rs +++ b/src/formulas/mixing_ratio.rs @@ -3,9 +3,9 @@ //! Mixing ratio is the ratio of the mass of a variable atmospheric constituent to the mass //! of dry air ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Mixing_ratio)). +use crate::Float; use crate::formulas::Formula2; use crate::quantities::{AtmosphericPressure, MixingRatio, QuantityHelpers, VapourPressure}; -use crate::Float; use crate::{constants::EPSILON, errors::InputError}; use float_cmp::approx_eq; @@ -33,9 +33,9 @@ impl Formula2 for Definiti vapour_pressure.check_range_si(0.0, 50_000.0)?; if vapour_pressure.0 > pressure.0 { - return Err(InputError::IncorrectArgumentSet(String::from( + return Err(InputError::IncorrectArgumentSet( "vapour_pressure cannot be greater than pressure", - ))); + )); } if approx_eq!( @@ -44,9 +44,9 @@ impl Formula2 for Definiti vapour_pressure.get_si_value(), ulps = 2 ) { - return Err(InputError::IncorrectArgumentSet(String::from( + return Err(InputError::IncorrectArgumentSet( "pressure and vapour_pressure cannot be equal", - ))); + )); } Ok(()) } @@ -63,7 +63,7 @@ impl Formula2 for Definiti #[cfg(test)] mod tests { - use crate::tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}; + use crate::tests::{Argument, test_with_2args, testing_traits::ReferenceAtmosphere}; use super::*; diff --git a/src/formulas/potential_temperature.rs b/src/formulas/potential_temperature.rs index 0c8d705..7a8dcdf 100644 --- a/src/formulas/potential_temperature.rs +++ b/src/formulas/potential_temperature.rs @@ -54,15 +54,15 @@ impl Formula3 pressure.0 { - return Err(InputError::IncorrectArgumentSet(String::from( + return Err(InputError::IncorrectArgumentSet( "vapour_pressure cannot be greater or equal to pressure", - ))); + )); } Ok(()) diff --git a/src/formulas/saturation_mixing_ratio.rs b/src/formulas/saturation_mixing_ratio.rs index 71aea7e..5c31276 100644 --- a/src/formulas/saturation_mixing_ratio.rs +++ b/src/formulas/saturation_mixing_ratio.rs @@ -3,12 +3,12 @@ //! Saturation mixing ration is the value of the mixing ratio of saturated air at the //! given temperature and pressure ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Saturation_mixing_ratio)). +use crate::Float; use crate::formulas::Formula2; use crate::quantities::{ AtmosphericPressure, MixingRatio, QuantityHelpers, RelativeHumidity, SaturationMixingRatio, SaturationVapourPressure, }; -use crate::Float; use crate::{constants::EPSILON, errors::InputError}; use float_cmp::approx_eq; @@ -33,9 +33,9 @@ impl Formula2 fo saturation_vapour_pressure.check_range_si(0.0, 50_000.0)?; if saturation_vapour_pressure.0 > pressure.0 { - return Err(InputError::IncorrectArgumentSet(String::from( + return Err(InputError::IncorrectArgumentSet( "saturation_vapour_pressure cannot be greater than pressure", - ))); + )); } if approx_eq!( @@ -44,9 +44,9 @@ impl Formula2 fo saturation_vapour_pressure.get_si_value(), ulps = 2 ) { - return Err(InputError::IncorrectArgumentSet(String::from( + return Err(InputError::IncorrectArgumentSet( "pressure and saturation_vapour_pressure cannot be equal", - ))); + )); } Ok(()) } @@ -93,7 +93,7 @@ impl Formula2 for Definition2 { #[cfg(test)] mod tests { - use crate::tests::{test_with_2args, testing_traits::ReferenceAtmosphere, Argument}; + use crate::tests::{Argument, test_with_2args, testing_traits::ReferenceAtmosphere}; use super::*; diff --git a/src/tests/four_arg.rs b/src/tests/four_arg.rs index 6b78640..2b5a27d 100644 --- a/src/tests/four_arg.rs +++ b/src/tests/four_arg.rs @@ -133,7 +133,7 @@ pub fn test_with_4args< Ok(r) => assert!(r.get_si_value().is_finite()), Err(e) => assert_eq!( - discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&InputError::IncorrectArgumentSet("")), discriminant(&e) ), } diff --git a/src/tests/one_arg.rs b/src/tests/one_arg.rs index ad47ca1..17ac347 100644 --- a/src/tests/one_arg.rs +++ b/src/tests/one_arg.rs @@ -53,7 +53,7 @@ pub fn test_with_1arg assert!(r.get_si_value().is_finite()), Err(e) => assert_eq!( - discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&InputError::IncorrectArgumentSet("")), discriminant(&e) ), } diff --git a/src/tests/three_arg.rs b/src/tests/three_arg.rs index 80b42b7..7f05e2c 100644 --- a/src/tests/three_arg.rs +++ b/src/tests/three_arg.rs @@ -80,7 +80,7 @@ pub fn test_with_3args< match result { Ok(r) => assert!(r.get_si_value().is_finite()), Err(e) => assert_eq!( - discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&InputError::IncorrectArgumentSet("")), discriminant(&e) ), } diff --git a/src/tests/two_arg.rs b/src/tests/two_arg.rs index d9b9edc..3988c08 100644 --- a/src/tests/two_arg.rs +++ b/src/tests/two_arg.rs @@ -2,13 +2,13 @@ use float_cmp::assert_approx_eq; #[cfg(feature = "array")] use ndarray::Array1; +use super::Argument; use super::check_result; use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; -use super::Argument; +use crate::Float; use crate::errors::InputError; use crate::formulas::Formula2; use crate::tests::check_range_error; -use crate::Float; use std::mem::discriminant; pub fn test_with_2args< @@ -61,7 +61,7 @@ pub fn test_with_2args< match result { Ok(r) => assert!(r.get_si_value().is_finite()), Err(e) => assert_eq!( - discriminant(&InputError::IncorrectArgumentSet(String::new())), + discriminant(&InputError::IncorrectArgumentSet("")), discriminant(&e) ), } From 27ada49531ff09da6fb23a5e4b6f64d12246bae5 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 May 2025 14:41:10 +0200 Subject: [PATCH 096/102] replace deprecated critertion::black_box --- benches/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/utils.rs b/benches/utils.rs index 12bb106..0cef4c6 100644 --- a/benches/utils.rs +++ b/benches/utils.rs @@ -1,6 +1,6 @@ #![allow(unused)] -use criterion::black_box; +use std::hint::black_box; use floccus::quantities::{ AtmosphericPressure, DewPointTemperature, DryBulbTemperature, EquivalentPotentialTemperature, MixingRatio, PotentialTemperature, RelativeHumidity, SaturationMixingRatio, From b2038437625520becb1a2b71435c95e132b981d9 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 May 2025 14:48:18 +0200 Subject: [PATCH 097/102] remove floccus_proc dependecy std::any:type_name can provide good enough information for debugging --- Cargo.toml | 1 - src/quantities.rs | 51 +++++++++++++++++++++-------------------------- src/tests.rs | 2 +- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a95f3aa..836f1f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ license = "Apache-2.0" exclude = [".github/*"] [dependencies] -floccus-proc = "0.3.0" thiserror = "2.0" float-cmp = "0.10" log = { version = "0.4", optional = true } diff --git a/src/quantities.rs b/src/quantities.rs index a7f72ee..d7057be 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -4,9 +4,8 @@ mod accessors; mod constructors; mod trait_impls; -use floccus_proc::Name; - -use crate::{errors::InputError, Float, Storage}; +use crate::{Float, Storage, errors::InputError}; +use std::any::type_name; use std::fmt::Debug; pub trait ThermodynamicQuantity: @@ -14,75 +13,71 @@ pub trait ThermodynamicQuantity: { } -pub(crate) trait QuantityName { - fn type_name_as_str() -> &'static str; -} - -pub(crate) trait QuantityHelpers: QuantityName + ThermodynamicQuantity { +pub(crate) trait QuantityHelpers: ThermodynamicQuantity { fn get_si_value(&self) -> Float; - fn name(&self) -> &'static str { - Self::type_name_as_str() + fn name() -> &'static str { + type_name::() } #[must_use] #[inline(always)] fn check_range_si(&self, lower_bound: Float, upper_bound: Float) -> Result<(), InputError> { if !(lower_bound..=upper_bound).contains(&self.get_si_value()) { - return Err(InputError::OutOfRange(self.name())); + return Err(InputError::OutOfRange(Self::name())); } Ok(()) } } -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct DryBulbTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct WetBulbTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct DewPointTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct VirtualTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct IsobaricEquivalentTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct AdiabaticEquivalentTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct PotentialTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct EquivalentPotentialTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct WetBulbPotentialTemperature(pub Storage::ThermodynamicTemperature); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct AtmosphericPressure(pub Storage::Pressure); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct VapourPressure(pub Storage::Pressure); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct SaturationVapourPressure(pub Storage::Pressure); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct VapourPressureDeficit(pub Storage::Pressure); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct MixingRatio(pub Storage::Ratio); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct SaturationMixingRatio(pub Storage::Ratio); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct SpecificHumidity(pub Storage::Ratio); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Name)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct RelativeHumidity(pub Storage::Ratio); diff --git a/src/tests.rs b/src/tests.rs index 80f0fe1..36ca171 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -31,7 +31,7 @@ impl Argument { } pub fn quantity_name(&self) -> &str { - I::type_name_as_str() + I::name() } pub fn ref_val(&self, atm: ReferenceAtmosphere) -> I { From 41fd78ae5f43eb6e805fa58f9db3ea8a62e82eab Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 May 2025 15:43:00 +0200 Subject: [PATCH 098/102] use macros to remove repeated code in quantity implementations --- src/quantities.rs | 117 +++++++++++++---------- src/quantities/accessors.rs | 163 --------------------------------- src/quantities/constructors.rs | 163 --------------------------------- src/quantities/trait_impls.rs | 111 ---------------------- 4 files changed, 68 insertions(+), 486 deletions(-) delete mode 100644 src/quantities/accessors.rs delete mode 100644 src/quantities/constructors.rs delete mode 100644 src/quantities/trait_impls.rs diff --git a/src/quantities.rs b/src/quantities.rs index d7057be..aa4d488 100644 --- a/src/quantities.rs +++ b/src/quantities.rs @@ -1,10 +1,6 @@ #![allow(missing_docs)] -mod accessors; -mod constructors; -mod trait_impls; - -use crate::{Float, Storage, errors::InputError}; +use crate::{Float, errors::InputError}; use std::any::type_name; use std::fmt::Debug; @@ -31,53 +27,76 @@ pub(crate) trait QuantityHelpers: ThermodynamicQuantity { } } -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct DryBulbTemperature(pub Storage::ThermodynamicTemperature); - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct WetBulbTemperature(pub Storage::ThermodynamicTemperature); - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct DewPointTemperature(pub Storage::ThermodynamicTemperature); - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct VirtualTemperature(pub Storage::ThermodynamicTemperature); - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct IsobaricEquivalentTemperature(pub Storage::ThermodynamicTemperature); - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct AdiabaticEquivalentTemperature(pub Storage::ThermodynamicTemperature); - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct PotentialTemperature(pub Storage::ThermodynamicTemperature); - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct EquivalentPotentialTemperature(pub Storage::ThermodynamicTemperature); - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct WetBulbPotentialTemperature(pub Storage::ThermodynamicTemperature); - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct AtmosphericPressure(pub Storage::Pressure); - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct VapourPressure(pub Storage::Pressure); +macro_rules! define_quantity { + ($quantity:ident, $storage:ident, $uom_module:ident, $si_unit:ident) => { + #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] + pub struct $quantity(pub crate::Storage::$storage); + + impl $quantity { + pub fn get(&self) -> Float + where + T: uom::si::$uom_module::Unit + uom::si::$uom_module::Conversion, + { + self.0.get::() + } + + pub fn new(value: Float) -> Self + where + T: uom::si::$uom_module::Unit + uom::si::$uom_module::Conversion, + { + Self(crate::Storage::$storage::new::(value)) + } + } -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct SaturationVapourPressure(pub Storage::Pressure); + impl ThermodynamicQuantity for $quantity {} -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct VapourPressureDeficit(pub Storage::Pressure); + impl QuantityHelpers for $quantity { + fn get_si_value(&self) -> Float { + self.get::() + } + } + }; +} -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct MixingRatio(pub Storage::Ratio); +macro_rules! define_temperature { + ($quantity:ident) => { + define_quantity!( + $quantity, + ThermodynamicTemperature, + thermodynamic_temperature, + kelvin + ); + }; +} -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct SaturationMixingRatio(pub Storage::Ratio); +macro_rules! define_pressure { + ($quantity:ident) => { + define_quantity!($quantity, Pressure, pressure, pascal); + }; +} -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct SpecificHumidity(pub Storage::Ratio); +macro_rules! define_ratio { + ($quantity:ident) => { + define_quantity!($quantity, Ratio, ratio, ratio); + }; +} -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] -pub struct RelativeHumidity(pub Storage::Ratio); +define_temperature!(DryBulbTemperature); +define_temperature!(WetBulbTemperature); +define_temperature!(DewPointTemperature); +define_temperature!(VirtualTemperature); +define_temperature!(IsobaricEquivalentTemperature); +define_temperature!(AdiabaticEquivalentTemperature); +define_temperature!(PotentialTemperature); +define_temperature!(EquivalentPotentialTemperature); +define_temperature!(WetBulbPotentialTemperature); + +define_pressure!(AtmosphericPressure); +define_pressure!(VapourPressure); +define_pressure!(SaturationVapourPressure); +define_pressure!(VapourPressureDeficit); + +define_ratio!(MixingRatio); +define_ratio!(SaturationMixingRatio); +define_ratio!(SpecificHumidity); +define_ratio!(RelativeHumidity); diff --git a/src/quantities/accessors.rs b/src/quantities/accessors.rs deleted file mode 100644 index 21af2d0..0000000 --- a/src/quantities/accessors.rs +++ /dev/null @@ -1,163 +0,0 @@ -use super::*; - -impl DryBulbTemperature { - pub fn get(&self) -> Float - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - self.0.get::() - } -} - -impl WetBulbTemperature { - pub fn get(&self) -> Float - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - self.0.get::() - } -} - -impl DewPointTemperature { - pub fn get(&self) -> Float - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - self.0.get::() - } -} - -impl VirtualTemperature { - pub fn get(&self) -> Float - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - self.0.get::() - } -} - -impl PotentialTemperature { - pub fn get(&self) -> Float - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - self.0.get::() - } -} - -impl IsobaricEquivalentTemperature { - pub fn get(&self) -> Float - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - self.0.get::() - } -} - -impl AdiabaticEquivalentTemperature { - pub fn get(&self) -> Float - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - self.0.get::() - } -} - -impl EquivalentPotentialTemperature { - pub fn get(&self) -> Float - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - self.0.get::() - } -} - -impl WetBulbPotentialTemperature { - pub fn get(&self) -> Float - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - self.0.get::() - } -} - -impl AtmosphericPressure { - pub fn get(&self) -> Float - where - T: uom::si::pressure::Unit + uom::si::pressure::Conversion, - { - self.0.get::() - } -} - -impl VapourPressure { - pub fn get(&self) -> Float - where - T: uom::si::pressure::Unit + uom::si::pressure::Conversion, - { - self.0.get::() - } -} - -impl SaturationVapourPressure { - pub fn get(&self) -> Float - where - T: uom::si::pressure::Unit + uom::si::pressure::Conversion, - { - self.0.get::() - } -} - -impl VapourPressureDeficit { - pub fn get(&self) -> Float - where - T: uom::si::pressure::Unit + uom::si::pressure::Conversion, - { - self.0.get::() - } -} - -impl MixingRatio { - pub fn get(&self) -> Float - where - T: uom::si::ratio::Unit + uom::si::ratio::Conversion, - { - self.0.get::() - } -} - -impl SaturationMixingRatio { - pub fn get(&self) -> Float - where - T: uom::si::ratio::Unit + uom::si::ratio::Conversion, - { - self.0.get::() - } -} - -impl SpecificHumidity { - pub fn get(&self) -> Float - where - T: uom::si::ratio::Unit + uom::si::ratio::Conversion, - { - self.0.get::() - } -} - -impl RelativeHumidity { - pub fn get(&self) -> Float - where - T: uom::si::ratio::Unit + uom::si::ratio::Conversion, - { - self.0.get::() - } -} diff --git a/src/quantities/constructors.rs b/src/quantities/constructors.rs deleted file mode 100644 index 1d12ebe..0000000 --- a/src/quantities/constructors.rs +++ /dev/null @@ -1,163 +0,0 @@ -use super::*; - -impl DryBulbTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl WetBulbTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl DewPointTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl VirtualTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl PotentialTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl IsobaricEquivalentTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl AdiabaticEquivalentTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl EquivalentPotentialTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl WetBulbPotentialTemperature { - pub fn new(value: Float) -> Self - where - T: uom::si::thermodynamic_temperature::Unit - + uom::si::thermodynamic_temperature::Conversion, - { - Self(Storage::ThermodynamicTemperature::new::(value)) - } -} - -impl AtmosphericPressure { - pub fn new(value: Float) -> Self - where - T: uom::si::pressure::Unit + uom::si::pressure::Conversion, - { - Self(Storage::Pressure::new::(value)) - } -} - -impl VapourPressure { - pub fn new(value: Float) -> Self - where - T: uom::si::pressure::Unit + uom::si::pressure::Conversion, - { - Self(Storage::Pressure::new::(value)) - } -} - -impl SaturationVapourPressure { - pub fn new(value: Float) -> Self - where - T: uom::si::pressure::Unit + uom::si::pressure::Conversion, - { - Self(Storage::Pressure::new::(value)) - } -} - -impl VapourPressureDeficit { - pub fn new(value: Float) -> Self - where - T: uom::si::pressure::Unit + uom::si::pressure::Conversion, - { - Self(Storage::Pressure::new::(value)) - } -} - -impl MixingRatio { - pub fn new(value: Float) -> Self - where - T: uom::si::ratio::Unit + uom::si::ratio::Conversion, - { - Self(Storage::Ratio::new::(value)) - } -} - -impl SaturationMixingRatio { - pub fn new(value: Float) -> Self - where - T: uom::si::ratio::Unit + uom::si::ratio::Conversion, - { - Self(Storage::Ratio::new::(value)) - } -} - -impl SpecificHumidity { - pub fn new(value: Float) -> Self - where - T: uom::si::ratio::Unit + uom::si::ratio::Conversion, - { - Self(Storage::Ratio::new::(value)) - } -} - -impl RelativeHumidity { - pub fn new(value: Float) -> Self - where - T: uom::si::ratio::Unit + uom::si::ratio::Conversion, - { - Self(Storage::Ratio::new::(value)) - } -} diff --git a/src/quantities/trait_impls.rs b/src/quantities/trait_impls.rs deleted file mode 100644 index c4f8d25..0000000 --- a/src/quantities/trait_impls.rs +++ /dev/null @@ -1,111 +0,0 @@ -use super::*; -use uom::si::{pressure::pascal, ratio::ratio, thermodynamic_temperature::kelvin}; - -impl ThermodynamicQuantity for DryBulbTemperature {} -impl ThermodynamicQuantity for WetBulbTemperature {} -impl ThermodynamicQuantity for DewPointTemperature {} -impl ThermodynamicQuantity for VirtualTemperature {} -impl ThermodynamicQuantity for IsobaricEquivalentTemperature {} -impl ThermodynamicQuantity for AdiabaticEquivalentTemperature {} -impl ThermodynamicQuantity for PotentialTemperature {} -impl ThermodynamicQuantity for EquivalentPotentialTemperature {} -impl ThermodynamicQuantity for WetBulbPotentialTemperature {} -impl ThermodynamicQuantity for AtmosphericPressure {} -impl ThermodynamicQuantity for VapourPressure {} -impl ThermodynamicQuantity for SaturationVapourPressure {} -impl ThermodynamicQuantity for VapourPressureDeficit {} -impl ThermodynamicQuantity for MixingRatio {} -impl ThermodynamicQuantity for SaturationMixingRatio {} -impl ThermodynamicQuantity for SpecificHumidity {} -impl ThermodynamicQuantity for RelativeHumidity {} - -impl QuantityHelpers for DryBulbTemperature { - fn get_si_value(&self) -> Float { - self.get::() - } -} -impl QuantityHelpers for WetBulbTemperature { - fn get_si_value(&self) -> Float { - self.get::() - } -} -impl QuantityHelpers for DewPointTemperature { - fn get_si_value(&self) -> Float { - self.get::() - } -} -impl QuantityHelpers for VirtualTemperature { - fn get_si_value(&self) -> Float { - self.get::() - } -} -impl QuantityHelpers for PotentialTemperature { - fn get_si_value(&self) -> Float { - self.get::() - } -} -impl QuantityHelpers for IsobaricEquivalentTemperature { - fn get_si_value(&self) -> Float { - self.get::() - } -} -impl QuantityHelpers for AdiabaticEquivalentTemperature { - fn get_si_value(&self) -> Float { - self.get::() - } -} -impl QuantityHelpers for EquivalentPotentialTemperature { - fn get_si_value(&self) -> Float { - self.get::() - } -} -impl QuantityHelpers for WetBulbPotentialTemperature { - fn get_si_value(&self) -> Float { - self.get::() - } -} - -impl QuantityHelpers for AtmosphericPressure { - fn get_si_value(&self) -> Float { - self.get::() - } -} -impl QuantityHelpers for VapourPressure { - fn get_si_value(&self) -> Float { - self.get::() - } -} -impl QuantityHelpers for SaturationVapourPressure { - fn get_si_value(&self) -> Float { - self.get::() - } -} - -impl QuantityHelpers for VapourPressureDeficit { - fn get_si_value(&self) -> Float { - self.get::() - } -} - -impl QuantityHelpers for MixingRatio { - fn get_si_value(&self) -> Float { - self.get::() - } -} - -impl QuantityHelpers for SaturationMixingRatio { - fn get_si_value(&self) -> Float { - self.get::() - } -} - -impl QuantityHelpers for SpecificHumidity { - fn get_si_value(&self) -> Float { - self.get::() - } -} -impl QuantityHelpers for RelativeHumidity { - fn get_si_value(&self) -> Float { - self.get::() - } -} From 17d7cba96bef4ce897f622354761444f69ed20cc Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 May 2025 16:31:23 +0200 Subject: [PATCH 099/102] replace custom error logging logic with use of tracing crate test are removed because there's no intrinsic logic present to test instrument proc macro must be applied to the compute function because it cannot handle prototype functions also improved readability of traits.rs --- .github/workflows/basic.yml | 28 +-- Cargo.toml | 9 +- .../adiabatic_equivalent_temperature.rs | 4 +- .../equivalent_potential_temperature.rs | 8 +- .../isobaric_equivalent_temperature.rs | 2 +- src/formulas/mixing_ratio.rs | 2 +- src/formulas/potential_temperature.rs | 2 +- src/formulas/relative_humidity.rs | 4 +- src/formulas/saturation_mixing_ratio.rs | 4 +- src/formulas/saturation_vapour_pressure.rs | 20 +- src/formulas/specific_humidity.rs | 2 +- src/formulas/traits.rs | 203 +++--------------- src/formulas/vapour_pressure.rs | 22 +- src/formulas/vapour_pressure_deficit.rs | 2 +- src/formulas/virtual_temperature.rs | 6 +- .../wet_bulb_potential_temperature.rs | 4 +- src/formulas/wet_bulb_temperature.rs | 4 +- src/tests/four_arg.rs | 26 +-- src/tests/one_arg.rs | 21 +- src/tests/three_arg.rs | 25 +-- src/tests/two_arg.rs | 17 -- 21 files changed, 101 insertions(+), 314 deletions(-) diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index e7b699c..af032fd 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -29,19 +29,19 @@ jobs: cargo test cargo test --no-default-features cargo test --features "double_precision" - cargo test --features "debug" + cargo test --features "argdebug" cargo test --features "array" cargo test --features "parallel" - cargo test --features "double_precision, debug" + cargo test --features "double_precision, argdebug" cargo test --features "double_precision, array" cargo test --features "double_precision, parallel" - cargo test --features "debug, array" - cargo test --features "debug, parallel" + cargo test --features "argdebug, array" + cargo test --features "argdebug, parallel" cargo test --features "array, parallel" - cargo test --features "double_precision, debug, array" - cargo test --features "double_precision, debug, parallel" + cargo test --features "double_precision, argdebug, array" + cargo test --features "double_precision, argdebug, parallel" cargo test --features "double_precision, array, parallel" - cargo test --features "debug, array, parallel" + cargo test --features "argdebug, array, parallel" cargo test --all-features cargo clean - name: Check release builds @@ -49,17 +49,17 @@ jobs: cargo build --release cargo build --release --no-default-features cargo build --release --features "double_precision" - cargo build --release --features "debug" + cargo build --release --features "argdebug" cargo build --release --features "array" cargo build --release --features "parallel" - cargo build --release --features "double_precision, debug" + cargo build --release --features "double_precision, argdebug" cargo build --release --features "double_precision, array" cargo build --release --features "double_precision, parallel" - cargo build --release --features "debug, array" - cargo build --release --features "debug, parallel" + cargo build --release --features "argdebug, array" + cargo build --release --features "argdebug, parallel" cargo build --release --features "array, parallel" - cargo build --release --features "double_precision, debug, array" - cargo build --release --features "double_precision, debug, parallel" + cargo build --release --features "double_precision, argdebug, array" + cargo build --release --features "double_precision, argdebug, parallel" cargo build --release --features "double_precision, array, parallel" - cargo build --release --features "debug, array, parallel" + cargo build --release --features "argdebug, array, parallel" cargo build --release --all-features diff --git a/Cargo.toml b/Cargo.toml index 836f1f1..032334c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,9 @@ exclude = [".github/*"] [dependencies] thiserror = "2.0" float-cmp = "0.10" -log = { version = "0.4", optional = true } +tracing = { version = "0.1", optional = true, default-features = false, features = [ + "attributes", +] } ndarray = { version = "0.16", default-features = false, optional = true } rayon = { version = "1.10", default-features = false, optional = true } uom = { version = "0.37", default-features = false, features = [ @@ -22,7 +24,6 @@ uom = { version = "0.37", default-features = false, features = [ "std", "autoconvert", "f32", - "f64", ] } [lib] @@ -37,8 +38,8 @@ floccus = { path = ".", features = ["double_precision"] } [features] default = [] -debug = ["dep:log"] -double_precision = [] +argdebug = ["dep:tracing"] +double_precision = ["uom/f64"] array = ["dep:ndarray"] parallel = ["array", "ndarray/rayon", "dep:rayon"] diff --git a/src/formulas/adiabatic_equivalent_temperature.rs b/src/formulas/adiabatic_equivalent_temperature.rs index 034f0f6..85f3676 100644 --- a/src/formulas/adiabatic_equivalent_temperature.rs +++ b/src/formulas/adiabatic_equivalent_temperature.rs @@ -30,7 +30,7 @@ pub struct Definition1; impl Formula2 for Definition1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, ) -> Result<(), InputError> { @@ -65,7 +65,7 @@ impl Formula2 Result<(), InputError> { diff --git a/src/formulas/equivalent_potential_temperature.rs b/src/formulas/equivalent_potential_temperature.rs index f60b77a..69f8d97 100644 --- a/src/formulas/equivalent_potential_temperature.rs +++ b/src/formulas/equivalent_potential_temperature.rs @@ -51,7 +51,7 @@ impl > for Kerry1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( temperature: DryBulbTemperature, pressure: AtmosphericPressure, mixing_ratio: MixingRatio, @@ -117,7 +117,7 @@ impl > for Bryan1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, relative_humidity: RelativeHumidity, @@ -181,7 +181,7 @@ impl > for Bolton1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( pressure: AtmosphericPressure, temperature: DryBulbTemperature, dewpoint: DewPointTemperature, @@ -308,7 +308,7 @@ impl EquivalentPotentialTemperature::new::(result) } - fn validate_inputs( + fn validate_inputs_internal( temperature: DryBulbTemperature, dewpoint: DewPointTemperature, mixing_ratio: MixingRatio, diff --git a/src/formulas/isobaric_equivalent_temperature.rs b/src/formulas/isobaric_equivalent_temperature.rs index 9ed4c0c..eef4bd4 100644 --- a/src/formulas/isobaric_equivalent_temperature.rs +++ b/src/formulas/isobaric_equivalent_temperature.rs @@ -25,7 +25,7 @@ pub struct Definition1; impl Formula2 for Definition1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, ) -> Result<(), InputError> { diff --git a/src/formulas/mixing_ratio.rs b/src/formulas/mixing_ratio.rs index 29cb179..0cd7b0b 100644 --- a/src/formulas/mixing_ratio.rs +++ b/src/formulas/mixing_ratio.rs @@ -25,7 +25,7 @@ pub struct Definition1; impl Formula2 for Definition1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( pressure: AtmosphericPressure, vapour_pressure: VapourPressure, ) -> Result<(), InputError> { diff --git a/src/formulas/potential_temperature.rs b/src/formulas/potential_temperature.rs index 7a8dcdf..6367735 100644 --- a/src/formulas/potential_temperature.rs +++ b/src/formulas/potential_temperature.rs @@ -39,7 +39,7 @@ impl Formula3 for Definition1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( mixing_ratio: MixingRatio, saturation_mixing_ratio: SaturationMixingRatio, ) -> Result<(), InputError> { @@ -51,7 +51,7 @@ pub struct Definition2; impl Formula2 for Definition2 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( vapour_pressure: VapourPressure, saturation_vapour_pressure: SaturationVapourPressure, ) -> Result<(), InputError> { diff --git a/src/formulas/saturation_mixing_ratio.rs b/src/formulas/saturation_mixing_ratio.rs index 5c31276..64a5353 100644 --- a/src/formulas/saturation_mixing_ratio.rs +++ b/src/formulas/saturation_mixing_ratio.rs @@ -25,7 +25,7 @@ pub struct Definition1; impl Formula2 for Definition1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( pressure: AtmosphericPressure, saturation_vapour_pressure: SaturationVapourPressure, ) -> Result<(), InputError> { @@ -72,7 +72,7 @@ pub struct Definition2; impl Formula2 for Definition2 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( mixing_ratio: MixingRatio, relative_humidity: RelativeHumidity, ) -> Result<(), InputError> { diff --git a/src/formulas/saturation_vapour_pressure.rs b/src/formulas/saturation_vapour_pressure.rs index 511af5f..c646cec 100644 --- a/src/formulas/saturation_vapour_pressure.rs +++ b/src/formulas/saturation_vapour_pressure.rs @@ -28,7 +28,7 @@ pub struct Definition1; impl Formula2 for Definition1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( vapour_pressure: VapourPressure, relative_humidity: RelativeHumidity, ) -> Result<(), InputError> { @@ -59,7 +59,7 @@ pub struct Buck1; impl Formula2 for Buck1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( temperature: DryBulbTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { @@ -108,7 +108,7 @@ pub struct Buck2; impl Formula2 for Buck2 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( temperature: DryBulbTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { @@ -157,7 +157,7 @@ pub struct Buck3; impl Formula2 for Buck3 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( temperature: DryBulbTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { @@ -201,7 +201,7 @@ pub struct Buck3Simplified; impl Formula1 for Buck3Simplified { #[inline(always)] - fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { + fn validate_inputs_internal(temperature: DryBulbTemperature) -> Result<(), InputError> { temperature.check_range_si(253.0, 324.0)?; Ok(()) @@ -235,7 +235,7 @@ pub struct Buck4; impl Formula2 for Buck4 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( temperature: DryBulbTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { @@ -279,7 +279,7 @@ pub struct Buck4Simplified; impl Formula1 for Buck4Simplified { #[inline(always)] - fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { + fn validate_inputs_internal(temperature: DryBulbTemperature) -> Result<(), InputError> { temperature.check_range_si(223.0, 274.0)?; Ok(()) @@ -312,7 +312,7 @@ pub struct Tetens1; impl Formula1 for Tetens1 { #[inline(always)] - fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { + fn validate_inputs_internal(temperature: DryBulbTemperature) -> Result<(), InputError> { temperature.check_range_si(273.0, 353.0)?; Ok(()) @@ -345,7 +345,7 @@ pub struct Wexler1; impl Formula1 for Wexler1 { #[inline(always)] - fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { + fn validate_inputs_internal(temperature: DryBulbTemperature) -> Result<(), InputError> { temperature.check_range_si(273.0, 374.0)?; Ok(()) @@ -390,7 +390,7 @@ pub struct Wexler2; impl Formula1 for Wexler2 { #[inline(always)] - fn validate_inputs(temperature: DryBulbTemperature) -> Result<(), InputError> { + fn validate_inputs_internal(temperature: DryBulbTemperature) -> Result<(), InputError> { temperature.check_range_si(173.0, 274.0)?; Ok(()) diff --git a/src/formulas/specific_humidity.rs b/src/formulas/specific_humidity.rs index 589446c..ca95449 100644 --- a/src/formulas/specific_humidity.rs +++ b/src/formulas/specific_humidity.rs @@ -25,7 +25,7 @@ pub struct Definition1; impl Formula2 for Definition1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( vapour_pressure: VapourPressure, pressure: AtmosphericPressure, ) -> Result<(), InputError> { diff --git a/src/formulas/traits.rs b/src/formulas/traits.rs index 6bed55d..9799548 100644 --- a/src/formulas/traits.rs +++ b/src/formulas/traits.rs @@ -1,63 +1,38 @@ #![allow(missing_docs)] +#![allow(clippy::missing_errors_doc)] use crate::{errors::InputError, quantities::ThermodynamicQuantity}; #[cfg(feature = "array")] use ndarray::{Array, ArrayView, Dimension, FoldWhile, Zip}; #[cfg(feature = "parallel")] use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; +#[cfg(feature = "argdebug")] +use tracing::instrument; pub trait Formula1 { - #[allow(missing_docs)] fn compute_unchecked(i1: I1) -> O; - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - fn validate_inputs(i1: I1) -> Result<(), InputError>; + fn validate_inputs_internal(i1: I1) -> Result<(), InputError>; + /// This function exist only to have tracing available + /// Hopefully compiler optimises it properly #[inline] - #[cfg(any(not(debug_assertions), not(feature = "debug")))] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - fn validate_inputs_internal(i1: I1) -> Result<(), InputError> { - Self::validate_inputs(i1) + #[cfg_attr(feature = "argdebug", instrument(level = "trace"))] + fn validate_inputs(i1: I1) -> Result<(), InputError> { + Self::validate_inputs_internal(i1) } - #[inline] - #[cfg(all(debug_assertions, feature = "debug"))] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - fn validate_inputs_internal(i1: I1) -> Result<(), InputError> { - use std::any::type_name; - - Self::validate_inputs(i1).or_else(|err| { - log::error!( - "Formula {} calculating {} from inputs {:?} returned error: {}", - type_name::(), - type_name::(), - i1, - err - ); - Err(err) - }) - } - - #[allow(clippy::missing_errors_doc)] - #[allow(missing_docs)] #[inline] fn compute(i1: I1) -> Result { - Self::validate_inputs_internal(i1)?; + Self::validate_inputs(i1)?; Ok(Self::compute_unchecked(i1)) } - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_vec(i1: &[I1]) -> Result, InputError> { i1.iter().map(|&i1| Self::compute(i1)).collect() } #[cfg(feature = "array")] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_ndarray<'a, D: Dimension + Copy, A: Into>>( i1: A, ) -> Result, InputError> @@ -67,7 +42,7 @@ pub trait Formula1 { let i1: ArrayView = i1.into(); Zip::from(i1) - .fold_while(Ok(()), |_, &i1| match Self::validate_inputs_internal(i1) { + .fold_while(Ok(()), |_, &i1| match Self::validate_inputs(i1) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), }) @@ -77,15 +52,11 @@ pub trait Formula1 { } #[cfg(feature = "parallel")] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_vec_parallel(i1: &[I1]) -> Result, InputError> { i1.into_par_iter().map(|&i1| Self::compute(i1)).collect() } #[cfg(feature = "parallel")] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_ndarray_parallel<'a, D: Dimension + Copy, A: Into>>( i1: A, ) -> Result, InputError> @@ -95,7 +66,7 @@ pub trait Formula1 { let i1: ArrayView = i1.into(); Zip::from(i1) - .fold_while(Ok(()), |_, &a| match Self::validate_inputs_internal(a) { + .fold_while(Ok(()), |_, &a| match Self::validate_inputs(a) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), }) @@ -106,51 +77,22 @@ pub trait Formula1 { } pub trait Formula2 { - #[allow(missing_docs)] fn compute_unchecked(i1: I1, i2: I2) -> O; - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - fn validate_inputs(i1: I1, i2: I2) -> Result<(), InputError>; - - #[inline] - #[cfg(any(not(debug_assertions), not(feature = "debug")))] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - fn validate_inputs_internal(i1: I1, i2: I2) -> Result<(), InputError> { - Self::validate_inputs(i1, i2) - } + fn validate_inputs_internal(i1: I1, i2: I2) -> Result<(), InputError>; #[inline] - #[cfg(all(debug_assertions, feature = "debug"))] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - fn validate_inputs_internal(i1: I1, i2: I2) -> Result<(), InputError> { - use std::any::type_name; - - Self::validate_inputs(i1, i2).or_else(|err| { - log::error!( - "Formula {} calculating {} from inputs {:?} {:?} returned error: {}", - type_name::(), - type_name::(), - i1, - i2, - err - ); - Err(err) - }) + #[cfg_attr(feature = "argdebug", instrument(level = "trace"))] + fn validate_inputs(i1: I1, i2: I2) -> Result<(), InputError> { + Self::validate_inputs_internal(i1, i2) } - #[allow(clippy::missing_errors_doc)] - #[allow(missing_docs)] #[inline] fn compute(i1: I1, i2: I2) -> Result { - Self::validate_inputs_internal(i1, i2)?; + Self::validate_inputs(i1, i2)?; Ok(Self::compute_unchecked(i1, i2)) } - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_vec(i1: &[I1], i2: &[I2]) -> Result, InputError> { i1.iter() .zip(i2.iter()) @@ -159,8 +101,6 @@ pub trait Formula2 FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), } @@ -193,8 +133,6 @@ pub trait Formula2 Result, InputError> { i1.into_par_iter() .zip(i2) @@ -203,8 +141,6 @@ pub trait Formula2 FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), } @@ -244,52 +180,22 @@ pub trait Formula3< I3: ThermodynamicQuantity, > { - #[allow(missing_docs)] fn compute_unchecked(i1: I1, i2: I2, i3: I3) -> O; - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - fn validate_inputs(i1: I1, i2: I2, i3: I3) -> Result<(), InputError>; + fn validate_inputs_internal(i1: I1, i2: I2, i3: I3) -> Result<(), InputError>; #[inline] - #[cfg(any(not(debug_assertions), not(feature = "debug")))] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - fn validate_inputs_internal(i1: I1, i2: I2, i3: I3) -> Result<(), InputError> { - Self::validate_inputs(i1, i2, i3) + #[cfg_attr(feature = "argdebug", instrument(level = "trace"))] + fn validate_inputs(i1: I1, i2: I2, i3: I3) -> Result<(), InputError> { + Self::validate_inputs_internal(i1, i2, i3) } - #[inline] - #[cfg(all(debug_assertions, feature = "debug"))] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - fn validate_inputs_internal(i1: I1, i2: I2, i3: I3) -> Result<(), InputError> { - use std::any::type_name; - - Self::validate_inputs(i1, i2, i3).or_else(|err| { - log::error!( - "Formula {} calculating {} from inputs {:?} {:?} {:?} returned error: {}", - type_name::(), - type_name::(), - i1, - i2, - i3, - err - ); - Err(err) - }) - } - - #[allow(clippy::missing_errors_doc)] - #[allow(missing_docs)] #[inline] fn compute(i1: I1, i2: I2, i3: I3) -> Result { - Self::validate_inputs_internal(i1, i2, i3)?; + Self::validate_inputs(i1, i2, i3)?; Ok(Self::compute_unchecked(i1, i2, i3)) } - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_vec(i1: &[I1], i2: &[I2], i3: &[I3]) -> Result, InputError> { i1.iter() .zip(i2.iter()) @@ -299,8 +205,6 @@ pub trait Formula3< } #[cfg(feature = "array")] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_ndarray< 'a, D: Dimension + Copy, @@ -326,7 +230,7 @@ pub trait Formula3< .and(i3) .fold_while( Ok(()), - |_, &i1, &i2, &i3| match Self::validate_inputs_internal(i1, i2, i3) { + |_, &i1, &i2, &i3| match Self::validate_inputs(i1, i2, i3) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), }, @@ -340,8 +244,6 @@ pub trait Formula3< } #[cfg(feature = "parallel")] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_vec_parallel(i1: &[I1], i2: &[I2], i3: &[I3]) -> Result, InputError> { i1.into_par_iter() .zip(i2) @@ -351,8 +253,6 @@ pub trait Formula3< } #[cfg(feature = "parallel")] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_ndarray_parallel< 'a, D: Dimension + Copy, @@ -378,7 +278,7 @@ pub trait Formula3< .and(i3) .fold_while( Ok(()), - |_, &i1, &i2, &i3| match Self::validate_inputs_internal(i1, i2, i3) { + |_, &i1, &i2, &i3| match Self::validate_inputs(i1, i2, i3) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), }, @@ -400,53 +300,22 @@ pub trait Formula4< I4: ThermodynamicQuantity, > { - #[allow(missing_docs)] fn compute_unchecked(i1: I1, i2: I2, i3: I3, i4: I4) -> O; - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - fn validate_inputs(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError>; + fn validate_inputs_internal(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError>; #[inline] - #[cfg(any(not(debug_assertions), not(feature = "debug")))] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - fn validate_inputs_internal(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError> { - Self::validate_inputs(i1, i2, i3, i4) + #[cfg_attr(feature = "argdebug", instrument(level = "trace"))] + fn validate_inputs(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError> { + Self::validate_inputs_internal(i1, i2, i3, i4) } - #[allow(clippy::missing_errors_doc)] - #[allow(missing_docs)] #[inline] fn compute(i1: I1, i2: I2, i3: I3, i4: I4) -> Result { - Self::validate_inputs_internal(i1, i2, i3, i4)?; + Self::validate_inputs(i1, i2, i3, i4)?; Ok(Self::compute_unchecked(i1, i2, i3, i4)) } - #[inline] - #[cfg(all(debug_assertions, feature = "debug"))] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] - fn validate_inputs_internal(i1: I1, i2: I2, i3: I3, i4: I4) -> Result<(), InputError> { - use std::any::type_name; - - Self::validate_inputs(i1, i2, i3, i4).or_else(|err| { - log::error!( - "Formula {} calculating {} from inputs {:?} {:?} {:?} {:?} returned error: {}", - type_name::(), - type_name::(), - i1, - i2, - i3, - i4, - err - ); - Err(err) - }) - } - - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_vec(i1: &[I1], i2: &[I2], i3: &[I3], i4: &[I4]) -> Result, InputError> { i1.iter() .zip(i2.iter()) @@ -457,8 +326,6 @@ pub trait Formula4< } #[cfg(feature = "array")] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_ndarray< 'a, D: Dimension + Copy, @@ -489,7 +356,7 @@ pub trait Formula4< .and(i4) .fold_while( Ok(()), - |_, &i1, &i2, &i3, &i4| match Self::validate_inputs_internal(i1, i2, i3, i4) { + |_, &i1, &i2, &i3, &i4| match Self::validate_inputs(i1, i2, i3, i4) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), }, @@ -504,8 +371,6 @@ pub trait Formula4< } #[cfg(feature = "parallel")] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_vec_parallel( i1: &[I1], i2: &[I2], @@ -521,8 +386,6 @@ pub trait Formula4< } #[cfg(feature = "parallel")] - #[allow(missing_docs)] - #[allow(clippy::missing_errors_doc)] fn compute_ndarray_parallel< 'a, D: Dimension + Copy, @@ -553,7 +416,7 @@ pub trait Formula4< .and(i4) .fold_while( Ok(()), - |_, &i1, &i2, &i3, &i4| match Self::validate_inputs_internal(i1, i2, i3, i4) { + |_, &i1, &i2, &i3, &i4| match Self::validate_inputs(i1, i2, i3, i4) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), }, diff --git a/src/formulas/vapour_pressure.rs b/src/formulas/vapour_pressure.rs index b70259a..6e1c69c 100644 --- a/src/formulas/vapour_pressure.rs +++ b/src/formulas/vapour_pressure.rs @@ -31,7 +31,7 @@ pub struct Definition1; impl Formula2 for Definition1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( specific_humidity: SpecificHumidity, pressure: AtmosphericPressure, ) -> Result<(), InputError> { @@ -65,7 +65,7 @@ pub struct Definition2; impl Formula2 for Definition2 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( saturation_vapour_pressure: SaturationVapourPressure, relative_humidity: RelativeHumidity, ) -> Result<(), InputError> { @@ -98,7 +98,7 @@ pub struct Buck1; impl Formula2 for Buck1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( dewpoint: DewPointTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { @@ -147,7 +147,7 @@ pub struct Buck2; impl Formula2 for Buck2 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( dewpoint: DewPointTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { @@ -196,7 +196,7 @@ pub struct Buck3; impl Formula2 for Buck3 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( dewpoint: DewPointTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { @@ -240,7 +240,7 @@ pub struct Buck3Simplified; impl Formula1 for Buck3Simplified { #[inline(always)] - fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { + fn validate_inputs_internal(dewpoint: DewPointTemperature) -> Result<(), InputError> { dewpoint.check_range_si(253.0, 324.0)?; Ok(()) @@ -274,7 +274,7 @@ pub struct Buck4; impl Formula2 for Buck4 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( dewpoint: DewPointTemperature, pressure: AtmosphericPressure, ) -> Result<(), InputError> { @@ -318,7 +318,7 @@ pub struct Buck4Simplified; impl Formula1 for Buck4Simplified { #[inline(always)] - fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { + fn validate_inputs_internal(dewpoint: DewPointTemperature) -> Result<(), InputError> { dewpoint.check_range_si(223.0, 274.0)?; Ok(()) @@ -351,7 +351,7 @@ pub struct Tetens1; impl Formula1 for Tetens1 { #[inline(always)] - fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { + fn validate_inputs_internal(dewpoint: DewPointTemperature) -> Result<(), InputError> { dewpoint.check_range_si(273.0, 353.0)?; Ok(()) @@ -384,7 +384,7 @@ pub struct Wexler1; impl Formula1 for Wexler1 { #[inline(always)] - fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { + fn validate_inputs_internal(dewpoint: DewPointTemperature) -> Result<(), InputError> { dewpoint.check_range_si(273.0, 374.0)?; Ok(()) @@ -429,7 +429,7 @@ pub struct Wexler2; impl Formula1 for Wexler2 { #[inline(always)] - fn validate_inputs(dewpoint: DewPointTemperature) -> Result<(), InputError> { + fn validate_inputs_internal(dewpoint: DewPointTemperature) -> Result<(), InputError> { dewpoint.check_range_si(173.0, 274.0)?; Ok(()) diff --git a/src/formulas/vapour_pressure_deficit.rs b/src/formulas/vapour_pressure_deficit.rs index cc46c87..e2c672b 100644 --- a/src/formulas/vapour_pressure_deficit.rs +++ b/src/formulas/vapour_pressure_deficit.rs @@ -21,7 +21,7 @@ pub struct Definition1; impl Formula2 for Definition1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( vapour_pressure: VapourPressure, saturation_vapour_pressure: SaturationVapourPressure, ) -> Result<(), InputError> { diff --git a/src/formulas/virtual_temperature.rs b/src/formulas/virtual_temperature.rs index fee78fa..dee536f 100644 --- a/src/formulas/virtual_temperature.rs +++ b/src/formulas/virtual_temperature.rs @@ -23,7 +23,7 @@ pub struct Definition1; impl Formula2 for Definition1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, ) -> Result<(), InputError> { @@ -61,7 +61,7 @@ impl Formula3 for Definition3 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( temperature: DryBulbTemperature, specific_humidity: SpecificHumidity, ) -> Result<(), InputError> { diff --git a/src/formulas/wet_bulb_potential_temperature.rs b/src/formulas/wet_bulb_potential_temperature.rs index 7e96150..8029d97 100644 --- a/src/formulas/wet_bulb_potential_temperature.rs +++ b/src/formulas/wet_bulb_potential_temperature.rs @@ -24,7 +24,7 @@ pub struct DaviesJones1; impl Formula1 for DaviesJones1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( equivalent_potential_temperature: EquivalentPotentialTemperature, ) -> Result<(), InputError> { equivalent_potential_temperature.check_range_si(257.0, 377.0)?; @@ -57,7 +57,7 @@ pub struct DaviesJones2; impl Formula1 for DaviesJones2 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( equivalent_potential_temperature: EquivalentPotentialTemperature, ) -> Result<(), InputError> { equivalent_potential_temperature.check_range_si(174.0, 377.0)?; diff --git a/src/formulas/wet_bulb_temperature.rs b/src/formulas/wet_bulb_temperature.rs index 51438dc..4a16f52 100644 --- a/src/formulas/wet_bulb_temperature.rs +++ b/src/formulas/wet_bulb_temperature.rs @@ -28,7 +28,7 @@ pub struct Stull1; impl Formula2 for Stull1 { #[inline(always)] - fn validate_inputs( + fn validate_inputs_internal( temperature: DryBulbTemperature, relative_humidity: RelativeHumidity, ) -> Result<(), InputError> { @@ -73,7 +73,7 @@ impl Formula2 Result<(), InputError> { diff --git a/src/tests/four_arg.rs b/src/tests/four_arg.rs index 2b5a27d..4563d94 100644 --- a/src/tests/four_arg.rs +++ b/src/tests/four_arg.rs @@ -3,13 +3,13 @@ use itertools::multiunzip; #[cfg(feature = "array")] use ndarray::Array1; +use super::Argument; use super::check_result; use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; -use super::Argument; +use crate::Float; use crate::errors::InputError; use crate::formulas::Formula4; use crate::tests::check_range_error; -use crate::Float; use std::mem::discriminant; pub fn test_with_4args< @@ -286,26 +286,4 @@ pub fn test_with_4args< result_imperial.get_si_value(), epsilon = 1e-12 ); - - #[cfg(feature = "debug")] - testing_logger::setup(); - #[cfg(feature = "debug")] - let _ = F::compute( - I1::new_si(-9999.0), - I2::new_si(-9999.0), - I3::new_si(-9999.0), - I4::new_si(-9999.0), - ); - - #[cfg(feature = "debug")] - testing_logger::validate(|captured_logs| { - assert_eq!(captured_logs.len(), 1); - let body = &captured_logs[0].body; - assert!(body.contains("Formula")); - assert!(body.contains("calculating")); - assert!(body.contains("from")); - assert!(body.contains("inputs")); - assert!(body.contains("returned error:")); - assert_eq!(captured_logs[0].level, log::Level::Error); - }); } diff --git a/src/tests/one_arg.rs b/src/tests/one_arg.rs index 17ac347..ef3e7f4 100644 --- a/src/tests/one_arg.rs +++ b/src/tests/one_arg.rs @@ -2,13 +2,13 @@ use float_cmp::assert_approx_eq; #[cfg(feature = "array")] use ndarray::Array1; +use super::Argument; use super::check_result; use super::testing_traits::{ReferenceAtmosphere, TestingQuantity}; -use super::Argument; +use crate::Float; use crate::errors::InputError; use crate::formulas::Formula1; use crate::tests::check_range_error; -use crate::Float; use std::mem::discriminant; pub fn test_with_1arg>( @@ -120,21 +120,4 @@ pub fn test_with_1arg Date: Sat, 24 May 2025 16:44:10 +0200 Subject: [PATCH 100/102] validate inputs in parallel for ndarray --- src/formulas/traits.rs | 75 +++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 48 deletions(-) diff --git a/src/formulas/traits.rs b/src/formulas/traits.rs index 9799548..3c3a9dd 100644 --- a/src/formulas/traits.rs +++ b/src/formulas/traits.rs @@ -65,12 +65,11 @@ pub trait Formula1 { { let i1: ArrayView = i1.into(); - Zip::from(i1) - .fold_while(Ok(()), |_, &a| match Self::validate_inputs(a) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }) - .into_inner()?; + Zip::from(i1).par_fold( + || Ok(()), + |_, &i1| Self::validate_inputs(i1), + |a, b| a.and(b), + )?; Ok(Zip::from(i1).par_map_collect(|&a| Self::compute_unchecked(a))) } @@ -119,11 +118,9 @@ pub trait Formula2 FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - } + .fold_while(Ok(()), |_, &i1, &i2| match Self::validate_inputs(i1, i2) { + Ok(_) => FoldWhile::Continue(Ok(())), + Err(e) => FoldWhile::Done(Err(e)), }) .into_inner()?; @@ -157,15 +154,11 @@ pub trait Formula2 = i1.into(); let i2: ArrayView = i2.into(); - Zip::from(i1) - .and(i2) - .fold_while(Ok(()), |_, &i1, &i2| { - match Self::validate_inputs(i1, i2) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - } - }) - .into_inner()?; + Zip::from(i1).and(i2).par_fold( + || Ok(()), + |_, &i1, &i2| Self::validate_inputs(i1, i2), + |a, b| a.and(b), + )?; Ok(Zip::from(i1) .and(i2) @@ -228,13 +221,12 @@ pub trait Formula3< Zip::from(i1) .and(i2) .and(i3) - .fold_while( - Ok(()), - |_, &i1, &i2, &i3| match Self::validate_inputs(i1, i2, i3) { + .fold_while(Ok(()), |_, &i1, &i2, &i3| { + match Self::validate_inputs(i1, i2, i3) { Ok(_) => FoldWhile::Continue(Ok(())), Err(e) => FoldWhile::Done(Err(e)), - }, - ) + } + }) .into_inner()?; Ok(Zip::from(i1) @@ -273,17 +265,11 @@ pub trait Formula3< let i2: ArrayView = i2.into(); let i3: ArrayView = i3.into(); - Zip::from(i1) - .and(i2) - .and(i3) - .fold_while( - Ok(()), - |_, &i1, &i2, &i3| match Self::validate_inputs(i1, i2, i3) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }, - ) - .into_inner()?; + Zip::from(i1).and(i2).and(i3).par_fold( + || Ok(()), + |_, &i1, &i2, &i3| Self::validate_inputs(i1, i2, i3), + |a, b| a.and(b), + )?; Ok(Zip::from(i1) .and(i2) @@ -410,18 +396,11 @@ pub trait Formula4< let i3: ArrayView = i3.into(); let i4: ArrayView = i4.into(); - Zip::from(i1) - .and(i2) - .and(i3) - .and(i4) - .fold_while( - Ok(()), - |_, &i1, &i2, &i3, &i4| match Self::validate_inputs(i1, i2, i3, i4) { - Ok(_) => FoldWhile::Continue(Ok(())), - Err(e) => FoldWhile::Done(Err(e)), - }, - ) - .into_inner()?; + Zip::from(i1).and(i2).and(i3).and(i4).par_fold( + || Ok(()), + |_, &i1, &i2, &i3, &i4| Self::validate_inputs(i1, i2, i3, i4), + |a, b| a.and(b), + )?; Ok(Zip::from(i1) .and(i2) From a70b05d187df499a9c6e06f1755c53341ad1cf81 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 May 2025 16:48:48 +0200 Subject: [PATCH 101/102] auto clippy fixes --- src/constants.rs | 2 +- src/formulas/equivalent_potential_temperature.rs | 7 ++----- src/formulas/wet_bulb_potential_temperature.rs | 2 +- src/formulas/wet_bulb_temperature.rs | 10 +++++----- src/tests.rs | 4 ++-- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 09722ad..c406794 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -131,7 +131,7 @@ pub(crate) const DIMLESS_ONE: Storage::Ratio = Storage::Ratio { value: 1.0, }; -/// Useful to convert TemperatureInterval into ThermodynamicTemperature +/// Useful to convert `TemperatureInterval` into `ThermodynamicTemperature` pub(crate) const ZERO_KELVIN: Storage::ThermodynamicTemperature = Storage::ThermodynamicTemperature { dimension: PhantomData, diff --git a/src/formulas/equivalent_potential_temperature.rs b/src/formulas/equivalent_potential_temperature.rs index 69f8d97..c94d2eb 100644 --- a/src/formulas/equivalent_potential_temperature.rs +++ b/src/formulas/equivalent_potential_temperature.rs @@ -217,12 +217,9 @@ impl let mixing_ratio = mixing_ratio::Definition1::compute_unchecked(pressure, vapour_pressure); - mixing_ratio.check_range_si(0.000_000_1, 2.0).or_else(|_| { - Err(InputError::IncorrectArgumentSet( + mixing_ratio.check_range_si(0.000_000_1, 2.0).map_err(|_| InputError::IncorrectArgumentSet( "pressure and vapour_pressure must give mixing_ratio less than 2 so cannot be close to each other", - )) - } - )?; + ))?; Ok(()) } diff --git a/src/formulas/wet_bulb_potential_temperature.rs b/src/formulas/wet_bulb_potential_temperature.rs index 8029d97..234a0b1 100644 --- a/src/formulas/wet_bulb_potential_temperature.rs +++ b/src/formulas/wet_bulb_potential_temperature.rs @@ -49,7 +49,7 @@ impl Formula1 for DaviesJones1 /// Formula for computing wet bulb potential temperature from equivalent potential temperature. /// /// Derived by R. Davies-Jones (2008) [(doi:10.1175/2007MWR2224.1)](https://doi.org/10.1175/2007MWR2224.1). -/// This is a very accurate rational-function approximation of [DaviesJones1] and two other (unimplemented) +/// This is a very accurate rational-function approximation of [`DaviesJones1`] and two other (unimplemented) /// formulas. /// /// Valid `equivalent_potential_temperature` range: 174K - 377K diff --git a/src/formulas/wet_bulb_temperature.rs b/src/formulas/wet_bulb_temperature.rs index 4a16f52..b025fa3 100644 --- a/src/formulas/wet_bulb_temperature.rs +++ b/src/formulas/wet_bulb_temperature.rs @@ -128,7 +128,7 @@ impl Formula2= 0.4 { + } else if (0.4..1.0).contains(&indicator) { t_w = eq_4_10(); #[cfg(test)] log::info!("eq 4.10"); @@ -200,7 +200,7 @@ mod tests { let wbt = DaviesJones1::compute_unchecked(t_e, pressure); testing_logger::validate(|captured_logs| { assert_eq!(captured_logs.len(), 1); - assert_eq!(captured_logs[0].body, "eq 4.8") + assert_eq!(captured_logs[0].body, "eq 4.8"); }); assert_approx_eq!(f64, 238.88007446399956, wbt.get_si_value()); @@ -210,7 +210,7 @@ mod tests { let wbt = DaviesJones1::compute_unchecked(t_e, pressure); testing_logger::validate(|captured_logs| { assert_eq!(captured_logs.len(), 1); - assert_eq!(captured_logs[0].body, "eq 4.9") + assert_eq!(captured_logs[0].body, "eq 4.9"); }); assert_approx_eq!(f64, 256.61913438141625, wbt.get_si_value()); @@ -220,7 +220,7 @@ mod tests { let wbt = DaviesJones1::compute_unchecked(t_e, pressure); testing_logger::validate(|captured_logs| { assert_eq!(captured_logs.len(), 1); - assert_eq!(captured_logs[0].body, "eq 4.10") + assert_eq!(captured_logs[0].body, "eq 4.10"); }); assert_approx_eq!(f64, 291.2797584152462, wbt.get_si_value()); @@ -230,7 +230,7 @@ mod tests { let wbt = DaviesJones1::compute_unchecked(t_e, pressure); testing_logger::validate(|captured_logs| { assert_eq!(captured_logs.len(), 1); - assert_eq!(captured_logs[0].body, "eq 4.11") + assert_eq!(captured_logs[0].body, "eq 4.11"); }); assert_approx_eq!(f64, 300.1638142668842, wbt.get_si_value()); } diff --git a/src/tests.rs b/src/tests.rs index 36ca171..78835ff 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -43,12 +43,12 @@ fn check_result(result: T, atm: ReferenceAtmosphere, eps: Fl let expected = T::ref_val_si(atm).get_si_value(); let result = result.get_si_value(); - assert_approx_eq!(Float, result, expected, epsilon = eps) + assert_approx_eq!(Float, result, expected, epsilon = eps); } pub fn check_range_error(result: InputError, expected_name: &str) { if let InputError::OutOfRange(name) = result { - assert_eq!(name, expected_name) + assert_eq!(name, expected_name); } else { panic!("wrong error type") } From 37132ab2deb7367217eda7825438dda294a33ff3 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 24 May 2025 17:04:11 +0200 Subject: [PATCH 102/102] more clippy fixes --- src/constants.rs | 2 +- src/errors.rs | 1 - .../adiabatic_equivalent_temperature.rs | 8 +-- .../equivalent_potential_temperature.rs | 12 ++-- .../isobaric_equivalent_temperature.rs | 4 +- src/formulas/mixing_ratio.rs | 4 +- src/formulas/potential_temperature.rs | 4 +- src/formulas/relative_humidity.rs | 8 +-- src/formulas/saturation_mixing_ratio.rs | 8 +-- src/formulas/saturation_vapour_pressure.rs | 71 ++++++++++--------- src/formulas/specific_humidity.rs | 4 +- src/formulas/vapour_pressure.rs | 45 ++++++------ src/formulas/vapour_pressure_deficit.rs | 4 +- src/formulas/virtual_temperature.rs | 12 ++-- .../wet_bulb_potential_temperature.rs | 26 +++---- src/formulas/wet_bulb_temperature.rs | 12 ++-- src/quantities.rs | 3 +- 17 files changed, 114 insertions(+), 114 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index c406794..8245ec9 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -39,7 +39,7 @@ pub const C_P: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { value: 1004.709, }; -/// Specific heat capacity of dry air at constant volume` (ECMWF, 2020) +/// Specific heat capacity of dry air at constant volume (ECMWF, 2020) pub const C_V: Storage::SpecificHeatCapacity = Storage::SpecificHeatCapacity { dimension: PhantomData, units: PhantomData, diff --git a/src/errors.rs b/src/errors.rs index 1973b9d..301e46c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -5,7 +5,6 @@ use thiserror::Error; #[derive(Error, Debug, PartialEq, Eq, Clone)] ///Error enum returned when provided input will cause function to return erronous result ///eg. `Inf` or `NaN` - pub enum InputError { #[error("Value of {0} out of a reasonable range.")] ///Error returned when provided input is out of reasonable range. diff --git a/src/formulas/adiabatic_equivalent_temperature.rs b/src/formulas/adiabatic_equivalent_temperature.rs index 85f3676..9322bc1 100644 --- a/src/formulas/adiabatic_equivalent_temperature.rs +++ b/src/formulas/adiabatic_equivalent_temperature.rs @@ -29,7 +29,7 @@ type FormulaQuantity = AdiabaticEquivalentTemperature; pub struct Definition1; impl Formula2 for Definition1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, @@ -40,7 +40,7 @@ impl Formula2 for Definition1 Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, @@ -64,7 +64,7 @@ pub struct Definition2; impl Formula2 for Definition2 { - #[inline(always)] + #[inline] fn validate_inputs_internal( equivalent_potential_temperature: EquivalentPotentialTemperature, pressure: AtmosphericPressure, @@ -75,7 +75,7 @@ impl Formula2 for Kerry1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, pressure: AtmosphericPressure, @@ -65,7 +65,7 @@ impl Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( temperature: DryBulbTemperature, pressure: AtmosphericPressure, @@ -116,7 +116,7 @@ impl PotentialTemperature, > for Bryan1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, @@ -131,7 +131,7 @@ impl Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, @@ -180,7 +180,7 @@ impl VapourPressure, > for Bolton1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( pressure: AtmosphericPressure, temperature: DryBulbTemperature, @@ -224,7 +224,7 @@ impl Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( pressure: AtmosphericPressure, temperature: DryBulbTemperature, diff --git a/src/formulas/isobaric_equivalent_temperature.rs b/src/formulas/isobaric_equivalent_temperature.rs index eef4bd4..cfce124 100644 --- a/src/formulas/isobaric_equivalent_temperature.rs +++ b/src/formulas/isobaric_equivalent_temperature.rs @@ -24,7 +24,7 @@ type FormulaQuantity = IsobaricEquivalentTemperature; pub struct Definition1; impl Formula2 for Definition1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, @@ -35,7 +35,7 @@ impl Formula2 for Definition1 Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, diff --git a/src/formulas/mixing_ratio.rs b/src/formulas/mixing_ratio.rs index 0cd7b0b..cfda1ef 100644 --- a/src/formulas/mixing_ratio.rs +++ b/src/formulas/mixing_ratio.rs @@ -24,7 +24,7 @@ type FormulaQuantity = MixingRatio; pub struct Definition1; impl Formula2 for Definition1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( pressure: AtmosphericPressure, vapour_pressure: VapourPressure, @@ -51,7 +51,7 @@ impl Formula2 for Definiti Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( pressure: AtmosphericPressure, vapour_pressure: VapourPressure, diff --git a/src/formulas/potential_temperature.rs b/src/formulas/potential_temperature.rs index 6367735..9a3e861 100644 --- a/src/formulas/potential_temperature.rs +++ b/src/formulas/potential_temperature.rs @@ -38,7 +38,7 @@ pub struct Definition1; impl Formula3 for Definition1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, pressure: AtmosphericPressure, @@ -68,7 +68,7 @@ impl Formula3 for Definition1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( mixing_ratio: MixingRatio, saturation_mixing_ratio: SaturationMixingRatio, @@ -32,7 +32,7 @@ impl Formula2 for Definitio Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( mixing_ratio: MixingRatio, saturation_mixing_ratio: SaturationMixingRatio, @@ -50,7 +50,7 @@ impl Formula2 for Definitio pub struct Definition2; impl Formula2 for Definition2 { - #[inline(always)] + #[inline] fn validate_inputs_internal( vapour_pressure: VapourPressure, saturation_vapour_pressure: SaturationVapourPressure, @@ -61,7 +61,7 @@ impl Formula2 for Def Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( vapour_pressure: VapourPressure, saturation_vapour_pressure: SaturationVapourPressure, diff --git a/src/formulas/saturation_mixing_ratio.rs b/src/formulas/saturation_mixing_ratio.rs index 64a5353..c76eed2 100644 --- a/src/formulas/saturation_mixing_ratio.rs +++ b/src/formulas/saturation_mixing_ratio.rs @@ -24,7 +24,7 @@ type FormulaQuantity = SaturationMixingRatio; pub struct Definition1; impl Formula2 for Definition1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( pressure: AtmosphericPressure, saturation_vapour_pressure: SaturationVapourPressure, @@ -51,7 +51,7 @@ impl Formula2 fo Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( pressure: AtmosphericPressure, saturation_vapour_pressure: SaturationVapourPressure, @@ -71,7 +71,7 @@ impl Formula2 fo pub struct Definition2; impl Formula2 for Definition2 { - #[inline(always)] + #[inline] fn validate_inputs_internal( mixing_ratio: MixingRatio, relative_humidity: RelativeHumidity, @@ -82,7 +82,7 @@ impl Formula2 for Definition2 { Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( mixing_ratio: MixingRatio, relative_humidity: RelativeHumidity, diff --git a/src/formulas/saturation_vapour_pressure.rs b/src/formulas/saturation_vapour_pressure.rs index c646cec..1ba141f 100644 --- a/src/formulas/saturation_vapour_pressure.rs +++ b/src/formulas/saturation_vapour_pressure.rs @@ -5,14 +5,14 @@ //! liquid or solid phase; that is, the vapor pressure of a system that has attained //! saturation but not supersaturation ([AMETSOC Glossary](https://glossary.ametsoc.org/wiki/Saturation_vapor_pressure)). +use crate::Float; +use crate::Storage::Pressure; use crate::errors::InputError; use crate::formulas::{Formula1, Formula2}; use crate::quantities::{ AtmosphericPressure, DryBulbTemperature, QuantityHelpers, RelativeHumidity, SaturationVapourPressure, VapourPressure, }; -use crate::Float; -use crate::Storage::Pressure; use uom::si::pressure::{hectopascal, kilopascal, pascal}; use uom::si::thermodynamic_temperature::{degree_celsius, kelvin}; @@ -27,7 +27,7 @@ type FormulaQuantity = SaturationVapourPressure; pub struct Definition1; impl Formula2 for Definition1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( vapour_pressure: VapourPressure, relative_humidity: RelativeHumidity, @@ -38,7 +38,7 @@ impl Formula2 for Definition1 Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( vapour_pressure: VapourPressure, relative_humidity: RelativeHumidity, @@ -58,7 +58,7 @@ impl Formula2 for Definition1 pub struct Buck1; impl Formula2 for Buck1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, pressure: AtmosphericPressure, @@ -69,7 +69,7 @@ impl Formula2 for Buck Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( temperature: DryBulbTemperature, pressure: AtmosphericPressure, @@ -107,7 +107,7 @@ impl Formula2 for Buck pub struct Buck2; impl Formula2 for Buck2 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, pressure: AtmosphericPressure, @@ -118,7 +118,7 @@ impl Formula2 for Buck Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( temperature: DryBulbTemperature, pressure: AtmosphericPressure, @@ -156,7 +156,7 @@ impl Formula2 for Buck pub struct Buck3; impl Formula2 for Buck3 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, pressure: AtmosphericPressure, @@ -167,7 +167,7 @@ impl Formula2 for Buck Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( temperature: DryBulbTemperature, pressure: AtmosphericPressure, @@ -200,14 +200,14 @@ impl Formula2 for Buck pub struct Buck3Simplified; impl Formula1 for Buck3Simplified { - #[inline(always)] + #[inline] fn validate_inputs_internal(temperature: DryBulbTemperature) -> Result<(), InputError> { temperature.check_range_si(253.0, 324.0)?; Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked(temperature: DryBulbTemperature) -> SaturationVapourPressure { let dewpoint = temperature.0.get::(); @@ -234,7 +234,7 @@ impl Formula1 for Buck3Simplified { pub struct Buck4; impl Formula2 for Buck4 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, pressure: AtmosphericPressure, @@ -245,7 +245,7 @@ impl Formula2 for Buck Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( temperature: DryBulbTemperature, pressure: AtmosphericPressure, @@ -278,15 +278,14 @@ impl Formula2 for Buck pub struct Buck4Simplified; impl Formula1 for Buck4Simplified { - #[inline(always)] + #[inline] fn validate_inputs_internal(temperature: DryBulbTemperature) -> Result<(), InputError> { temperature.check_range_si(223.0, 274.0)?; Ok(()) } - #[inline(always)] - + #[inline] fn compute_unchecked(temperature: DryBulbTemperature) -> SaturationVapourPressure { let dewpoint = temperature.0.get::(); @@ -311,14 +310,14 @@ impl Formula1 for Buck4Simplified { pub struct Tetens1; impl Formula1 for Tetens1 { - #[inline(always)] + #[inline] fn validate_inputs_internal(temperature: DryBulbTemperature) -> Result<(), InputError> { temperature.check_range_si(273.0, 353.0)?; Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked(temperature: DryBulbTemperature) -> SaturationVapourPressure { let dewpoint = temperature.0.get::(); @@ -344,14 +343,14 @@ impl Formula1 for Tetens1 { pub struct Wexler1; impl Formula1 for Wexler1 { - #[inline(always)] + #[inline] fn validate_inputs_internal(temperature: DryBulbTemperature) -> Result<(), InputError> { temperature.check_range_si(273.0, 374.0)?; Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked(temperature: DryBulbTemperature) -> SaturationVapourPressure { let dewpoint = temperature.get_si_value(); @@ -367,11 +366,13 @@ impl Formula1 for Wexler1 { 2.858_487, ]; - let mut ln_p = g[7] * dewpoint.ln(); - - for i in 0..=6 { - ln_p += g[i] * dewpoint.powi(i as i32 - 2); - } + let ln_p = g + .iter() + .enumerate() + .take(7) + .fold(g[7] * dewpoint.ln(), |acc, (i, &x)| { + acc + x * dewpoint.powi(i as i32 - 2) + }); let result = Pressure::new::(ln_p.exp()); @@ -389,14 +390,14 @@ impl Formula1 for Wexler1 { pub struct Wexler2; impl Formula1 for Wexler2 { - #[inline(always)] + #[inline] fn validate_inputs_internal(temperature: DryBulbTemperature) -> Result<(), InputError> { temperature.check_range_si(173.0, 274.0)?; Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked(temperature: DryBulbTemperature) -> SaturationVapourPressure { let dewpoint = temperature.0.get::(); @@ -410,11 +411,13 @@ impl Formula1 for Wexler2 { 0.691_865_1, ]; - let mut ln_p = big_k[5] * dewpoint.ln(); - - for j in 0..=4 { - ln_p += big_k[j] * dewpoint.powi(j as i32 - 1); - } + let ln_p = big_k + .iter() + .enumerate() + .take(5) + .fold(big_k[5] * dewpoint.ln(), |acc, (i, &x)| { + acc + x * dewpoint.powi(i as i32 - 1) + }); let result = Pressure::new::(ln_p.exp()); @@ -426,7 +429,7 @@ impl Formula1 for Wexler2 { mod tests { use crate::{ quantities::{AtmosphericPressure, DryBulbTemperature, RelativeHumidity, VapourPressure}, - tests::{test_with_1arg, test_with_2args, testing_traits::ReferenceAtmosphere, Argument}, + tests::{Argument, test_with_1arg, test_with_2args, testing_traits::ReferenceAtmosphere}, }; use super::*; diff --git a/src/formulas/specific_humidity.rs b/src/formulas/specific_humidity.rs index ca95449..f2bd48b 100644 --- a/src/formulas/specific_humidity.rs +++ b/src/formulas/specific_humidity.rs @@ -24,7 +24,7 @@ type FormulaQuantity = SpecificHumidity; pub struct Definition1; impl Formula2 for Definition1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( vapour_pressure: VapourPressure, pressure: AtmosphericPressure, @@ -35,7 +35,7 @@ impl Formula2 for Definiti Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( vapour_pressure: VapourPressure, pressure: AtmosphericPressure, diff --git a/src/formulas/vapour_pressure.rs b/src/formulas/vapour_pressure.rs index 6e1c69c..af30b4d 100644 --- a/src/formulas/vapour_pressure.rs +++ b/src/formulas/vapour_pressure.rs @@ -30,7 +30,7 @@ type FormulaQuantity = VapourPressure; pub struct Definition1; impl Formula2 for Definition1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( specific_humidity: SpecificHumidity, pressure: AtmosphericPressure, @@ -41,7 +41,7 @@ impl Formula2 for Defini Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( specific_humidity: SpecificHumidity, pressure: AtmosphericPressure, @@ -64,7 +64,7 @@ impl Formula2 for Defini pub struct Definition2; impl Formula2 for Definition2 { - #[inline(always)] + #[inline] fn validate_inputs_internal( saturation_vapour_pressure: SaturationVapourPressure, relative_humidity: RelativeHumidity, @@ -75,7 +75,7 @@ impl Formula2 for D Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( saturation_vapour_pressure: SaturationVapourPressure, relative_humidity: RelativeHumidity, @@ -97,7 +97,7 @@ impl Formula2 for D pub struct Buck1; impl Formula2 for Buck1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( dewpoint: DewPointTemperature, pressure: AtmosphericPressure, @@ -108,7 +108,7 @@ impl Formula2 for Buc Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( dewpoint: DewPointTemperature, pressure: AtmosphericPressure, @@ -146,7 +146,7 @@ impl Formula2 for Buc pub struct Buck2; impl Formula2 for Buck2 { - #[inline(always)] + #[inline] fn validate_inputs_internal( dewpoint: DewPointTemperature, pressure: AtmosphericPressure, @@ -157,7 +157,7 @@ impl Formula2 for Buc Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( dewpoint: DewPointTemperature, pressure: AtmosphericPressure, @@ -195,7 +195,7 @@ impl Formula2 for Buc pub struct Buck3; impl Formula2 for Buck3 { - #[inline(always)] + #[inline] fn validate_inputs_internal( dewpoint: DewPointTemperature, pressure: AtmosphericPressure, @@ -206,7 +206,7 @@ impl Formula2 for Buc Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( dewpoint: DewPointTemperature, pressure: AtmosphericPressure, @@ -239,14 +239,14 @@ impl Formula2 for Buc pub struct Buck3Simplified; impl Formula1 for Buck3Simplified { - #[inline(always)] + #[inline] fn validate_inputs_internal(dewpoint: DewPointTemperature) -> Result<(), InputError> { dewpoint.check_range_si(253.0, 324.0)?; Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked(dewpoint: DewPointTemperature) -> VapourPressure { let dewpoint = dewpoint.0.get::(); @@ -273,7 +273,7 @@ impl Formula1 for Buck3Simplified { pub struct Buck4; impl Formula2 for Buck4 { - #[inline(always)] + #[inline] fn validate_inputs_internal( dewpoint: DewPointTemperature, pressure: AtmosphericPressure, @@ -284,7 +284,7 @@ impl Formula2 for Buc Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( dewpoint: DewPointTemperature, pressure: AtmosphericPressure, @@ -317,15 +317,14 @@ impl Formula2 for Buc pub struct Buck4Simplified; impl Formula1 for Buck4Simplified { - #[inline(always)] + #[inline] fn validate_inputs_internal(dewpoint: DewPointTemperature) -> Result<(), InputError> { dewpoint.check_range_si(223.0, 274.0)?; Ok(()) } - #[inline(always)] - + #[inline] fn compute_unchecked(dewpoint: DewPointTemperature) -> VapourPressure { let dewpoint = dewpoint.0.get::(); @@ -350,14 +349,14 @@ impl Formula1 for Buck4Simplified { pub struct Tetens1; impl Formula1 for Tetens1 { - #[inline(always)] + #[inline] fn validate_inputs_internal(dewpoint: DewPointTemperature) -> Result<(), InputError> { dewpoint.check_range_si(273.0, 353.0)?; Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked(dewpoint: DewPointTemperature) -> VapourPressure { let dewpoint = dewpoint.0.get::(); @@ -383,14 +382,14 @@ impl Formula1 for Tetens1 { pub struct Wexler1; impl Formula1 for Wexler1 { - #[inline(always)] + #[inline] fn validate_inputs_internal(dewpoint: DewPointTemperature) -> Result<(), InputError> { dewpoint.check_range_si(273.0, 374.0)?; Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked(dewpoint: DewPointTemperature) -> VapourPressure { let dewpoint = dewpoint.get_si_value(); @@ -428,14 +427,14 @@ impl Formula1 for Wexler1 { pub struct Wexler2; impl Formula1 for Wexler2 { - #[inline(always)] + #[inline] fn validate_inputs_internal(dewpoint: DewPointTemperature) -> Result<(), InputError> { dewpoint.check_range_si(173.0, 274.0)?; Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked(dewpoint: DewPointTemperature) -> VapourPressure { let dewpoint = dewpoint.0.get::(); diff --git a/src/formulas/vapour_pressure_deficit.rs b/src/formulas/vapour_pressure_deficit.rs index e2c672b..a9bf4f2 100644 --- a/src/formulas/vapour_pressure_deficit.rs +++ b/src/formulas/vapour_pressure_deficit.rs @@ -20,7 +20,7 @@ type FormulaQuantity = VapourPressureDeficit; pub struct Definition1; impl Formula2 for Definition1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( vapour_pressure: VapourPressure, saturation_vapour_pressure: SaturationVapourPressure, @@ -31,7 +31,7 @@ impl Formula2 for Def Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( vapour_pressure: VapourPressure, saturation_vapour_pressure: SaturationVapourPressure, diff --git a/src/formulas/virtual_temperature.rs b/src/formulas/virtual_temperature.rs index dee536f..d492e04 100644 --- a/src/formulas/virtual_temperature.rs +++ b/src/formulas/virtual_temperature.rs @@ -22,7 +22,7 @@ type FormulaQuantity = VirtualTemperature; pub struct Definition1; impl Formula2 for Definition1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, @@ -33,7 +33,7 @@ impl Formula2 for Definition1 Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( temperature: DryBulbTemperature, mixing_ratio: MixingRatio, @@ -60,7 +60,7 @@ pub struct Definition2; impl Formula3 for Definition2 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, pressure: AtmosphericPressure, @@ -73,7 +73,7 @@ impl Formula3 for Definition3 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, specific_humidity: SpecificHumidity, @@ -106,7 +106,7 @@ impl Formula2 for Definit Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( temperature: DryBulbTemperature, specific_humidity: SpecificHumidity, diff --git a/src/formulas/wet_bulb_potential_temperature.rs b/src/formulas/wet_bulb_potential_temperature.rs index 234a0b1..cad1ed4 100644 --- a/src/formulas/wet_bulb_potential_temperature.rs +++ b/src/formulas/wet_bulb_potential_temperature.rs @@ -23,7 +23,7 @@ type FormulaQuantity = WetBulbPotentialTemperature; pub struct DaviesJones1; impl Formula1 for DaviesJones1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( equivalent_potential_temperature: EquivalentPotentialTemperature, ) -> Result<(), InputError> { @@ -32,7 +32,7 @@ impl Formula1 for DaviesJones1 Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( equivalent_potential_temperature: EquivalentPotentialTemperature, ) -> WetBulbPotentialTemperature { @@ -56,7 +56,7 @@ impl Formula1 for DaviesJones1 pub struct DaviesJones2; impl Formula1 for DaviesJones2 { - #[inline(always)] + #[inline] fn validate_inputs_internal( equivalent_potential_temperature: EquivalentPotentialTemperature, ) -> Result<(), InputError> { @@ -65,22 +65,22 @@ impl Formula1 for DaviesJones2 Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( equivalent_potential_temperature: EquivalentPotentialTemperature, ) -> WetBulbPotentialTemperature { let theta_e = equivalent_potential_temperature.get::(); let x = theta_e / 273.15; - let a0 = 7.101574; - let a1 = -20.68208; - let a2 = 16.11182; - let a3 = 2.574631; - let a4 = -5.205688; - let b1 = -3.552497; - let b2 = 3.781782; - let b3 = -0.6899655; - let b4 = -0.5929340; + let a0 = 7.101_574; + let a1 = -20.682_08; + let a2 = 16.111_82; + let a3 = 2.574_631; + let a4 = -5.205_688; + let b1 = -3.552_497; + let b2 = 3.781_782; + let b3 = -0.689_965_5; + let b4 = -0.592_934_0; let exponent = (a0 + a1 * x + a2 * x.powi(2) + a3 * x.powi(3) + a4 * x.powi(4)) / (1. + b1 * x + b2 * x.powi(2) + b3 * x.powi(3) + b4 * x.powi(4)); diff --git a/src/formulas/wet_bulb_temperature.rs b/src/formulas/wet_bulb_temperature.rs index b025fa3..ed3f2b6 100644 --- a/src/formulas/wet_bulb_temperature.rs +++ b/src/formulas/wet_bulb_temperature.rs @@ -21,13 +21,13 @@ type FormulaQuantity = WetBulbTemperature; /// /// Result error is within -1K to +0.65K, with mean absolute error of 0.28K /// -/// Valid `temperature` range: 253K - 324K - +/// Valid `temperature` range: 253K - 324K\ +/// /// Valid `relative_humidity` range: 0.05 - 0.99 pub struct Stull1; impl Formula2 for Stull1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( temperature: DryBulbTemperature, relative_humidity: RelativeHumidity, @@ -38,7 +38,7 @@ impl Formula2 for Stull1 Ok(()) } - #[inline(always)] + #[inline] fn compute_unchecked( temperature: DryBulbTemperature, relative_humidity: RelativeHumidity, @@ -72,7 +72,7 @@ pub struct DaviesJones1; impl Formula2 for DaviesJones1 { - #[inline(always)] + #[inline] fn validate_inputs_internal( equivalent_temperature: AdiabaticEquivalentTemperature, pressure: AtmosphericPressure, @@ -82,7 +82,7 @@ impl Formula2() } - #[must_use] - #[inline(always)] + #[inline] fn check_range_si(&self, lower_bound: Float, upper_bound: Float) -> Result<(), InputError> { if !(lower_bound..=upper_bound).contains(&self.get_si_value()) { return Err(InputError::OutOfRange(Self::name()));