ucon 0.3.5rc1__tar.gz → 0.3.5rc2__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 (38) hide show
  1. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/PKG-INFO +42 -32
  2. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/README.md +41 -31
  3. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ROADMAP.md +97 -89
  4. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon/core.py +22 -15
  5. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon/quantity.py +1 -1
  6. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon.egg-info/PKG-INFO +42 -32
  7. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/.github/workflows/publish.yaml +0 -0
  8. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/.github/workflows/tests.yaml +0 -0
  9. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/.gitignore +0 -0
  10. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/LICENSE +0 -0
  11. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/NOTICE +0 -0
  12. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/decisions/composable-unit-algebra.md +0 -0
  13. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/decisions/composite-units.md +0 -0
  14. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/decisions/unit-algebra-naming.md +0 -0
  15. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/decisions/unity-distance-metric-for-nearest-scale.md +0 -0
  16. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/explainers/type-operation-matrix.md +0 -0
  17. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/explainers/why-algebraic-closure-matters.md +0 -0
  18. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/explainers/why-type-safety-matters.md +0 -0
  19. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/proposals/interface-unifying-the-value-layer.md +0 -0
  20. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/proposals/project_unified-algebraic-core.md +0 -0
  21. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/proposals/support-for-fractional-exponents.md +0 -0
  22. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/proposals/unified-unit-presentation.md +0 -0
  23. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/noxfile.py +0 -0
  24. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/requirements.txt +0 -0
  25. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/setup.cfg +0 -0
  26. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/setup.py +0 -0
  27. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/tests/__init__.py +0 -0
  28. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/tests/ucon/__init__.py +0 -0
  29. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/tests/ucon/test_algebra.py +0 -0
  30. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/tests/ucon/test_core.py +0 -0
  31. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/tests/ucon/test_quantity.py +0 -0
  32. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/tests/ucon/test_units.py +0 -0
  33. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon/__init__.py +0 -0
  34. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon/algebra.py +0 -0
  35. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon/units.py +0 -0
  36. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon.egg-info/SOURCES.txt +0 -0
  37. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon.egg-info/dependency_links.txt +0 -0
  38. {ucon-0.3.5rc1 → ucon-0.3.5rc2}/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.5rc1
3
+ Version: 0.3.5rc2
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 conversions
59
- - Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, ect.)
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,22 @@ 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.dimension` | Represents the exponent tuple of a physical quantitys base dimensions (e.g., T, L, M, I, Θ, J, N). | Internal representation of dimensional algebra; building derived quantities (e.g., area, velocity, force). |
74
- | **`Dimension`** | `ucon.dimension` | Encapsulates physical dimensions (e.g., length, time, mass) as algebraic combinations of vectors. | Enforcing dimensional consistency; defining relationships between quantities (e.g., length / time = velocity). |
75
- | **`Unit`** | `ucon.unit` | Represents a named, dimensioned measurement unit (e.g., meter, second, joule). | Attaching human-readable units to quantities; defining or composing new units (`newton = kilogram * meter / second²`). |
73
+ | **`Vector`** | `ucon.algebra` | Represents the exponent tuple of a physical quantity's base dimensions (e.g., T, L, M, I, Θ, J, N). | Internal representation of dimensional algebra; building derived quantities (e.g., area, velocity, force). |
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
- | **`Exponent`** | `ucon.core` | Represents base-power pairs (e.g., 10³, 2¹⁰) used by `Scale`. | Performing arithmetic on powers and bases; normalizing scales across conversions. |
78
- | **`Number`** | `ucon.core` | Combines a numeric quantity with a unit and scale; the primary measurable type. | Performing arithmetic with units; converting between compatible units; representing physical quantities like 5 m/s. |
79
- | **`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
- | **`units` module** | `ucon.units` | Defines canonical unit instances (SI and common derived units). | Quick access to standard physical units (`units.meter`, `units.second`, `units.newton`, etc.). | |
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.quantity` | Combines a numeric quantity with a unit; the primary measurable type. | Performing arithmetic with units; representing physical quantities like 5 m/s. |
81
+ | **`Ratio`** | `ucon.quantity` | Represents the division of two `Number` objects; captures relationships between quantities. | Expressing rates, densities, efficiencies (e.g., energy / time = power, length / time = velocity). |
82
+ | **`units` module** | `ucon.units` | Defines canonical unit instances (SI and common derived units). | Quick access to standard physical units (`units.meter`, `units.second`, `units.newton`, etc.). |
81
83
 
82
84
  ### Under the Hood
83
85
 
84
86
  `ucon` models unit math through a hierarchy where each layer builds on the last:
85
87
 
86
- <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/0c704737a52b9e4a87cda5c839e9aa40f7e5bb48/ucon.data-model_v035.png align="center" alt="ucon Data Model" width=600/>
88
+ <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/f24134c362829dc72e7dff18bfcaa24b9be01b54/ucon.data-model_v035.png align="center" alt="ucon Data Model" width=600/>
87
89
 
88
90
  ## Why `ucon`?
89
91
 
@@ -91,24 +93,22 @@ Python already has mature libraries for handling units and physical quantities
91
93
 
92
94
  | Library | Focus | Limitation |
93
95
  | --------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
94
- | **Pint** | Runtime unit conversion and compatibility checking | Treats quantities as decorated numbers — conversions work, but the algebra behind them isnt inspectable or type-safe. |
96
+ | **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
97
  | **SymPy** | Symbolic algebra and simplification of unit expressions | Excellent for symbolic reasoning, but not designed for runtime validation, conversion, or serialization. |
96
98
  | **Unum** | Unit-aware arithmetic and unit propagation | Tracks units through arithmetic but lacks explicit dimensional algebra, conversion taxonomy, or runtime introspection. |
97
99
 
98
100
  Together, these tools can _use_ units, but none can explicitly represent and verify the relationships between units and dimensions.
99
101
 
100
- Thats the gap `ucon` fills.
102
+ That's the gap `ucon` fills.
101
103
 
102
104
  It treats units, dimensions, and scales as first-class objects and builds a composable algebra around them.
103
105
  This allows you to:
104
106
  - Represent dimensional meaning explicitly (`Dimension`, `Vector`);
105
107
  - 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
108
  - Extend the system with custom unit registries and conversion families.
109
109
 
110
110
  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 doesnt just track names: it enforces physics:
111
+ `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
112
  ```python
113
113
  from ucon import Number, units
114
114
 
@@ -136,7 +136,8 @@ This sort of dimensional analysis:
136
136
  ```
137
137
  becomes straightforward when you define a measurement:
138
138
  ```python
139
- from ucon import Number, Scale, Units, Ratio
139
+ from ucon import Number, Scale, units
140
+ from ucon.quantity import Ratio
140
141
 
141
142
  # Two milliliters of bromine
142
143
  mL = Scale.milli * units.liter
@@ -149,27 +150,36 @@ bromine_density = Ratio(
149
150
  )
150
151
 
151
152
  # Multiply to find mass
152
- grams_bromine = two_mL_bromine * bromine_density
153
- print(grams_bromine) # <6.238 gram>
153
+ grams_bromine = bromine_density.evaluate() * two_mL_bromine
154
+ print(grams_bromine) # <6.238 g>
154
155
  ```
155
156
 
156
- Scale conversion is automatic and precise:
157
-
157
+ Scale prefixes compose naturally:
158
158
  ```python
159
- grams_bromine.to(Scale.milli) # <6238.0 milligram>
160
- grams_bromine.to(Scale.kibi) # <0.006091796875 kibigram>
159
+ km = Scale.kilo * units.meter # UnitProduct with kilo-scaled meter
160
+ mg = Scale.milli * units.gram # UnitProduct with milli-scaled gram
161
+
162
+ print(km.shorthand) # 'km'
163
+ print(mg.shorthand) # 'mg'
164
+
165
+ # Scale arithmetic
166
+ print(km.fold_scale()) # 1000.0
167
+ print(mg.fold_scale()) # 0.001
161
168
  ```
162
169
 
170
+ > **Note:** Unit _conversions_ (e.g., `number.to(units.inch)`) are planned for v0.4.x
171
+ > via the `ConversionGraph` abstraction. See [ROADMAP.md](./ROADMAP.md).
172
+
163
173
  ---
164
174
 
165
175
  ## Roadmap Highlights
166
176
 
167
- | Version | Theme | Focus |
168
- |----------|-------|--------|
169
- | [**0.3.x**](https://github.com/withtwoemms/ucon/milestone/1) | Primitive Type Refinement | Unified algebraic foundation |
170
- | [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System | Linear & affine conversions |
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 |
177
+ | Version | Theme | Focus | Status |
178
+ |----------|-------|--------|--------|
179
+ | **0.3.5** | Dimensional Algebra | Unit/Scale separation, `UnitFactor`, `UnitProduct` | ✅ Complete |
180
+ | [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System | `ConversionGraph`, `Number.to()` | 🚧 Up Next |
181
+ | [**0.6.x**](https://github.com/withtwoemms/ucon/milestone/4) | Nonlinear / Specialized Units | Decibel, Percent, pH | ⏳ Planned |
182
+ | [**0.8.x**](https://github.com/withtwoemms/ucon/milestone/6) | Pydantic Integration | Type-safe quantity validation | ⏳ Planned |
173
183
 
174
184
  See full roadmap: [ROADMAP.md](./ROADMAP.md)
175
185
 
@@ -182,13 +192,13 @@ Ensure `nox` is installed.
182
192
  ```
183
193
  pip install -r requirements.txt
184
194
  ```
185
- Then run the full test suite (agains all supported python versions) before committing:
195
+ Then run the full test suite (against all supported python versions) before committing:
186
196
 
187
197
  ```bash
188
198
  nox -s test
189
199
  ```
190
200
  ---
191
201
 
192
- > If it can be measured, it can be represented.
202
+ > "If it can be measured, it can be represented.
193
203
  If it can be represented, it can be validated.
194
- If it can be validated, it can be trusted.”
204
+ 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 conversions
22
- - Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, ect.)
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,22 @@ 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.dimension` | Represents the exponent tuple of a physical quantitys base dimensions (e.g., T, L, M, I, Θ, J, N). | Internal representation of dimensional algebra; building derived quantities (e.g., area, velocity, force). |
37
- | **`Dimension`** | `ucon.dimension` | Encapsulates physical dimensions (e.g., length, time, mass) as algebraic combinations of vectors. | Enforcing dimensional consistency; defining relationships between quantities (e.g., length / time = velocity). |
38
- | **`Unit`** | `ucon.unit` | Represents a named, dimensioned measurement unit (e.g., meter, second, joule). | Attaching human-readable units to quantities; defining or composing new units (`newton = kilogram * meter / second²`). |
36
+ | **`Vector`** | `ucon.algebra` | Represents the exponent tuple of a physical quantity's base dimensions (e.g., T, L, M, I, Θ, J, N). | Internal representation of dimensional algebra; building derived quantities (e.g., area, velocity, force). |
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
- | **`Exponent`** | `ucon.core` | Represents base-power pairs (e.g., 10³, 2¹⁰) used by `Scale`. | Performing arithmetic on powers and bases; normalizing scales across conversions. |
41
- | **`Number`** | `ucon.core` | Combines a numeric quantity with a unit and scale; the primary measurable type. | Performing arithmetic with units; converting between compatible units; representing physical quantities like 5 m/s. |
42
- | **`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
- | **`units` module** | `ucon.units` | Defines canonical unit instances (SI and common derived units). | Quick access to standard physical units (`units.meter`, `units.second`, `units.newton`, etc.). | |
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.quantity` | Combines a numeric quantity with a unit; the primary measurable type. | Performing arithmetic with units; representing physical quantities like 5 m/s. |
44
+ | **`Ratio`** | `ucon.quantity` | Represents the division of two `Number` objects; captures relationships between quantities. | Expressing rates, densities, efficiencies (e.g., energy / time = power, length / time = velocity). |
45
+ | **`units` module** | `ucon.units` | Defines canonical unit instances (SI and common derived units). | Quick access to standard physical units (`units.meter`, `units.second`, `units.newton`, etc.). |
44
46
 
45
47
  ### Under the Hood
46
48
 
47
49
  `ucon` models unit math through a hierarchy where each layer builds on the last:
48
50
 
49
- <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/0c704737a52b9e4a87cda5c839e9aa40f7e5bb48/ucon.data-model_v035.png align="center" alt="ucon Data Model" width=600/>
51
+ <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/f24134c362829dc72e7dff18bfcaa24b9be01b54/ucon.data-model_v035.png align="center" alt="ucon Data Model" width=600/>
50
52
 
51
53
  ## Why `ucon`?
52
54
 
@@ -54,24 +56,22 @@ Python already has mature libraries for handling units and physical quantities
54
56
 
55
57
  | Library | Focus | Limitation |
56
58
  | --------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
57
- | **Pint** | Runtime unit conversion and compatibility checking | Treats quantities as decorated numbers — conversions work, but the algebra behind them isnt inspectable or type-safe. |
59
+ | **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
60
  | **SymPy** | Symbolic algebra and simplification of unit expressions | Excellent for symbolic reasoning, but not designed for runtime validation, conversion, or serialization. |
59
61
  | **Unum** | Unit-aware arithmetic and unit propagation | Tracks units through arithmetic but lacks explicit dimensional algebra, conversion taxonomy, or runtime introspection. |
60
62
 
61
63
  Together, these tools can _use_ units, but none can explicitly represent and verify the relationships between units and dimensions.
62
64
 
63
- Thats the gap `ucon` fills.
65
+ That's the gap `ucon` fills.
64
66
 
65
67
  It treats units, dimensions, and scales as first-class objects and builds a composable algebra around them.
66
68
  This allows you to:
67
69
  - Represent dimensional meaning explicitly (`Dimension`, `Vector`);
68
70
  - 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
71
  - Extend the system with custom unit registries and conversion families.
72
72
 
73
73
  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 doesnt just track names: it enforces physics:
74
+ `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
75
  ```python
76
76
  from ucon import Number, units
77
77
 
@@ -99,7 +99,8 @@ This sort of dimensional analysis:
99
99
  ```
100
100
  becomes straightforward when you define a measurement:
101
101
  ```python
102
- from ucon import Number, Scale, Units, Ratio
102
+ from ucon import Number, Scale, units
103
+ from ucon.quantity import Ratio
103
104
 
104
105
  # Two milliliters of bromine
105
106
  mL = Scale.milli * units.liter
@@ -112,27 +113,36 @@ bromine_density = Ratio(
112
113
  )
113
114
 
114
115
  # Multiply to find mass
115
- grams_bromine = two_mL_bromine * bromine_density
116
- print(grams_bromine) # <6.238 gram>
116
+ grams_bromine = bromine_density.evaluate() * two_mL_bromine
117
+ print(grams_bromine) # <6.238 g>
117
118
  ```
118
119
 
119
- Scale conversion is automatic and precise:
120
-
120
+ Scale prefixes compose naturally:
121
121
  ```python
122
- grams_bromine.to(Scale.milli) # <6238.0 milligram>
123
- grams_bromine.to(Scale.kibi) # <0.006091796875 kibigram>
122
+ km = Scale.kilo * units.meter # UnitProduct with kilo-scaled meter
123
+ mg = Scale.milli * units.gram # UnitProduct with milli-scaled gram
124
+
125
+ print(km.shorthand) # 'km'
126
+ print(mg.shorthand) # 'mg'
127
+
128
+ # Scale arithmetic
129
+ print(km.fold_scale()) # 1000.0
130
+ print(mg.fold_scale()) # 0.001
124
131
  ```
125
132
 
133
+ > **Note:** Unit _conversions_ (e.g., `number.to(units.inch)`) are planned for v0.4.x
134
+ > via the `ConversionGraph` abstraction. See [ROADMAP.md](./ROADMAP.md).
135
+
126
136
  ---
127
137
 
128
138
  ## Roadmap Highlights
129
139
 
130
- | Version | Theme | Focus |
131
- |----------|-------|--------|
132
- | [**0.3.x**](https://github.com/withtwoemms/ucon/milestone/1) | Primitive Type Refinement | Unified algebraic foundation |
133
- | [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System | Linear & affine conversions |
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 |
140
+ | Version | Theme | Focus | Status |
141
+ |----------|-------|--------|--------|
142
+ | **0.3.5** | Dimensional Algebra | Unit/Scale separation, `UnitFactor`, `UnitProduct` | ✅ Complete |
143
+ | [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System | `ConversionGraph`, `Number.to()` | 🚧 Up Next |
144
+ | [**0.6.x**](https://github.com/withtwoemms/ucon/milestone/4) | Nonlinear / Specialized Units | Decibel, Percent, pH | ⏳ Planned |
145
+ | [**0.8.x**](https://github.com/withtwoemms/ucon/milestone/6) | Pydantic Integration | Type-safe quantity validation | ⏳ Planned |
136
146
 
137
147
  See full roadmap: [ROADMAP.md](./ROADMAP.md)
138
148
 
@@ -145,13 +155,13 @@ Ensure `nox` is installed.
145
155
  ```
146
156
  pip install -r requirements.txt
147
157
  ```
148
- Then run the full test suite (agains all supported python versions) before committing:
158
+ Then run the full test suite (against all supported python versions) before committing:
149
159
 
150
160
  ```bash
151
161
  nox -s test
152
162
  ```
153
163
  ---
154
164
 
155
- > If it can be measured, it can be represented.
165
+ > "If it can be measured, it can be represented.
156
166
  If it can be represented, it can be validated.
157
- If it can be validated, it can be trusted.”
167
+ If it can be validated, it can be trusted."
@@ -4,61 +4,68 @@
4
4
 
5
5
  ---
6
6
 
7
- ## 🪜 Current Version: **v0.3.0**
7
+ ## 🪜 Current Version: **v0.3.5**
8
8
 
9
9
  Stable baseline for:
10
- - `ucon.core` (`Number`, `Scale`, `Ratio`)
11
- - `ucon.unit` (basic unit representation and composition)
10
+ - `ucon.core` (`Dimension`, `Scale`, `Unit`, `UnitFactor`, `UnitProduct`)
11
+ - `ucon.quantity` (`Number`, `Ratio`)
12
12
  - `ucon.units` (canonical SI definitions)
13
13
  - Initial CI, testing, and packaging
14
14
 
15
15
  ---
16
16
 
17
- ## 🚀 v0.3.x — Dimensional Algebra (In Progress)
17
+ ## v0.3.x — Dimensional Algebra (Complete)
18
18
 
19
19
  ### 🔹 Summary
20
- > Introduces `ucon.dimension` as the foundation for algebraic reasoning.
20
+ > Introduces dimensional algebra and establishes the Unit/Scale separation
21
+ > that underpins all downstream work.
21
22
 
22
23
  ### ✅ Goals
23
- - [x] Implement `Vector` and `Dimension` classes
24
- - [x] Integrate dimensions into `Unit`
25
- - [x] Refactor `ucon.units` to use dimensional definitions
24
+ - [x] Implement `Vector` and `Dimension` classes
25
+ - [x] Integrate dimensions into `Unit`
26
+ - [x] Refactor `ucon.units` to use dimensional definitions
26
27
  - [x] Publish documentation for dimensional operations
27
- - [x] Verify uniqueness and hashing correctness across all Dimensions
28
- - [x] Redesign `Exponent` to support algebraic operations (`__mul__`, `__truediv__`, `to_base`, etc.)
29
- - [x] Remove redundant evaluated caching in favor of property-based computation
30
- - [x] Integrate `Scale` with Exponent for consistent prefix arithmetic
31
- - [ ] Update `Number` and `Ratio` to use Exponent-driven scaling
28
+ - [x] Verify uniqueness and hashing correctness across all Dimensions
29
+ - [x] Redesign `Exponent` to support algebraic operations (`__mul__`, `__truediv__`, `to_base`, etc.)
30
+ - [x] Remove redundant evaluated caching in favor of property-based computation
31
+ - [x] Integrate `Scale` with Exponent for consistent prefix arithmetic
32
32
  - [x] Add regression tests for prefix math (`kilo / milli → mega`, `2¹⁰ / 10³ → 1.024×`)
33
- - [ ] Document Exponent/Scale relationship in developer guide
33
+ - [x] Separate `scale` from `Unit`; delegate to `UnitFactor(unit, scale)`
34
+ - [x] Introduce `UnitProduct` with `fold_scale()` and `_residual_scale_factor`
35
+ - [x] `Number.value` returns as-expressed magnitude; `_canonical_magnitude` folds scale internally
36
+ - [x] Remove dead code and unify naming (`UnitFactor`, `UnitProduct`) across all docstrings and repr
34
37
 
35
38
  ### 🧩 Outcomes
36
- - All units acquire explicit dimensional semantics
37
- - Enables composable and type-safe dimensional operations
38
- - Establishes the mathematical foundation for future conversions
39
- - Unified algebraic foundation for all scaling and magnitude operations
40
- - ~Precise, reversible cross-base math (`2ⁿ 10ᵐ`)~
41
- - Simplified, consistent `Scale` and `Number` behavior
42
- - Ready for integration into the conversion engine (`ucon.conversions`)
39
+ - All units acquire explicit dimensional semantics
40
+ - Enables composable and type-safe dimensional operations
41
+ - Establishes the mathematical foundation for future conversions
42
+ - Unified algebraic foundation for all scaling and magnitude operations
43
+ - Clean Unit/Scale separation: `Unit` is an atomic symbol, `UnitFactor` pairs it with a `Scale`
44
+ - `UnitProduct` correctly tracks residual scale from cancelled units
45
+ - Type system is ready for a `ConversionGraph` to be built on top
43
46
 
44
47
  ---
45
48
 
46
- ## ⚙️ v0.4.x — Conversion System Foundations
49
+ ## ⚙️ v0.4.x — Conversion System Foundations (Up Next)
47
50
 
48
51
  ### 🔹 Summary
49
52
  > Implements unified conversion engine for standard, linear, and affine conversions.
50
53
 
51
54
  ### ✅ Goals
52
- - [ ] Introduce `ucon.conversions` registry keyed by `Dimension`
53
- - [ ] Add support for `standard`, `linear`, and `affine` conversion types
54
- - [ ] Implement `.to(target_unit)` for `Number`
55
- - [ ] Round-trip validation for reversible conversions
56
- - [ ] Extend tests to include temperature, pressure, and base SI conversions
55
+ - [ ] Introduce `ConversionGraph` registry keyed by `Dimension`
56
+ - [ ] Add support for `standard`, `linear`, and `affine` conversion types
57
+ - [ ] Implement `Number.to(target_unit)` and `Number.simplify()`
58
+ - [ ] Scale-only conversions short-circuit without graph lookup
59
+ - [ ] Composite-to-composite conversion via per-component decomposition
60
+ - [ ] Round-trip validation for reversible conversions
61
+ - [ ] Extend tests to include temperature, pressure, and base SI conversions
62
+ - [ ] Document Exponent/Scale relationship in developer guide
57
63
 
58
64
  ### 🧩 Outcomes
59
- - Unified conversion taxonomy
60
- - Reversible, dimension-checked conversions
61
- - Forms the basis for nonlinear and domain-specific conversion families
65
+ - Unified conversion taxonomy
66
+ - Reversible, dimension-checked conversions
67
+ - Scale-aware graph that leverages the `Unit`/`UnitFactor` separation from v0.3.x
68
+ - Forms the basis for nonlinear and domain-specific conversion families
62
69
 
63
70
  ---
64
71
 
@@ -68,16 +75,16 @@ Stable baseline for:
68
75
  > Introduces an extensible registry system for custom units and aliases.
69
76
 
70
77
  ### ✅ Goals
71
- - [x] Implement `have(name)` membership check
72
- - [ ] Add `UnitSystem` abstraction
73
- - [ ] Support `registry.add(unit)` and dynamic system registration
74
- - [ ] Validate alias uniqueness and collision prevention
75
- - [ ] Include examples for user-defined unit extensions
78
+ - [x] Implement `have(name)` membership check
79
+ - [ ] Add `UnitSystem` abstraction
80
+ - [ ] Support `registry.add(unit)` and dynamic system registration
81
+ - [ ] Validate alias uniqueness and collision prevention
82
+ - [ ] Include examples for user-defined unit extensions
76
83
 
77
84
  ### 🧩 Outcomes
78
- - Registry-based extensibility for domain-specific systems
79
- - Dynamic unit registration and discovery
80
- - Groundwork for plugin-style system extensions
85
+ - Registry-based extensibility for domain-specific systems
86
+ - Dynamic unit registration and discovery
87
+ - Groundwork for plugin-style system extensions
81
88
 
82
89
  ---
83
90
 
@@ -87,20 +94,20 @@ Stable baseline for:
87
94
  > Adds support for logarithmic, fractional, and other specialized dimensionless conversions.
88
95
 
89
96
  ### ✅ Goals
90
- - [ ] Extend conversion registry schema with `"nonlinear"` family
91
- - [ ] Add `to_base` / `from_base` lambdas for function-based mappings
92
- - [ ] Define sample nonlinear conversions (`decibel`, `bel`, `pH`)
93
- - [ ] Add tolerance-aware tests for nonlinear conversions
97
+ - [ ] Extend conversion registry schema with `"nonlinear"` family
98
+ - [ ] Add `to_base` / `from_base` lambdas for function-based mappings
99
+ - [ ] Define sample nonlinear conversions (`decibel`, `bel`, `pH`)
100
+ - [ ] Add tolerance-aware tests for nonlinear conversions
94
101
  - [ ] Introduce structured dimensionless unit family (`radian`, `percent`, `ppm`, `count`, etc.)
95
102
  - [ ] Define canonical dimensionless subtypes for angular, fractional, and count semantics
96
103
  - [ ] Ensure automatic collapse of equivalent units (`m/m → none`, `J/J → none`) via Ratio
97
104
 
98
105
  ### 🧩 Outcomes
99
- - Support for function-based (nonlinear) physical conversions
100
- - Unified algebraic framework across all conversion types
106
+ - Support for function-based (nonlinear) physical conversions
107
+ - Unified algebraic framework across all conversion types
101
108
  - Rich, semantically meaningful representation of dimensionless quantities
102
109
  - Enables acoustics (dB), geometry (rad, sr), statistics (probability), and fractional scales (%, ppm)
103
-
110
+
104
111
  ---
105
112
 
106
113
  ## 🧰 v0.7.x — Testing, Developer Experience, & API Polish
@@ -109,16 +116,16 @@ Stable baseline for:
109
116
  > Strengthens tests, developer ergonomics, and runtime feedback.
110
117
 
111
118
  ### ✅ Goals
112
- - [ ] Reach 95%+ test coverage
113
- - [ ] Add property-based tests for dimensional invariants
114
- - [ ] Improve error reporting, `__repr__`, and exception messaging
115
- - [ ] Validate public API imports and maintain consistent naming
116
- - [ ] Add CI coverage reports and build badges
119
+ - [ ] Reach 95%+ test coverage
120
+ - [ ] Add property-based tests for dimensional invariants
121
+ - [ ] Improve error reporting, `__repr__`, and exception messaging
122
+ - [ ] Validate public API imports and maintain consistent naming
123
+ - [ ] Add CI coverage reports and build badges
117
124
 
118
125
  ### 🧩 Outcomes
119
- - Reliable, developer-friendly foundation
120
- - Consistent runtime behavior and output clarity
121
- - Prepares API for public documentation and 1.0 freeze
126
+ - Reliable, developer-friendly foundation
127
+ - Consistent runtime behavior and output clarity
128
+ - Prepares API for public documentation and 1.0 freeze
122
129
 
123
130
  ---
124
131
 
@@ -128,16 +135,16 @@ Stable baseline for:
128
135
  > Introduces seamless integration with **Pydantic v2**, enabling validation, serialization, and typed dimensional models.
129
136
 
130
137
  ### ✅ Goals
131
- - [ ] Define Pydantic-compatible field types (`UnitType`, `NumberType`)
132
- - [ ] Implement `__get_pydantic_core_schema__` for Units and Numbers
133
- - [ ] Support automatic conversion/validation for user-defined models
134
- - [ ] Add YAML / JSON encoding for quantities (`Number(unit="meter", quantity=5)`)
135
- - [ ] Add Pydantic-based examples (API config, simulation parameters)
138
+ - [ ] Define Pydantic-compatible field types (`UnitType`, `NumberType`)
139
+ - [ ] Implement `__get_pydantic_core_schema__` for Units and Numbers
140
+ - [ ] Support automatic conversion/validation for user-defined models
141
+ - [ ] Add YAML / JSON encoding for quantities (`Number(unit="meter", quantity=5)`)
142
+ - [ ] Add Pydantic-based examples (API config, simulation parameters)
136
143
 
137
144
  ### 🧩 Outcomes
138
- - Native validation and serialization for dimensioned quantities
139
- - Enables safe configuration in data models and APIs
140
- - Bridges `ucon`’s algebraic model with modern Python typing ecosystems
145
+ - Native validation and serialization for dimensioned quantities
146
+ - Enables safe configuration in data models and APIs
147
+ - Bridges `ucon`'s algebraic model with modern Python typing ecosystems
141
148
 
142
149
  ---
143
150
 
@@ -147,16 +154,16 @@ Stable baseline for:
147
154
  > Completes documentation, finalizes examples, and preps release candidates.
148
155
 
149
156
  ### ✅ Goals
150
- - [ ] Write comprehensive README and developer guide
151
- - [ ] Publish API reference docs (Sphinx / MkDocs)
152
- - [ ] Add SymPy / Pint comparison appendix
153
- - [ ] Freeze and document all public APIs
154
- - [ ] Publish one or more release candidates (RC1, RC2)
157
+ - [ ] Write comprehensive README and developer guide
158
+ - [ ] Publish API reference docs (Sphinx / MkDocs)
159
+ - [ ] Add SymPy / Pint comparison appendix
160
+ - [ ] Freeze and document all public APIs
161
+ - [ ] Publish one or more release candidates (RC1, RC2)
155
162
 
156
163
  ### 🧩 Outcomes
157
- - Complete public-facing documentation
158
- - API frozen and versioned for stability
159
- - Ready for final testing and validation before 1.0
164
+ - Complete public-facing documentation
165
+ - API frozen and versioned for stability
166
+ - Ready for final testing and validation before 1.0
160
167
 
161
168
  ---
162
169
 
@@ -166,15 +173,15 @@ Stable baseline for:
166
173
  > First major release: a unified algebra for composable, type-safe, and semantically clear unit conversion.
167
174
 
168
175
  ### ✅ Goals
169
- - [ ] Tag and release to PyPI
170
- - [ ] Validate packaging and dependency metadata
171
- - [ ] Include examples and tutorials in docs
172
- - [ ] Announce 1.0 on GitHub and PyPI
176
+ - [ ] Tag and release to PyPI
177
+ - [ ] Validate packaging and dependency metadata
178
+ - [ ] Include examples and tutorials in docs
179
+ - [ ] Announce 1.0 on GitHub and PyPI
173
180
 
174
181
  ### 🧩 Outcomes
175
- - Stable, well-tested release
176
- - Fully type-safe and validated core
177
- - Production-ready for integration into scientific and engineering workflows
182
+ - Stable, well-tested release
183
+ - Fully type-safe and validated core
184
+ - Production-ready for integration into scientific and engineering workflows
178
185
 
179
186
  ---
180
187
 
@@ -192,24 +199,24 @@ Stable baseline for:
192
199
 
193
200
  ## 🗓️ Milestone Summary
194
201
 
195
- | Version | Theme | Key Focus | Target | Status |
196
- |----------|--------|------------|---------|---------|
197
- | **0.3.0** | Dimensional Algebra | Introduce `ucon.dimension` | **Nov 2025** | 🚧 In Progress |
198
- | **0.4.0** | Conversion Engine | Standard, linear, affine conversions | **Jan 2026** | ⏳ Planned |
199
- | **0.5.0** | Unit Systems & Registries | Extensible registry system | **Mar 2026** | ⏳ Planned |
200
- | **0.6.0** | Nonlinear Conversions | Logarithmic / exponential families | **May 2026** | ⏳ Planned |
201
- | **0.7.0** | Testing & API Polish | Coverage, ergonomics, stability | **Jul 2026** | ⏳ Planned |
202
- | **0.8.0** | 🧩 **Pydantic Integration** | Typed validation, serialization | **Sep 2026** | 🧭 Newly Added |
203
- | **0.9.x** | Documentation & RC | Freeze API, publish docs, RCs | **Nov 2026** | ⏳ Planned |
204
- | **1.0.0** | Stable Release | Publish production-ready core | **Jan 2027** | 🔮 Future |
202
+ | Version | Theme | Key Focus | Status |
203
+ |----------|--------|------------|---------|
204
+ | **0.3.5** | Dimensional Algebra | Unit/Scale separation, `UnitFactor`, `UnitProduct` | Complete |
205
+ | **0.4.0** | Conversion Engine | `ConversionGraph`, `Number.to()`, scale-aware lookup | 🚧 Up Next |
206
+ | **0.5.0** | Unit Systems & Registries | Extensible registry system | ⏳ Planned |
207
+ | **0.6.0** | Nonlinear Conversions | Logarithmic / exponential families | ⏳ Planned |
208
+ | **0.7.0** | Testing & API Polish | Coverage, ergonomics, stability | ⏳ Planned |
209
+ | **0.8.0** | Pydantic Integration | Typed validation, serialization | Planned |
210
+ | **0.9.x** | Documentation & RC | Freeze API, publish docs, RCs | ⏳ Planned |
211
+ | **1.0.0** | Stable Release | Publish production-ready core | 🔮 Future |
205
212
 
206
213
  ---
207
214
 
208
215
  ### ✨ Guiding Principle
209
216
 
210
- > If it can be measured, it can be represented.
211
- > If it can be represented, it can be validated.
212
- > If it can be validated, it can be trusted.”
217
+ > "If it can be measured, it can be represented.
218
+ > If it can be represented, it can be validated.
219
+ > If it can be validated, it can be trusted."
213
220
 
214
221
  ---
215
222
 
@@ -220,3 +227,4 @@ Stable baseline for:
220
227
  class Config(BaseModel):
221
228
  length: NumberType[Dimension.length]
222
229
  time: NumberType[Dimension.time]
230
+ ```
@@ -240,8 +240,7 @@ class Scale(Enum):
240
240
 
241
241
  def __mul__(self, other):
242
242
  # --- Case 1: applying Scale to simple Unit --------------------
243
- if isinstance(other, Unit) and not isinstance(other, UnitProduct):
244
- # Unit no longer has scale attribute - always safe to apply
243
+ if isinstance(other, Unit):
245
244
  return UnitProduct({UnitFactor(unit=other, scale=self): 1})
246
245
 
247
246
  # --- Case 2: other cases are NOT handled here -----------------
@@ -343,8 +342,6 @@ class Unit:
343
342
  Unit * Unit -> UnitProduct
344
343
  Unit * UnitProduct -> UnitProduct
345
344
  """
346
- from ucon.core import UnitProduct # local import to avoid circulars
347
-
348
345
  if isinstance(other, UnitProduct):
349
346
  # let UnitProduct handle merging
350
347
  return other.__rmul__(self)
@@ -356,12 +353,13 @@ class Unit:
356
353
 
357
354
  def __truediv__(self, other):
358
355
  """
359
- Unit / Unit:
360
- - If same unit => dimensionless Unit()
361
- - If denominator is dimensionless => self
362
- - Else => UnitProduct
356
+ Unit / Unit or Unit / UnitProduct => UnitProduct
363
357
  """
364
- from ucon.core import UnitProduct # local import
358
+ if isinstance(other, UnitProduct):
359
+ combined = {self: 1.0}
360
+ for u, exp in other.factors.items():
361
+ combined[u] = combined.get(u, 0.0) - exp
362
+ return UnitProduct(combined)
365
363
 
366
364
  if not isinstance(other, Unit):
367
365
  return NotImplemented
@@ -385,13 +383,13 @@ class Unit:
385
383
  """
386
384
  Unit ** n => UnitProduct with that exponent.
387
385
  """
388
- from ucon.core import UnitProduct # local import
389
-
390
386
  return UnitProduct({self: power})
391
387
 
392
388
  # ----------------- equality & hashing -----------------
393
389
 
394
390
  def __eq__(self, other):
391
+ if isinstance(other, UnitProduct):
392
+ return other.__eq__(self)
395
393
  if not isinstance(other, Unit):
396
394
  return NotImplemented
397
395
  return (
@@ -498,7 +496,7 @@ class UnitFactor:
498
496
  return NotImplemented
499
497
 
500
498
 
501
- class UnitProduct(Unit):
499
+ class UnitProduct:
502
500
  """
503
501
  Represents a product or quotient of Units.
504
502
 
@@ -527,8 +525,7 @@ class UnitProduct(Unit):
527
525
  encountered UnitFactor (keeps user-intent scale).
528
526
  """
529
527
 
530
- # UnitProduct always starts dimensionless
531
- super().__init__(name="", dimension=Dimension.none)
528
+ self.name = ""
532
529
  self.aliases = ()
533
530
 
534
531
  merged: dict[UnitFactor, float] = {}
@@ -706,6 +703,16 @@ class UnitProduct(Unit):
706
703
  result *= factor.scale.value.evaluated ** power
707
704
  return result
708
705
 
706
+ # ------------- Helpers ---------------------------------------------------
707
+
708
+ def _norm(self, aliases: tuple[str, ...]) -> tuple[str, ...]:
709
+ """Normalize alias bag: drop empty/whitespace-only aliases."""
710
+ return tuple(a for a in aliases if a.strip())
711
+
712
+ def __pow__(self, power):
713
+ """UnitProduct ** n => new UnitProduct with scaled exponents."""
714
+ return UnitProduct({u: exp * power for u, exp in self.factors.items()})
715
+
709
716
  # ------------- Algebra ---------------------------------------------------
710
717
 
711
718
  def __mul__(self, other):
@@ -799,7 +806,7 @@ class UnitProduct(Unit):
799
806
  return f"<{self.__class__.__name__} {self.shorthand}>"
800
807
 
801
808
  def __eq__(self, other):
802
- if isinstance(other, Unit) and not isinstance(other, UnitProduct):
809
+ if isinstance(other, Unit):
803
810
  # Only equal to a plain Unit if we have exactly that unit^1
804
811
  # Here, the tuple comparison will invoke UnitFactor.__eq__(Unit)
805
812
  # on the key when factors are keyed by UnitFactor.
@@ -43,7 +43,7 @@ class Number:
43
43
  <2.5 (m/s)>
44
44
  """
45
45
  quantity: Union[float, int] = 1.0
46
- unit: Unit = units.none
46
+ unit: Union[Unit, UnitProduct] = units.none
47
47
 
48
48
  @property
49
49
  def value(self) -> float:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ucon
3
- Version: 0.3.5rc1
3
+ Version: 0.3.5rc2
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 conversions
59
- - Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, ect.)
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,22 @@ 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.dimension` | Represents the exponent tuple of a physical quantitys base dimensions (e.g., T, L, M, I, Θ, J, N). | Internal representation of dimensional algebra; building derived quantities (e.g., area, velocity, force). |
74
- | **`Dimension`** | `ucon.dimension` | Encapsulates physical dimensions (e.g., length, time, mass) as algebraic combinations of vectors. | Enforcing dimensional consistency; defining relationships between quantities (e.g., length / time = velocity). |
75
- | **`Unit`** | `ucon.unit` | Represents a named, dimensioned measurement unit (e.g., meter, second, joule). | Attaching human-readable units to quantities; defining or composing new units (`newton = kilogram * meter / second²`). |
73
+ | **`Vector`** | `ucon.algebra` | Represents the exponent tuple of a physical quantity's base dimensions (e.g., T, L, M, I, Θ, J, N). | Internal representation of dimensional algebra; building derived quantities (e.g., area, velocity, force). |
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
- | **`Exponent`** | `ucon.core` | Represents base-power pairs (e.g., 10³, 2¹⁰) used by `Scale`. | Performing arithmetic on powers and bases; normalizing scales across conversions. |
78
- | **`Number`** | `ucon.core` | Combines a numeric quantity with a unit and scale; the primary measurable type. | Performing arithmetic with units; converting between compatible units; representing physical quantities like 5 m/s. |
79
- | **`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
- | **`units` module** | `ucon.units` | Defines canonical unit instances (SI and common derived units). | Quick access to standard physical units (`units.meter`, `units.second`, `units.newton`, etc.). | |
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.quantity` | Combines a numeric quantity with a unit; the primary measurable type. | Performing arithmetic with units; representing physical quantities like 5 m/s. |
81
+ | **`Ratio`** | `ucon.quantity` | Represents the division of two `Number` objects; captures relationships between quantities. | Expressing rates, densities, efficiencies (e.g., energy / time = power, length / time = velocity). |
82
+ | **`units` module** | `ucon.units` | Defines canonical unit instances (SI and common derived units). | Quick access to standard physical units (`units.meter`, `units.second`, `units.newton`, etc.). |
81
83
 
82
84
  ### Under the Hood
83
85
 
84
86
  `ucon` models unit math through a hierarchy where each layer builds on the last:
85
87
 
86
- <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/0c704737a52b9e4a87cda5c839e9aa40f7e5bb48/ucon.data-model_v035.png align="center" alt="ucon Data Model" width=600/>
88
+ <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/f24134c362829dc72e7dff18bfcaa24b9be01b54/ucon.data-model_v035.png align="center" alt="ucon Data Model" width=600/>
87
89
 
88
90
  ## Why `ucon`?
89
91
 
@@ -91,24 +93,22 @@ Python already has mature libraries for handling units and physical quantities
91
93
 
92
94
  | Library | Focus | Limitation |
93
95
  | --------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
94
- | **Pint** | Runtime unit conversion and compatibility checking | Treats quantities as decorated numbers — conversions work, but the algebra behind them isnt inspectable or type-safe. |
96
+ | **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
97
  | **SymPy** | Symbolic algebra and simplification of unit expressions | Excellent for symbolic reasoning, but not designed for runtime validation, conversion, or serialization. |
96
98
  | **Unum** | Unit-aware arithmetic and unit propagation | Tracks units through arithmetic but lacks explicit dimensional algebra, conversion taxonomy, or runtime introspection. |
97
99
 
98
100
  Together, these tools can _use_ units, but none can explicitly represent and verify the relationships between units and dimensions.
99
101
 
100
- Thats the gap `ucon` fills.
102
+ That's the gap `ucon` fills.
101
103
 
102
104
  It treats units, dimensions, and scales as first-class objects and builds a composable algebra around them.
103
105
  This allows you to:
104
106
  - Represent dimensional meaning explicitly (`Dimension`, `Vector`);
105
107
  - 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
108
  - Extend the system with custom unit registries and conversion families.
109
109
 
110
110
  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 doesnt just track names: it enforces physics:
111
+ `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
112
  ```python
113
113
  from ucon import Number, units
114
114
 
@@ -136,7 +136,8 @@ This sort of dimensional analysis:
136
136
  ```
137
137
  becomes straightforward when you define a measurement:
138
138
  ```python
139
- from ucon import Number, Scale, Units, Ratio
139
+ from ucon import Number, Scale, units
140
+ from ucon.quantity import Ratio
140
141
 
141
142
  # Two milliliters of bromine
142
143
  mL = Scale.milli * units.liter
@@ -149,27 +150,36 @@ bromine_density = Ratio(
149
150
  )
150
151
 
151
152
  # Multiply to find mass
152
- grams_bromine = two_mL_bromine * bromine_density
153
- print(grams_bromine) # <6.238 gram>
153
+ grams_bromine = bromine_density.evaluate() * two_mL_bromine
154
+ print(grams_bromine) # <6.238 g>
154
155
  ```
155
156
 
156
- Scale conversion is automatic and precise:
157
-
157
+ Scale prefixes compose naturally:
158
158
  ```python
159
- grams_bromine.to(Scale.milli) # <6238.0 milligram>
160
- grams_bromine.to(Scale.kibi) # <0.006091796875 kibigram>
159
+ km = Scale.kilo * units.meter # UnitProduct with kilo-scaled meter
160
+ mg = Scale.milli * units.gram # UnitProduct with milli-scaled gram
161
+
162
+ print(km.shorthand) # 'km'
163
+ print(mg.shorthand) # 'mg'
164
+
165
+ # Scale arithmetic
166
+ print(km.fold_scale()) # 1000.0
167
+ print(mg.fold_scale()) # 0.001
161
168
  ```
162
169
 
170
+ > **Note:** Unit _conversions_ (e.g., `number.to(units.inch)`) are planned for v0.4.x
171
+ > via the `ConversionGraph` abstraction. See [ROADMAP.md](./ROADMAP.md).
172
+
163
173
  ---
164
174
 
165
175
  ## Roadmap Highlights
166
176
 
167
- | Version | Theme | Focus |
168
- |----------|-------|--------|
169
- | [**0.3.x**](https://github.com/withtwoemms/ucon/milestone/1) | Primitive Type Refinement | Unified algebraic foundation |
170
- | [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System | Linear & affine conversions |
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 |
177
+ | Version | Theme | Focus | Status |
178
+ |----------|-------|--------|--------|
179
+ | **0.3.5** | Dimensional Algebra | Unit/Scale separation, `UnitFactor`, `UnitProduct` | ✅ Complete |
180
+ | [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System | `ConversionGraph`, `Number.to()` | 🚧 Up Next |
181
+ | [**0.6.x**](https://github.com/withtwoemms/ucon/milestone/4) | Nonlinear / Specialized Units | Decibel, Percent, pH | ⏳ Planned |
182
+ | [**0.8.x**](https://github.com/withtwoemms/ucon/milestone/6) | Pydantic Integration | Type-safe quantity validation | ⏳ Planned |
173
183
 
174
184
  See full roadmap: [ROADMAP.md](./ROADMAP.md)
175
185
 
@@ -182,13 +192,13 @@ Ensure `nox` is installed.
182
192
  ```
183
193
  pip install -r requirements.txt
184
194
  ```
185
- Then run the full test suite (agains all supported python versions) before committing:
195
+ Then run the full test suite (against all supported python versions) before committing:
186
196
 
187
197
  ```bash
188
198
  nox -s test
189
199
  ```
190
200
  ---
191
201
 
192
- > If it can be measured, it can be represented.
202
+ > "If it can be measured, it can be represented.
193
203
  If it can be represented, it can be validated.
194
- If it can be validated, it can be trusted.”
204
+ If it can be validated, it can be trusted."
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes