Metadata-Version: 2.5
Name: unitx
Version: 0.8.2
Summary: A modern, intuitive Python library for units, quantities, and dimensional analysis.
Project-URL: Homepage, https://github.com/DaitTHU/unitx
Project-URL: Issues, https://github.com/DaitTHU/unitx/issues
Author: Dait
License-Expression: MIT
License-File: LICENSE
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.12
Provides-Extra: numpy
Requires-Dist: numpy>=2.1; extra == 'numpy'
Provides-Extra: test
Requires-Dist: pyright>=1.1.414; extra == 'test'
Requires-Dist: pytest>=8; extra == 'test'
Requires-Dist: ruff>=0.8; extra == 'test'
Description-Content-Type: text/markdown

# unitx

[![PyPI version](https://img.shields.io/pypi/v/unitx)](https://pypi.org/project/unitx/)
[![Python versions](https://img.shields.io/pypi/pyversions/unitx)](https://pypi.org/project/unitx/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

Write physical formulas with units and constants. `unitx` combines unit
conversion and dimensional analysis with Python scalar arithmetic and optional
NumPy arrays. Unit scales and rational exponents retain their exact structure.

The library is in early development; public APIs may change during the 0.x series.

## Install

Python **3.12 or later** is required. The scalar library has no mandatory
runtime dependencies:

```sh
python -m pip install unitx
```

For arrays, install NumPy support. Add Matplotlib to run the plotting example:

```sh
python -m pip install 'unitx[numpy]' matplotlib
```

For development, install into the same interpreter that runs your code:

```sh
python -m pip install -e '.[test,numpy]'
```

This README describes the current source tree. For an older release, consult
its matching Git tag.

## Start with units and constants

`si` provides units; `SI` provides physical constants. Both are read-only
namespaces in `unitx.si`:

```pycon
>>> from unitx.si import SI, si
>>> distance = 3 * si.m
>>> elapsed = 2 * si.s
>>> speed = distance / elapsed
>>> print(speed)
1.5 m/s
>>> print(speed.to(si.cm / si.s))
150.0 cm/s
>>> print(SI.c)
299792458 m/s
>>> rest_energy = (SI.me * SI.c**2).to(si.MeV)
>>> round(float(rest_energy.value), 6)
0.510999

```

Multiplying a number by a unit constructs a Quantity. Multiplication and
division combine units; addition and subtraction align compatible quantities
to the left operand's unit. Incompatible conversions and arithmetic raise
`DimensionError`.

## NumPy example: blackbody radiation

Broadcast a column of wavelengths against three temperatures, then plot
Planck's spectral radiance:

```python
import numpy as np
from matplotlib import pyplot as plt

from unitx.si import SI, si

lam = np.linspace(0.01, 3, 100)[:, None] * si.um  # wavelength
T = np.asarray([3000, 4000, 5000]) * si.K  # temperature

nu = SI.c / lam  # frequency
radiance = 2 * SI.h * SI.c**2 / (lam**5 * np.expm1(SI.h * nu / (SI.kB * T)))
radiance = radiance.to('kW/sr/m2/nm')  # spectral radiance per unit wavelength

plt.plot(lam.value, radiance.value)
plt.xlabel(f'Wavelength [{lam.unit}]')
plt.ylabel(f'Spectral radiance [{radiance.unit}]')
plt.legend([f'T = {t}' for t in T])
plt.title('Blackbody radiation')
plt.show()
```

![Blackbody spectral radiance](docs/images/blackbody.png)

One Quantity wraps the entire ndarray with one unit. NumPy handles broadcasting;
unitx checks dimensions and combines units. `SI.h` and `SI.kB` retain Decimal
values in the catalogue and enter NumPy calculations as floats.

`.to()` returns a new Quantity. `.value` exposes the numerical data for plotting;
`.value_in(unit)` also converts it to a chosen unit. Implicit unit removal with
`np.asarray(quantity)` or `np.array(quantity)` raises TypeError.

The [runnable example](examples/blackbody.py) uses `np.asarray([...]) * unit`
without an explicit dtype. This automatic construction inference is checked
with NumPy 2.5 stubs and Pyright; the example also runs on NumPy 2.1. Older
NumPy stubs may still infer Any for this expression. NumPy's own stubs can
infer different types for `np.array` and `np.asarray`;
`np.array([...]) * unit` can become Any. `unit * array` and
`Quantity(array, unit)` provide alternative construction forms. Arbitrary
shape/dtype inference and wrapped return types for every NumPy function are not
promised. See the [supported operations, storage rules, and typing limits](docs/numpy.md).

## Values and conversion

Use `Quantity` and `Unit` directly when constructing values from strings:

```pycon
>>> from unitx import Quantity, Unit
>>> length = Quantity(1, 'm')
>>> print(length + Quantity(50, 'cm'))
3/2 m
>>> length.value_in('cm')
100
>>> Quantity(1, 'cm').value_in('m')
Fraction(1, 100)
>>> Quantity(1, 'm') == Quantity(100, 'cm')
True
>>> Unit('m') == Unit('cm')
False

```

A quantity's `.value` is paired with its `.unit`. Units retain their formal
identity: metre and centimetre are different units, although quantities can
compare equal after accounting for their scales.

Python scalar operations follow the magnitude's numerical domain. Decimal
operations retain Decimal context behavior; Fraction magnitudes provide exact
rational arithmetic. Scalar equality compares exact stored values, so binary
float `0.1` is distinct from Fraction(1, 10). Use symmetric `.isclose()` for
approximate comparisons:

```pycon
>>> Quantity(0.1 + 0.2, 'm').isclose(Quantity(0.3, 'm'))
True
>>> a, b = Quantity(1, 'm'), Quantity(100.05, 'cm')
>>> a.isclose(b, rel_tol=0, abs_tol=1 * si.mm)
True

```

NumPy numerical operations instead follow the documented array conversion,
promotion, rounding, and overflow rules. Array comparisons return boolean
masks, and NumPy-backed quantities are unhashable.

Read the [scalar numerical contract](docs/scalars.md) for Decimal conversion,
exact comparisons, powers, tolerances, and optional SymPy factorization.

## Expressions and custom units

Parse symbols, registered names, prefixes, products, division, and rational
powers:

```pycon
>>> Unit('kg m/s2') == Unit('kg*m/s**2')
True
>>> Unit('m^(1/2)') ** 2 == si.m
True
>>> Unit('ms') == Unit('m s')
False
>>> Unit('[astronomical unit] / s') == si.au / si.s
True

```

Multiplication and division associate left to right: write `m/(s A)` for a
shared denominator. Multiword names can stand alone; use `[registered name]`
inside expressions. Readable `str()` output is not a guaranteed parser input.

Define a unit from an existing unit or a positive finite quantity:

```pycon
>>> stride = (2 * si.m).define('demoStride', 'demo stride', prefixable=False)
>>> (3 * stride).value_in('m')
6
>>> Unit('demo stride') == stride
True

```

Definitions register spellings in the current process and return a new Unit.
Use `Factor` or `SymbolicConstant` with a unit when an additional exact unit
scale is intended; ordinary numbers construct quantities.

See the [unit and quantity guide](docs/units.md) for ScaledUnit, dynamic
registration, spelling-specific prefix permissions, catalogue conventions,
copying, and serialization.

## Scope and types

- Supported magnitudes are Python scalars and optional plain numeric NumPy
  arrays/scalars. Other array backends, ndarray subclasses, object arrays,
  and implicit list/tuple magnitudes are unsupported.
- Conversion uses multiplication by a scale. Offset temperature scales such
  as Celsius and Fahrenheit are unsupported.
- Covered single-output NumPy ufuncs support unit-aware `out` buffers.
  Item assignment, true in-place operators, function/reduction `out`, and
  heterogeneous-unit reductions remain outside the current support matrix.
- The package ships inline annotations and `py.typed`. Construction and
  unit-only operations preserve the magnitude type; `.T` also retains it.
  Array calculations and conversion use concrete ndarray/NumPy-scalar result
  unions, making native `.value` methods available without claiming an
  unchanged dtype. Scalar protocols avoid recursive arithmetic return types
  so editor analysis remains responsive.

The package root exports `Quantity`, `Unit`, `Dimension`, `DIMENSIONLESS`,
`ONE`, `TEN`, and `PI`; `SI` and `si` are imported from `unitx.si`.
Other building blocks are available from their modules. Modules beginning
with `_` are internal.

## Documentation and development

- [Unit and quantity guide](docs/units.md)
- [Scalar numerical behavior](docs/scalars.md)
- [NumPy support matrix and typing](docs/numpy.md)
- [Migration from 0.3.0](docs/migration.md)
- [Changelog](CHANGELOG.md)

Run the checks from the repository root:

```sh
python -m pytest -q
python -m ruff check .
python -m ruff format --check .
python -m doctest README.md docs/units.md docs/scalars.md
```

Pytest includes runtime tests, source doctests, and Pyright checks. CI covers
Python 3.12–3.15, optional SymPy, and minimum/current NumPy versions. Strict
scalar consumers are also checked without NumPy installed.

Publishing a GitHub Release triggers the [release workflow](.github/workflows/release.yml)
to validate and upload wheel/sdist distributions to PyPI. Manual workflow runs
validate and store distributions without uploading them. Release tags must
match the project version.

Licensed under the [MIT License](LICENSE).
