diff --git a/CHANGELOG.md b/CHANGELOG.md index b3c36cb..b3faad8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Electric and Coulomb constants added. [#47](https://github.com/itt-ustutt/quantity/issues/47) +- ATM and POISE added as derived units. [#46](https://github.com/itt-ustutt/quantity/issues/46) ## [0.13.0] - 2026-01-06 ### Changed diff --git a/si-units/CHANGELOG.md b/si-units/CHANGELOG.md index 1b14330..a400b2e 100644 --- a/si-units/CHANGELOG.md +++ b/si-units/CHANGELOG.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- Correctly formats output of arrays with a unit. + +### Added +- Getter and Setter generalized to common NumPy indexing, such that general slicing operations are possible in multiple dimensions. ## [0.11.1] - 2026-01-22 ### Fixed diff --git a/si-units/docs/derived.md b/si-units/docs/derived.md index 94cdf0a..a76f13c 100644 --- a/si-units/docs/derived.md +++ b/si-units/docs/derived.md @@ -23,28 +23,32 @@ For convenience, a number of commonly used units that are not directly combinati These constants simplify the specification of properties, that are not given in SI units. However, as the representation of quantities is unique, they do not appear in formatted outputs. -| Unit | Unit symbol | Quantity | Definition | -| -------- | ------------ | ------------- | --------------------------------------------- | -| ANGSTROM | $\text{Å}$ | length | $10^{-10}~\text{m}$ | -| AMU | $\text{u}$ | mass | $1.6605390671738466\times 10^{-27}~\text{kg}$ | -| AU | $\text{au}$ | length | $149597870700~\text{m}$ | -| BAR | $\text{bar}$ | pressure | $10^5~\text{Pa}$ | -| CALORIE | $\text{cal}$ | energy | $4.184~\text{J}$ | -| DAY | $\text{d}$ | time | $86400~\text{s}$ | -| DEBYE | $\text{De}$ | dipole moment | $\sqrt{10^{-19}~\text{JÅ}^3}$ | -| DEGREES | $^\circ$ | angle | $\frac{\pi}{180}~\text{rad}$ | -| GRAM | $\text{g}$ | mass | $10^{-3}~\text{kg}$ | -| HOUR | $\text{h}$ | time | $3600~\text{s}$ | -| LITER | $\text{l}$ | volume | $10^{-3}~\text{m}^3$ | -| MINUTE | $\text{min}$ | time | $60~\text{s}$ | -| RADIANS | $\text{rad}$ | angle | | +| Unit | Unit symbol | Quantity | Definition | +| -------- | --------------- | ------------- | --------------------------------------------- | +| ANGSTROM | $\text{Å}$ | length | $10^{-10}~\text{m}$ | +| AMU | $\text{u}$ | mass | $1.6605390671738466\times 10^{-27}~\text{kg}$ | +| ATM | $\text{bar}$ | presssure | $101325~\text{N/m^2}$ | +| AU | $\text{au}$ | length | $149597870700~\text{m}$ | +| BAR | $\text{bar}$ | pressure | $10^5~\text{Pa}$ | +| CALORIE | $\text{cal}$ | energy | $4.184~\text{J}$ | +| DAY | $\text{d}$ | time | $86400~\text{s}$ | +| DEBYE | $\text{De}$ | dipole moment | $\sqrt{10^{-19}~\text{JÅ}^3}$ | +| DEGREES | $^\circ$ | angle | $\frac{\pi}{180}~\text{rad}$ | +| GRAM | $\text{g}$ | mass | $10^{-3}~\text{kg}$ | +| HOUR | $\text{h}$ | time | $3600~\text{s}$ | +| LITER | $\text{l}$ | volume | $10^{-3}~\text{m}^3$ | +| MINUTE | $\text{min}$ | time | $60~\text{s}$ | +| POISE | $\text{m Pa s}$ | viscosity | $0.1~\text{Pa s}$ | +| RADIANS | $\text{rad}$ | angle | | ## Additional Constants -| Constant | Name | Symbol | Value | -| -------- | ---------------------- | ------------------------- | ------------------------------------------------------------------- | -| G | Gravitational constant | $G$ | $6.6743\times 10^{-11}~\frac{\text{m}^3}{\text{kg}\cdot\text{s}^2}$ | -| RGAS | Ideal gas constant | $R=N_\text{Av}k_\text{B}$ | $8.31446261815324~\frac{\text{J}}{\text{mol}\cdot\text{K}}$ | +| Constant | Name | Symbol | Value | +| -------- | ---------------------- | ----------------------------------------- | ---------------------------------------------------------------------- | +| G | Gravitational constant | $G$ | $6.6743\times 10^{-11}~\frac{\text{m}^3}{\text{kg}\cdot\text{s}^2}$ | +| EPSILON0 | Electric constant | $\epsilon_0$ | $8.8541878188e-12~\frac{\text{A}\cdot\text{s}}{\text{V}\cdot\text{m}}$ | +| KE | Coulomb constant | $k_\text{e} = \frac{1}{4 \pi \epsilon_0}$ | $8987551786.170797~\frac{\text{N}\cdot\text{m}^2}{\text{C}^2}$ | +| RGAS | Ideal gas constant | $R=N_\text{Av}k_\text{B}$ | $8.31446261815324~\frac{\text{J}}{\text{mol}\cdot\text{K}}$ | ### Example diff --git a/si-units/src/fmt.rs b/si-units/src/fmt.rs index bce1152..6a80bf6 100644 --- a/si-units/src/fmt.rs +++ b/si-units/src/fmt.rs @@ -73,6 +73,15 @@ impl SINumber { format!("{}\\,{}", float_to_latex(self.value), &self.unit.to_latex()) } } + + pub fn into_scaled_parts(&self) -> (f64, String) { + if let Some((unit, symbol, _, _, _)) = DERIVED_UNITS.get(&self.unit) { + if !self.value.is_nan() { + return (self.value / unit.value, symbol.clone()); + } + } + (self.value, self.unit.to_string()) + } } fn float_to_latex(f: f64) -> String { diff --git a/si-units/src/lib.rs b/si-units/src/lib.rs index 9067b22..71d7980 100644 --- a/si-units/src/lib.rs +++ b/si-units/src/lib.rs @@ -1,5 +1,7 @@ #![warn(clippy::all)] #![allow(non_snake_case)] +use std::f64::consts::PI; + use ndarray::{Array1, arr1}; use numpy::IntoPyArray; use pyo3::basic::CompareOp; @@ -78,11 +80,20 @@ impl PySIObject { if let Ok(v) = self.value.extract::(py) { Ok(SINumber::new(v, self.unit).to_string()) } else { - let value = self - .value - .call_method0(py, "__repr__")? - .extract::(py)?; - Ok(format!("{} {}", value, self.unit)) + let (multiplier, symbol) = SINumber::new(1.0, self.unit).into_scaled_parts(); + + let value_str = if (multiplier - 1.0).abs() > 1e-15 { + let scaled_val = self.value.call_method1(py, "__mul__", (multiplier,))?; + scaled_val + .call_method0(py, "__repr__")? + .extract::(py)? + } else { + self.value + .call_method0(py, "__repr__")? + .extract::(py)? + }; + + Ok(format!("{} {}", value_str, symbol)) } } @@ -258,25 +269,43 @@ impl PySIObject { .and_then(|v| v.extract::(py)) } - fn __getitem__(&self, py: Python, idx: isize) -> PyResult { + fn __getitem__(&self, py: Python, idx: &Bound<'_, PyAny>) -> PyResult { let value = self.value.call_method1(py, "__getitem__", (idx,))?; Ok(Self::new(value, self.unit)) } - fn __setitem__(&self, py: Python, idx: isize, value: SINumber) -> PyResult<()> { - if self.unit == value.unit { + fn __setitem__( + &self, + py: Python, + idx: &Bound<'_, PyAny>, + value: &Bound<'_, PyAny>, + ) -> PyResult<()> { + if let Ok(v) = value.extract::() { + if self.unit == v.unit { + self.value.call_method1(py, "__setitem__", (idx, v.value))?; + return Ok(()); + } + return Err(QuantityError::InconsistentUnits { + unit1: self.unit, + unit2: v.unit, + } + .into()); + } + + let ob = value.extract::>()?; + if self.unit == ob.unit { self.value - .call_method1(py, "__setitem__", (idx, value.value))?; + .call_method1(py, "__setitem__", (idx, &ob.value))?; Ok(()) } else { Err(QuantityError::InconsistentUnits { unit1: self.unit, - unit2: value.unit, - })? + unit2: ob.unit, + } + .into()) } } } - #[derive(Clone, Copy)] pub struct SIObject { value: T, @@ -396,6 +425,9 @@ const _TESLA: SIUnit = SIUnit([0, 1, -2, -1, 0, 0, 0]); const _HENRY: SIUnit = SIUnit([2, 1, -2, -2, 0, 0, 0]); const _METER_PER_SECOND: SIUnit = SIUnit([1, 0, -1, 0, 0, 0, 0]); const _LUMEN_PER_WATT: SIUnit = SIUnit([-2, -1, 3, 0, 0, 0, 1]); +const _FARAD_PER_METER: SIUnit = SIUnit([-3, -1, 4, 2, 0, 0, 0]); +const _METER_PER_FARAD: SIUnit = SIUnit([3, 1, -4, -2, 0, 0, 0]); +const _PASCAL_SECOND: SIUnit = SIUnit([-1, 1, -1, 0, 0, 0, 0]); /// Prefix quecto $\\left(\text{q}=10^{-30}\\right)$ pub const QUECTO: f64 = 1e-30; @@ -471,6 +503,13 @@ pub fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> { add_constant(m, "KB", 1.380649e-23, _JOULE_PER_KELVIN)?; add_constant(m, "NAV", 6.02214076e23, _PER_MOL)?; add_constant(m, "KCD", 683.0, _LUMEN_PER_WATT)?; + add_constant(m, "EPSILON0", 8.8541878188e-12, _FARAD_PER_METER)?; + add_constant( + m, + "KE", + 1.0_f64 / (4.0_f64 * PI * 8.8541878188e-12), + _METER_PER_FARAD, + )?; add_constant(m, "HERTZ", 1.0, _HERTZ)?; add_constant(m, "NEWTON", 1.0, _NEWTON)?; @@ -489,6 +528,7 @@ pub fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> { add_constant(m, "ANGSTROM", 1e-10, _METER)?; add_constant(m, "AMU", 1.6605390671738466e-27, _KILOGRAM)?; add_constant(m, "AU", 149597870700.0, _METER)?; + add_constant(m, "ATM", 101325.0, _PASCAL)?; add_constant(m, "BAR", 1e5, _PASCAL)?; add_constant(m, "CALORIE", 4.184, _JOULE)?; m.add("CELSIUS", Celsius)?; @@ -499,6 +539,7 @@ pub fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> { add_constant(m, "HOUR", 3600.0, _SECOND)?; add_constant(m, "LITER", 1e-3, _CUBIC_METER)?; add_constant(m, "MINUTE", 60.0, _SECOND)?; + add_constant(m, "POISE", 0.1, _PASCAL_SECOND)?; m.add("RADIANS", Angle(1.0))?; add_constant(m, "G", 6.6743e-11, SIUnit([3, -1, -2, 0, 0, 0, 0]))?; diff --git a/si-units/src/si_units/__init__.py b/si-units/src/si_units/__init__.py index c95390d..d73dec0 100644 --- a/si-units/src/si_units/__init__.py +++ b/si-units/src/si_units/__init__.py @@ -17,6 +17,7 @@ >>> print(f"The pressure is {pressure / BAR:.2f} bar") The pressure is 1.24 bar """ + # from si_units._core import SIObject from si_units._core import ( SIObject, @@ -43,6 +44,7 @@ KB, NAV, KCD, + KE, HERTZ, NEWTON, PASCAL, @@ -58,13 +60,16 @@ HENRY, ANGSTROM, AMU, + ATM, AU, BAR, CALORIE, + EPSILON0, GRAM, HOUR, LITER, MINUTE, + POISE, G, RGAS, QUECTO, @@ -119,6 +124,7 @@ "KB", "NAV", "KCD", + "KE", "HERTZ", "NEWTON", "PASCAL", @@ -134,13 +140,16 @@ "HENRY", "ANGSTROM", "AMU", + "ATM", "AU", "BAR", "CALORIE", + "EPSILON0", "GRAM", "HOUR", "LITER", "MINUTE", + "POISE", "G", "RGAS", "QUECTO", diff --git a/src/fmt.rs b/src/fmt.rs index d40d646..13ab9e5 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -44,6 +44,8 @@ impl fmt::Display for Quantity { #[cfg(feature = "pyo3")] pub(crate) trait PrintUnit { const UNIT: &'static str; + + fn scale() -> f64; } macro_rules! impl_fmt { @@ -95,6 +97,10 @@ macro_rules! impl_fmt { #[cfg(feature = "python")] impl PrintUnit for Quantity> { const UNIT: &'static str = $symbol; + + fn scale() -> f64 { + $unit.0 + } } }; } diff --git a/src/lib.rs b/src/lib.rs index 78f03ee..eb54c29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -531,6 +531,10 @@ pub const HOUR: Time = Quantity(3600.0, PhantomData); pub const LITER: Volume = Quantity(1e-3, PhantomData); /// Additional unit minute $\\left(1\\,\text{min}=60,\text{s}\\right)$ pub const MINUTE: Time = Quantity(60.0, PhantomData); +/// Additional unit atmosphere $\left(1\,\text{atm}=101325\,\text{Pa}\right)$ +pub const ATM: Pressure = Quantity(101325.0, PhantomData); +/// Additional unit poise $\left(1\,\text{P}=0.1\,\text{Pa}\cdot\text{s}\right)$ +pub const POISE: Viscosity = Quantity(0.1, PhantomData); /// Boltzmann constant $\\left(k_\text{B}=1.380649\times 10^{-23}\\,\\frac{\text{J}}{\text{K}}\\right)$ pub const KB: Entropy = Quantity(1.380649e-23, PhantomData);