Skip to content

Commit

Permalink
New implementation of si-units Python package (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
prehner authored Sep 22, 2024
1 parent 722f803 commit 9140be8
Show file tree
Hide file tree
Showing 12 changed files with 1,264 additions and 29 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test_documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
python-version: 3.9
- name: Install python dependencies
run: |
pip install sphinx sphinx-rtd-theme numpydoc numpy
pip install sphinx sphinx-rtd-theme numpydoc numpy torch
- name: Build wheels
uses: messense/maturin-action@v1
with:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Cargo.lock
*.so
si-units/docs/_build
si-units/docs/api/generated
si-units/docs/generated
/venv
.ipynb_checkpoints
*.ipynb
25 changes: 18 additions & 7 deletions si-units/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
[package]
name = "si-units"
version = "0.8.0"
authors = ["Philipp Rehner <prehner@ethz.ch>",
"Gernot Bauer <bauer@itt.uni-stuttgart.de>"]
authors = [
"Philipp Rehner <prehner@ethz.ch>",
"Gernot Bauer <bauer@itt.uni-stuttgart.de>",
]
rust-version = "1.81"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Representation of SI unit valued scalars and arrays."
Expand All @@ -18,8 +21,16 @@ name = "si_units"
crate-type = ["cdylib"]

[dependencies]
quantity = { path = "..", features = ["python"]}

[dependencies.pyo3]
version = "0.21"
features = ["extension-module", "abi3", "abi3-py37"]
ang = "0.6"
bincode = "1.3"
ndarray = "0.15"
pyo3 = { version = "0.21", features = [
"extension-module",
"abi3",
"abi3-py37",
] }
numpy = "0.21"
thiserror = "1.0"
serde = { version = "1.0", features = ["derive"] }
approx = "0.5"
regex = "1.10"
7 changes: 2 additions & 5 deletions si-units/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
# si-units

[![crate](https://img.shields.io/crates/v/quantity.svg)](https://crates.io/crates/quantity)
[![documentation](https://docs.rs/quantity/badge.svg)](https://docs.rs/quantity)
[![documentation](https://img.shields.io/badge/docs-github--pages-blue)](https://itt-ustutt.github.io/quantity/index.html)
[![PyPI version](https://badge.fury.io/py/si_units.svg)](https://badge.fury.io/py/si_units)

Representation of SI unit valued scalars and arrays. Python bindings to the [quantity](https://crates.io/crates/quantity) Rust library.
Representation of quantities with SI units.

The package is written with flexibility in mind and is able to represent arbitrarily complex units.
Additional to simple scalar quantities, it also provides utilities for vector valued quantities based on numpy, where all entries share the same unit.
In addition to simple scalar quantities, it can be used to decorate any complex data type (numpy arrays, PyTorch tensors) to provide unit checks.

## Installation and Usage

Expand Down Expand Up @@ -45,7 +43,6 @@ import numpy as np
ms = np.linspace(2.0, 4.0, 3) * METER
sqms = ms**2
print(sqms) # [4, 9, 16] m²
print(sqms.sqrt()) # [2, 3, 4] m
```

## Documentation
Expand Down
5 changes: 1 addition & 4 deletions si-units/docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,5 @@ Datatypes
.. autosummary::
:toctree: generated/

SINumber
PySIObject
SIArray1
SIArray2
SIArray3
SIArray4
14 changes: 7 additions & 7 deletions si-units/docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ This example demonstrates how dimensioned arrays can be constructed using numpy.
z = 62222.3333333333 p = 0.0549875048
z = 70000.0000000000 p = 0.0215180823
Using `numpy` Functions
~~~~~~~~~~~~~~~~~~~~~~~
Using `numpy` or `torch` Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Functions such as `exp`, `sqrt` and `cbrt` work with methods or the equivalent numpy functions.

Expand All @@ -84,13 +84,13 @@ Functions such as `exp`, `sqrt` and `cbrt` work with methods or the equivalent n
>>> sqm.sqrt() # this is equivalent
1 m

This also works with numpy.ndarray's.
This also works with torch.Tensor's.

>>> from si_units import *
>>> import numpy as np
>>> ms = np.array([2.0, 3.0, 4.0]) * METER
>>> import torch
>>> ms = torch.tensor([2.0, 3.0, 4.0]) * METER
>>> sqms = ms**2
>>> sqms
[4, 9, 16]
tensor([ 4., 9., 16.])
>>> sqms.sqrt()
[2, 3, 4] m
tensor([2., 3., 4.]) m
1 change: 1 addition & 0 deletions si-units/license-apache
1 change: 1 addition & 0 deletions si-units/license-mit
107 changes: 107 additions & 0 deletions si-units/src/extra_units.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use crate::{PySIObject, QuantityError, _JOULE, _KELVIN, _METER};
use ang::Angle;
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use pyo3::types::PyNotImplemented;
use pyo3::PyTypeInfo;

#[pyclass(module = "si_units")]
#[derive(Clone, Copy)]
pub struct Celsius;

#[pymethods]
impl Celsius {
fn __rmul__(&self, lhs: &Bound<'_, PyAny>) -> PyResult<PySIObject> {
let delta: Py<PyAny> = 273.15.into_py(lhs.py());
let delta = delta.bind(lhs.py());
let mut value = lhs.call_method1("__add__", (delta,))?;
if PyNotImplemented::is_exact_type_of_bound(&value) {
value = delta.call_method1("__add__", (lhs,))?;
}
Ok(PySIObject::new(value.unbind(), _KELVIN))
}

#[classattr]
fn __array_priority__() -> u64 {
1000
}
}

#[pyclass(module = "si_units")]
#[derive(Clone, Copy)]
pub struct Debye(pub f64);

#[pymethods]
impl Debye {
fn _repr_latex_(&self) -> String {
format!("${}$", self.to_latex())
}

fn __repr__(&self) -> PyResult<String> {
Ok(self.0.to_string())
}

fn __rmul__<'py>(&self, lhs: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
if let Ok(l) = lhs.extract::<f64>() {
return Ok(Bound::new(lhs.py(), Debye(l * self.0))?.into_any());
};
Err(PyErr::new::<PyTypeError, _>("not implemented!".to_string()))
}

fn __pow__(&self, py: Python, n: i32, _mod: Option<u32>) -> PyResult<PySIObject> {
if n % 2 == 1 {
Err(QuantityError::DebyePower)?
} else {
let value = (self.0.powi(2) * 1e-19 * 1e-30).powi(n / 2);
let unit = (_JOULE * _METER.powi(3)).powi(n / 2);
Ok(PySIObject::new(value.into_py(py), unit))
}
}
}

#[pyclass(name = "Angle", module = "si_units")]
#[derive(Clone, Copy)]
pub struct PyAngle(pub(crate) Angle<f64>);

impl From<Angle> for PyAngle {
fn from(angle: Angle) -> Self {
Self(angle)
}
}

impl From<PyAngle> for Angle {
fn from(angle: PyAngle) -> Self {
angle.0
}
}

#[pymethods]
impl PyAngle {
fn __repr__(&self) -> PyResult<String> {
Ok(self.0.to_string())
}

fn __add__(&self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}

fn __sub__(&self, rhs: Self) -> Self {
Self(self.0 - rhs.0)
}

fn __mul__(&self, rhs: f64) -> Self {
Self(self.0 * rhs)
}

fn __rmul__(&self, lhs: f64) -> Self {
Self(lhs * self.0)
}

fn __truediv__(&self, rhs: f64) -> Self {
Self(self.0 / rhs)
}

fn __neg__(&self) -> Self {
Self(-self.0)
}
}
Loading

0 comments on commit 9140be8

Please sign in to comment.