Skip to content

Commit 5bc2df0

Browse files
committed
Release v0.13.2
2 parents 7357a21 + 50d0b93 commit 5bc2df0

11 files changed

Lines changed: 260 additions & 41 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9-
## [0.13.1] - 2026-05-21
9+
## [0.13.2] - 2026-05-21
1010
### Added
1111
- Made `Quantity::new` available in `const` contexts.
1212

13+
## [0.13.1] - 2026-05-01
14+
### Added
15+
- Added implementations for trigonometric funtions for `Angle<T>` with `T: DualNum<f64>` when the `num-dual` feature is active. [#105](https://github.com/itt-ustutt/quantity/pull/105)
16+
- Added `ATM` and `POISE` as additional units for pressure and viscosity.
17+
- Added `EPSILON0` and `KE` as additional constants. [#113](https://github.com/itt-ustutt/quantity/pull/113)
18+
1319
## [0.13.0] - 2026-01-06
1420
### Changed
1521
- Use const generics instead of types from `typenum` to represent units. [#95](https://github.com/itt-ustutt/quantity/pull/95)

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "quantity"
3-
version = "0.13.1"
3+
version = "0.13.2"
44
authors = [
55
"Philipp Rehner <prehner@ethz.ch>",
66
"Gernot Bauer <bauer@itt.uni-stuttgart.de>",
@@ -35,6 +35,9 @@ pyo3 = { version = "0.27", optional = true }
3535
numpy = { version = "0.27", optional = true }
3636
num-dual = { version = "0.13", optional = true }
3737

38+
[dev-dependencies]
39+
approx = "0.5"
40+
3841
[features]
3942
default = []
4043
## Use generalized (hyper-)dual numbers from the [num-dual] crate as value of a quantity.

si-units/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [0.11.4] - 2026-05-01
10+
### Changed
11+
- Generalized indexing, such that, e.g., general multidimensional slicing operations are possible for quantities containing NumPy arrays. [#110](https://github.com/itt-ustutt/quantity/pull/110)
12+
13+
### Added
14+
- Added `ATM` and `POISE` as additional units for pressure and viscosity.
15+
- Added `sum` for `SIObject`s that can be summed. [#111](https://github.com/itt-ustutt/quantity/pull/111)
16+
- Added `EPSILON0` and `KE` as additional constants. [#113](https://github.com/itt-ustutt/quantity/pull/113)
17+
18+
### Fixed
19+
- Fixed the String representation of non-scalar mass quantities. [#111](https://github.com/itt-ustutt/quantity/pull/111)
20+
21+
## [0.11.3] - 2026-04-22
22+
### Added
23+
- Added stub information for methods and constants. [#106](https://github.com/itt-ustutt/quantity/pull/106)
24+
925
## [0.11.2] - 2026-04-18
1026
### Added
1127
- Added getters for `value` and `unit` of `SIObject`. [#102](https://github.com/itt-ustutt/quantity/pull/102)

si-units/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "si-units"
3-
version = "0.11.2"
3+
version = "0.11.4"
44
authors = [
55
"Philipp Rehner <prehner@ethz.ch>",
66
"Gernot Bauer <bauer@itt.uni-stuttgart.de>",

si-units/docs/derived.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ of quantities is unique, they do not appear in formatted outputs.
2929
| AMU | $\text{u}$ | mass | $1.6605390671738466\times 10^{-27}~\text{kg}$ |
3030
| AU | $\text{au}$ | length | $149597870700~\text{m}$ |
3131
| BAR | $\text{bar}$ | pressure | $10^5~\text{Pa}$ |
32+
| ATM | $\text{atm}$ | pressure | $101325~\text{Pa}$ |
33+
| POISE | $\text{P}$ | viscosity | $0.1~\text{Pa}\cdot\text{s}$ |
3234
| CALORIE | $\text{cal}$ | energy | $4.184~\text{J}$ |
3335
| DAY | $\text{d}$ | time | $86400~\text{s}$ |
3436
| DEBYE | $\text{De}$ | dipole moment | $\sqrt{10^{-19}~\text{JÅ}^3}$ |
@@ -45,6 +47,8 @@ of quantities is unique, they do not appear in formatted outputs.
4547
| -------- | ---------------------- | ------------------------- | ------------------------------------------------------------------- |
4648
| G | Gravitational constant | $G$ | $6.6743\times 10^{-11}~\frac{\text{m}^3}{\text{kg}\cdot\text{s}^2}$ |
4749
| RGAS | Ideal gas constant | $R=N_\text{Av}k_\text{B}$ | $8.31446261815324~\frac{\text{J}}{\text{mol}\cdot\text{K}}$ |
50+
| EPSILON0 | Electric constant | $\varepsilon_0$ | $8.8541878188e-12~\frac{\text{F}}{\text{m}}$ |
51+
| KE | Coulomb constant | $k_\text{e} = \frac{1}{4\pi\varepsilon_0}$ | $8987551786.1708~\frac{\text{m}}{\text{F}}$ |
4852

4953
### Example
5054

si-units/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "si-units"
3-
version = "0.11.2"
3+
version = "0.11.4"
44
description = "Representation of quantites, i.e. of unit valued scalars and arrays."
55
readme = "README.md"
66
authors = [

si-units/src/extra_units.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use crate::{PySIObject, QuantityError, _JOULE, _KELVIN, _METER};
1+
use crate::{_JOULE, _KELVIN, _METER, PySIObject, QuantityError};
2+
use pyo3::PyTypeInfo;
23
use pyo3::exceptions::PyTypeError;
34
use pyo3::prelude::*;
45
use pyo3::types::{PyFloat, PyNotImplemented};
5-
use pyo3::PyTypeInfo;
66

77
#[pyclass(module = "si_units")]
88
#[derive(Clone, Copy)]
@@ -110,6 +110,10 @@ impl Angle {
110110
Self(-self.0)
111111
}
112112

113+
fn value_in(&self, unit: Self) -> f64 {
114+
self.0 / unit.0
115+
}
116+
113117
fn sin(&self) -> f64 {
114118
self.0.sin()
115119
}

si-units/src/lib.rs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use pyo3::exceptions::PyRuntimeError;
77
use pyo3::prelude::*;
88
use pyo3::types::{PyFloat, PyNotImplemented};
99
use pyo3::{PyErr, PyTypeInfo};
10+
use std::f64::consts::FRAC_1_PI;
1011
use thiserror::Error;
1112

1213
mod si_unit;
@@ -79,10 +80,18 @@ impl PySIObject {
7980
if let Ok(v) = self.value.extract::<f64>(py) {
8081
Ok(SINumber::new(v, self.unit).to_string())
8182
} else {
82-
let value = self
83-
.value
84-
.call_method0(py, "__repr__")?
85-
.extract::<String>(py)?;
83+
let mut value = self.value.bind(py).clone();
84+
if self.unit == _KILOGRAM
85+
|| self.unit == _KILOGRAM / _MOL
86+
|| self.unit == _KILOGRAM / _METER.powi(3)
87+
{
88+
// For these three specific units, unit.to_string() will not return the
89+
// base unit (kg...) but rather g, in order to determine an appropriate
90+
// prefix for scalars. In this general representation, there are no prefixes
91+
// so we adjust the value to match the unit before printing.
92+
value = self.value.bind(py).mul(1e3)?;
93+
}
94+
let value = value.call_method0("__repr__")?.extract::<String>()?;
8695
Ok(format!("{} {}", value, self.unit))
8796
}
8897
}
@@ -135,11 +144,17 @@ impl PySIObject {
135144
self.unit.eq(&other.unit)
136145
}
137146

138-
pub fn value_in<'py>(&self, py: Python<'py>, unit: &Self) -> PyResult<Bound<'py, PyAny>> {
139-
self.check_units(unit)?;
140-
self.value
141-
.bind(py)
142-
.call_method1("__truediv__", (&unit.value,))
147+
pub fn value_in<'py>(
148+
&self,
149+
py: Python<'py>,
150+
unit: &Bound<'py, PyAny>,
151+
) -> PyResult<Bound<'py, PyAny>> {
152+
if unit.cast::<Celsius>().is_ok() {
153+
return self.__truediv__(unit);
154+
}
155+
let u = unit.extract::<PyRef<Self>>()?;
156+
self.check_units(&u)?;
157+
self.value.bind(py).call_method1("__truediv__", (&u.value,))
143158
}
144159

145160
#[classattr]
@@ -266,15 +281,15 @@ impl PySIObject {
266281
.and_then(|v| v.extract::<usize>(py))
267282
}
268283

269-
fn __getitem__(&self, py: Python, idx: isize) -> PyResult<Self> {
284+
fn __getitem__(&self, py: Python, idx: &Bound<'_, PyAny>) -> PyResult<Self> {
270285
let value = self.value.call_method1(py, "__getitem__", (idx,))?;
271286
Ok(Self::new(value, self.unit))
272287
}
273288

274-
fn __setitem__(&self, py: Python, idx: isize, value: SINumber) -> PyResult<()> {
289+
fn __setitem__(&self, py: Python, idx: &Bound<'_, PyAny>, value: &Self) -> PyResult<()> {
275290
if self.unit == value.unit {
276291
self.value
277-
.call_method1(py, "__setitem__", (idx, value.value))?;
292+
.call_method1(py, "__setitem__", (idx, &value.value))?;
278293
Ok(())
279294
} else {
280295
Err(QuantityError::InconsistentUnits {
@@ -284,6 +299,11 @@ impl PySIObject {
284299
}
285300
}
286301

302+
fn sum(&self, py: Python) -> PyResult<Self> {
303+
let value = self.value.call_method0(py, "sum")?;
304+
Ok(Self::new(value, self.unit))
305+
}
306+
287307
#[getter]
288308
fn unit(&self) -> [i8; 7] {
289309
self.unit.0
@@ -503,6 +523,8 @@ pub fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
503523
add_constant(m, "AMU", 1.6605390671738466e-27, _KILOGRAM)?;
504524
add_constant(m, "AU", 149597870700.0, _METER)?;
505525
add_constant(m, "BAR", 1e5, _PASCAL)?;
526+
add_constant(m, "ATM", 101325., _PASCAL)?;
527+
add_constant(m, "POISE", 0.1, _PASCAL * _SECOND)?;
506528
add_constant(m, "CALORIE", 4.184, _JOULE)?;
507529
m.add("CELSIUS", Celsius)?;
508530
add_constant(m, "DAY", 86400.0, _SECOND)?;
@@ -517,6 +539,13 @@ pub fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
517539
add_constant(m, "G", 6.6743e-11, SIUnit([3, -1, -2, 0, 0, 0, 0]))?;
518540
let rgas = 1.380649e-23 * 6.02214076e23;
519541
add_constant(m, "RGAS", rgas, _JOULE_PER_MOL_AND_KELVIN)?;
542+
add_constant(m, "EPSILON0", 8.8541878188e-12, _FARAD / _METER)?;
543+
add_constant(
544+
m,
545+
"KE",
546+
FRAC_1_PI / (4.0 * 8.8541878188e-12),
547+
_METER / _FARAD,
548+
)?;
520549

521550
m.add("QUECTO", QUECTO)?;
522551
m.add("RONTO", RONTO)?;

si-units/src/si_units/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,17 @@
6060
AMU,
6161
AU,
6262
BAR,
63+
ATM,
64+
POISE,
6365
CALORIE,
6466
GRAM,
6567
HOUR,
6668
LITER,
6769
MINUTE,
6870
G,
6971
RGAS,
72+
EPSILON0,
73+
KE,
7074
QUECTO,
7175
RONTO,
7276
YOCTO,
@@ -136,13 +140,17 @@
136140
"AMU",
137141
"AU",
138142
"BAR",
143+
"ATM",
144+
"POISE",
139145
"CALORIE",
140146
"GRAM",
141147
"HOUR",
142148
"LITER",
143149
"MINUTE",
144150
"G",
145151
"RGAS",
152+
"EPSILON0",
153+
"KE",
146154
"QUECTO",
147155
"RONTO",
148156
"YOCTO",

si-units/src/si_units/_core.pyi

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Self
1+
from typing import Any, Final, Self
22

33
class SIObject:
44
"""Combination of value and unit.
@@ -106,6 +106,34 @@ class SIObject:
106106
"""
107107
...
108108

109+
__array_priority__: int
110+
value: Any
111+
@property
112+
def unit(self) -> list[int]: ...
113+
@property
114+
def shape(self) -> Any: ...
115+
def __getnewargs__(self) -> tuple[Any, list[int]]: ...
116+
def __repr__(self) -> str: ...
117+
def _repr_latex_(self) -> str | None: ...
118+
def __eq__(self, other: object) -> Any: ...
119+
def __ne__(self, other: object) -> Any: ...
120+
def __lt__(self, other: Self) -> Any: ...
121+
def __le__(self, other: Self) -> Any: ...
122+
def __gt__(self, other: Self) -> Any: ...
123+
def __ge__(self, other: Self) -> Any: ...
124+
def __add__(self, rhs: Self) -> Self: ...
125+
def __sub__(self, rhs: Self) -> Self: ...
126+
def __mul__(self, rhs: Any) -> Any: ...
127+
def __rmul__(self, lhs: Any) -> Any: ...
128+
def __truediv__(self, rhs: Any) -> Any: ...
129+
def __rtruediv__(self, lhs: Any) -> Any: ...
130+
def __pow__(self, i: int, _mod: object | None = None) -> Self: ...
131+
def __neg__(self) -> Self: ...
132+
def __abs__(self) -> Self: ...
133+
def __len__(self) -> int: ...
134+
def __getitem__(self, idx: int) -> Self: ...
135+
def __setitem__(self, idx: int, value: Self) -> None: ...
136+
109137
def array(value: SIObject | list[SIObject]) -> SIObject:
110138
"""Build SIObject from scalar or list.
111139
@@ -158,3 +186,98 @@ def logspace(start: SIObject, end: SIObject, n: int) -> SIObject:
158186
the same unit, or if n is not positive.
159187
"""
160188
...
189+
190+
class Celsius:
191+
__array_priority__: int
192+
def __rmul__(self, lhs: Any) -> SIObject: ...
193+
194+
class Debye:
195+
def __rmul__(self, lhs: float) -> Self: ...
196+
def __pow__(self, n: int, _mod: object | None = None) -> SIObject: ...
197+
198+
class Angle:
199+
def __init__(self, value: float) -> None: ...
200+
def __getnewargs__(self) -> float: ...
201+
def __add__(self, rhs: Self) -> Self: ...
202+
def __sub__(self, rhs: Self) -> Self: ...
203+
def __mul__(self, rhs: float) -> Self: ...
204+
def __rmul__(self, lhs: float) -> Self: ...
205+
def __truediv__(self, rhs: Any) -> Any: ...
206+
def __neg__(self) -> Self: ...
207+
def value_in(self, unit: Self) -> float: ...
208+
def sin(self) -> float: ...
209+
def cos(self) -> float: ...
210+
def tan(self) -> float: ...
211+
212+
SECOND: Final[SIObject]
213+
METER: Final[SIObject]
214+
KILOGRAM: Final[SIObject]
215+
AMPERE: Final[SIObject]
216+
KELVIN: Final[SIObject]
217+
CELSIUS: Final[Celsius]
218+
DEBYE: Final[Debye]
219+
DEGREES: Final[Angle]
220+
RADIANS: Final[Angle]
221+
DAY: Final[SIObject]
222+
MOL: Final[SIObject]
223+
CANDELA: Final[SIObject]
224+
DVCS: Final[SIObject]
225+
CLIGHT: Final[SIObject]
226+
PLANCK: Final[SIObject]
227+
QE: Final[SIObject]
228+
KB: Final[SIObject]
229+
NAV: Final[SIObject]
230+
KCD: Final[SIObject]
231+
HERTZ: Final[SIObject]
232+
NEWTON: Final[SIObject]
233+
PASCAL: Final[SIObject]
234+
JOULE: Final[SIObject]
235+
WATT: Final[SIObject]
236+
COULOMB: Final[SIObject]
237+
VOLT: Final[SIObject]
238+
FARAD: Final[SIObject]
239+
OHM: Final[SIObject]
240+
SIEMENS: Final[SIObject]
241+
WEBER: Final[SIObject]
242+
TESLA: Final[SIObject]
243+
HENRY: Final[SIObject]
244+
ANGSTROM: Final[SIObject]
245+
AMU: Final[SIObject]
246+
AU: Final[SIObject]
247+
BAR: Final[SIObject]
248+
ATM: Final[SIObject]
249+
POISE: Final[SIObject]
250+
CALORIE: Final[SIObject]
251+
GRAM: Final[SIObject]
252+
HOUR: Final[SIObject]
253+
LITER: Final[SIObject]
254+
MINUTE: Final[SIObject]
255+
G: Final[SIObject]
256+
RGAS: Final[SIObject]
257+
EPSILON0: Final[SIObject]
258+
KE: Final[SIObject]
259+
QUECTO: Final[float]
260+
RONTO: Final[float]
261+
YOCTO: Final[float]
262+
ZEPTO: Final[float]
263+
ATTO: Final[float]
264+
FEMTO: Final[float]
265+
PICO: Final[float]
266+
NANO: Final[float]
267+
MICRO: Final[float]
268+
MILLI: Final[float]
269+
CENTI: Final[float]
270+
DECI: Final[float]
271+
DECA: Final[float]
272+
HECTO: Final[float]
273+
KILO: Final[float]
274+
MEGA: Final[float]
275+
GIGA: Final[float]
276+
TERA: Final[float]
277+
PETA: Final[float]
278+
EXA: Final[float]
279+
ZETTA: Final[float]
280+
YOTTA: Final[float]
281+
RONNA: Final[float]
282+
QUETTA: Final[float]
283+
__version__: Final[str]

0 commit comments

Comments
 (0)