Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

link to the PR, not the issue

- ATM and POISE added as derived units. [#46](https://github.com/itt-ustutt/quantity/issues/46)

## [0.13.0] - 2026-01-06
### Changed
Expand Down
5 changes: 5 additions & 0 deletions si-units/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also link the PR here


### Added
- Getter and Setter generalized to common NumPy indexing, such that general slicing operations are possible in multiple dimensions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Getter and Setter generalized to common NumPy indexing, such that general slicing operations are possible in multiple dimensions.
- Indexing generalized, such that, e.g., general multidimensional slicing operations are possible for quantities containing NumPy arrays.


## [0.11.1] - 2026-01-22
### Fixed
Expand Down
42 changes: 23 additions & 19 deletions si-units/docs/derived.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 9 additions & 0 deletions si-units/src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
65 changes: 53 additions & 12 deletions si-units/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -78,11 +80,20 @@ impl PySIObject {
if let Ok(v) = self.value.extract::<f64>(py) {
Ok(SINumber::new(v, self.unit).to_string())
} else {
let value = self
.value
.call_method0(py, "__repr__")?
.extract::<String>(py)?;
Ok(format!("{} {}", value, self.unit))
let (multiplier, symbol) = SINumber::new(1.0, self.unit).into_scaled_parts();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for the annoying thing with grams and kilograms? I'm not sure I follow here.


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::<String>(py)?
} else {
self.value
.call_method0(py, "__repr__")?
.extract::<String>(py)?
};

Ok(format!("{} {}", value_str, symbol))
}
}

Expand Down Expand Up @@ -258,25 +269,43 @@ impl PySIObject {
.and_then(|v| v.extract::<usize>(py))
}

fn __getitem__(&self, py: Python, idx: isize) -> PyResult<Self> {
fn __getitem__(&self, py: Python, idx: &Bound<'_, PyAny>) -> PyResult<Self> {
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>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
value: &Bound<'_, PyAny>,
value: &PySIObject,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only need SINumber if we want to make sure that we can only pass a scalar (e.g., in linspace). With the more general indexing, we can simply pass any SI object.

) -> PyResult<()> {
if let Ok(v) = value.extract::<SINumber>() {
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());
}
Comment on lines +283 to +293

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if let Ok(v) = value.extract::<SINumber>() {
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());
}

I don't think that does anything.


let ob = value.extract::<PyRef<PySIObject>>()?;
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<T> {
value: T,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)?;
Expand All @@ -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)?;
Expand All @@ -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]))?;
Expand Down
9 changes: 9 additions & 0 deletions si-units/src/si_units/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -43,6 +44,7 @@
KB,
NAV,
KCD,
KE,
HERTZ,
NEWTON,
PASCAL,
Expand All @@ -58,13 +60,16 @@
HENRY,
ANGSTROM,
AMU,
ATM,
AU,
BAR,
CALORIE,
EPSILON0,
GRAM,
HOUR,
LITER,
MINUTE,
POISE,
G,
RGAS,
QUECTO,
Expand Down Expand Up @@ -119,6 +124,7 @@
"KB",
"NAV",
"KCD",
"KE",
"HERTZ",
"NEWTON",
"PASCAL",
Expand All @@ -134,13 +140,16 @@
"HENRY",
"ANGSTROM",
"AMU",
"ATM",
"AU",
"BAR",
"CALORIE",
"EPSILON0",
"GRAM",
"HOUR",
"LITER",
"MINUTE",
"POISE",
"G",
"RGAS",
"QUECTO",
Expand Down
6 changes: 6 additions & 0 deletions src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ impl<Inner: fmt::Display> fmt::Display for Quantity<Inner, _Dimensionless> {
#[cfg(feature = "pyo3")]
pub(crate) trait PrintUnit {
const UNIT: &'static str;

fn scale() -> f64;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this used anywhere?

}

macro_rules! impl_fmt {
Expand Down Expand Up @@ -95,6 +97,10 @@ macro_rules! impl_fmt {
#[cfg(feature = "python")]
impl<T> PrintUnit for Quantity<T, SIUnit<$t, $l, $m, $i, $theta, $n, 0>> {
const UNIT: &'static str = $symbol;

fn scale() -> f64 {
$unit.0
}
}
};
}
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down