iliekturtles / uom

Units of measurement -- type-safe zero-cost dimensional analysis

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Suggestion: Using const generics to express quantities info

mikialex opened this issue · comments

commented

I'm investigating similar ideas like uom and looking at uom's implementation. But I think maybe use unstable feature const generics will make it much simpler.

here is the playground link for this code. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=9c4510c55df9fec6593c878d46321220

#[derive(Eq, PartialEq, Clone, Copy)]
pub struct Quantity<T> {
  pub factor: i8,
  pub unit: T,
}

impl<T: Copy> const std::ops::Add<Self> for Quantity<T> {
  type Output = Self;

  fn add(self, rhs: Self) -> Self::Output {
    Self {
      factor: self.factor + rhs.factor,
      unit: self.unit,
    }
  }
}

impl<T: Copy> const std::ops::Sub<Self> for Quantity<T> {
  type Output = Self;

  fn sub(self, rhs: Self) -> Self::Output {
    Self {
      factor: self.factor - rhs.factor,
      unit: self.unit,
    }
  }
}

impl<T> Quantity<T> {
  pub const fn new(factor: i8, unit: T) -> Self {
    Self { factor, unit }
  }
}

/// [International System of Quantities](https://jcgm.bipm.org/vim/en/1.6.html) (ISQ).
///
/// * `L`: Length dimension.
/// * `M`: Mass dimension.
/// * `T`: Time dimension.
/// * `I`: Electric current dimension.
/// * `Th`: Thermodynamic temperature dimension.
/// * `N`: Amount of substance dimension.
/// * `J`: Luminous intensity dimension.
#[derive(Eq, PartialEq, Copy, Clone)]
pub struct ISQ {
  pub length: Quantity<MeterBase>,
  pub mass: Quantity<KiloGramBase>,
  pub time: Quantity<SecondBase>,
  pub current: Quantity<AmpereBase>,
  pub temperature: Quantity<KelvinBase>,
  pub substance: Quantity<MoleBase>,
  pub luminous: Quantity<CandelaBase>,
}

impl const Default for ISQ {
  fn default() -> Self {
    Self {
      length: Quantity::new(0, MeterBase),
      mass: Quantity::new(0, KiloGramBase),
      time: Quantity::new(0, SecondBase),
      current: Quantity::new(0, AmpereBase),
      temperature: Quantity::new(0, KelvinBase),
      substance: Quantity::new(0, MoleBase),
      luminous: Quantity::new(0, CandelaBase),
    }
  }
}

impl const std::ops::Mul for ISQ {
  type Output = Self;

  fn mul(self, rhs: Self) -> Self::Output {
    Self {
      length: self.length + rhs.length,
      mass: self.mass + rhs.mass,
      time: self.time + rhs.time,
      current: self.current + rhs.current,
      temperature: self.temperature + rhs.temperature,
      substance: self.substance + rhs.substance,
      luminous: self.luminous + rhs.luminous,
    }
  }
}

impl const std::ops::Div for ISQ {
  type Output = Self;

  fn div(self, rhs: Self) -> Self::Output {
    Self {
      length: self.length - rhs.length,
      mass: self.mass - rhs.mass,
      time: self.time - rhs.time,
      current: self.current - rhs.current,
      temperature: self.temperature - rhs.temperature,
      substance: self.substance - rhs.substance,
      luminous: self.luminous - rhs.luminous,
    }
  }
}

#[derive(Eq, PartialEq, Copy, Clone, Default)]
pub struct MeterBase;
#[derive(Eq, PartialEq, Copy, Clone, Default)]
pub struct KiloGramBase;
#[derive(Eq, PartialEq, Copy, Clone, Default)]
pub struct SecondBase;
#[derive(Eq, PartialEq, Copy, Clone, Default)]
pub struct AmpereBase;
#[derive(Eq, PartialEq, Copy, Clone, Default)]
pub struct KelvinBase;
#[derive(Eq, PartialEq, Copy, Clone, Default)]
pub struct MoleBase;
#[derive(Eq, PartialEq, Copy, Clone, Default)]
pub struct CandelaBase;

#[derive(Clone, Copy)]
pub struct PhysicalValue<T, const Q: ISQ>(pub T);

impl<T, const Q: ISQ> std::ops::Add<Self> for PhysicalValue<T, Q>
where
  T: std::ops::Add<T, Output = T>,
{
  type Output = Self;

  fn add(self, rhs: Self) -> Self::Output {
    PhysicalValue(self.0 + rhs.0)
  }
}

impl<T, const Q1: ISQ, const Q2: ISQ> std::ops::Mul<PhysicalValue<T, Q2>> for PhysicalValue<T, Q1>
where
  T: std::ops::Mul<T, Output = T>,
  PhysicalValue<T, { Q1 * Q2 }>: Sync,
{
  type Output = PhysicalValue<T, { Q1 * Q2 }>;

  fn mul(self, rhs: PhysicalValue<T, Q2>) -> Self::Output {
    PhysicalValue(self.0 * rhs.0)
  }
}

impl<T, const Q1: ISQ, const Q2: ISQ> std::ops::Div<PhysicalValue<T, Q2>> for PhysicalValue<T, Q1>
where
  T: std::ops::Div<T, Output = T>,
  PhysicalValue<T, { Q1 / Q2 }>: Sync,
{
  type Output = PhysicalValue<T, { Q1 / Q2 }>;

  fn div(self, rhs: PhysicalValue<T, Q2>) -> Self::Output {
    PhysicalValue(self.0 / rhs.0)
  }
}

pub const METER_UNIT: ISQ = ISQ {
  length: Quantity::new(1, MeterBase),
  ..Default::default()
};
pub type Meter<T> = PhysicalValue<T, METER_UNIT>;

pub const KG_UNIT: ISQ = ISQ {
  mass: Quantity::new(1, KiloGramBase),
  ..Default::default()
};
pub type KiloGram<T> = PhysicalValue<T, KG_UNIT>;

pub const SECOND_UNIT: ISQ = ISQ {
  time: Quantity::new(1, SecondBase),
  ..Default::default()
};
pub type Second<T> = PhysicalValue<T, SECOND_UNIT>;

pub type MeterPerSecond<T> = PhysicalValue<T, { METER_UNIT / SECOND_UNIT }>;
pub type MeterPerSecondSquare<T> = PhysicalValue<T, { METER_UNIT / (SECOND_UNIT * SECOND_UNIT) }>;
pub type Newton<T> = PhysicalValue<T, { KG_UNIT * METER_UNIT / (SECOND_UNIT * SECOND_UNIT) }>;

pub fn test() {
  let time: Second<f32> = PhysicalValue(1.);
  let distance: Meter<f32> = PhysicalValue(1.);
  let mass: KiloGram<f32> = PhysicalValue(1.);

  let velocity = distance / time;
  let acceleration = velocity / time;

  // f = am
  let force: Newton<f32> = acceleration * mass;
}

It's just a suggestion, will the uom adopt this similar approach in the future?

I've also tested similar approach writing a toy library inspired by uom (dimensional_quantity) and found it quite convenient.
This also simplifies defining rules for Temperature and TemperatureInterval quantities, that allows to implement quantities with same dimension but different kind like Entropy and HeatCapacity easily.

The intention is to transition to const generics with a 2.0 or 3.0 release. The current design and using typenum will eventually be stabilized as 1.0. See some past discussion #134 (comment).

Would it make more sense to work in parallel on another crate that can leverage Const generics and generic associated types?

I will accept changes into a separate branch that could be merged once it has feature parity and isn't using nightly-only features. And of course anyone can work in a separate crate.

I am currently working on such a library. I will open a PR once i make my initial release

commented

I am currently working on such a library. I will open a PR once i make my initial release

I was thinking along the same lines do you have a public repository to accept contributions?

Hi. I have uploaded just now.

I have been quite busy lately and i just cant get over how to implement the auto-unit-part.

anyways this is the link: https://github.com/haennes/const_uom

I am ready accept contributions by anyone and on any topic. :)

I've been working on a similar crate for some time now and I've been using it in "production" for a couple of months by now and been pretty happy with the results. In case anybody is curious: https://crates.io/crates/diman

Finally uploaded an initial version of my REFACTORED code. here https://github.com/haennes/const_units
Note: It does not run as another major refactor is needed to make main.rs the build script. (which results in lots of things needing to be moved around)
I will do this after getting home from vacation. PRs are welcome! (could be anything. from fixing typos to documenting changes, to code contributions.)

Sorry for teasing this such a long time ago.

Just Updated it. The Units now are able to be generated. Still awaiting the somewhat stabilization of const trait-impls to advance further.

const_ops and const_traits are already implemented and constified. however Ratios, which internally require num-traits to be constified, still need to be constified.

I will happily accept contributions :)

Hello,
its me again.
I have now - after a couple of failed attempts to program this - published a document detailing the code architecture as well as the Interface of such a const version of uom.

It would be cool if someone could take a look at this and state their opinion about this.
@iliekturtles I think it would be best if you decided where this should be heading.

I am especially interested about your thoughts on:

  • using toml files in general
  • groups
  • how a unit is represented
  • where to stick in the information about the system of a unit
  • how conversions are handled

edit:
I originally envisioned sth like this to support different Systems without a conflict:

trait StorageDT {}


//T is the QName enum for the System
enum Quantity<T> {
    Simple(T),
    Complex(Vec<T>),
}

struct Unit<DT: StorageDT, SYSTEM, const QUANTITY: Quantity<SYSTEM>>(DT);

Sadly this isn't implemented at this point...
I couldn't find an rfc for this feature. I am however pretty confident that it exists somewhere.

Sorry for the extended delay! I've had a tab with this issue open for the last three months. I'm not going to have time to review and answer your questions in the foreseeable future. I'm going to prioritize open PRs at the moment but will leave the issue open for when I do have time to get to it.

Hi,
that fine. I have been tinkering around with my digital infrastructure at home.
Ok, I will probably start working on this in about 1 or 2 months from now.
I will mention significant progress updates here.
Contributers are welcome :)

If I at some Point - in the distant future 😂 get the feeling, that the fork is mature enough I will open a PR.

Thanks for your answer, leaving this tab open for three months suggests to me that there is an interest in this issue :)