ucon 0.2.2rc1__tar.gz → 0.3.2rc6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ucon-0.3.2rc6/.github/workflows/publish.yaml +64 -0
- ucon-0.3.2rc6/.github/workflows/tests.yaml +51 -0
- ucon-0.3.2rc6/PKG-INFO +219 -0
- ucon-0.3.2rc6/README.md +183 -0
- ucon-0.3.2rc6/ROADMAP.md +222 -0
- ucon-0.3.2rc6/docs/unity-distance-metric-for-nearest-scale.md +72 -0
- ucon-0.3.2rc6/noxfile.py +128 -0
- ucon-0.3.2rc6/requirements.txt +2 -0
- {ucon-0.2.2rc1 → ucon-0.3.2rc6}/setup.py +7 -2
- ucon-0.3.2rc6/tests/__init__.py +0 -0
- ucon-0.3.2rc6/tests/ucon/__init__.py +0 -0
- ucon-0.3.2rc6/tests/ucon/test_core.py +535 -0
- ucon-0.3.2rc6/tests/ucon/test_dimension.py +206 -0
- ucon-0.3.2rc6/tests/ucon/test_unit.py +143 -0
- ucon-0.3.2rc6/tests/ucon/test_units.py +21 -0
- ucon-0.3.2rc6/ucon/__init__.py +49 -0
- ucon-0.3.2rc6/ucon/core.py +353 -0
- ucon-0.3.2rc6/ucon/dimension.py +172 -0
- ucon-0.3.2rc6/ucon/unit.py +92 -0
- ucon-0.3.2rc6/ucon/units.py +84 -0
- ucon-0.3.2rc6/ucon.egg-info/PKG-INFO +219 -0
- ucon-0.3.2rc6/ucon.egg-info/SOURCES.txt +25 -0
- ucon-0.3.2rc6/ucon.egg-info/top_level.txt +2 -0
- ucon-0.2.2rc1/.github/workflows/publish.yaml +0 -59
- ucon-0.2.2rc1/.github/workflows/tests.yaml +0 -34
- ucon-0.2.2rc1/PKG-INFO +0 -81
- ucon-0.2.2rc1/README.md +0 -57
- ucon-0.2.2rc1/noxfile.py +0 -54
- ucon-0.2.2rc1/requirements.txt +0 -3
- ucon-0.2.2rc1/test_ucon.py +0 -193
- ucon-0.2.2rc1/ucon.egg-info/PKG-INFO +0 -81
- ucon-0.2.2rc1/ucon.egg-info/SOURCES.txt +0 -14
- ucon-0.2.2rc1/ucon.egg-info/top_level.txt +0 -1
- ucon-0.2.2rc1/ucon.py +0 -194
- {ucon-0.2.2rc1 → ucon-0.3.2rc6}/.gitignore +0 -0
- {ucon-0.2.2rc1 → ucon-0.3.2rc6}/LICENSE +0 -0
- {ucon-0.2.2rc1 → ucon-0.3.2rc6}/setup.cfg +0 -0
- {ucon-0.2.2rc1 → ucon-0.3.2rc6}/ucon.egg-info/dependency_links.txt +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main] # mainline merges → Test PyPI
|
|
6
|
+
tags: ['*'] # tags → Test & Prod PyPI
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout source
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0 # needed for ancestry check
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: '3.14'
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: pip install -r requirements.txt
|
|
25
|
+
|
|
26
|
+
- name: Build distribution 📦
|
|
27
|
+
run: PYTHONWARNINGS=ignore LOCAL_VERSION_SCHEME=true nox -s build
|
|
28
|
+
|
|
29
|
+
- name: Publish to Test PyPI
|
|
30
|
+
if: github.ref == 'refs/heads/main'
|
|
31
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
32
|
+
with:
|
|
33
|
+
user: __token__
|
|
34
|
+
password: ${{ secrets.test_pypi_password }}
|
|
35
|
+
skip_existing: true
|
|
36
|
+
repository_url: https://test.pypi.org/legacy/
|
|
37
|
+
|
|
38
|
+
- name: Verify tag is on mainline
|
|
39
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
40
|
+
run: |
|
|
41
|
+
git fetch origin main --depth=1
|
|
42
|
+
TAG=${GITHUB_REF#refs/tags/}
|
|
43
|
+
TAG_COMMIT=$(git rev-list -n 1 "$TAG")
|
|
44
|
+
if ! git merge-base --is-ancestor "$TAG_COMMIT" origin/main; then
|
|
45
|
+
echo "::error::Tag $TAG is not based on main; skipping publish."
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
- name: 🧪 Publish tag build to Test PyPI
|
|
50
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
51
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
52
|
+
with:
|
|
53
|
+
user: __token__
|
|
54
|
+
password: ${{ secrets.test_pypi_password }}
|
|
55
|
+
skip_existing: true
|
|
56
|
+
repository_url: https://test.pypi.org/legacy/
|
|
57
|
+
|
|
58
|
+
- name: 🚀 Publish to Prod PyPI
|
|
59
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
60
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
61
|
+
with:
|
|
62
|
+
user: __token__
|
|
63
|
+
password: ${{ secrets.pypi_password }}
|
|
64
|
+
skip_existing: true
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: ['**']
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
run_tests:
|
|
11
|
+
runs-on: ubuntu-22.04
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version:
|
|
16
|
+
- '3.7'
|
|
17
|
+
- '3.8'
|
|
18
|
+
- '3.9'
|
|
19
|
+
- '3.10'
|
|
20
|
+
- '3.11'
|
|
21
|
+
- '3.12'
|
|
22
|
+
- '3.13'
|
|
23
|
+
- '3.14'
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- name: Checkout code
|
|
27
|
+
uses: actions/checkout@v5
|
|
28
|
+
|
|
29
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
30
|
+
uses: actions/setup-python@v6
|
|
31
|
+
with:
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
|
|
34
|
+
- name: Install dependencies
|
|
35
|
+
run: |
|
|
36
|
+
python -m pip install --upgrade pip
|
|
37
|
+
pip install -r requirements.txt
|
|
38
|
+
|
|
39
|
+
- name: Run tests
|
|
40
|
+
run: nox -s test
|
|
41
|
+
|
|
42
|
+
- name: Upload coverage to Codecov
|
|
43
|
+
if: success() && github.repository_owner == 'withtwoemms'
|
|
44
|
+
run: bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }}
|
|
45
|
+
|
|
46
|
+
- name: Archive coverage report
|
|
47
|
+
if: always()
|
|
48
|
+
uses: actions/upload-artifact@v4
|
|
49
|
+
with:
|
|
50
|
+
name: python-${{ matrix.python-version }}-coverage-${{ github.run_id }}
|
|
51
|
+
path: ${{ github.workspace }}/coverage.xml
|
ucon-0.3.2rc6/PKG-INFO
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ucon
|
|
3
|
+
Version: 0.3.2rc6
|
|
4
|
+
Summary: a tool for dimensional analysis: a "Unit CONverter"
|
|
5
|
+
Home-page: https://github.com/withtwoemms/ucon
|
|
6
|
+
Author: Emmanuel I. Obi
|
|
7
|
+
Maintainer: Emmanuel I. Obi
|
|
8
|
+
Maintainer-email: withtwoemms@gmail.com
|
|
9
|
+
License: MIT
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: description-content-type
|
|
30
|
+
Dynamic: home-page
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
Dynamic: maintainer
|
|
34
|
+
Dynamic: maintainer-email
|
|
35
|
+
Dynamic: summary
|
|
36
|
+
|
|
37
|
+
<img src="https://gist.githubusercontent.com/withtwoemms/0cb9e6bc8df08f326771a89eeb790f8e/raw/dde6c7d3b8a7d79eb1006ace03fb834e044cdebc/ucon-logo.png" align="left" width="420" />
|
|
38
|
+
|
|
39
|
+
# ucon
|
|
40
|
+
|
|
41
|
+
> Pronounced: _yoo · cahn_
|
|
42
|
+
> A lightweight, **unit-aware computation library** for Python — built on first-principles.
|
|
43
|
+
|
|
44
|
+
[](https://github.com/withtwoemms/ucon/actions?query=workflow%3Atests)
|
|
45
|
+
[](https://codecov.io/gh/withtwoemms/ucon)
|
|
46
|
+
[](https://github.com/withtwoemms/ucon/actions?query=workflow%3Apublish)
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Overview
|
|
51
|
+
|
|
52
|
+
`ucon` helps Python understand the *physical meaning* of your numbers.
|
|
53
|
+
It combines **units**, **scales**, and **dimensions** into a composable algebra that supports:
|
|
54
|
+
|
|
55
|
+
- Dimensional analysis through `Number` and `Ratio`
|
|
56
|
+
- Scale-aware arithmetic and conversions
|
|
57
|
+
- Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, ect.)
|
|
58
|
+
- A clean foundation for physics, chemistry, data modeling, and beyond
|
|
59
|
+
|
|
60
|
+
Think of it as **`decimal.Decimal` for the physical world** — precise, predictable, and type-safe.
|
|
61
|
+
|
|
62
|
+
## Introduction
|
|
63
|
+
|
|
64
|
+
The crux of this tiny library is to provide abstractions that simplify the answering of questions like:
|
|
65
|
+
|
|
66
|
+
> _"If given two milliliters of bromine (liquid Br<sub>2</sub>), how many grams of bromine does one have?"_
|
|
67
|
+
|
|
68
|
+
To best answer this question, we turn to an age-old technique ([dimensional analysis](https://en.wikipedia.org/wiki/Dimensional_analysis)) which essentially allows for the solution to be written as a product of ratios. `ucon` comes equipped with some useful primitives:
|
|
69
|
+
| Type | Defined In | Purpose | Typical Use Cases |
|
|
70
|
+
| ----------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| **`Vector`** | `ucon.dimension` | Represents the exponent tuple of a physical quantity’s base dimensions (e.g., T, L, M, I, Θ, J, N). | Internal representation of dimensional algebra; building derived quantities (e.g., area, velocity, force). |
|
|
72
|
+
| **`Dimension`** | `ucon.dimension` | Encapsulates physical dimensions (e.g., length, time, mass) as algebraic combinations of vectors. | Enforcing dimensional consistency; defining relationships between quantities (e.g., length / time = velocity). |
|
|
73
|
+
| **`Unit`** | `ucon.unit` | Represents a named, dimensioned measurement unit (e.g., meter, second, joule). | Attaching human-readable units to quantities; defining or composing new units (`newton = kilogram * meter / second²`). |
|
|
74
|
+
| **`Scale`** | `ucon.core` | Encodes powers of base magnitudes (binary or decimal prefixes like kilo-, milli-, mebi-). | Adjusting numeric scale without changing dimension (e.g., kilometer ↔ meter, byte ↔ kibibyte). |
|
|
75
|
+
| **`Exponent`** | `ucon.core` | Represents base-power pairs (e.g., 10³, 2¹⁰) used by `Scale`. | Performing arithmetic on powers and bases; normalizing scales across conversions. |
|
|
76
|
+
| **`Number`** | `ucon.core` | Combines a numeric quantity with a unit and scale; the primary measurable type. | Performing arithmetic with units; converting between compatible units; representing physical quantities like 5 m/s. |
|
|
77
|
+
| **`Ratio`** | `ucon.core` | Represents the division of two `Number` objects; captures relationships between quantities. | Expressing rates, densities, efficiencies (e.g., energy / time = power, length / time = velocity). |
|
|
78
|
+
| **`units` module** | `ucon.units` | Defines canonical unit instances (SI and common derived units). | Quick access to standard physical units (`units.meter`, `units.second`, `units.newton`, etc.). | |
|
|
79
|
+
|
|
80
|
+
### Under the Hood
|
|
81
|
+
|
|
82
|
+
`ucon` models unit math through a hierarchy where each layer builds on the last:
|
|
83
|
+
|
|
84
|
+
```mermaid
|
|
85
|
+
---
|
|
86
|
+
config:
|
|
87
|
+
layout: elk
|
|
88
|
+
elk:
|
|
89
|
+
mergeEdges: true # Combines parallel edges
|
|
90
|
+
nodePlacementStrategy: SIMPLE # Other options: SIMPLE, NETWORK_SIMPLEX, BRANDES_KOEPF (default)
|
|
91
|
+
---
|
|
92
|
+
flowchart LR
|
|
93
|
+
%% --- Algebraic substrate ---
|
|
94
|
+
subgraph "Algebraic Substrate"
|
|
95
|
+
A[Exponent] --> B[Scale]
|
|
96
|
+
end
|
|
97
|
+
%% --- Physical ontology ---
|
|
98
|
+
subgraph "Physical Ontology"
|
|
99
|
+
D[Dimension] --> E[Unit]
|
|
100
|
+
end
|
|
101
|
+
%% --- Value layer ---
|
|
102
|
+
subgraph "Value Layer"
|
|
103
|
+
F[Number]
|
|
104
|
+
G[Ratio]
|
|
105
|
+
end
|
|
106
|
+
%% --- Cross-layer relationships ---
|
|
107
|
+
E --> F
|
|
108
|
+
B --> F
|
|
109
|
+
%% Ratio composes Numbers and also evaluates to a Number
|
|
110
|
+
F --> G
|
|
111
|
+
G --> F
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Why `ucon`?
|
|
115
|
+
|
|
116
|
+
Python already has mature libraries for handling units and physical quantities — Pint, SymPy, and Unum — each solving part of the same problem from different angles:
|
|
117
|
+
|
|
118
|
+
| Library | Focus | Limitation |
|
|
119
|
+
| --------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
120
|
+
| **Pint** | Runtime unit conversion and compatibility checking | Treats quantities as decorated numbers — conversions work, but the algebra behind them isn’t inspectable or type-safe. |
|
|
121
|
+
| **SymPy** | Symbolic algebra and simplification of unit expressions | Excellent for symbolic reasoning, but not designed for runtime validation, conversion, or serialization. |
|
|
122
|
+
| **Unum** | Unit-aware arithmetic and unit propagation | Tracks units through arithmetic but lacks explicit dimensional algebra, conversion taxonomy, or runtime introspection. |
|
|
123
|
+
|
|
124
|
+
Together, these tools can _use_ units, but none can explicitly represent and verify the relationships between units and dimensions.
|
|
125
|
+
|
|
126
|
+
That’s the gap `ucon` fills.
|
|
127
|
+
|
|
128
|
+
It treats units, dimensions, and scales as first-class objects and builds a composable algebra around them.
|
|
129
|
+
This allows you to:
|
|
130
|
+
- Represent dimensional meaning explicitly (`Dimension`, `Vector`);
|
|
131
|
+
- Compose and compute with type-safe, introspectable quantities (`Unit`, `Number`);
|
|
132
|
+
- Perform reversible, declarative conversions (standard, linear, affine, nonlinear);
|
|
133
|
+
- Serialize and validate measurements with Pydantic integration;
|
|
134
|
+
- Extend the system with custom unit registries and conversion families.
|
|
135
|
+
|
|
136
|
+
Where Pint, Unum, and SymPy focus on _how_ to compute with units,
|
|
137
|
+
`ucon` focuses on why those computations make sense. Every operation checks the dimensional structure, _not just the unit labels_. This means ucon doesn’t just track names: it enforces physics:
|
|
138
|
+
```python
|
|
139
|
+
from ucon import Number, units
|
|
140
|
+
|
|
141
|
+
length = Number(quantity=5, unit=units.meter)
|
|
142
|
+
time = Number(quantity=2, unit=units.second)
|
|
143
|
+
|
|
144
|
+
speed = length / time # ✅ valid: L / T = velocity
|
|
145
|
+
invalid = length + time # ❌ raises: incompatible dimensions
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Setup
|
|
149
|
+
|
|
150
|
+
Simple:
|
|
151
|
+
```bash
|
|
152
|
+
pip install ucon
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Usage
|
|
156
|
+
|
|
157
|
+
This sort of dimensional analysis:
|
|
158
|
+
```
|
|
159
|
+
2 mL bromine | 3.119 g bromine
|
|
160
|
+
--------------x----------------- #=> 6.238 g bromine
|
|
161
|
+
1 | 1 mL bromine
|
|
162
|
+
```
|
|
163
|
+
becomes straightforward when you define a measurement:
|
|
164
|
+
```python
|
|
165
|
+
from ucon import Number, Scale, Units, Ratio
|
|
166
|
+
|
|
167
|
+
# Two milliliters of bromine
|
|
168
|
+
two_mL_bromine = Number(unit=Units.liter, scale=Scale.milli, quantity=2)
|
|
169
|
+
|
|
170
|
+
# Density of bromine: 3.119 g/mL
|
|
171
|
+
bromine_density = Ratio(
|
|
172
|
+
numerator=Number(unit=Units.gram, quantity=3.119),
|
|
173
|
+
denominator=Number(unit=Units.liter, scale=Scale.milli),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Multiply to find mass
|
|
177
|
+
grams_bromine = two_mL_bromine * bromine_density
|
|
178
|
+
print(grams_bromine) # <6.238 gram>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Scale conversion is automatic and precise:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
grams_bromine.to(Scale.milli) # <6238.0 milligram>
|
|
185
|
+
grams_bromine.to(Scale.kibi) # <0.006091796875 kibigram>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Roadmap Highlights
|
|
191
|
+
|
|
192
|
+
| Version | Theme | Focus |
|
|
193
|
+
|----------|-------|--------|
|
|
194
|
+
| [**0.3.x**](https://github.com/withtwoemms/ucon/milestone/1) | Primitive Type Refinement | Unified algebraic foundation |
|
|
195
|
+
| [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System | Linear & affine conversions |
|
|
196
|
+
| [**0.6.x**](https://github.com/withtwoemms/ucon/milestone/4) | Nonlinear / Specialized Units | Decibel, Percent, pH |
|
|
197
|
+
| [**0.8.x**](https://github.com/withtwoemms/ucon/milestone/6) | Pydantic Integration | Type-safe quantity validation |
|
|
198
|
+
|
|
199
|
+
See full roadmap: [ROADMAP.md](./ROADMAP.md)
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Contributing
|
|
204
|
+
|
|
205
|
+
Contributions, issues, and pull requests are welcome!
|
|
206
|
+
Ensure `nox` is installed.
|
|
207
|
+
```
|
|
208
|
+
pip install -r requirements.txt
|
|
209
|
+
```
|
|
210
|
+
Then run the full test suite (agains all supported python versions) before committing:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
nox -s test
|
|
214
|
+
```
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
> “If it can be measured, it can be represented.
|
|
218
|
+
If it can be represented, it can be validated.
|
|
219
|
+
If it can be validated, it can be trusted.”
|
ucon-0.3.2rc6/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
<img src="https://gist.githubusercontent.com/withtwoemms/0cb9e6bc8df08f326771a89eeb790f8e/raw/dde6c7d3b8a7d79eb1006ace03fb834e044cdebc/ucon-logo.png" align="left" width="420" />
|
|
2
|
+
|
|
3
|
+
# ucon
|
|
4
|
+
|
|
5
|
+
> Pronounced: _yoo · cahn_
|
|
6
|
+
> A lightweight, **unit-aware computation library** for Python — built on first-principles.
|
|
7
|
+
|
|
8
|
+
[](https://github.com/withtwoemms/ucon/actions?query=workflow%3Atests)
|
|
9
|
+
[](https://codecov.io/gh/withtwoemms/ucon)
|
|
10
|
+
[](https://github.com/withtwoemms/ucon/actions?query=workflow%3Apublish)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
`ucon` helps Python understand the *physical meaning* of your numbers.
|
|
17
|
+
It combines **units**, **scales**, and **dimensions** into a composable algebra that supports:
|
|
18
|
+
|
|
19
|
+
- Dimensional analysis through `Number` and `Ratio`
|
|
20
|
+
- Scale-aware arithmetic and conversions
|
|
21
|
+
- Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, ect.)
|
|
22
|
+
- A clean foundation for physics, chemistry, data modeling, and beyond
|
|
23
|
+
|
|
24
|
+
Think of it as **`decimal.Decimal` for the physical world** — precise, predictable, and type-safe.
|
|
25
|
+
|
|
26
|
+
## Introduction
|
|
27
|
+
|
|
28
|
+
The crux of this tiny library is to provide abstractions that simplify the answering of questions like:
|
|
29
|
+
|
|
30
|
+
> _"If given two milliliters of bromine (liquid Br<sub>2</sub>), how many grams of bromine does one have?"_
|
|
31
|
+
|
|
32
|
+
To best answer this question, we turn to an age-old technique ([dimensional analysis](https://en.wikipedia.org/wiki/Dimensional_analysis)) which essentially allows for the solution to be written as a product of ratios. `ucon` comes equipped with some useful primitives:
|
|
33
|
+
| Type | Defined In | Purpose | Typical Use Cases |
|
|
34
|
+
| ----------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| **`Vector`** | `ucon.dimension` | Represents the exponent tuple of a physical quantity’s base dimensions (e.g., T, L, M, I, Θ, J, N). | Internal representation of dimensional algebra; building derived quantities (e.g., area, velocity, force). |
|
|
36
|
+
| **`Dimension`** | `ucon.dimension` | Encapsulates physical dimensions (e.g., length, time, mass) as algebraic combinations of vectors. | Enforcing dimensional consistency; defining relationships between quantities (e.g., length / time = velocity). |
|
|
37
|
+
| **`Unit`** | `ucon.unit` | Represents a named, dimensioned measurement unit (e.g., meter, second, joule). | Attaching human-readable units to quantities; defining or composing new units (`newton = kilogram * meter / second²`). |
|
|
38
|
+
| **`Scale`** | `ucon.core` | Encodes powers of base magnitudes (binary or decimal prefixes like kilo-, milli-, mebi-). | Adjusting numeric scale without changing dimension (e.g., kilometer ↔ meter, byte ↔ kibibyte). |
|
|
39
|
+
| **`Exponent`** | `ucon.core` | Represents base-power pairs (e.g., 10³, 2¹⁰) used by `Scale`. | Performing arithmetic on powers and bases; normalizing scales across conversions. |
|
|
40
|
+
| **`Number`** | `ucon.core` | Combines a numeric quantity with a unit and scale; the primary measurable type. | Performing arithmetic with units; converting between compatible units; representing physical quantities like 5 m/s. |
|
|
41
|
+
| **`Ratio`** | `ucon.core` | Represents the division of two `Number` objects; captures relationships between quantities. | Expressing rates, densities, efficiencies (e.g., energy / time = power, length / time = velocity). |
|
|
42
|
+
| **`units` module** | `ucon.units` | Defines canonical unit instances (SI and common derived units). | Quick access to standard physical units (`units.meter`, `units.second`, `units.newton`, etc.). | |
|
|
43
|
+
|
|
44
|
+
### Under the Hood
|
|
45
|
+
|
|
46
|
+
`ucon` models unit math through a hierarchy where each layer builds on the last:
|
|
47
|
+
|
|
48
|
+
```mermaid
|
|
49
|
+
---
|
|
50
|
+
config:
|
|
51
|
+
layout: elk
|
|
52
|
+
elk:
|
|
53
|
+
mergeEdges: true # Combines parallel edges
|
|
54
|
+
nodePlacementStrategy: SIMPLE # Other options: SIMPLE, NETWORK_SIMPLEX, BRANDES_KOEPF (default)
|
|
55
|
+
---
|
|
56
|
+
flowchart LR
|
|
57
|
+
%% --- Algebraic substrate ---
|
|
58
|
+
subgraph "Algebraic Substrate"
|
|
59
|
+
A[Exponent] --> B[Scale]
|
|
60
|
+
end
|
|
61
|
+
%% --- Physical ontology ---
|
|
62
|
+
subgraph "Physical Ontology"
|
|
63
|
+
D[Dimension] --> E[Unit]
|
|
64
|
+
end
|
|
65
|
+
%% --- Value layer ---
|
|
66
|
+
subgraph "Value Layer"
|
|
67
|
+
F[Number]
|
|
68
|
+
G[Ratio]
|
|
69
|
+
end
|
|
70
|
+
%% --- Cross-layer relationships ---
|
|
71
|
+
E --> F
|
|
72
|
+
B --> F
|
|
73
|
+
%% Ratio composes Numbers and also evaluates to a Number
|
|
74
|
+
F --> G
|
|
75
|
+
G --> F
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Why `ucon`?
|
|
79
|
+
|
|
80
|
+
Python already has mature libraries for handling units and physical quantities — Pint, SymPy, and Unum — each solving part of the same problem from different angles:
|
|
81
|
+
|
|
82
|
+
| Library | Focus | Limitation |
|
|
83
|
+
| --------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| **Pint** | Runtime unit conversion and compatibility checking | Treats quantities as decorated numbers — conversions work, but the algebra behind them isn’t inspectable or type-safe. |
|
|
85
|
+
| **SymPy** | Symbolic algebra and simplification of unit expressions | Excellent for symbolic reasoning, but not designed for runtime validation, conversion, or serialization. |
|
|
86
|
+
| **Unum** | Unit-aware arithmetic and unit propagation | Tracks units through arithmetic but lacks explicit dimensional algebra, conversion taxonomy, or runtime introspection. |
|
|
87
|
+
|
|
88
|
+
Together, these tools can _use_ units, but none can explicitly represent and verify the relationships between units and dimensions.
|
|
89
|
+
|
|
90
|
+
That’s the gap `ucon` fills.
|
|
91
|
+
|
|
92
|
+
It treats units, dimensions, and scales as first-class objects and builds a composable algebra around them.
|
|
93
|
+
This allows you to:
|
|
94
|
+
- Represent dimensional meaning explicitly (`Dimension`, `Vector`);
|
|
95
|
+
- Compose and compute with type-safe, introspectable quantities (`Unit`, `Number`);
|
|
96
|
+
- Perform reversible, declarative conversions (standard, linear, affine, nonlinear);
|
|
97
|
+
- Serialize and validate measurements with Pydantic integration;
|
|
98
|
+
- Extend the system with custom unit registries and conversion families.
|
|
99
|
+
|
|
100
|
+
Where Pint, Unum, and SymPy focus on _how_ to compute with units,
|
|
101
|
+
`ucon` focuses on why those computations make sense. Every operation checks the dimensional structure, _not just the unit labels_. This means ucon doesn’t just track names: it enforces physics:
|
|
102
|
+
```python
|
|
103
|
+
from ucon import Number, units
|
|
104
|
+
|
|
105
|
+
length = Number(quantity=5, unit=units.meter)
|
|
106
|
+
time = Number(quantity=2, unit=units.second)
|
|
107
|
+
|
|
108
|
+
speed = length / time # ✅ valid: L / T = velocity
|
|
109
|
+
invalid = length + time # ❌ raises: incompatible dimensions
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Setup
|
|
113
|
+
|
|
114
|
+
Simple:
|
|
115
|
+
```bash
|
|
116
|
+
pip install ucon
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Usage
|
|
120
|
+
|
|
121
|
+
This sort of dimensional analysis:
|
|
122
|
+
```
|
|
123
|
+
2 mL bromine | 3.119 g bromine
|
|
124
|
+
--------------x----------------- #=> 6.238 g bromine
|
|
125
|
+
1 | 1 mL bromine
|
|
126
|
+
```
|
|
127
|
+
becomes straightforward when you define a measurement:
|
|
128
|
+
```python
|
|
129
|
+
from ucon import Number, Scale, Units, Ratio
|
|
130
|
+
|
|
131
|
+
# Two milliliters of bromine
|
|
132
|
+
two_mL_bromine = Number(unit=Units.liter, scale=Scale.milli, quantity=2)
|
|
133
|
+
|
|
134
|
+
# Density of bromine: 3.119 g/mL
|
|
135
|
+
bromine_density = Ratio(
|
|
136
|
+
numerator=Number(unit=Units.gram, quantity=3.119),
|
|
137
|
+
denominator=Number(unit=Units.liter, scale=Scale.milli),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Multiply to find mass
|
|
141
|
+
grams_bromine = two_mL_bromine * bromine_density
|
|
142
|
+
print(grams_bromine) # <6.238 gram>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Scale conversion is automatic and precise:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
grams_bromine.to(Scale.milli) # <6238.0 milligram>
|
|
149
|
+
grams_bromine.to(Scale.kibi) # <0.006091796875 kibigram>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Roadmap Highlights
|
|
155
|
+
|
|
156
|
+
| Version | Theme | Focus |
|
|
157
|
+
|----------|-------|--------|
|
|
158
|
+
| [**0.3.x**](https://github.com/withtwoemms/ucon/milestone/1) | Primitive Type Refinement | Unified algebraic foundation |
|
|
159
|
+
| [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System | Linear & affine conversions |
|
|
160
|
+
| [**0.6.x**](https://github.com/withtwoemms/ucon/milestone/4) | Nonlinear / Specialized Units | Decibel, Percent, pH |
|
|
161
|
+
| [**0.8.x**](https://github.com/withtwoemms/ucon/milestone/6) | Pydantic Integration | Type-safe quantity validation |
|
|
162
|
+
|
|
163
|
+
See full roadmap: [ROADMAP.md](./ROADMAP.md)
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Contributing
|
|
168
|
+
|
|
169
|
+
Contributions, issues, and pull requests are welcome!
|
|
170
|
+
Ensure `nox` is installed.
|
|
171
|
+
```
|
|
172
|
+
pip install -r requirements.txt
|
|
173
|
+
```
|
|
174
|
+
Then run the full test suite (agains all supported python versions) before committing:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
nox -s test
|
|
178
|
+
```
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
> “If it can be measured, it can be represented.
|
|
182
|
+
If it can be represented, it can be validated.
|
|
183
|
+
If it can be validated, it can be trusted.”
|