ucon 0.4.2__tar.gz → 0.5.1__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.
Files changed (49) hide show
  1. {ucon-0.4.2 → ucon-0.5.1}/PKG-INFO +55 -6
  2. {ucon-0.4.2 → ucon-0.5.1}/README.md +54 -5
  3. ucon-0.5.1/ROADMAP.md +271 -0
  4. ucon-0.5.1/docs/decisions/pseudo-dimension-tuple-values.md +183 -0
  5. {ucon-0.4.2 → ucon-0.5.1}/tests/ucon/test_algebra.py +4 -4
  6. ucon-0.5.1/tests/ucon/test_dimensionless_units.py +248 -0
  7. ucon-0.5.1/tests/ucon/test_uncertainty.py +264 -0
  8. {ucon-0.4.2 → ucon-0.5.1}/ucon/algebra.py +4 -3
  9. {ucon-0.4.2 → ucon-0.5.1}/ucon/core.py +183 -17
  10. {ucon-0.4.2 → ucon-0.5.1}/ucon/graph.py +18 -0
  11. {ucon-0.4.2 → ucon-0.5.1}/ucon/maps.py +22 -1
  12. {ucon-0.4.2 → ucon-0.5.1}/ucon/units.py +26 -2
  13. {ucon-0.4.2 → ucon-0.5.1}/ucon.egg-info/PKG-INFO +55 -6
  14. {ucon-0.4.2 → ucon-0.5.1}/ucon.egg-info/SOURCES.txt +3 -0
  15. ucon-0.4.2/ROADMAP.md +0 -244
  16. {ucon-0.4.2 → ucon-0.5.1}/.github/workflows/publish.yaml +0 -0
  17. {ucon-0.4.2 → ucon-0.5.1}/.github/workflows/tests.yaml +0 -0
  18. {ucon-0.4.2 → ucon-0.5.1}/.gitignore +0 -0
  19. {ucon-0.4.2 → ucon-0.5.1}/LICENSE +0 -0
  20. {ucon-0.4.2 → ucon-0.5.1}/NOTICE +0 -0
  21. {ucon-0.4.2 → ucon-0.5.1}/docs/decisions/composable-unit-algebra.md +0 -0
  22. {ucon-0.4.2 → ucon-0.5.1}/docs/decisions/composite-units.md +0 -0
  23. {ucon-0.4.2 → ucon-0.5.1}/docs/decisions/unit-algebra-naming.md +0 -0
  24. {ucon-0.4.2 → ucon-0.5.1}/docs/decisions/unity-distance-metric-for-nearest-scale.md +0 -0
  25. {ucon-0.4.2 → ucon-0.5.1}/docs/explainers/exponent-scale-relationship.md +0 -0
  26. {ucon-0.4.2 → ucon-0.5.1}/docs/explainers/type-operation-matrix.md +0 -0
  27. {ucon-0.4.2 → ucon-0.5.1}/docs/explainers/why-algebraic-closure-matters.md +0 -0
  28. {ucon-0.4.2 → ucon-0.5.1}/docs/explainers/why-type-safety-matters.md +0 -0
  29. {ucon-0.4.2 → ucon-0.5.1}/docs/proposals/interface-unifying-the-value-layer.md +0 -0
  30. {ucon-0.4.2 → ucon-0.5.1}/docs/proposals/project_unified-algebraic-core.md +0 -0
  31. {ucon-0.4.2 → ucon-0.5.1}/docs/proposals/support-for-fractional-exponents.md +0 -0
  32. {ucon-0.4.2 → ucon-0.5.1}/docs/proposals/unified-unit-presentation.md +0 -0
  33. {ucon-0.4.2 → ucon-0.5.1}/noxfile.py +0 -0
  34. {ucon-0.4.2 → ucon-0.5.1}/requirements.txt +0 -0
  35. {ucon-0.4.2 → ucon-0.5.1}/setup.cfg +0 -0
  36. {ucon-0.4.2 → ucon-0.5.1}/setup.py +0 -0
  37. {ucon-0.4.2 → ucon-0.5.1}/tests/__init__.py +0 -0
  38. {ucon-0.4.2 → ucon-0.5.1}/tests/ucon/__init__.py +0 -0
  39. {ucon-0.4.2 → ucon-0.5.1}/tests/ucon/conversion/__init__.py +0 -0
  40. {ucon-0.4.2 → ucon-0.5.1}/tests/ucon/conversion/test_graph.py +0 -0
  41. {ucon-0.4.2 → ucon-0.5.1}/tests/ucon/conversion/test_map.py +0 -0
  42. {ucon-0.4.2 → ucon-0.5.1}/tests/ucon/test_core.py +0 -0
  43. {ucon-0.4.2 → ucon-0.5.1}/tests/ucon/test_default_graph_conversions.py +0 -0
  44. {ucon-0.4.2 → ucon-0.5.1}/tests/ucon/test_quantity.py +0 -0
  45. {ucon-0.4.2 → ucon-0.5.1}/tests/ucon/test_units.py +0 -0
  46. {ucon-0.4.2 → ucon-0.5.1}/ucon/__init__.py +0 -0
  47. {ucon-0.4.2 → ucon-0.5.1}/ucon/quantity.py +0 -0
  48. {ucon-0.4.2 → ucon-0.5.1}/ucon.egg-info/dependency_links.txt +0 -0
  49. {ucon-0.4.2 → ucon-0.5.1}/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.4.2
3
+ Version: 0.5.1
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
- <img src="https://gist.githubusercontent.com/withtwoemms/0cb9e6bc8df08f326771a89eeb790f8e/raw/dde6c7d3b8a7d79eb1006ace03fb834e044cdebc/ucon-logo.png" align="left" width="200" />
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
  [![codecov](https://codecov.io/gh/withtwoemms/ucon/graph/badge.svg?token=BNONQTRJWG)](https://codecov.io/gh/withtwoemms/ucon)
46
51
  [![publish](https://github.com/withtwoemms/ucon/workflows/publish/badge.svg)](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,8 @@ 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
70
+ - Uncertainty propagation through arithmetic and conversions
60
71
  - A clean foundation for physics, chemistry, data modeling, and beyond
61
72
 
62
73
  Think of it as **`decimal.Decimal` for the physical world** — precise, predictable, and type-safe.
@@ -188,16 +199,54 @@ distance_mi = distance.to(units.mile)
188
199
  print(distance_mi) # <3.107... mi>
189
200
  ```
190
201
 
202
+ Dimensionless units have semantic isolation — angles, solid angles, and ratios are distinct:
203
+ ```python
204
+ import math
205
+ from ucon import units
206
+
207
+ # Angle conversions
208
+ angle = units.radian(math.pi)
209
+ print(angle.to(units.degree)) # <180.0 deg>
210
+
211
+ # Ratio conversions
212
+ ratio = units.percent(50)
213
+ print(ratio.to(units.ppm)) # <500000.0 ppm>
214
+
215
+ # Cross-family conversions are prevented
216
+ units.radian(1).to(units.percent) # raises ConversionNotFound
217
+ ```
218
+
219
+ Uncertainty propagates through arithmetic and conversions:
220
+ ```python
221
+ from ucon import units
222
+
223
+ # Measurements with uncertainty
224
+ length = units.meter(1.234, uncertainty=0.005)
225
+ width = units.meter(0.567, uncertainty=0.003)
226
+
227
+ print(length) # <1.234 ± 0.005 m>
228
+
229
+ # Uncertainty propagates through arithmetic (quadrature)
230
+ area = length * width
231
+ print(area) # <0.699678 ± 0.00424... m²>
232
+
233
+ # Uncertainty propagates through conversion
234
+ length_ft = length.to(units.foot)
235
+ print(length_ft) # <4.048... ± 0.0164... ft>
236
+ ```
237
+
191
238
  ---
192
239
 
193
240
  ## Roadmap Highlights
194
241
 
195
242
  | Version | Theme | Focus | Status |
196
243
  |----------|-------|--------|--------|
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 |
244
+ | **0.3.x** | Dimensional Algebra | Unit/Scale separation, `UnitFactor`, `UnitProduct` | ✅ Complete |
245
+ | **0.4.x** | Conversion System | `ConversionGraph`, `Number.to()`, callable units | Complete |
246
+ | **0.5.0** | Dimensionless Units | Pseudo-dimensions for angle, solid angle, ratio | Complete |
247
+ | **0.5.x** | Uncertainty | Propagation through arithmetic and conversions | Complete |
248
+ | **0.5.x** | Unit Systems | `BasisMap`, `UnitSystem` | 🚧 In Progress |
249
+ | **0.7.x** | Pydantic Integration | Type-safe quantity validation | ⏳ Planned |
201
250
 
202
251
  See full roadmap: [ROADMAP.md](./ROADMAP.md)
203
252
 
@@ -1,4 +1,9 @@
1
- <img src="https://gist.githubusercontent.com/withtwoemms/0cb9e6bc8df08f326771a89eeb790f8e/raw/dde6c7d3b8a7d79eb1006ace03fb834e044cdebc/ucon-logo.png" align="left" width="200" />
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
  [![codecov](https://codecov.io/gh/withtwoemms/ucon/graph/badge.svg?token=BNONQTRJWG)](https://codecov.io/gh/withtwoemms/ucon)
9
14
  [![publish](https://github.com/withtwoemms/ucon/workflows/publish/badge.svg)](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,8 @@ 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
33
+ - Uncertainty propagation through arithmetic and conversions
23
34
  - A clean foundation for physics, chemistry, data modeling, and beyond
24
35
 
25
36
  Think of it as **`decimal.Decimal` for the physical world** — precise, predictable, and type-safe.
@@ -151,16 +162,54 @@ distance_mi = distance.to(units.mile)
151
162
  print(distance_mi) # <3.107... mi>
152
163
  ```
153
164
 
165
+ Dimensionless units have semantic isolation — angles, solid angles, and ratios are distinct:
166
+ ```python
167
+ import math
168
+ from ucon import units
169
+
170
+ # Angle conversions
171
+ angle = units.radian(math.pi)
172
+ print(angle.to(units.degree)) # <180.0 deg>
173
+
174
+ # Ratio conversions
175
+ ratio = units.percent(50)
176
+ print(ratio.to(units.ppm)) # <500000.0 ppm>
177
+
178
+ # Cross-family conversions are prevented
179
+ units.radian(1).to(units.percent) # raises ConversionNotFound
180
+ ```
181
+
182
+ Uncertainty propagates through arithmetic and conversions:
183
+ ```python
184
+ from ucon import units
185
+
186
+ # Measurements with uncertainty
187
+ length = units.meter(1.234, uncertainty=0.005)
188
+ width = units.meter(0.567, uncertainty=0.003)
189
+
190
+ print(length) # <1.234 ± 0.005 m>
191
+
192
+ # Uncertainty propagates through arithmetic (quadrature)
193
+ area = length * width
194
+ print(area) # <0.699678 ± 0.00424... m²>
195
+
196
+ # Uncertainty propagates through conversion
197
+ length_ft = length.to(units.foot)
198
+ print(length_ft) # <4.048... ± 0.0164... ft>
199
+ ```
200
+
154
201
  ---
155
202
 
156
203
  ## Roadmap Highlights
157
204
 
158
205
  | Version | Theme | Focus | Status |
159
206
  |----------|-------|--------|--------|
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 |
207
+ | **0.3.x** | Dimensional Algebra | Unit/Scale separation, `UnitFactor`, `UnitProduct` | ✅ Complete |
208
+ | **0.4.x** | Conversion System | `ConversionGraph`, `Number.to()`, callable units | Complete |
209
+ | **0.5.0** | Dimensionless Units | Pseudo-dimensions for angle, solid angle, ratio | Complete |
210
+ | **0.5.x** | Uncertainty | Propagation through arithmetic and conversions | Complete |
211
+ | **0.5.x** | Unit Systems | `BasisMap`, `UnitSystem` | 🚧 In Progress |
212
+ | **0.7.x** | Pydantic Integration | Type-safe quantity validation | ⏳ Planned |
164
213
 
165
214
  See full roadmap: [ROADMAP.md](./ROADMAP.md)
166
215
 
ucon-0.5.1/ROADMAP.md ADDED
@@ -0,0 +1,271 @@
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 | Complete |
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` with `derivative()`)
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
+ - Uncertainty propagation: `meter(1.234, uncertainty=0.005)` with quadrature arithmetic
49
+
50
+ ---
51
+
52
+ ## v0.3.x — Dimensional Algebra (Complete)
53
+
54
+ **Theme:** Algebraic foundation.
55
+
56
+ - [x] `Vector` and `Dimension` classes
57
+ - [x] Unit/Scale separation: `Unit` is atomic, `UnitFactor` pairs unit+scale
58
+ - [x] `UnitProduct` with `fold_scale()` and `_residual_scale_factor`
59
+ - [x] `Exponent` algebraic operations (`__mul__`, `__truediv__`, `to_base`)
60
+ - [x] Scale/Exponent integration for prefix arithmetic
61
+
62
+ **Outcomes:**
63
+ - All units acquire explicit dimensional semantics
64
+ - Enables composable and type-safe dimensional operations
65
+ - Establishes the mathematical foundation for future conversions
66
+ - Unified algebraic foundation for all scaling and magnitude operations
67
+ - Clean Unit/Scale separation: `Unit` is an atomic symbol, `UnitFactor` pairs it with a `Scale`
68
+ - `UnitProduct` correctly tracks residual scale from cancelled units
69
+ - Type system is ready for a `ConversionGraph` to be built on top
70
+
71
+ ---
72
+
73
+ ## v0.4.x — Conversion System Foundations (Complete)
74
+
75
+ **Theme:** First useful release.
76
+
77
+ - [x] `Map` hierarchy (`LinearMap`, `AffineMap`, `ComposedMap`)
78
+ - [x] `Quantity` class (callable unit constructor)
79
+ - [x] `ConversionGraph` with edge API and BFS path finding
80
+ - [x] `Number.to()` wired to graph
81
+ - [x] Default graph with SI + Imperial + conventional units
82
+ - [x] Unit registry with `get_unit_by_name()`
83
+ - [x] Graph management (`get_default_graph`, `set_default_graph`, `using_graph`)
84
+ - [x] `Dimension.information` with `bit`, `byte`
85
+ - [x] `Vector` extended to 8 components (added B for information)
86
+ - [x] Binary scale prefixes: `kibi`, `mebi`, `gibi`, `tebi`, `pebi`, `exbi`
87
+ - [x] `Number.simplify()` for base-scale normalization
88
+ - [x] Temperature, pressure, and base SI conversion tests
89
+ - [x] Exponent/Scale developer guide
90
+
91
+ **Outcomes:**
92
+ - Unified conversion taxonomy
93
+ - Reversible, dimension-checked conversions
94
+ - Scale-aware graph that leverages the `Unit`/`UnitFactor` separation from v0.3.x
95
+ - Ergonomic API: units are callable, returning `Number` instances
96
+ - Information dimension support (bit, byte) with binary prefix compatibility
97
+ - `Number.simplify()` for expressing quantities in base scale
98
+ - Forms the basis for nonlinear and domain-specific conversion families
99
+
100
+ ---
101
+
102
+ ## v0.5.0 — Dimensionless Units (Complete)
103
+
104
+ **Theme:** Complete the dimension model.
105
+
106
+ - [x] Pseudo-dimensions: `angle`, `solid_angle`, `ratio` (same zero vector, distinct enum identity)
107
+ - [x] Angle units: `radian`, `degree`, `gradian`, `arcminute`, `arcsecond`, `turn`
108
+ - [x] Solid angle units: `steradian`, `square_degree`
109
+ - [x] Ratio units: `percent`, `permille`, `ppm`, `ppb`, `basis_point`
110
+ - [x] Cross-pseudo-dimension conversion fails (enforced isolation)
111
+ - [x] Conversion edges for all new units
112
+
113
+ **Outcomes:**
114
+ - Semantic isolation prevents nonsensical conversions (radian → percent)
115
+ - Rich dimensionless unit coverage for geometry, optics, finance, chemistry
116
+ - Complete dimension model ready for metrology extensions
117
+
118
+ ---
119
+
120
+ ## v0.5.x — Uncertainty Propagation (Complete)
121
+
122
+ **Theme:** Metrology foundation.
123
+
124
+ - [x] `Number.uncertainty: float | None`
125
+ - [x] Propagation through arithmetic (uncorrelated, quadrature)
126
+ - [x] Propagation through conversion via `Map.derivative()`
127
+ - [x] Construction: `meter(1.234, uncertainty=0.005)`
128
+ - [x] Display: `1.234 ± 0.005 meter`
129
+
130
+ **Outcomes:**
131
+ - First-class uncertainty support for scientific and engineering workflows
132
+ - Correct propagation through both arithmetic and unit conversion
133
+ - Foundation for full metrology capabilities
134
+
135
+ ---
136
+
137
+ ## v0.5.x — BasisMap + UnitSystem
138
+
139
+ **Theme:** Cross-system architecture.
140
+
141
+ - [ ] `UnitSystem` class (named grouping of base units)
142
+ - [ ] `BasisMap` class (structural equivalence between systems)
143
+ - [ ] Prebuilt systems: `si`, `imperial`, `cgs`
144
+ - [ ] `graph.connect_systems()` for bulk edge creation
145
+ - [ ] Support for custom domain dimensions
146
+
147
+ **Outcomes:**
148
+ - `BasisMap` enables system-aware "express in base units" functionality
149
+ - Named unit systems for domain-specific workflows
150
+ - Foundation for plugin-style system extensions
151
+
152
+ ---
153
+
154
+ ## v0.6.0 — NumPy Array Support
155
+
156
+ **Theme:** Scientific computing integration.
157
+
158
+ - [ ] `Number` wraps `np.ndarray` values
159
+ - [ ] Vectorized conversion
160
+ - [ ] Vectorized arithmetic with uncertainty propagation
161
+ - [ ] Performance benchmarks
162
+
163
+ **Outcomes:**
164
+ - Seamless integration with NumPy-based scientific workflows
165
+ - Efficient batch conversions for large datasets
166
+ - Performance characteristics documented and optimized
167
+
168
+ ---
169
+
170
+ ## v0.7.0 — Pydantic + Serialization
171
+
172
+ **Theme:** API and persistence integration.
173
+
174
+ - [ ] Native Pydantic v2 support for `Number`
175
+ - [ ] JSON serialization/deserialization
176
+ - [ ] Pickle support
177
+ - [ ] Optional: MCP server for unit conversion tool
178
+
179
+ **Outcomes:**
180
+ - Native validation and serialization for dimensioned quantities
181
+ - Enables safe configuration in data models and APIs
182
+ - Bridges ucon's algebraic model with modern Python typing ecosystems
183
+
184
+ ---
185
+
186
+ ## v0.8.0 — String Parsing
187
+
188
+ **Theme:** Ergonomic input.
189
+
190
+ - [ ] `parse("60 mph")` → `Number`
191
+ - [ ] `parse("kg * m / s^2")` → `UnitProduct`
192
+ - [ ] Alias resolution (`meters`, `metre`, `m` all work)
193
+ - [ ] Uncertainty parsing: `parse("1.234 ± 0.005 m")`
194
+
195
+ **Outcomes:**
196
+ - Human-friendly unit input for interactive and configuration use cases
197
+ - Robust alias handling for international and domain-specific conventions
198
+ - Complete round-trip: parse → compute → serialize
199
+
200
+ ---
201
+
202
+ ## v0.9.0 — Constants + Logarithmic Units
203
+
204
+ **Theme:** Physical completeness.
205
+
206
+ - [ ] Physical constants with uncertainties: `c`, `h`, `G`, `k_B`, `N_A`, etc.
207
+ - [ ] `LogMap` for logarithmic conversions
208
+ - [ ] Logarithmic units: `decibel`, `bel`, `neper`
209
+ - [ ] pH scale support
210
+ - [ ] Currency dimension (with caveats about exchange rates)
211
+
212
+ **Outcomes:**
213
+ - Support for function-based (nonlinear) physical conversions
214
+ - Enables acoustics (dB), chemistry (pH), and signal processing domains
215
+ - Physical constants with CODATA uncertainties
216
+
217
+ ---
218
+
219
+ ## v0.10.0 — DataFrame Integration
220
+
221
+ **Theme:** Data science workflows.
222
+
223
+ - [ ] Polars integration: `NumberColumn` type
224
+ - [ ] Pandas integration: `NumberSeries` type
225
+ - [ ] Column-wise conversion
226
+ - [ ] Unit-aware arithmetic on columns
227
+
228
+ **Outcomes:**
229
+ - First-class support for data science workflows
230
+ - Unit-safe transformations on tabular data
231
+ - Interoperability with modern DataFrame ecosystems
232
+
233
+ ---
234
+
235
+ ## v1.0.0 — API Stability
236
+
237
+ **Theme:** Production ready.
238
+
239
+ - [ ] API freeze with semantic versioning commitment
240
+ - [ ] Comprehensive documentation
241
+ - [ ] Performance benchmarks documented
242
+ - [ ] Security review complete
243
+ - [ ] 2+ year LTS commitment
244
+
245
+ **Outcomes:**
246
+ - Stable, well-tested release
247
+ - Fully type-safe and validated core
248
+ - Production-ready for integration into scientific and engineering workflows
249
+
250
+ ---
251
+
252
+ ## Post-1.0 Vision
253
+
254
+ | Feature | Notes |
255
+ |---------|-------|
256
+ | Uncertainty correlation | Full covariance tracking |
257
+ | Cython optimization | Performance parity with unyt |
258
+ | Additional integrations | SQLAlchemy, msgpack, protobuf |
259
+ | Localization | Unit names in multiple languages |
260
+ | NIST/CODATA updates | Automated constant updates |
261
+ | Type-safe generics | `Number[Dimension.length]` for IDE hints |
262
+ | Symbolic bridge to SymPy | Export units for symbolic manipulation |
263
+ | Visualization | Dimensional relationship graphs |
264
+
265
+ ---
266
+
267
+ ## Guiding Principle
268
+
269
+ > "If it can be measured, it can be represented.
270
+ > If it can be represented, it can be validated.
271
+ > 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
- with self.assertRaises(AssertionError):
75
- v == "not a vector"
76
- with self.assertRaises(AssertionError):
77
- v == None
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)