ucon 0.3.5rc1__tar.gz → 0.4.0__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.5rc1 → ucon-0.4.0}/PKG-INFO +58 -30
- {ucon-0.3.5rc1 → ucon-0.4.0}/README.md +57 -29
- ucon-0.4.0/ROADMAP.md +244 -0
- ucon-0.4.0/tests/ucon/conversion/__init__.py +0 -0
- ucon-0.4.0/tests/ucon/conversion/test_graph.py +175 -0
- ucon-0.4.0/tests/ucon/conversion/test_map.py +163 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/tests/ucon/test_algebra.py +34 -34
- {ucon-0.3.5rc1 → ucon-0.4.0}/tests/ucon/test_core.py +20 -20
- {ucon-0.3.5rc1 → ucon-0.4.0}/tests/ucon/test_quantity.py +205 -14
- {ucon-0.3.5rc1 → ucon-0.4.0}/ucon/__init__.py +6 -2
- {ucon-0.3.5rc1 → ucon-0.4.0}/ucon/algebra.py +9 -5
- {ucon-0.3.5rc1 → ucon-0.4.0}/ucon/core.py +388 -68
- ucon-0.4.0/ucon/graph.py +415 -0
- ucon-0.4.0/ucon/maps.py +161 -0
- ucon-0.4.0/ucon/quantity.py +17 -0
- ucon-0.4.0/ucon/units.py +130 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/ucon.egg-info/PKG-INFO +58 -30
- {ucon-0.3.5rc1 → ucon-0.4.0}/ucon.egg-info/SOURCES.txt +5 -0
- ucon-0.3.5rc1/ROADMAP.md +0 -222
- ucon-0.3.5rc1/ucon/quantity.py +0 -196
- ucon-0.3.5rc1/ucon/units.py +0 -87
- {ucon-0.3.5rc1 → ucon-0.4.0}/.github/workflows/publish.yaml +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/.github/workflows/tests.yaml +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/.gitignore +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/LICENSE +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/NOTICE +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/docs/decisions/composable-unit-algebra.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/docs/decisions/composite-units.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/docs/decisions/unit-algebra-naming.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/docs/decisions/unity-distance-metric-for-nearest-scale.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/docs/explainers/type-operation-matrix.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/docs/explainers/why-algebraic-closure-matters.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/docs/explainers/why-type-safety-matters.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/docs/proposals/interface-unifying-the-value-layer.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/docs/proposals/project_unified-algebraic-core.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/docs/proposals/support-for-fractional-exponents.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/docs/proposals/unified-unit-presentation.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/noxfile.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/requirements.txt +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/setup.cfg +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/setup.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/tests/__init__.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/tests/ucon/__init__.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/tests/ucon/test_units.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/ucon.egg-info/dependency_links.txt +0 -0
- {ucon-0.3.5rc1 → ucon-0.4.0}/ucon.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ucon
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: a tool for dimensional analysis: a "Unit CONverter"
|
|
5
5
|
Home-page: https://github.com/withtwoemms/ucon
|
|
6
6
|
Author: Emmanuel I. Obi
|
|
@@ -55,8 +55,8 @@ Dynamic: summary
|
|
|
55
55
|
It combines **units**, **scales**, and **dimensions** into a composable algebra that supports:
|
|
56
56
|
|
|
57
57
|
- Dimensional analysis through `Number` and `Ratio`
|
|
58
|
-
- Scale-aware arithmetic and
|
|
59
|
-
- Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`,
|
|
58
|
+
- Scale-aware arithmetic via `UnitFactor` and `UnitProduct`
|
|
59
|
+
- Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, etc.)
|
|
60
60
|
- A clean foundation for physics, chemistry, data modeling, and beyond
|
|
61
61
|
|
|
62
62
|
Think of it as **`decimal.Decimal` for the physical world** — precise, predictable, and type-safe.
|
|
@@ -70,20 +70,24 @@ The crux of this tiny library is to provide abstractions that simplify the answe
|
|
|
70
70
|
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:
|
|
71
71
|
| Type | Defined In | Purpose | Typical Use Cases |
|
|
72
72
|
| ----------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
73
|
-
| **`Vector`** | `ucon.
|
|
74
|
-
| **`
|
|
75
|
-
| **`
|
|
73
|
+
| **`Vector`** | `ucon.algebra` | Represents the 8-component exponent tuple of a physical quantity's base dimensions (T, L, M, I, Θ, J, N, B). | Internal representation of dimensional algebra; building derived quantities (e.g., area, velocity, force). |
|
|
74
|
+
| **`Exponent`** | `ucon.algebra` | Represents base-power pairs (e.g., 10³, 2¹⁰) used by `Scale`. | Performing arithmetic on powers and bases; normalizing scales across conversions. |
|
|
75
|
+
| **`Dimension`** | `ucon.core` | 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). |
|
|
76
76
|
| **`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). |
|
|
77
|
-
| **`
|
|
78
|
-
| **`
|
|
77
|
+
| **`Unit`** | `ucon.core` | An atomic, scale-free measurement symbol (e.g., meter, second, joule) with a `Dimension`. | Defining base units; serving as graph nodes for future conversions. |
|
|
78
|
+
| **`UnitFactor`** | `ucon.core` | Pairs a `Unit` with a `Scale` (e.g., kilo + gram = kg). Used as keys inside `UnitProduct`. | Preserving user-provided scale prefixes through algebraic operations. |
|
|
79
|
+
| **`UnitProduct`** | `ucon.core` | A product/quotient of `UnitFactor`s with exponent tracking and simplification. | Representing composite units like m/s, kg·m/s², kJ·h. |
|
|
80
|
+
| **`Number`** | `ucon.core` | Combines a numeric quantity with a unit; the primary measurable type. | Performing arithmetic with units; representing physical quantities like 5 m/s. |
|
|
79
81
|
| **`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). |
|
|
80
|
-
| **`
|
|
82
|
+
| **`Map`** hierarchy | `ucon.maps` | Composable conversion morphisms: `LinearMap`, `AffineMap`, `ComposedMap`. | Defining conversion functions between units (e.g., meter→foot, celsius→kelvin). |
|
|
83
|
+
| **`ConversionGraph`** | `ucon.graph` | Registry of unit conversion edges with BFS path composition. | Converting between units via `Number.to(target)`; managing default and custom graphs. |
|
|
84
|
+
| **`units` module** | `ucon.units` | Defines canonical unit instances (SI, imperial, information, and derived units). | Quick access to standard physical units (`units.meter`, `units.foot`, `units.byte`, etc.). |
|
|
81
85
|
|
|
82
86
|
### Under the Hood
|
|
83
87
|
|
|
84
88
|
`ucon` models unit math through a hierarchy where each layer builds on the last:
|
|
85
89
|
|
|
86
|
-
<img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/
|
|
90
|
+
<img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/5df6a7fb2a6426ee6804096c092c10bed1b30b6f/ucon.data-model_v040.png align="center" alt="ucon Data Model" width=600/>
|
|
87
91
|
|
|
88
92
|
## Why `ucon`?
|
|
89
93
|
|
|
@@ -91,24 +95,22 @@ Python already has mature libraries for handling units and physical quantities
|
|
|
91
95
|
|
|
92
96
|
| Library | Focus | Limitation |
|
|
93
97
|
| --------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
94
|
-
| **Pint** | Runtime unit conversion and compatibility checking | Treats quantities as decorated numbers — conversions work, but the algebra behind them isn
|
|
98
|
+
| **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. |
|
|
95
99
|
| **SymPy** | Symbolic algebra and simplification of unit expressions | Excellent for symbolic reasoning, but not designed for runtime validation, conversion, or serialization. |
|
|
96
100
|
| **Unum** | Unit-aware arithmetic and unit propagation | Tracks units through arithmetic but lacks explicit dimensional algebra, conversion taxonomy, or runtime introspection. |
|
|
97
101
|
|
|
98
102
|
Together, these tools can _use_ units, but none can explicitly represent and verify the relationships between units and dimensions.
|
|
99
103
|
|
|
100
|
-
That
|
|
104
|
+
That's the gap `ucon` fills.
|
|
101
105
|
|
|
102
106
|
It treats units, dimensions, and scales as first-class objects and builds a composable algebra around them.
|
|
103
107
|
This allows you to:
|
|
104
108
|
- Represent dimensional meaning explicitly (`Dimension`, `Vector`);
|
|
105
109
|
- Compose and compute with type-safe, introspectable quantities (`Unit`, `Number`);
|
|
106
|
-
- Perform reversible, declarative conversions (standard, linear, affine, nonlinear);
|
|
107
|
-
- Serialize and validate measurements with Pydantic integration;
|
|
108
110
|
- Extend the system with custom unit registries and conversion families.
|
|
109
111
|
|
|
110
112
|
Where Pint, Unum, and SymPy focus on _how_ to compute with units,
|
|
111
|
-
`ucon` focuses on why those computations make sense. Every operation checks the dimensional structure, _not just the unit labels_. This means ucon doesn
|
|
113
|
+
`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:
|
|
112
114
|
```python
|
|
113
115
|
from ucon import Number, units
|
|
114
116
|
|
|
@@ -136,7 +138,8 @@ This sort of dimensional analysis:
|
|
|
136
138
|
```
|
|
137
139
|
becomes straightforward when you define a measurement:
|
|
138
140
|
```python
|
|
139
|
-
from ucon import Number, Scale,
|
|
141
|
+
from ucon import Number, Scale, units
|
|
142
|
+
from ucon.quantity import Ratio
|
|
140
143
|
|
|
141
144
|
# Two milliliters of bromine
|
|
142
145
|
mL = Scale.milli * units.liter
|
|
@@ -149,27 +152,52 @@ bromine_density = Ratio(
|
|
|
149
152
|
)
|
|
150
153
|
|
|
151
154
|
# Multiply to find mass
|
|
152
|
-
grams_bromine =
|
|
153
|
-
print(grams_bromine) # <6.238
|
|
155
|
+
grams_bromine = bromine_density.evaluate() * two_mL_bromine
|
|
156
|
+
print(grams_bromine) # <6.238 g>
|
|
154
157
|
```
|
|
155
158
|
|
|
156
|
-
Scale
|
|
159
|
+
Scale prefixes compose naturally:
|
|
160
|
+
```python
|
|
161
|
+
km = Scale.kilo * units.meter # UnitProduct with kilo-scaled meter
|
|
162
|
+
mg = Scale.milli * units.gram # UnitProduct with milli-scaled gram
|
|
163
|
+
|
|
164
|
+
print(km.shorthand) # 'km'
|
|
165
|
+
print(mg.shorthand) # 'mg'
|
|
166
|
+
|
|
167
|
+
# Scale arithmetic
|
|
168
|
+
print(km.fold_scale()) # 1000.0
|
|
169
|
+
print(mg.fold_scale()) # 0.001
|
|
170
|
+
```
|
|
157
171
|
|
|
172
|
+
Units are callable for ergonomic quantity construction:
|
|
158
173
|
```python
|
|
159
|
-
|
|
160
|
-
|
|
174
|
+
from ucon import units, Scale
|
|
175
|
+
|
|
176
|
+
# Callable syntax: unit(quantity) → Number
|
|
177
|
+
height = units.meter(1.8)
|
|
178
|
+
speed = (units.mile / units.hour)(60)
|
|
179
|
+
|
|
180
|
+
# Convert between units
|
|
181
|
+
height_ft = height.to(units.foot)
|
|
182
|
+
print(height_ft) # <5.905... ft>
|
|
183
|
+
|
|
184
|
+
# Scaled units work too
|
|
185
|
+
km = Scale.kilo * units.meter
|
|
186
|
+
distance = km(5)
|
|
187
|
+
distance_mi = distance.to(units.mile)
|
|
188
|
+
print(distance_mi) # <3.107... mi>
|
|
161
189
|
```
|
|
162
190
|
|
|
163
191
|
---
|
|
164
192
|
|
|
165
193
|
## Roadmap Highlights
|
|
166
194
|
|
|
167
|
-
| Version | Theme | Focus |
|
|
168
|
-
|
|
169
|
-
|
|
|
170
|
-
| [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System |
|
|
171
|
-
| [**0.6.x**](https://github.com/withtwoemms/ucon/milestone/4) | Nonlinear / Specialized Units | Decibel, Percent, pH |
|
|
172
|
-
| [**0.8.x**](https://github.com/withtwoemms/ucon/milestone/6) | Pydantic Integration | Type-safe quantity validation |
|
|
195
|
+
| Version | Theme | Focus | Status |
|
|
196
|
+
|----------|-------|--------|--------|
|
|
197
|
+
| **0.3.5** | Dimensional Algebra | Unit/Scale separation, `UnitFactor`, `UnitProduct` | ✅ Complete |
|
|
198
|
+
| [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System | `ConversionGraph`, `Number.to()`, callable units | 🚧 In Progress |
|
|
199
|
+
| [**0.6.x**](https://github.com/withtwoemms/ucon/milestone/4) | Nonlinear / Specialized Units | Decibel, Percent, pH | ⏳ Planned |
|
|
200
|
+
| [**0.8.x**](https://github.com/withtwoemms/ucon/milestone/6) | Pydantic Integration | Type-safe quantity validation | ⏳ Planned |
|
|
173
201
|
|
|
174
202
|
See full roadmap: [ROADMAP.md](./ROADMAP.md)
|
|
175
203
|
|
|
@@ -182,13 +210,13 @@ Ensure `nox` is installed.
|
|
|
182
210
|
```
|
|
183
211
|
pip install -r requirements.txt
|
|
184
212
|
```
|
|
185
|
-
Then run the full test suite (
|
|
213
|
+
Then run the full test suite (against all supported python versions) before committing:
|
|
186
214
|
|
|
187
215
|
```bash
|
|
188
216
|
nox -s test
|
|
189
217
|
```
|
|
190
218
|
---
|
|
191
219
|
|
|
192
|
-
>
|
|
220
|
+
> "If it can be measured, it can be represented.
|
|
193
221
|
If it can be represented, it can be validated.
|
|
194
|
-
If it can be validated, it can be trusted
|
|
222
|
+
If it can be validated, it can be trusted."
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
It combines **units**, **scales**, and **dimensions** into a composable algebra that supports:
|
|
19
19
|
|
|
20
20
|
- Dimensional analysis through `Number` and `Ratio`
|
|
21
|
-
- Scale-aware arithmetic and
|
|
22
|
-
- Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`,
|
|
21
|
+
- Scale-aware arithmetic via `UnitFactor` and `UnitProduct`
|
|
22
|
+
- Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, etc.)
|
|
23
23
|
- A clean foundation for physics, chemistry, data modeling, and beyond
|
|
24
24
|
|
|
25
25
|
Think of it as **`decimal.Decimal` for the physical world** — precise, predictable, and type-safe.
|
|
@@ -33,20 +33,24 @@ The crux of this tiny library is to provide abstractions that simplify the answe
|
|
|
33
33
|
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:
|
|
34
34
|
| Type | Defined In | Purpose | Typical Use Cases |
|
|
35
35
|
| ----------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
-
| **`Vector`** | `ucon.
|
|
37
|
-
| **`
|
|
38
|
-
| **`
|
|
36
|
+
| **`Vector`** | `ucon.algebra` | Represents the 8-component exponent tuple of a physical quantity's base dimensions (T, L, M, I, Θ, J, N, B). | Internal representation of dimensional algebra; building derived quantities (e.g., area, velocity, force). |
|
|
37
|
+
| **`Exponent`** | `ucon.algebra` | Represents base-power pairs (e.g., 10³, 2¹⁰) used by `Scale`. | Performing arithmetic on powers and bases; normalizing scales across conversions. |
|
|
38
|
+
| **`Dimension`** | `ucon.core` | 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). |
|
|
39
39
|
| **`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). |
|
|
40
|
-
| **`
|
|
41
|
-
| **`
|
|
40
|
+
| **`Unit`** | `ucon.core` | An atomic, scale-free measurement symbol (e.g., meter, second, joule) with a `Dimension`. | Defining base units; serving as graph nodes for future conversions. |
|
|
41
|
+
| **`UnitFactor`** | `ucon.core` | Pairs a `Unit` with a `Scale` (e.g., kilo + gram = kg). Used as keys inside `UnitProduct`. | Preserving user-provided scale prefixes through algebraic operations. |
|
|
42
|
+
| **`UnitProduct`** | `ucon.core` | A product/quotient of `UnitFactor`s with exponent tracking and simplification. | Representing composite units like m/s, kg·m/s², kJ·h. |
|
|
43
|
+
| **`Number`** | `ucon.core` | Combines a numeric quantity with a unit; the primary measurable type. | Performing arithmetic with units; representing physical quantities like 5 m/s. |
|
|
42
44
|
| **`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). |
|
|
43
|
-
| **`
|
|
45
|
+
| **`Map`** hierarchy | `ucon.maps` | Composable conversion morphisms: `LinearMap`, `AffineMap`, `ComposedMap`. | Defining conversion functions between units (e.g., meter→foot, celsius→kelvin). |
|
|
46
|
+
| **`ConversionGraph`** | `ucon.graph` | Registry of unit conversion edges with BFS path composition. | Converting between units via `Number.to(target)`; managing default and custom graphs. |
|
|
47
|
+
| **`units` module** | `ucon.units` | Defines canonical unit instances (SI, imperial, information, and derived units). | Quick access to standard physical units (`units.meter`, `units.foot`, `units.byte`, etc.). |
|
|
44
48
|
|
|
45
49
|
### Under the Hood
|
|
46
50
|
|
|
47
51
|
`ucon` models unit math through a hierarchy where each layer builds on the last:
|
|
48
52
|
|
|
49
|
-
<img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/
|
|
53
|
+
<img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/5df6a7fb2a6426ee6804096c092c10bed1b30b6f/ucon.data-model_v040.png align="center" alt="ucon Data Model" width=600/>
|
|
50
54
|
|
|
51
55
|
## Why `ucon`?
|
|
52
56
|
|
|
@@ -54,24 +58,22 @@ Python already has mature libraries for handling units and physical quantities
|
|
|
54
58
|
|
|
55
59
|
| Library | Focus | Limitation |
|
|
56
60
|
| --------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
57
|
-
| **Pint** | Runtime unit conversion and compatibility checking | Treats quantities as decorated numbers — conversions work, but the algebra behind them isn
|
|
61
|
+
| **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. |
|
|
58
62
|
| **SymPy** | Symbolic algebra and simplification of unit expressions | Excellent for symbolic reasoning, but not designed for runtime validation, conversion, or serialization. |
|
|
59
63
|
| **Unum** | Unit-aware arithmetic and unit propagation | Tracks units through arithmetic but lacks explicit dimensional algebra, conversion taxonomy, or runtime introspection. |
|
|
60
64
|
|
|
61
65
|
Together, these tools can _use_ units, but none can explicitly represent and verify the relationships between units and dimensions.
|
|
62
66
|
|
|
63
|
-
That
|
|
67
|
+
That's the gap `ucon` fills.
|
|
64
68
|
|
|
65
69
|
It treats units, dimensions, and scales as first-class objects and builds a composable algebra around them.
|
|
66
70
|
This allows you to:
|
|
67
71
|
- Represent dimensional meaning explicitly (`Dimension`, `Vector`);
|
|
68
72
|
- Compose and compute with type-safe, introspectable quantities (`Unit`, `Number`);
|
|
69
|
-
- Perform reversible, declarative conversions (standard, linear, affine, nonlinear);
|
|
70
|
-
- Serialize and validate measurements with Pydantic integration;
|
|
71
73
|
- Extend the system with custom unit registries and conversion families.
|
|
72
74
|
|
|
73
75
|
Where Pint, Unum, and SymPy focus on _how_ to compute with units,
|
|
74
|
-
`ucon` focuses on why those computations make sense. Every operation checks the dimensional structure, _not just the unit labels_. This means ucon doesn
|
|
76
|
+
`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:
|
|
75
77
|
```python
|
|
76
78
|
from ucon import Number, units
|
|
77
79
|
|
|
@@ -99,7 +101,8 @@ This sort of dimensional analysis:
|
|
|
99
101
|
```
|
|
100
102
|
becomes straightforward when you define a measurement:
|
|
101
103
|
```python
|
|
102
|
-
from ucon import Number, Scale,
|
|
104
|
+
from ucon import Number, Scale, units
|
|
105
|
+
from ucon.quantity import Ratio
|
|
103
106
|
|
|
104
107
|
# Two milliliters of bromine
|
|
105
108
|
mL = Scale.milli * units.liter
|
|
@@ -112,27 +115,52 @@ bromine_density = Ratio(
|
|
|
112
115
|
)
|
|
113
116
|
|
|
114
117
|
# Multiply to find mass
|
|
115
|
-
grams_bromine =
|
|
116
|
-
print(grams_bromine) # <6.238
|
|
118
|
+
grams_bromine = bromine_density.evaluate() * two_mL_bromine
|
|
119
|
+
print(grams_bromine) # <6.238 g>
|
|
117
120
|
```
|
|
118
121
|
|
|
119
|
-
Scale
|
|
122
|
+
Scale prefixes compose naturally:
|
|
123
|
+
```python
|
|
124
|
+
km = Scale.kilo * units.meter # UnitProduct with kilo-scaled meter
|
|
125
|
+
mg = Scale.milli * units.gram # UnitProduct with milli-scaled gram
|
|
126
|
+
|
|
127
|
+
print(km.shorthand) # 'km'
|
|
128
|
+
print(mg.shorthand) # 'mg'
|
|
129
|
+
|
|
130
|
+
# Scale arithmetic
|
|
131
|
+
print(km.fold_scale()) # 1000.0
|
|
132
|
+
print(mg.fold_scale()) # 0.001
|
|
133
|
+
```
|
|
120
134
|
|
|
135
|
+
Units are callable for ergonomic quantity construction:
|
|
121
136
|
```python
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
from ucon import units, Scale
|
|
138
|
+
|
|
139
|
+
# Callable syntax: unit(quantity) → Number
|
|
140
|
+
height = units.meter(1.8)
|
|
141
|
+
speed = (units.mile / units.hour)(60)
|
|
142
|
+
|
|
143
|
+
# Convert between units
|
|
144
|
+
height_ft = height.to(units.foot)
|
|
145
|
+
print(height_ft) # <5.905... ft>
|
|
146
|
+
|
|
147
|
+
# Scaled units work too
|
|
148
|
+
km = Scale.kilo * units.meter
|
|
149
|
+
distance = km(5)
|
|
150
|
+
distance_mi = distance.to(units.mile)
|
|
151
|
+
print(distance_mi) # <3.107... mi>
|
|
124
152
|
```
|
|
125
153
|
|
|
126
154
|
---
|
|
127
155
|
|
|
128
156
|
## Roadmap Highlights
|
|
129
157
|
|
|
130
|
-
| Version | Theme | Focus |
|
|
131
|
-
|
|
132
|
-
|
|
|
133
|
-
| [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System |
|
|
134
|
-
| [**0.6.x**](https://github.com/withtwoemms/ucon/milestone/4) | Nonlinear / Specialized Units | Decibel, Percent, pH |
|
|
135
|
-
| [**0.8.x**](https://github.com/withtwoemms/ucon/milestone/6) | Pydantic Integration | Type-safe quantity validation |
|
|
158
|
+
| Version | Theme | Focus | Status |
|
|
159
|
+
|----------|-------|--------|--------|
|
|
160
|
+
| **0.3.5** | Dimensional Algebra | Unit/Scale separation, `UnitFactor`, `UnitProduct` | ✅ Complete |
|
|
161
|
+
| [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System | `ConversionGraph`, `Number.to()`, callable units | 🚧 In Progress |
|
|
162
|
+
| [**0.6.x**](https://github.com/withtwoemms/ucon/milestone/4) | Nonlinear / Specialized Units | Decibel, Percent, pH | ⏳ Planned |
|
|
163
|
+
| [**0.8.x**](https://github.com/withtwoemms/ucon/milestone/6) | Pydantic Integration | Type-safe quantity validation | ⏳ Planned |
|
|
136
164
|
|
|
137
165
|
See full roadmap: [ROADMAP.md](./ROADMAP.md)
|
|
138
166
|
|
|
@@ -145,13 +173,13 @@ Ensure `nox` is installed.
|
|
|
145
173
|
```
|
|
146
174
|
pip install -r requirements.txt
|
|
147
175
|
```
|
|
148
|
-
Then run the full test suite (
|
|
176
|
+
Then run the full test suite (against all supported python versions) before committing:
|
|
149
177
|
|
|
150
178
|
```bash
|
|
151
179
|
nox -s test
|
|
152
180
|
```
|
|
153
181
|
---
|
|
154
182
|
|
|
155
|
-
>
|
|
183
|
+
> "If it can be measured, it can be represented.
|
|
156
184
|
If it can be represented, it can be validated.
|
|
157
|
-
If it can be validated, it can be trusted
|
|
185
|
+
If it can be validated, it can be trusted."
|
ucon-0.4.0/ROADMAP.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# 🧭 ucon Roadmap
|
|
2
|
+
|
|
3
|
+
> *A clear path from algebraic foundation to a stable 1.0 release.*
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🪜 Current Version: **v0.4.0** (in progress)
|
|
8
|
+
|
|
9
|
+
Building on v0.3.5 baseline:
|
|
10
|
+
- `ucon.core` (`Dimension`, `Scale`, `Unit`, `UnitFactor`, `UnitProduct`, `Number`, `Ratio`)
|
|
11
|
+
- `ucon.maps` (`Map`, `LinearMap`, `AffineMap`, `ComposedMap`)
|
|
12
|
+
- `ucon.graph` (`ConversionGraph`, default graph, `get_default_graph()`, `using_graph()`)
|
|
13
|
+
- `ucon.units` (SI + imperial + information units, callable syntax)
|
|
14
|
+
- Callable unit API: `meter(5)`, `(mile / hour)(60)`
|
|
15
|
+
- `Number.simplify()` for base-scale normalization
|
|
16
|
+
- `Dimension.information` with `units.bit`, `units.byte`
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## ✅ v0.3.x — Dimensional Algebra (Complete)
|
|
21
|
+
|
|
22
|
+
### 🔹 Summary
|
|
23
|
+
> Introduces dimensional algebra and establishes the Unit/Scale separation
|
|
24
|
+
> that underpins all downstream work.
|
|
25
|
+
|
|
26
|
+
### ✅ Goals
|
|
27
|
+
- [x] Implement `Vector` and `Dimension` classes
|
|
28
|
+
- [x] Integrate dimensions into `Unit`
|
|
29
|
+
- [x] Refactor `ucon.units` to use dimensional definitions
|
|
30
|
+
- [x] Publish documentation for dimensional operations
|
|
31
|
+
- [x] Verify uniqueness and hashing correctness across all Dimensions
|
|
32
|
+
- [x] Redesign `Exponent` to support algebraic operations (`__mul__`, `__truediv__`, `to_base`, etc.)
|
|
33
|
+
- [x] Remove redundant evaluated caching in favor of property-based computation
|
|
34
|
+
- [x] Integrate `Scale` with Exponent for consistent prefix arithmetic
|
|
35
|
+
- [x] Add regression tests for prefix math (`kilo / milli → mega`, `2¹⁰ / 10³ → 1.024×`)
|
|
36
|
+
- [x] Separate `scale` from `Unit`; delegate to `UnitFactor(unit, scale)`
|
|
37
|
+
- [x] Introduce `UnitProduct` with `fold_scale()` and `_residual_scale_factor`
|
|
38
|
+
- [x] `Number.value` returns as-expressed magnitude; `_canonical_magnitude` folds scale internally
|
|
39
|
+
- [x] Remove dead code and unify naming (`UnitFactor`, `UnitProduct`) across all docstrings and repr
|
|
40
|
+
|
|
41
|
+
### 🧩 Outcomes
|
|
42
|
+
- All units acquire explicit dimensional semantics
|
|
43
|
+
- Enables composable and type-safe dimensional operations
|
|
44
|
+
- Establishes the mathematical foundation for future conversions
|
|
45
|
+
- Unified algebraic foundation for all scaling and magnitude operations
|
|
46
|
+
- Clean Unit/Scale separation: `Unit` is an atomic symbol, `UnitFactor` pairs it with a `Scale`
|
|
47
|
+
- `UnitProduct` correctly tracks residual scale from cancelled units
|
|
48
|
+
- Type system is ready for a `ConversionGraph` to be built on top
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## ⚙️ v0.4.x — Conversion System Foundations (In Progress)
|
|
53
|
+
|
|
54
|
+
### 🔹 Summary
|
|
55
|
+
> Implements unified conversion engine for standard, linear, and affine conversions.
|
|
56
|
+
> Introduces callable unit API for ergonomic quantity construction.
|
|
57
|
+
|
|
58
|
+
### ✅ Goals
|
|
59
|
+
- [x] Introduce `ConversionGraph` registry keyed by `Dimension`
|
|
60
|
+
- [x] Add support for `standard`, `linear`, and `affine` conversion types
|
|
61
|
+
- [x] Implement `Number.to(target_unit)` conversion API
|
|
62
|
+
- [x] Scale-only conversions short-circuit without graph lookup
|
|
63
|
+
- [x] Composite-to-composite conversion via per-component decomposition
|
|
64
|
+
- [x] Round-trip validation for reversible conversions (inverse maps)
|
|
65
|
+
- [x] Callable unit syntax: `meter(5)`, `(mile / hour)(60)`
|
|
66
|
+
- [x] Default graph with common SI and imperial conversions
|
|
67
|
+
- [x] Imperial units: `foot`, `mile`, `yard`, `inch`, `pound`, `ounce`, `fahrenheit`, `gallon`
|
|
68
|
+
- [x] `Number.simplify()` — Express in base scale
|
|
69
|
+
- [x] `Dimension.information` with `units.bit` and `units.byte`
|
|
70
|
+
- [x] `Vector` extended to 8 components (added B for information)
|
|
71
|
+
- [x] Information unit conversions in default graph (byte ↔ bit)
|
|
72
|
+
- [ ] Extend tests to include temperature, pressure, and base SI conversions
|
|
73
|
+
- [ ] Document Exponent/Scale relationship in developer guide
|
|
74
|
+
|
|
75
|
+
### 🧩 Outcomes
|
|
76
|
+
- Unified conversion taxonomy
|
|
77
|
+
- Reversible, dimension-checked conversions
|
|
78
|
+
- Scale-aware graph that leverages the `Unit`/`UnitFactor` separation from v0.3.x
|
|
79
|
+
- Ergonomic API: units are callable, returning `Number` instances
|
|
80
|
+
- Information dimension support (bit, byte) with binary prefix compatibility
|
|
81
|
+
- `Number.simplify()` for expressing quantities in base scale
|
|
82
|
+
- Forms the basis for nonlinear and domain-specific conversion families
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 🧱 v0.5.x — Unit Systems & Registries
|
|
87
|
+
|
|
88
|
+
### 🔹 Summary
|
|
89
|
+
> Introduces an extensible registry system for custom units and aliases.
|
|
90
|
+
|
|
91
|
+
### ✅ Goals
|
|
92
|
+
- [x] Implement `have(name)` membership check
|
|
93
|
+
- [ ] Add `UnitSystem` abstraction
|
|
94
|
+
- [ ] Support `registry.add(unit)` and dynamic system registration
|
|
95
|
+
- [ ] Validate alias uniqueness and collision prevention
|
|
96
|
+
- [ ] Include examples for user-defined unit extensions
|
|
97
|
+
|
|
98
|
+
### 🧩 Outcomes
|
|
99
|
+
- Registry-based extensibility for domain-specific systems
|
|
100
|
+
- Dynamic unit registration and discovery
|
|
101
|
+
- Groundwork for plugin-style system extensions
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 🧪 v0.6.x — Nonlinear & Specialized Conversions
|
|
106
|
+
|
|
107
|
+
### 🔹 Summary
|
|
108
|
+
> Adds support for logarithmic, fractional, and other specialized dimensionless conversions.
|
|
109
|
+
|
|
110
|
+
### ✅ Goals
|
|
111
|
+
- [ ] Extend conversion registry schema with `"nonlinear"` family
|
|
112
|
+
- [ ] Add `to_base` / `from_base` lambdas for function-based mappings
|
|
113
|
+
- [ ] Define sample nonlinear conversions (`decibel`, `bel`, `pH`)
|
|
114
|
+
- [ ] Add tolerance-aware tests for nonlinear conversions
|
|
115
|
+
- [ ] Introduce structured dimensionless unit family (`radian`, `percent`, `ppm`, `count`, etc.)
|
|
116
|
+
- [ ] Define canonical dimensionless subtypes for angular, fractional, and count semantics
|
|
117
|
+
- [ ] Ensure automatic collapse of equivalent units (`m/m → none`, `J/J → none`) via Ratio
|
|
118
|
+
|
|
119
|
+
### 🧩 Outcomes
|
|
120
|
+
- Support for function-based (nonlinear) physical conversions
|
|
121
|
+
- Unified algebraic framework across all conversion types
|
|
122
|
+
- Rich, semantically meaningful representation of dimensionless quantities
|
|
123
|
+
- Enables acoustics (dB), geometry (rad, sr), statistics (probability), and fractional scales (%, ppm)
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 🧰 v0.7.x — Testing, Developer Experience, & API Polish
|
|
128
|
+
|
|
129
|
+
### 🔹 Summary
|
|
130
|
+
> Strengthens tests, developer ergonomics, and runtime feedback.
|
|
131
|
+
|
|
132
|
+
### ✅ Goals
|
|
133
|
+
- [ ] Reach 95%+ test coverage
|
|
134
|
+
- [ ] Add property-based tests for dimensional invariants
|
|
135
|
+
- [ ] Improve error reporting, `__repr__`, and exception messaging
|
|
136
|
+
- [ ] Validate public API imports and maintain consistent naming
|
|
137
|
+
- [ ] Add CI coverage reports and build badges
|
|
138
|
+
|
|
139
|
+
### 🧩 Outcomes
|
|
140
|
+
- Reliable, developer-friendly foundation
|
|
141
|
+
- Consistent runtime behavior and output clarity
|
|
142
|
+
- Prepares API for public documentation and 1.0 freeze
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 🧩 v0.8.x — Pydantic Integration
|
|
147
|
+
|
|
148
|
+
### 🔹 Summary
|
|
149
|
+
> Introduces seamless integration with **Pydantic v2**, enabling validation, serialization, and typed dimensional models.
|
|
150
|
+
|
|
151
|
+
### ✅ Goals
|
|
152
|
+
- [ ] Define Pydantic-compatible field types (`UnitType`, `NumberType`)
|
|
153
|
+
- [ ] Implement `__get_pydantic_core_schema__` for Units and Numbers
|
|
154
|
+
- [ ] Support automatic conversion/validation for user-defined models
|
|
155
|
+
- [ ] Add YAML / JSON encoding for quantities (`Number(unit="meter", quantity=5)`)
|
|
156
|
+
- [ ] Add Pydantic-based examples (API config, simulation parameters)
|
|
157
|
+
|
|
158
|
+
### 🧩 Outcomes
|
|
159
|
+
- Native validation and serialization for dimensioned quantities
|
|
160
|
+
- Enables safe configuration in data models and APIs
|
|
161
|
+
- Bridges `ucon`'s algebraic model with modern Python typing ecosystems
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 📘 v0.9.x — Documentation & RC Phase
|
|
166
|
+
|
|
167
|
+
### 🔹 Summary
|
|
168
|
+
> Completes documentation, finalizes examples, and preps release candidates.
|
|
169
|
+
|
|
170
|
+
### ✅ Goals
|
|
171
|
+
- [ ] Write comprehensive README and developer guide
|
|
172
|
+
- [ ] Publish API reference docs (Sphinx / MkDocs)
|
|
173
|
+
- [ ] Add SymPy / Pint comparison appendix
|
|
174
|
+
- [ ] Freeze and document all public APIs
|
|
175
|
+
- [ ] Publish one or more release candidates (RC1, RC2)
|
|
176
|
+
|
|
177
|
+
### 🧩 Outcomes
|
|
178
|
+
- Complete public-facing documentation
|
|
179
|
+
- API frozen and versioned for stability
|
|
180
|
+
- Ready for final testing and validation before 1.0
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 🏁 v1.0.0 — Stable, Introspective Core
|
|
185
|
+
|
|
186
|
+
### 🔹 Summary
|
|
187
|
+
> First major release: a unified algebra for composable, type-safe, and semantically clear unit conversion.
|
|
188
|
+
|
|
189
|
+
### ✅ Goals
|
|
190
|
+
- [ ] Tag and release to PyPI
|
|
191
|
+
- [ ] Validate packaging and dependency metadata
|
|
192
|
+
- [ ] Include examples and tutorials in docs
|
|
193
|
+
- [ ] Announce 1.0 on GitHub and PyPI
|
|
194
|
+
|
|
195
|
+
### 🧩 Outcomes
|
|
196
|
+
- Stable, well-tested release
|
|
197
|
+
- Fully type-safe and validated core
|
|
198
|
+
- Production-ready for integration into scientific and engineering workflows
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 🧠 Post-1.0 Vision
|
|
203
|
+
|
|
204
|
+
| Future Direction | Description |
|
|
205
|
+
|------------------|-------------|
|
|
206
|
+
| **Graph-based conversion paths** | Automatically discover multi-hop conversions between compatible units |
|
|
207
|
+
| **Type-safe generics** | `Number[Dimension.length]` support for type checking and IDE hints |
|
|
208
|
+
| **Symbolic bridge to SymPy** | Export units and expressions for symbolic manipulation |
|
|
209
|
+
| **Visualization** | Dimensional relationship graphs and dependency trees |
|
|
210
|
+
| **Plugin architecture** | Load conversions and systems dynamically (YAML/JSON plugins) |
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 🗓️ Milestone Summary
|
|
215
|
+
|
|
216
|
+
| Version | Theme | Key Focus | Status |
|
|
217
|
+
|----------|--------|------------|---------|
|
|
218
|
+
| **0.3.5** | Dimensional Algebra | Unit/Scale separation, `UnitFactor`, `UnitProduct` | ✅ Complete |
|
|
219
|
+
| **0.4.0** | Conversion Engine | `ConversionGraph`, `Number.to()`, callable units | 🚧 In Progress |
|
|
220
|
+
| **0.5.0** | Unit Systems & Registries | Extensible registry system | ⏳ Planned |
|
|
221
|
+
| **0.6.0** | Nonlinear Conversions | Logarithmic / exponential families | ⏳ Planned |
|
|
222
|
+
| **0.7.0** | Testing & API Polish | Coverage, ergonomics, stability | ⏳ Planned |
|
|
223
|
+
| **0.8.0** | Pydantic Integration | Typed validation, serialization | ⏳ Planned |
|
|
224
|
+
| **0.9.x** | Documentation & RC | Freeze API, publish docs, RCs | ⏳ Planned |
|
|
225
|
+
| **1.0.0** | Stable Release | Publish production-ready core | 🔮 Future |
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
### ✨ Guiding Principle
|
|
230
|
+
|
|
231
|
+
> "If it can be measured, it can be represented.
|
|
232
|
+
> If it can be represented, it can be validated.
|
|
233
|
+
> If it can be validated, it can be trusted."
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### 💡 Why Pydantic Integration Matters
|
|
238
|
+
|
|
239
|
+
- Enables **runtime validation** of dimensional correctness:
|
|
240
|
+
```python
|
|
241
|
+
class Config(BaseModel):
|
|
242
|
+
length: NumberType[Dimension.length]
|
|
243
|
+
time: NumberType[Dimension.time]
|
|
244
|
+
```
|
|
File without changes
|