Skip to content

Commit

Permalink
Merge pull request #69 from telus-agcg/feature/NAUM-10-commensurable_eq
Browse files Browse the repository at this point in the history
NAUM-10 Add v2::ops::CommensurableEq trait and impl
  • Loading branch information
turboladen authored May 28, 2024
2 parents 3d8e410 + f4abf3f commit ee83d44
Show file tree
Hide file tree
Showing 16 changed files with 229 additions and 4 deletions.
6 changes: 4 additions & 2 deletions crates/api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
implemented for `Measurement`.
- NAUM-8: Added traits `v2::convert::ToReduced<T>` and `TryToReduced<T>`, and implemented for `Unit`
and `Measurement`, respectively.
- NAUM-9: Added traits `v2::dim::Dimension<D>` and `IsCommensurableWith<D, Rhs>` and implemented,
respectively for `Atom`, `Measurement`, `Term`, `Cow<'a, [Term]'>`, `Unit`.
- NAUM-9: Added traits `v2::dim::Dimension<D>` and `IsCommensurableWith<D, Rhs>` and implemented for
`Atom`, `Measurement`, `Term`, `Cow<'a, [Term]'>`, `Unit`.
- NAUM-10: Added trait `v2::ops::IsCommensurableWith` and implemented for `Measurement`, `Unit`,
`Term`, and `Atom`.
- Added `Unit::into_terms()` for cases where you only need the `Term`s of the `Unit`.
- Added `unit` constant: `UNITY`
- Added `term` constants: `UNITY`, `UNITY_ARRAY`, and `UNITY_ARRAY_REF`.
Expand Down
24 changes: 24 additions & 0 deletions crates/api/benches/unit_benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,29 @@ fn from_str_group(c: &mut Criterion) {
//-----------------------------------------------------------------------------
bench_over_inputs_math!(partial_eq_group, "Unit::partial_eq()", ==);

fn commensurable_eq_group(c: &mut Criterion) {
#[cfg(feature = "v2")]
{
use wise_units::v2::ops::CommensurableEq;

let mut group = c.benchmark_group("Unit::commensurable_eq()");

for pair in common::UNIT_PAIRS {
group.bench_with_input(
BenchmarkId::new("commensurable_eq", format!("{}->{}", pair.0, pair.1)),
&pair,
|b, (lhs_str, rhs_str)| {
let lhs = Unit::from_str(lhs_str).unwrap();
let rhs = Unit::from_str(rhs_str).unwrap();

b.iter(|| lhs.commensurable_eq(&rhs));
},
);
}

group.finish()
}
}
//-----------------------------------------------------------------------------
// impl PartialOrd
//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -173,6 +196,7 @@ criterion_group!(
display_group,
from_str_group,
partial_eq_group,
commensurable_eq_group,
mul_group,
div_group,
partial_ord_gt_group,
Expand Down
1 change: 1 addition & 0 deletions crates/api/src/atom/v2.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod dim;
mod ops;
11 changes: 10 additions & 1 deletion crates/api/src/atom/v2/dim.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
use crate::{v2::dim::Dimension, Atom, Composable, Composition};
use crate::{
v2::dim::{Dimension, IsCommensurableWith},
Atom, Composable, Composition, IsCompatibleWith,
};

impl Dimension<Composition> for Atom {
fn dimension(&self) -> Composition {
Composable::composition(self)
}
}

impl IsCommensurableWith<Composition> for Atom {
fn is_commensurable_with(&self, rhs: &Self) -> bool {
IsCompatibleWith::is_compatible_with(self, rhs)
}
}
30 changes: 30 additions & 0 deletions crates/api/src/atom/v2/ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use approx::ulps_eq;

use crate::{
v2::{dim::IsCommensurableWith, ops::CommensurableEq},
Atom, Composition, UcumUnit,
};

impl CommensurableEq<Composition> for Atom {
fn commensurable_eq(&self, other: &Self) -> Option<bool> {
if !self.is_commensurable_with(other) {
return None;
}

Some(ulps_eq!(self.scalar(), other.scalar()))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn commensurable_eq_test() {
let lhs = Atom::Meter;

assert_eq!(lhs.commensurable_eq(&lhs), Some(true));
assert_eq!(lhs.commensurable_eq(&Atom::FootInternational), Some(false));
assert!(lhs.commensurable_eq(&Atom::Gram).is_none());
}
}
3 changes: 2 additions & 1 deletion crates/api/src/measurement/partial_eq.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{is_compatible_with::IsCompatibleWith, measurement::Measurement, ucum_unit::UcumUnit};
use approx::ulps_eq;

use crate::{is_compatible_with::IsCompatibleWith, measurement::Measurement, ucum_unit::UcumUnit};

/// `Measurement`s are `PartialEq` if
///
/// a) their `Unit`s are compatible
Expand Down
1 change: 1 addition & 0 deletions crates/api/src/measurement/v2.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
mod convert;
mod dim;
mod ops;
38 changes: 38 additions & 0 deletions crates/api/src/measurement/v2/ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use approx::ulps_eq;

use crate::{
v2::{dim::IsCommensurableWith, ops::CommensurableEq},
Composition, Measurement, UcumUnit,
};

impl CommensurableEq<Composition> for Measurement {
fn commensurable_eq(&self, other: &Self) -> Option<bool> {
if !self.is_commensurable_with(other) {
return None;
}

Some(ulps_eq!(self.scalar(), other.scalar()))
}
}

#[cfg(test)]
mod tests {
use crate::testing::const_units::{GRAM, KILOMETER, METER};

use super::*;

#[test]
fn commensurable_eq_test() {
let lhs = Measurement::new(1000.0, METER);
assert_eq!(lhs.commensurable_eq(&lhs), Some(true));

let rhs = Measurement::new(1.0, KILOMETER);
assert_eq!(lhs.commensurable_eq(&rhs), Some(true));

let rhs = Measurement::new(1.1, KILOMETER);
assert_eq!(lhs.commensurable_eq(&rhs), Some(false));

let rhs = Measurement::new(1000.0, GRAM);
assert_eq!(lhs.commensurable_eq(&rhs), None);
}
}
1 change: 1 addition & 0 deletions crates/api/src/term/v2.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod dim;
mod ops;
36 changes: 36 additions & 0 deletions crates/api/src/term/v2/ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use approx::ulps_eq;

use crate::{
v2::{dim::IsCommensurableWith, ops::CommensurableEq},
Composition, Term, UcumUnit,
};

impl CommensurableEq<Composition> for Term {
fn commensurable_eq(&self, other: &Self) -> Option<bool> {
if !self.is_commensurable_with(other) {
return None;
}

Some(ulps_eq!(self.scalar(), other.scalar()))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn commensurable_eq_test() {
let lhs = term!(Meter, factor: 1000);
let rhs = term!(Kilo, Meter);
assert_eq!(lhs.commensurable_eq(&rhs), Some(true));

let lhs = term!(Meter, factor: 10);
let rhs = term!(Kilo, Meter);
assert_eq!(lhs.commensurable_eq(&rhs), Some(false));

let lhs = term!(Meter);
let rhs = term!(Gram);
assert!(lhs.commensurable_eq(&rhs).is_none());
}
}
10 changes: 10 additions & 0 deletions crates/api/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ pub(crate) mod const_units {
}]),
};

pub(crate) const GRAM: Unit = Unit {
terms: Cow::Borrowed(&[Term {
factor: None,
prefix: None,
atom: Some(Atom::Gram),
exponent: None,
annotation: None,
}]),
};

pub(crate) const GRAM_METER: Unit = Unit {
terms: Cow::Borrowed(&[
Term {
Expand Down
2 changes: 2 additions & 0 deletions crates/api/src/unit/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ mod tests {
#[test]
#[allow(clippy::eq_op)]
fn validate_div() {
assert_eq!(METER / METER, UNITY);

let expected = Unit::from_str("m/km").unwrap();
assert_eq!(METER / KILOMETER, expected);

Expand Down
1 change: 1 addition & 0 deletions crates/api/src/unit/v2.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
mod convert;
mod dim;
mod ops;
36 changes: 36 additions & 0 deletions crates/api/src/unit/v2/ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use approx::ulps_eq;

use crate::{
v2::{dim::IsCommensurableWith, ops::CommensurableEq},
Composition, UcumUnit, Unit,
};

impl CommensurableEq<Composition> for Unit {
fn commensurable_eq(&self, other: &Self) -> Option<bool> {
if !self.is_commensurable_with(other) {
return None;
}

Some(ulps_eq!(self.scalar(), other.scalar()))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn commensurable_eq_test() {
let lhs = unit!(term!(Meter, factor: 1000));
let rhs = unit!(term!(Kilo, Meter));
assert_eq!(lhs.commensurable_eq(&rhs), Some(true));

let lhs = unit!(term!(Meter, factor: 10));
let rhs = unit!(term!(Kilo, Meter));
assert_eq!(lhs.commensurable_eq(&rhs), Some(false));

let lhs = unit!(term!(Meter));
let rhs = unit!(term!(Gram));
assert!(lhs.commensurable_eq(&rhs).is_none());
}
}
1 change: 1 addition & 0 deletions crates/api/src/v2.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod convert;
pub mod dim;
pub mod ops;
32 changes: 32 additions & 0 deletions crates/api/src/v2/ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::ops::Mul;

use super::dim::IsCommensurableWith;

/// Trait for comparisons that checks for equality first. Equality checks must only be done with
/// types that are commensurable. Return `None` if types are _not_ commensurable, otherwise
/// `Some(result)`.
///
/// This trait should be (well, should become) the de facto method for checking equality between
/// measurements (notice lower-case "m" there, indicating objects that can measure, not necessarily
/// only `Measurement`), etc. Historically, we've used `PartialEq` implementations, but those
/// should've been reserved for object equality, not semantic equality (this trait checks the
/// latter, not the former).
///
pub trait CommensurableEq<D, Rhs = Self>: IsCommensurableWith<D, Rhs>
where
Rhs: IsCommensurableWith<D>,
D: PartialEq + Mul<i32, Output = D>,
{
/// This method tests the commensurability between `self` and `other`, then checks if their
/// scalar values are equal.
///
fn commensurable_eq(&self, other: &Rhs) -> Option<bool>;

/// This method tests the commensurability between `self` and `other`, then checks if their
/// scalar values are _not_ equal.
///
#[inline]
fn commensurable_ne(&self, other: &Rhs) -> Option<bool> {
self.commensurable_eq(other).map(|r| !r)
}
}

0 comments on commit ee83d44

Please sign in to comment.