Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: Using const generics to express quantities info #381

Open
mikialex opened this issue Oct 12, 2022 · 13 comments
Open

Suggestion: Using const generics to express quantities info #381

mikialex opened this issue Oct 12, 2022 · 13 comments

Comments

@mikialex
Copy link

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?

@crystal-growth
Copy link
Contributor

crystal-growth commented Oct 17, 2022

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.

@iliekturtles
Copy link
Owner

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).

@elpiel
Copy link

elpiel commented Nov 23, 2022

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

@iliekturtles
Copy link
Owner

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.

@haennes
Copy link

haennes commented Dec 24, 2022

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

@JASory
Copy link

JASory commented Feb 19, 2023

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?

@haennes
Copy link

haennes commented Feb 20, 2023

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. :)

@Tehforsch
Copy link

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

@haennes
Copy link

haennes commented Jul 6, 2023

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.

@haennes
Copy link

haennes commented Aug 3, 2023

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 :)

@haennes
Copy link

haennes commented Nov 2, 2023

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.

@iliekturtles
Copy link
Owner

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.

@haennes
Copy link

haennes commented Jan 26, 2024

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants