ucon 0.4.2__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.
Files changed (48) hide show
  1. {ucon-0.4.2 → ucon-0.5.0}/PKG-INFO +34 -6
  2. {ucon-0.4.2 → ucon-0.5.0}/README.md +33 -5
  3. ucon-0.5.0/ROADMAP.md +270 -0
  4. ucon-0.5.0/docs/decisions/pseudo-dimension-tuple-values.md +183 -0
  5. {ucon-0.4.2 → ucon-0.5.0}/tests/ucon/test_algebra.py +4 -4
  6. ucon-0.5.0/tests/ucon/test_dimensionless_units.py +248 -0
  7. {ucon-0.4.2 → ucon-0.5.0}/ucon/algebra.py +4 -3
  8. {ucon-0.4.2 → ucon-0.5.0}/ucon/core.py +32 -5
  9. {ucon-0.4.2 → ucon-0.5.0}/ucon/graph.py +18 -0
  10. {ucon-0.4.2 → ucon-0.5.0}/ucon/units.py +26 -2
  11. {ucon-0.4.2 → ucon-0.5.0}/ucon.egg-info/PKG-INFO +34 -6
  12. {ucon-0.4.2 → ucon-0.5.0}/ucon.egg-info/SOURCES.txt +2 -0
  13. ucon-0.4.2/ROADMAP.md +0 -244
  14. {ucon-0.4.2 → ucon-0.5.0}/.github/workflows/publish.yaml +0 -0
  15. {ucon-0.4.2 → ucon-0.5.0}/.github/workflows/tests.yaml +0 -0
  16. {ucon-0.4.2 → ucon-0.5.0}/.gitignore +0 -0
  17. {ucon-0.4.2 → ucon-0.5.0}/LICENSE +0 -0
  18. {ucon-0.4.2 → ucon-0.5.0}/NOTICE +0 -0
  19. {ucon-0.4.2 → ucon-0.5.0}/docs/decisions/composable-unit-algebra.md +0 -0
  20. {ucon-0.4.2 → ucon-0.5.0}/docs/decisions/composite-units.md +0 -0
  21. {ucon-0.4.2 → ucon-0.5.0}/docs/decisions/unit-algebra-naming.md +0 -0
  22. {ucon-0.4.2 → ucon-0.5.0}/docs/decisions/unity-distance-metric-for-nearest-scale.md +0 -0
  23. {ucon-0.4.2 → ucon-0.5.0}/docs/explainers/exponent-scale-relationship.md +0 -0
  24. {ucon-0.4.2 → ucon-0.5.0}/docs/explainers/type-operation-matrix.md +0 -0
  25. {ucon-0.4.2 → ucon-0.5.0}/docs/explainers/why-algebraic-closure-matters.md +0 -0
  26. {ucon-0.4.2 → ucon-0.5.0}/docs/explainers/why-type-safety-matters.md +0 -0
  27. {ucon-0.4.2 → ucon-0.5.0}/docs/proposals/interface-unifying-the-value-layer.md +0 -0
  28. {ucon-0.4.2 → ucon-0.5.0}/docs/proposals/project_unified-algebraic-core.md +0 -0
  29. {ucon-0.4.2 → ucon-0.5.0}/docs/proposals/support-for-fractional-exponents.md +0 -0
  30. {ucon-0.4.2 → ucon-0.5.0}/docs/proposals/unified-unit-presentation.md +0 -0
  31. {ucon-0.4.2 → ucon-0.5.0}/noxfile.py +0 -0
  32. {ucon-0.4.2 → ucon-0.5.0}/requirements.txt +0 -0
  33. {ucon-0.4.2 → ucon-0.5.0}/setup.cfg +0 -0
  34. {ucon-0.4.2 → ucon-0.5.0}/setup.py +0 -0
  35. {ucon-0.4.2 → ucon-0.5.0}/tests/__init__.py +0 -0
  36. {ucon-0.4.2 → ucon-0.5.0}/tests/ucon/__init__.py +0 -0
  37. {ucon-0.4.2 → ucon-0.5.0}/tests/ucon/conversion/__init__.py +0 -0
  38. {ucon-0.4.2 → ucon-0.5.0}/tests/ucon/conversion/test_graph.py +0 -0
  39. {ucon-0.4.2 → ucon-0.5.0}/tests/ucon/conversion/test_map.py +0 -0
  40. {ucon-0.4.2 → ucon-0.5.0}/tests/ucon/test_core.py +0 -0
  41. {ucon-0.4.2 → ucon-0.5.0}/tests/ucon/test_default_graph_conversions.py +0 -0
  42. {ucon-0.4.2 → ucon-0.5.0}/tests/ucon/test_quantity.py +0 -0
  43. {ucon-0.4.2 → ucon-0.5.0}/tests/ucon/test_units.py +0 -0
  44. {ucon-0.4.2 → ucon-0.5.0}/ucon/__init__.py +0 -0
  45. {ucon-0.4.2 → ucon-0.5.0}/ucon/maps.py +0 -0
  46. {ucon-0.4.2 → ucon-0.5.0}/ucon/quantity.py +0 -0
  47. {ucon-0.4.2 → ucon-0.5.0}/ucon.egg-info/dependency_links.txt +0 -0
  48. {ucon-0.4.2 → 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.4.2
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
- <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,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.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 |
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
- <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,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.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 |
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
- 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)