ucon 0.4.1__tar.gz → 0.5.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.4.1 → ucon-0.5.0}/PKG-INFO +34 -6
- {ucon-0.4.1 → ucon-0.5.0}/README.md +33 -5
- ucon-0.5.0/ROADMAP.md +270 -0
- ucon-0.5.0/docs/decisions/pseudo-dimension-tuple-values.md +183 -0
- {ucon-0.4.1 → ucon-0.5.0}/tests/ucon/test_algebra.py +4 -4
- ucon-0.5.0/tests/ucon/test_dimensionless_units.py +248 -0
- {ucon-0.4.1 → ucon-0.5.0}/tests/ucon/test_quantity.py +60 -0
- {ucon-0.4.1 → ucon-0.5.0}/ucon/algebra.py +4 -3
- {ucon-0.4.1 → ucon-0.5.0}/ucon/core.py +50 -9
- {ucon-0.4.1 → ucon-0.5.0}/ucon/graph.py +18 -0
- {ucon-0.4.1 → ucon-0.5.0}/ucon/units.py +26 -2
- {ucon-0.4.1 → ucon-0.5.0}/ucon.egg-info/PKG-INFO +34 -6
- {ucon-0.4.1 → ucon-0.5.0}/ucon.egg-info/SOURCES.txt +2 -0
- ucon-0.4.1/ROADMAP.md +0 -244
- {ucon-0.4.1 → ucon-0.5.0}/.github/workflows/publish.yaml +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/.github/workflows/tests.yaml +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/.gitignore +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/LICENSE +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/NOTICE +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/docs/decisions/composable-unit-algebra.md +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/docs/decisions/composite-units.md +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/docs/decisions/unit-algebra-naming.md +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/docs/decisions/unity-distance-metric-for-nearest-scale.md +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/docs/explainers/exponent-scale-relationship.md +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/docs/explainers/type-operation-matrix.md +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/docs/explainers/why-algebraic-closure-matters.md +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/docs/explainers/why-type-safety-matters.md +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/docs/proposals/interface-unifying-the-value-layer.md +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/docs/proposals/project_unified-algebraic-core.md +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/docs/proposals/support-for-fractional-exponents.md +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/docs/proposals/unified-unit-presentation.md +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/noxfile.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/requirements.txt +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/setup.cfg +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/setup.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/tests/__init__.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/tests/ucon/__init__.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/tests/ucon/conversion/__init__.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/tests/ucon/conversion/test_graph.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/tests/ucon/conversion/test_map.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/tests/ucon/test_core.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/tests/ucon/test_default_graph_conversions.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/tests/ucon/test_units.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/ucon/__init__.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/ucon/maps.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/ucon/quantity.py +0 -0
- {ucon-0.4.1 → ucon-0.5.0}/ucon.egg-info/dependency_links.txt +0 -0
- {ucon-0.4.1 → ucon-0.5.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.5.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
|
|
@@ -35,7 +35,12 @@ Dynamic: maintainer
|
|
|
35
35
|
Dynamic: maintainer-email
|
|
36
36
|
Dynamic: summary
|
|
37
37
|
|
|
38
|
-
<
|
|
38
|
+
<table>
|
|
39
|
+
<tr>
|
|
40
|
+
<td width="200">
|
|
41
|
+
<img src="https://gist.githubusercontent.com/withtwoemms/0cb9e6bc8df08f326771a89eeb790f8e/raw/221c60e85ac8361c7d202896b52c1a279081b54c/ucon-logo.png" align="left" width="200" />
|
|
42
|
+
</td>
|
|
43
|
+
<td>
|
|
39
44
|
|
|
40
45
|
# ucon
|
|
41
46
|
|
|
@@ -45,6 +50,10 @@ Dynamic: summary
|
|
|
45
50
|
[](https://codecov.io/gh/withtwoemms/ucon)
|
|
46
51
|
[](https://github.com/withtwoemms/ucon/actions?query=workflow%3Apublish)
|
|
47
52
|
|
|
53
|
+
</td>
|
|
54
|
+
</tr>
|
|
55
|
+
</table>
|
|
56
|
+
|
|
48
57
|
> A lightweight, **unit-aware computation library** for Python — built on first-principles.
|
|
49
58
|
|
|
50
59
|
---
|
|
@@ -57,6 +66,7 @@ It combines **units**, **scales**, and **dimensions** into a composable algebra
|
|
|
57
66
|
- Dimensional analysis through `Number` and `Ratio`
|
|
58
67
|
- Scale-aware arithmetic via `UnitFactor` and `UnitProduct`
|
|
59
68
|
- Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, etc.)
|
|
69
|
+
- Pseudo-dimensions for angles, solid angles, and ratios with semantic isolation
|
|
60
70
|
- A clean foundation for physics, chemistry, data modeling, and beyond
|
|
61
71
|
|
|
62
72
|
Think of it as **`decimal.Decimal` for the physical world** — precise, predictable, and type-safe.
|
|
@@ -188,16 +198,34 @@ distance_mi = distance.to(units.mile)
|
|
|
188
198
|
print(distance_mi) # <3.107... mi>
|
|
189
199
|
```
|
|
190
200
|
|
|
201
|
+
Dimensionless units have semantic isolation — angles, solid angles, and ratios are distinct:
|
|
202
|
+
```python
|
|
203
|
+
import math
|
|
204
|
+
from ucon import units
|
|
205
|
+
|
|
206
|
+
# Angle conversions
|
|
207
|
+
angle = units.radian(math.pi)
|
|
208
|
+
print(angle.to(units.degree)) # <180.0 deg>
|
|
209
|
+
|
|
210
|
+
# Ratio conversions
|
|
211
|
+
ratio = units.percent(50)
|
|
212
|
+
print(ratio.to(units.ppm)) # <500000.0 ppm>
|
|
213
|
+
|
|
214
|
+
# Cross-family conversions are prevented
|
|
215
|
+
units.radian(1).to(units.percent) # raises ConversionNotFound
|
|
216
|
+
```
|
|
217
|
+
|
|
191
218
|
---
|
|
192
219
|
|
|
193
220
|
## Roadmap Highlights
|
|
194
221
|
|
|
195
222
|
| Version | Theme | Focus | Status |
|
|
196
223
|
|----------|-------|--------|--------|
|
|
197
|
-
| **0.3.
|
|
198
|
-
|
|
|
199
|
-
|
|
|
200
|
-
|
|
|
224
|
+
| **0.3.x** | Dimensional Algebra | Unit/Scale separation, `UnitFactor`, `UnitProduct` | ✅ Complete |
|
|
225
|
+
| **0.4.x** | Conversion System | `ConversionGraph`, `Number.to()`, callable units | ✅ Complete |
|
|
226
|
+
| **0.5.0** | Dimensionless Units | Pseudo-dimensions for angle, solid angle, ratio | ✅ Complete |
|
|
227
|
+
| **0.5.x** | Metrology | Uncertainty propagation, `UnitSystem` | 🚧 In Progress |
|
|
228
|
+
| **0.7.x** | Pydantic Integration | Type-safe quantity validation | ⏳ Planned |
|
|
201
229
|
|
|
202
230
|
See full roadmap: [ROADMAP.md](./ROADMAP.md)
|
|
203
231
|
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
<
|
|
1
|
+
<table>
|
|
2
|
+
<tr>
|
|
3
|
+
<td width="200">
|
|
4
|
+
<img src="https://gist.githubusercontent.com/withtwoemms/0cb9e6bc8df08f326771a89eeb790f8e/raw/221c60e85ac8361c7d202896b52c1a279081b54c/ucon-logo.png" align="left" width="200" />
|
|
5
|
+
</td>
|
|
6
|
+
<td>
|
|
2
7
|
|
|
3
8
|
# ucon
|
|
4
9
|
|
|
@@ -8,6 +13,10 @@
|
|
|
8
13
|
[](https://codecov.io/gh/withtwoemms/ucon)
|
|
9
14
|
[](https://github.com/withtwoemms/ucon/actions?query=workflow%3Apublish)
|
|
10
15
|
|
|
16
|
+
</td>
|
|
17
|
+
</tr>
|
|
18
|
+
</table>
|
|
19
|
+
|
|
11
20
|
> A lightweight, **unit-aware computation library** for Python — built on first-principles.
|
|
12
21
|
|
|
13
22
|
---
|
|
@@ -20,6 +29,7 @@ It combines **units**, **scales**, and **dimensions** into a composable algebra
|
|
|
20
29
|
- Dimensional analysis through `Number` and `Ratio`
|
|
21
30
|
- Scale-aware arithmetic via `UnitFactor` and `UnitProduct`
|
|
22
31
|
- Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, etc.)
|
|
32
|
+
- Pseudo-dimensions for angles, solid angles, and ratios with semantic isolation
|
|
23
33
|
- A clean foundation for physics, chemistry, data modeling, and beyond
|
|
24
34
|
|
|
25
35
|
Think of it as **`decimal.Decimal` for the physical world** — precise, predictable, and type-safe.
|
|
@@ -151,16 +161,34 @@ distance_mi = distance.to(units.mile)
|
|
|
151
161
|
print(distance_mi) # <3.107... mi>
|
|
152
162
|
```
|
|
153
163
|
|
|
164
|
+
Dimensionless units have semantic isolation — angles, solid angles, and ratios are distinct:
|
|
165
|
+
```python
|
|
166
|
+
import math
|
|
167
|
+
from ucon import units
|
|
168
|
+
|
|
169
|
+
# Angle conversions
|
|
170
|
+
angle = units.radian(math.pi)
|
|
171
|
+
print(angle.to(units.degree)) # <180.0 deg>
|
|
172
|
+
|
|
173
|
+
# Ratio conversions
|
|
174
|
+
ratio = units.percent(50)
|
|
175
|
+
print(ratio.to(units.ppm)) # <500000.0 ppm>
|
|
176
|
+
|
|
177
|
+
# Cross-family conversions are prevented
|
|
178
|
+
units.radian(1).to(units.percent) # raises ConversionNotFound
|
|
179
|
+
```
|
|
180
|
+
|
|
154
181
|
---
|
|
155
182
|
|
|
156
183
|
## Roadmap Highlights
|
|
157
184
|
|
|
158
185
|
| Version | Theme | Focus | Status |
|
|
159
186
|
|----------|-------|--------|--------|
|
|
160
|
-
| **0.3.
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
187
|
+
| **0.3.x** | Dimensional Algebra | Unit/Scale separation, `UnitFactor`, `UnitProduct` | ✅ Complete |
|
|
188
|
+
| **0.4.x** | Conversion System | `ConversionGraph`, `Number.to()`, callable units | ✅ Complete |
|
|
189
|
+
| **0.5.0** | Dimensionless Units | Pseudo-dimensions for angle, solid angle, ratio | ✅ Complete |
|
|
190
|
+
| **0.5.x** | Metrology | Uncertainty propagation, `UnitSystem` | 🚧 In Progress |
|
|
191
|
+
| **0.7.x** | Pydantic Integration | Type-safe quantity validation | ⏳ Planned |
|
|
164
192
|
|
|
165
193
|
See full roadmap: [ROADMAP.md](./ROADMAP.md)
|
|
166
194
|
|
ucon-0.5.0/ROADMAP.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# ucon Roadmap
|
|
2
|
+
|
|
3
|
+
> *A clear path from algebraic foundation to a stable 1.0 release.*
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Vision
|
|
8
|
+
|
|
9
|
+
ucon is a dimensional analysis library for engineers building systems where unit handling is infrastructure, not just convenience.
|
|
10
|
+
|
|
11
|
+
**Target users:**
|
|
12
|
+
|
|
13
|
+
- Library authors embedding unit handling without global state
|
|
14
|
+
- Domain specialists defining dimensions that match their field
|
|
15
|
+
- Modern stack developers wanting first-class Pydantic, Polars, MCP support
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Release Timeline
|
|
20
|
+
|
|
21
|
+
| Version | Theme | Status |
|
|
22
|
+
|---------|-------|--------|
|
|
23
|
+
| v0.3.x | Dimensional Algebra | Complete |
|
|
24
|
+
| v0.4.x | Core Conversion + Information | Complete |
|
|
25
|
+
| v0.5.0 | Dimensionless Units | Complete |
|
|
26
|
+
| v0.5.x | Uncertainty Propagation | Planned |
|
|
27
|
+
| v0.5.x | BasisMap + UnitSystem | Planned |
|
|
28
|
+
| v0.6.0 | NumPy Array Support | Planned |
|
|
29
|
+
| v0.7.0 | Pydantic + Serialization | Planned |
|
|
30
|
+
| v0.8.0 | String Parsing | Planned |
|
|
31
|
+
| v0.9.0 | Constants + Logarithmic Units | Planned |
|
|
32
|
+
| v0.10.0 | DataFrame Integration | Planned |
|
|
33
|
+
| v1.0.0 | API Stability | Planned |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Current Version: **v0.5.x** (in progress)
|
|
38
|
+
|
|
39
|
+
Building on v0.5.0 baseline:
|
|
40
|
+
- `ucon.core` (`Dimension`, `Scale`, `Unit`, `UnitFactor`, `UnitProduct`, `Number`, `Ratio`)
|
|
41
|
+
- `ucon.maps` (`Map`, `LinearMap`, `AffineMap`, `ComposedMap`)
|
|
42
|
+
- `ucon.graph` (`ConversionGraph`, default graph, `get_default_graph()`, `using_graph()`)
|
|
43
|
+
- `ucon.units` (SI + imperial + information + angle + ratio units, callable syntax)
|
|
44
|
+
- Callable unit API: `meter(5)`, `(mile / hour)(60)`
|
|
45
|
+
- `Number.simplify()` for base-scale normalization
|
|
46
|
+
- `Dimension.information` with `units.bit`, `units.byte`
|
|
47
|
+
- Pseudo-dimensions: `angle`, `solid_angle`, `ratio` with semantic isolation
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## v0.3.x — Dimensional Algebra (Complete)
|
|
52
|
+
|
|
53
|
+
**Theme:** Algebraic foundation.
|
|
54
|
+
|
|
55
|
+
- [x] `Vector` and `Dimension` classes
|
|
56
|
+
- [x] Unit/Scale separation: `Unit` is atomic, `UnitFactor` pairs unit+scale
|
|
57
|
+
- [x] `UnitProduct` with `fold_scale()` and `_residual_scale_factor`
|
|
58
|
+
- [x] `Exponent` algebraic operations (`__mul__`, `__truediv__`, `to_base`)
|
|
59
|
+
- [x] Scale/Exponent integration for prefix arithmetic
|
|
60
|
+
|
|
61
|
+
**Outcomes:**
|
|
62
|
+
- All units acquire explicit dimensional semantics
|
|
63
|
+
- Enables composable and type-safe dimensional operations
|
|
64
|
+
- Establishes the mathematical foundation for future conversions
|
|
65
|
+
- Unified algebraic foundation for all scaling and magnitude operations
|
|
66
|
+
- Clean Unit/Scale separation: `Unit` is an atomic symbol, `UnitFactor` pairs it with a `Scale`
|
|
67
|
+
- `UnitProduct` correctly tracks residual scale from cancelled units
|
|
68
|
+
- Type system is ready for a `ConversionGraph` to be built on top
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## v0.4.x — Conversion System Foundations (Complete)
|
|
73
|
+
|
|
74
|
+
**Theme:** First useful release.
|
|
75
|
+
|
|
76
|
+
- [x] `Map` hierarchy (`LinearMap`, `AffineMap`, `ComposedMap`)
|
|
77
|
+
- [x] `Quantity` class (callable unit constructor)
|
|
78
|
+
- [x] `ConversionGraph` with edge API and BFS path finding
|
|
79
|
+
- [x] `Number.to()` wired to graph
|
|
80
|
+
- [x] Default graph with SI + Imperial + conventional units
|
|
81
|
+
- [x] Unit registry with `get_unit_by_name()`
|
|
82
|
+
- [x] Graph management (`get_default_graph`, `set_default_graph`, `using_graph`)
|
|
83
|
+
- [x] `Dimension.information` with `bit`, `byte`
|
|
84
|
+
- [x] `Vector` extended to 8 components (added B for information)
|
|
85
|
+
- [x] Binary scale prefixes: `kibi`, `mebi`, `gibi`, `tebi`, `pebi`, `exbi`
|
|
86
|
+
- [x] `Number.simplify()` for base-scale normalization
|
|
87
|
+
- [x] Temperature, pressure, and base SI conversion tests
|
|
88
|
+
- [x] Exponent/Scale developer guide
|
|
89
|
+
|
|
90
|
+
**Outcomes:**
|
|
91
|
+
- Unified conversion taxonomy
|
|
92
|
+
- Reversible, dimension-checked conversions
|
|
93
|
+
- Scale-aware graph that leverages the `Unit`/`UnitFactor` separation from v0.3.x
|
|
94
|
+
- Ergonomic API: units are callable, returning `Number` instances
|
|
95
|
+
- Information dimension support (bit, byte) with binary prefix compatibility
|
|
96
|
+
- `Number.simplify()` for expressing quantities in base scale
|
|
97
|
+
- Forms the basis for nonlinear and domain-specific conversion families
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## v0.5.0 — Dimensionless Units (Complete)
|
|
102
|
+
|
|
103
|
+
**Theme:** Complete the dimension model.
|
|
104
|
+
|
|
105
|
+
- [x] Pseudo-dimensions: `angle`, `solid_angle`, `ratio` (same zero vector, distinct enum identity)
|
|
106
|
+
- [x] Angle units: `radian`, `degree`, `gradian`, `arcminute`, `arcsecond`, `turn`
|
|
107
|
+
- [x] Solid angle units: `steradian`, `square_degree`
|
|
108
|
+
- [x] Ratio units: `percent`, `permille`, `ppm`, `ppb`, `basis_point`
|
|
109
|
+
- [x] Cross-pseudo-dimension conversion fails (enforced isolation)
|
|
110
|
+
- [x] Conversion edges for all new units
|
|
111
|
+
|
|
112
|
+
**Outcomes:**
|
|
113
|
+
- Semantic isolation prevents nonsensical conversions (radian → percent)
|
|
114
|
+
- Rich dimensionless unit coverage for geometry, optics, finance, chemistry
|
|
115
|
+
- Complete dimension model ready for metrology extensions
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## v0.5.x — Uncertainty Propagation
|
|
120
|
+
|
|
121
|
+
**Theme:** Metrology foundation.
|
|
122
|
+
|
|
123
|
+
- [ ] `Number.uncertainty: float | None`
|
|
124
|
+
- [ ] Propagation through arithmetic (uncorrelated, quadrature)
|
|
125
|
+
- [ ] Propagation through conversion via `Map.derivative()`
|
|
126
|
+
- [ ] Construction: `meter(1.234, uncertainty=0.005)`
|
|
127
|
+
- [ ] Display: `1.234 ± 0.005 meter`
|
|
128
|
+
|
|
129
|
+
**Outcomes:**
|
|
130
|
+
- First-class uncertainty support for scientific and engineering workflows
|
|
131
|
+
- Correct propagation through both arithmetic and unit conversion
|
|
132
|
+
- Foundation for full metrology capabilities
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## v0.5.x — BasisMap + UnitSystem
|
|
137
|
+
|
|
138
|
+
**Theme:** Cross-system architecture.
|
|
139
|
+
|
|
140
|
+
- [ ] `UnitSystem` class (named grouping of base units)
|
|
141
|
+
- [ ] `BasisMap` class (structural equivalence between systems)
|
|
142
|
+
- [ ] Prebuilt systems: `si`, `imperial`, `cgs`
|
|
143
|
+
- [ ] `graph.connect_systems()` for bulk edge creation
|
|
144
|
+
- [ ] Support for custom domain dimensions
|
|
145
|
+
|
|
146
|
+
**Outcomes:**
|
|
147
|
+
- `BasisMap` enables system-aware "express in base units" functionality
|
|
148
|
+
- Named unit systems for domain-specific workflows
|
|
149
|
+
- Foundation for plugin-style system extensions
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## v0.6.0 — NumPy Array Support
|
|
154
|
+
|
|
155
|
+
**Theme:** Scientific computing integration.
|
|
156
|
+
|
|
157
|
+
- [ ] `Number` wraps `np.ndarray` values
|
|
158
|
+
- [ ] Vectorized conversion
|
|
159
|
+
- [ ] Vectorized arithmetic with uncertainty propagation
|
|
160
|
+
- [ ] Performance benchmarks
|
|
161
|
+
|
|
162
|
+
**Outcomes:**
|
|
163
|
+
- Seamless integration with NumPy-based scientific workflows
|
|
164
|
+
- Efficient batch conversions for large datasets
|
|
165
|
+
- Performance characteristics documented and optimized
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## v0.7.0 — Pydantic + Serialization
|
|
170
|
+
|
|
171
|
+
**Theme:** API and persistence integration.
|
|
172
|
+
|
|
173
|
+
- [ ] Native Pydantic v2 support for `Number`
|
|
174
|
+
- [ ] JSON serialization/deserialization
|
|
175
|
+
- [ ] Pickle support
|
|
176
|
+
- [ ] Optional: MCP server for unit conversion tool
|
|
177
|
+
|
|
178
|
+
**Outcomes:**
|
|
179
|
+
- Native validation and serialization for dimensioned quantities
|
|
180
|
+
- Enables safe configuration in data models and APIs
|
|
181
|
+
- Bridges ucon's algebraic model with modern Python typing ecosystems
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## v0.8.0 — String Parsing
|
|
186
|
+
|
|
187
|
+
**Theme:** Ergonomic input.
|
|
188
|
+
|
|
189
|
+
- [ ] `parse("60 mph")` → `Number`
|
|
190
|
+
- [ ] `parse("kg * m / s^2")` → `UnitProduct`
|
|
191
|
+
- [ ] Alias resolution (`meters`, `metre`, `m` all work)
|
|
192
|
+
- [ ] Uncertainty parsing: `parse("1.234 ± 0.005 m")`
|
|
193
|
+
|
|
194
|
+
**Outcomes:**
|
|
195
|
+
- Human-friendly unit input for interactive and configuration use cases
|
|
196
|
+
- Robust alias handling for international and domain-specific conventions
|
|
197
|
+
- Complete round-trip: parse → compute → serialize
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## v0.9.0 — Constants + Logarithmic Units
|
|
202
|
+
|
|
203
|
+
**Theme:** Physical completeness.
|
|
204
|
+
|
|
205
|
+
- [ ] Physical constants with uncertainties: `c`, `h`, `G`, `k_B`, `N_A`, etc.
|
|
206
|
+
- [ ] `LogMap` for logarithmic conversions
|
|
207
|
+
- [ ] Logarithmic units: `decibel`, `bel`, `neper`
|
|
208
|
+
- [ ] pH scale support
|
|
209
|
+
- [ ] Currency dimension (with caveats about exchange rates)
|
|
210
|
+
|
|
211
|
+
**Outcomes:**
|
|
212
|
+
- Support for function-based (nonlinear) physical conversions
|
|
213
|
+
- Enables acoustics (dB), chemistry (pH), and signal processing domains
|
|
214
|
+
- Physical constants with CODATA uncertainties
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## v0.10.0 — DataFrame Integration
|
|
219
|
+
|
|
220
|
+
**Theme:** Data science workflows.
|
|
221
|
+
|
|
222
|
+
- [ ] Polars integration: `NumberColumn` type
|
|
223
|
+
- [ ] Pandas integration: `NumberSeries` type
|
|
224
|
+
- [ ] Column-wise conversion
|
|
225
|
+
- [ ] Unit-aware arithmetic on columns
|
|
226
|
+
|
|
227
|
+
**Outcomes:**
|
|
228
|
+
- First-class support for data science workflows
|
|
229
|
+
- Unit-safe transformations on tabular data
|
|
230
|
+
- Interoperability with modern DataFrame ecosystems
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## v1.0.0 — API Stability
|
|
235
|
+
|
|
236
|
+
**Theme:** Production ready.
|
|
237
|
+
|
|
238
|
+
- [ ] API freeze with semantic versioning commitment
|
|
239
|
+
- [ ] Comprehensive documentation
|
|
240
|
+
- [ ] Performance benchmarks documented
|
|
241
|
+
- [ ] Security review complete
|
|
242
|
+
- [ ] 2+ year LTS commitment
|
|
243
|
+
|
|
244
|
+
**Outcomes:**
|
|
245
|
+
- Stable, well-tested release
|
|
246
|
+
- Fully type-safe and validated core
|
|
247
|
+
- Production-ready for integration into scientific and engineering workflows
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Post-1.0 Vision
|
|
252
|
+
|
|
253
|
+
| Feature | Notes |
|
|
254
|
+
|---------|-------|
|
|
255
|
+
| Uncertainty correlation | Full covariance tracking |
|
|
256
|
+
| Cython optimization | Performance parity with unyt |
|
|
257
|
+
| Additional integrations | SQLAlchemy, msgpack, protobuf |
|
|
258
|
+
| Localization | Unit names in multiple languages |
|
|
259
|
+
| NIST/CODATA updates | Automated constant updates |
|
|
260
|
+
| Type-safe generics | `Number[Dimension.length]` for IDE hints |
|
|
261
|
+
| Symbolic bridge to SymPy | Export units for symbolic manipulation |
|
|
262
|
+
| Visualization | Dimensional relationship graphs |
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Guiding Principle
|
|
267
|
+
|
|
268
|
+
> "If it can be measured, it can be represented.
|
|
269
|
+
> If it can be represented, it can be validated.
|
|
270
|
+
> If it can be validated, it can be trusted."
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Tuple Values for Pseudo-Dimensions
|
|
2
|
+
*(Preventing Python Enum Aliasing)*
|
|
3
|
+
|
|
4
|
+
## 1. Context
|
|
5
|
+
|
|
6
|
+
ucon v0.5.0 introduced **pseudo-dimensions**—semantic categories for dimensionless quantities:
|
|
7
|
+
|
|
8
|
+
- `Dimension.angle` (radian, degree, etc.)
|
|
9
|
+
- `Dimension.solid_angle` (steradian, square_degree)
|
|
10
|
+
- `Dimension.ratio` (percent, ppm, etc.)
|
|
11
|
+
|
|
12
|
+
These pseudo-dimensions share the **zero vector** with `Dimension.none`, preserving algebraic consistency:
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
angle × length = length # angle has zero vector, so it's multiplicatively transparent
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
However, they must remain **semantically distinct** to prevent nonsensical conversions like `radian(1).to(percent)`.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. The Problem
|
|
23
|
+
|
|
24
|
+
Python's `Enum` treats members with identical values as **aliases**:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
class Dimension(Enum):
|
|
28
|
+
none = Vector()
|
|
29
|
+
angle = Vector() # Alias for `none`!
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
With this definition:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
>>> Dimension.angle is Dimension.none
|
|
36
|
+
True
|
|
37
|
+
>>> Dimension.angle == Dimension.none
|
|
38
|
+
True
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This defeats the purpose of pseudo-dimensions.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 3. Decision
|
|
46
|
+
|
|
47
|
+
Use **tuple values** `(Vector(), "tag")` for pseudo-dimensions:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
class Dimension(Enum):
|
|
51
|
+
# Base dimensions (single Vector values)
|
|
52
|
+
time = Vector(T=1)
|
|
53
|
+
length = Vector(L=1)
|
|
54
|
+
# ...
|
|
55
|
+
|
|
56
|
+
# Dimensionless
|
|
57
|
+
none = Vector()
|
|
58
|
+
|
|
59
|
+
# Pseudo-dimensions (tuple values to prevent aliasing)
|
|
60
|
+
angle = (Vector(), "angle")
|
|
61
|
+
solid_angle = (Vector(), "solid_angle")
|
|
62
|
+
ratio = (Vector(), "ratio")
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def vector(self) -> Vector:
|
|
66
|
+
"""Extract the Vector component, handling both regular and pseudo-dimensions."""
|
|
67
|
+
if isinstance(self.value, tuple):
|
|
68
|
+
return self.value[0]
|
|
69
|
+
return self.value
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 4. Rationale
|
|
75
|
+
|
|
76
|
+
### 4.1. Why Tuples Work
|
|
77
|
+
|
|
78
|
+
Python Enum compares member values for identity. Since each tuple is a distinct object—even if the first element is equal—Enum treats them as separate members:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
>>> (Vector(), "angle") == (Vector(), "solid_angle")
|
|
82
|
+
False
|
|
83
|
+
>>> Dimension.angle is Dimension.solid_angle
|
|
84
|
+
False
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 4.2. Why Not Override `__eq__` and `__hash__`?
|
|
88
|
+
|
|
89
|
+
Enum's comparison behavior is baked into its metaclass and cannot be cleanly overridden without breaking Enum invariants. The tuple approach works with Enum's existing machinery rather than against it.
|
|
90
|
+
|
|
91
|
+
### 4.3. Why Use a `vector` Property?
|
|
92
|
+
|
|
93
|
+
The `@property vector` provides a uniform interface for accessing the dimensional vector regardless of whether the dimension is regular or pseudo:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
Dimension.length.vector # → Vector(L=1)
|
|
97
|
+
Dimension.angle.vector # → Vector()
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This keeps algebraic operations simple—they always operate on vectors.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 5. Advantages
|
|
105
|
+
|
|
106
|
+
1. **Zero runtime overhead** — No metaclass hacks, no __eq__ overrides
|
|
107
|
+
2. **Enum-native** — Uses standard Enum behavior, not fighting it
|
|
108
|
+
3. **Clear semantics** — The tag string documents intent
|
|
109
|
+
4. **Stable hashing** — Pseudo-dimensions can be dict keys and set members
|
|
110
|
+
5. **Algebraic consistency preserved** — `angle.vector == none.vector`, so algebra works correctly
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 6. Shortcomings
|
|
115
|
+
|
|
116
|
+
1. **Two value shapes** — Regular dimensions have `Vector` values; pseudo-dimensions have `(Vector, str)` tuples. The `vector` property abstracts this, but it's an internal irregularity.
|
|
117
|
+
|
|
118
|
+
2. **`Dimension.angle.value` is a tuple** — Direct access to `.value` returns the tuple, which may surprise users expecting a `Vector`. Users should use `.vector` instead.
|
|
119
|
+
|
|
120
|
+
3. **Tag strings are arbitrary** — The "angle", "solid_angle", "ratio" strings exist only to differentiate tuple values. They're not used programmatically beyond ensuring uniqueness.
|
|
121
|
+
|
|
122
|
+
4. **Not extensible to many pseudo-dimensions** — While sufficient for the three current pseudo-dimensions, adding many more would require careful tag management. (This is unlikely to be an issue in practice.)
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 7. Alternatives Considered
|
|
127
|
+
|
|
128
|
+
### 7.1. Subclass Vector for Each Pseudo-Dimension
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
class AngleVector(Vector): pass
|
|
132
|
+
class Dimension(Enum):
|
|
133
|
+
angle = AngleVector()
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Rejected** — Creates unnecessary class proliferation and complicates vector arithmetic.
|
|
137
|
+
|
|
138
|
+
### 7.2. Use Integer Tags
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
angle = (Vector(), 1)
|
|
142
|
+
solid_angle = (Vector(), 2)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Rejected** — String tags are self-documenting; integers require external lookup.
|
|
146
|
+
|
|
147
|
+
### 7.3. Custom Enum Metaclass
|
|
148
|
+
|
|
149
|
+
Override Enum's value handling to allow identical Vector values as distinct members.
|
|
150
|
+
|
|
151
|
+
**Rejected** — Too complex, fragile across Python versions, and violates principle of least surprise.
|
|
152
|
+
|
|
153
|
+
### 7.4. Separate Enum for Pseudo-Dimensions
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
class PseudoDimension(Enum):
|
|
157
|
+
angle = auto()
|
|
158
|
+
solid_angle = auto()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Rejected** — Requires parallel type handling throughout the codebase and complicates `Dimension` as the single source of truth.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 8. Consequences
|
|
166
|
+
|
|
167
|
+
### Positive
|
|
168
|
+
|
|
169
|
+
- Pseudo-dimensions work correctly as dict keys and set members
|
|
170
|
+
- Algebraic operations continue to use vector arithmetic unchanged
|
|
171
|
+
- `Dimension._resolve()` returns `none` for zero-vector results (not pseudo-dimensions)
|
|
172
|
+
- Cross-pseudo-dimension conversions fail with `ConversionNotFound`
|
|
173
|
+
|
|
174
|
+
### Negative
|
|
175
|
+
|
|
176
|
+
- Internal complexity: two value shapes in one Enum
|
|
177
|
+
- Users must use `.vector` instead of `.value` for consistent access
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 9. Related Decisions
|
|
182
|
+
|
|
183
|
+
- **Dimension._resolve()** — Always returns `none` for zero vectors, never pseudo-dimensions
|
|
@@ -71,10 +71,10 @@ class TestVectorEdgeCases(TestCase):
|
|
|
71
71
|
|
|
72
72
|
def test_vector_equality_with_non_vector(self):
|
|
73
73
|
v = Vector()
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
# Non-Vector comparisons return NotImplemented, which Python
|
|
75
|
+
# resolves to False (not equal) rather than raising an error
|
|
76
|
+
self.assertFalse(v == "not a vector")
|
|
77
|
+
self.assertFalse(v == None)
|
|
78
78
|
|
|
79
79
|
def test_hash_consistency_for_equal_vectors(self):
|
|
80
80
|
v1 = Vector(1, 0, 0, 0, 0, 0, 0, 0)
|