ucon 0.3.5__tar.gz → 0.3.5rc1__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.5 → ucon-0.3.5rc1}/PKG-INFO +32 -42
  2. {ucon-0.3.5 → ucon-0.3.5rc1}/README.md +31 -41
  3. {ucon-0.3.5 → ucon-0.3.5rc1}/ROADMAP.md +89 -97
  4. {ucon-0.3.5 → ucon-0.3.5rc1}/ucon/__init__.py +1 -3
  5. {ucon-0.3.5 → ucon-0.3.5rc1}/ucon/core.py +15 -22
  6. {ucon-0.3.5 → ucon-0.3.5rc1}/ucon/quantity.py +1 -1
  7. {ucon-0.3.5 → ucon-0.3.5rc1}/ucon.egg-info/PKG-INFO +32 -42
  8. {ucon-0.3.5 → ucon-0.3.5rc1}/.github/workflows/publish.yaml +0 -0
  9. {ucon-0.3.5 → ucon-0.3.5rc1}/.github/workflows/tests.yaml +0 -0
  10. {ucon-0.3.5 → ucon-0.3.5rc1}/.gitignore +0 -0
  11. {ucon-0.3.5 → ucon-0.3.5rc1}/LICENSE +0 -0
  12. {ucon-0.3.5 → ucon-0.3.5rc1}/NOTICE +0 -0
  13. {ucon-0.3.5 → ucon-0.3.5rc1}/docs/decisions/composable-unit-algebra.md +0 -0
  14. {ucon-0.3.5 → ucon-0.3.5rc1}/docs/decisions/composite-units.md +0 -0
  15. {ucon-0.3.5 → ucon-0.3.5rc1}/docs/decisions/unit-algebra-naming.md +0 -0
  16. {ucon-0.3.5 → ucon-0.3.5rc1}/docs/decisions/unity-distance-metric-for-nearest-scale.md +0 -0
  17. {ucon-0.3.5 → ucon-0.3.5rc1}/docs/explainers/type-operation-matrix.md +0 -0
  18. {ucon-0.3.5 → ucon-0.3.5rc1}/docs/explainers/why-algebraic-closure-matters.md +0 -0
  19. {ucon-0.3.5 → ucon-0.3.5rc1}/docs/explainers/why-type-safety-matters.md +0 -0
  20. {ucon-0.3.5 → ucon-0.3.5rc1}/docs/proposals/interface-unifying-the-value-layer.md +0 -0
  21. {ucon-0.3.5 → ucon-0.3.5rc1}/docs/proposals/project_unified-algebraic-core.md +0 -0
  22. {ucon-0.3.5 → ucon-0.3.5rc1}/docs/proposals/support-for-fractional-exponents.md +0 -0
  23. {ucon-0.3.5 → ucon-0.3.5rc1}/docs/proposals/unified-unit-presentation.md +0 -0
  24. {ucon-0.3.5 → ucon-0.3.5rc1}/noxfile.py +0 -0
  25. {ucon-0.3.5 → ucon-0.3.5rc1}/requirements.txt +0 -0
  26. {ucon-0.3.5 → ucon-0.3.5rc1}/setup.cfg +0 -0
  27. {ucon-0.3.5 → ucon-0.3.5rc1}/setup.py +0 -0
  28. {ucon-0.3.5 → ucon-0.3.5rc1}/tests/__init__.py +0 -0
  29. {ucon-0.3.5 → ucon-0.3.5rc1}/tests/ucon/__init__.py +0 -0
  30. {ucon-0.3.5 → ucon-0.3.5rc1}/tests/ucon/test_algebra.py +0 -0
  31. {ucon-0.3.5 → ucon-0.3.5rc1}/tests/ucon/test_core.py +0 -0
  32. {ucon-0.3.5 → ucon-0.3.5rc1}/tests/ucon/test_quantity.py +0 -0
  33. {ucon-0.3.5 → ucon-0.3.5rc1}/tests/ucon/test_units.py +0 -0
  34. {ucon-0.3.5 → ucon-0.3.5rc1}/ucon/algebra.py +0 -0
  35. {ucon-0.3.5 → ucon-0.3.5rc1}/ucon/units.py +0 -0
  36. {ucon-0.3.5 → ucon-0.3.5rc1}/ucon.egg-info/SOURCES.txt +0 -0
  37. {ucon-0.3.5 → ucon-0.3.5rc1}/ucon.egg-info/dependency_links.txt +0 -0
  38. {ucon-0.3.5 → ucon-0.3.5rc1}/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.5
3
+ Version: 0.3.5rc1
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 via `UnitFactor` and `UnitProduct`
59
- - Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, etc.)
58
+ - Scale-aware arithmetic and conversions
59
+ - Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, ect.)
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,22 +70,20 @@ 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.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). |
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²`). |
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
- | **`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.). |
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.). | |
83
81
 
84
82
  ### Under the Hood
85
83
 
86
84
  `ucon` models unit math through a hierarchy where each layer builds on the last:
87
85
 
88
- <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/f24134c362829dc72e7dff18bfcaa24b9be01b54/ucon.data-model_v035.png align="center" alt="ucon Data Model" width=600/>
86
+ <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/0c704737a52b9e4a87cda5c839e9aa40f7e5bb48/ucon.data-model_v035.png align="center" alt="ucon Data Model" width=600/>
89
87
 
90
88
  ## Why `ucon`?
91
89
 
@@ -93,22 +91,24 @@ Python already has mature libraries for handling units and physical quantities
93
91
 
94
92
  | Library | Focus | Limitation |
95
93
  | --------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
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. |
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. |
97
95
  | **SymPy** | Symbolic algebra and simplification of unit expressions | Excellent for symbolic reasoning, but not designed for runtime validation, conversion, or serialization. |
98
96
  | **Unum** | Unit-aware arithmetic and unit propagation | Tracks units through arithmetic but lacks explicit dimensional algebra, conversion taxonomy, or runtime introspection. |
99
97
 
100
98
  Together, these tools can _use_ units, but none can explicitly represent and verify the relationships between units and dimensions.
101
99
 
102
- That's the gap `ucon` fills.
100
+ Thats the gap `ucon` fills.
103
101
 
104
102
  It treats units, dimensions, and scales as first-class objects and builds a composable algebra around them.
105
103
  This allows you to:
106
104
  - Represent dimensional meaning explicitly (`Dimension`, `Vector`);
107
105
  - 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 doesn't 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 doesnt just track names: it enforces physics:
112
112
  ```python
113
113
  from ucon import Number, units
114
114
 
@@ -136,8 +136,7 @@ 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
140
- from ucon.quantity import Ratio
139
+ from ucon import Number, Scale, Units, Ratio
141
140
 
142
141
  # Two milliliters of bromine
143
142
  mL = Scale.milli * units.liter
@@ -150,36 +149,27 @@ bromine_density = Ratio(
150
149
  )
151
150
 
152
151
  # Multiply to find mass
153
- grams_bromine = bromine_density.evaluate() * two_mL_bromine
154
- print(grams_bromine) # <6.238 g>
152
+ grams_bromine = two_mL_bromine * bromine_density
153
+ print(grams_bromine) # <6.238 gram>
155
154
  ```
156
155
 
157
- Scale prefixes compose naturally:
158
- ```python
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'
156
+ Scale conversion is automatic and precise:
164
157
 
165
- # Scale arithmetic
166
- print(km.fold_scale()) # 1000.0
167
- print(mg.fold_scale()) # 0.001
158
+ ```python
159
+ grams_bromine.to(Scale.milli) # <6238.0 milligram>
160
+ grams_bromine.to(Scale.kibi) # <0.006091796875 kibigram>
168
161
  ```
169
162
 
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
-
173
163
  ---
174
164
 
175
165
  ## Roadmap Highlights
176
166
 
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 |
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 |
183
173
 
184
174
  See full roadmap: [ROADMAP.md](./ROADMAP.md)
185
175
 
@@ -192,13 +182,13 @@ Ensure `nox` is installed.
192
182
  ```
193
183
  pip install -r requirements.txt
194
184
  ```
195
- Then run the full test suite (against all supported python versions) before committing:
185
+ Then run the full test suite (agains all supported python versions) before committing:
196
186
 
197
187
  ```bash
198
188
  nox -s test
199
189
  ```
200
190
  ---
201
191
 
202
- > "If it can be measured, it can be represented.
192
+ > If it can be measured, it can be represented.
203
193
  If it can be represented, it can be validated.
204
- If it can be validated, it can be trusted."
194
+ 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 via `UnitFactor` and `UnitProduct`
22
- - Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, etc.)
21
+ - Scale-aware arithmetic and conversions
22
+ - Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, ect.)
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,22 +33,20 @@ 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.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). |
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²`). |
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
- | **`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.). |
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.). | |
46
44
 
47
45
  ### Under the Hood
48
46
 
49
47
  `ucon` models unit math through a hierarchy where each layer builds on the last:
50
48
 
51
- <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/f24134c362829dc72e7dff18bfcaa24b9be01b54/ucon.data-model_v035.png align="center" alt="ucon Data Model" width=600/>
49
+ <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/0c704737a52b9e4a87cda5c839e9aa40f7e5bb48/ucon.data-model_v035.png align="center" alt="ucon Data Model" width=600/>
52
50
 
53
51
  ## Why `ucon`?
54
52
 
@@ -56,22 +54,24 @@ Python already has mature libraries for handling units and physical quantities
56
54
 
57
55
  | Library | Focus | Limitation |
58
56
  | --------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
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. |
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. |
60
58
  | **SymPy** | Symbolic algebra and simplification of unit expressions | Excellent for symbolic reasoning, but not designed for runtime validation, conversion, or serialization. |
61
59
  | **Unum** | Unit-aware arithmetic and unit propagation | Tracks units through arithmetic but lacks explicit dimensional algebra, conversion taxonomy, or runtime introspection. |
62
60
 
63
61
  Together, these tools can _use_ units, but none can explicitly represent and verify the relationships between units and dimensions.
64
62
 
65
- That's the gap `ucon` fills.
63
+ Thats the gap `ucon` fills.
66
64
 
67
65
  It treats units, dimensions, and scales as first-class objects and builds a composable algebra around them.
68
66
  This allows you to:
69
67
  - Represent dimensional meaning explicitly (`Dimension`, `Vector`);
70
68
  - 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 doesn't 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 doesnt just track names: it enforces physics:
75
75
  ```python
76
76
  from ucon import Number, units
77
77
 
@@ -99,8 +99,7 @@ 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
103
- from ucon.quantity import Ratio
102
+ from ucon import Number, Scale, Units, Ratio
104
103
 
105
104
  # Two milliliters of bromine
106
105
  mL = Scale.milli * units.liter
@@ -113,36 +112,27 @@ bromine_density = Ratio(
113
112
  )
114
113
 
115
114
  # Multiply to find mass
116
- grams_bromine = bromine_density.evaluate() * two_mL_bromine
117
- print(grams_bromine) # <6.238 g>
115
+ grams_bromine = two_mL_bromine * bromine_density
116
+ print(grams_bromine) # <6.238 gram>
118
117
  ```
119
118
 
120
- Scale prefixes compose naturally:
121
- ```python
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'
119
+ Scale conversion is automatic and precise:
127
120
 
128
- # Scale arithmetic
129
- print(km.fold_scale()) # 1000.0
130
- print(mg.fold_scale()) # 0.001
121
+ ```python
122
+ grams_bromine.to(Scale.milli) # <6238.0 milligram>
123
+ grams_bromine.to(Scale.kibi) # <0.006091796875 kibigram>
131
124
  ```
132
125
 
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
-
136
126
  ---
137
127
 
138
128
  ## Roadmap Highlights
139
129
 
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 |
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 |
146
136
 
147
137
  See full roadmap: [ROADMAP.md](./ROADMAP.md)
148
138
 
@@ -155,13 +145,13 @@ Ensure `nox` is installed.
155
145
  ```
156
146
  pip install -r requirements.txt
157
147
  ```
158
- Then run the full test suite (against all supported python versions) before committing:
148
+ Then run the full test suite (agains all supported python versions) before committing:
159
149
 
160
150
  ```bash
161
151
  nox -s test
162
152
  ```
163
153
  ---
164
154
 
165
- > "If it can be measured, it can be represented.
155
+ > If it can be measured, it can be represented.
166
156
  If it can be represented, it can be validated.
167
- If it can be validated, it can be trusted."
157
+ If it can be validated, it can be trusted.”
@@ -4,68 +4,61 @@
4
4
 
5
5
  ---
6
6
 
7
- ## 🪜 Current Version: **v0.3.5**
7
+ ## 🪜 Current Version: **v0.3.0**
8
8
 
9
9
  Stable baseline for:
10
- - `ucon.core` (`Dimension`, `Scale`, `Unit`, `UnitFactor`, `UnitProduct`)
11
- - `ucon.quantity` (`Number`, `Ratio`)
10
+ - `ucon.core` (`Number`, `Scale`, `Ratio`)
11
+ - `ucon.unit` (basic unit representation and composition)
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 (Complete)
17
+ ## 🚀 v0.3.x — Dimensional Algebra (In Progress)
18
18
 
19
19
  ### 🔹 Summary
20
- > Introduces dimensional algebra and establishes the Unit/Scale separation
21
- > that underpins all downstream work.
20
+ > Introduces `ucon.dimension` as the foundation for algebraic reasoning.
22
21
 
23
22
  ### ✅ Goals
24
- - [x] Implement `Vector` and `Dimension` classes
25
- - [x] Integrate dimensions into `Unit`
26
- - [x] Refactor `ucon.units` to use dimensional definitions
23
+ - [x] Implement `Vector` and `Dimension` classes
24
+ - [x] Integrate dimensions into `Unit`
25
+ - [x] Refactor `ucon.units` to use dimensional definitions
27
26
  - [x] Publish documentation for dimensional operations
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
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
32
32
  - [x] Add regression tests for prefix math (`kilo / milli → mega`, `2¹⁰ / 10³ → 1.024×`)
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
33
+ - [ ] Document Exponent/Scale relationship in developer guide
37
34
 
38
35
  ### 🧩 Outcomes
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
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`)
46
43
 
47
44
  ---
48
45
 
49
- ## ⚙️ v0.4.x — Conversion System Foundations (Up Next)
46
+ ## ⚙️ v0.4.x — Conversion System Foundations
50
47
 
51
48
  ### 🔹 Summary
52
49
  > Implements unified conversion engine for standard, linear, and affine conversions.
53
50
 
54
51
  ### ✅ Goals
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
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
63
57
 
64
58
  ### 🧩 Outcomes
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
59
+ - Unified conversion taxonomy
60
+ - Reversible, dimension-checked conversions
61
+ - Forms the basis for nonlinear and domain-specific conversion families
69
62
 
70
63
  ---
71
64
 
@@ -75,16 +68,16 @@ Stable baseline for:
75
68
  > Introduces an extensible registry system for custom units and aliases.
76
69
 
77
70
  ### ✅ Goals
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
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
83
76
 
84
77
  ### 🧩 Outcomes
85
- - Registry-based extensibility for domain-specific systems
86
- - Dynamic unit registration and discovery
87
- - Groundwork for plugin-style system extensions
78
+ - Registry-based extensibility for domain-specific systems
79
+ - Dynamic unit registration and discovery
80
+ - Groundwork for plugin-style system extensions
88
81
 
89
82
  ---
90
83
 
@@ -94,20 +87,20 @@ Stable baseline for:
94
87
  > Adds support for logarithmic, fractional, and other specialized dimensionless conversions.
95
88
 
96
89
  ### ✅ Goals
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
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
101
94
  - [ ] Introduce structured dimensionless unit family (`radian`, `percent`, `ppm`, `count`, etc.)
102
95
  - [ ] Define canonical dimensionless subtypes for angular, fractional, and count semantics
103
96
  - [ ] Ensure automatic collapse of equivalent units (`m/m → none`, `J/J → none`) via Ratio
104
97
 
105
98
  ### 🧩 Outcomes
106
- - Support for function-based (nonlinear) physical conversions
107
- - Unified algebraic framework across all conversion types
99
+ - Support for function-based (nonlinear) physical conversions
100
+ - Unified algebraic framework across all conversion types
108
101
  - Rich, semantically meaningful representation of dimensionless quantities
109
102
  - Enables acoustics (dB), geometry (rad, sr), statistics (probability), and fractional scales (%, ppm)
110
-
103
+
111
104
  ---
112
105
 
113
106
  ## 🧰 v0.7.x — Testing, Developer Experience, & API Polish
@@ -116,16 +109,16 @@ Stable baseline for:
116
109
  > Strengthens tests, developer ergonomics, and runtime feedback.
117
110
 
118
111
  ### ✅ Goals
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
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
124
117
 
125
118
  ### 🧩 Outcomes
126
- - Reliable, developer-friendly foundation
127
- - Consistent runtime behavior and output clarity
128
- - Prepares API for public documentation and 1.0 freeze
119
+ - Reliable, developer-friendly foundation
120
+ - Consistent runtime behavior and output clarity
121
+ - Prepares API for public documentation and 1.0 freeze
129
122
 
130
123
  ---
131
124
 
@@ -135,16 +128,16 @@ Stable baseline for:
135
128
  > Introduces seamless integration with **Pydantic v2**, enabling validation, serialization, and typed dimensional models.
136
129
 
137
130
  ### ✅ Goals
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)
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)
143
136
 
144
137
  ### 🧩 Outcomes
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
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
148
141
 
149
142
  ---
150
143
 
@@ -154,16 +147,16 @@ Stable baseline for:
154
147
  > Completes documentation, finalizes examples, and preps release candidates.
155
148
 
156
149
  ### ✅ Goals
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)
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)
162
155
 
163
156
  ### 🧩 Outcomes
164
- - Complete public-facing documentation
165
- - API frozen and versioned for stability
166
- - Ready for final testing and validation before 1.0
157
+ - Complete public-facing documentation
158
+ - API frozen and versioned for stability
159
+ - Ready for final testing and validation before 1.0
167
160
 
168
161
  ---
169
162
 
@@ -173,15 +166,15 @@ Stable baseline for:
173
166
  > First major release: a unified algebra for composable, type-safe, and semantically clear unit conversion.
174
167
 
175
168
  ### ✅ Goals
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
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
180
173
 
181
174
  ### 🧩 Outcomes
182
- - Stable, well-tested release
183
- - Fully type-safe and validated core
184
- - Production-ready for integration into scientific and engineering workflows
175
+ - Stable, well-tested release
176
+ - Fully type-safe and validated core
177
+ - Production-ready for integration into scientific and engineering workflows
185
178
 
186
179
  ---
187
180
 
@@ -199,24 +192,24 @@ Stable baseline for:
199
192
 
200
193
  ## 🗓️ Milestone Summary
201
194
 
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 |
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 |
212
205
 
213
206
  ---
214
207
 
215
208
  ### ✨ Guiding Principle
216
209
 
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."
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.”
220
213
 
221
214
  ---
222
215
 
@@ -227,4 +220,3 @@ Stable baseline for:
227
220
  class Config(BaseModel):
228
221
  length: NumberType[Dimension.length]
229
222
  time: NumberType[Dimension.time]
230
- ```
@@ -38,7 +38,7 @@ Design Philosophy
38
38
  """
39
39
  from ucon import units
40
40
  from ucon.algebra import Exponent
41
- from ucon.core import Dimension, Scale, Unit, UnitFactor, UnitProduct
41
+ from ucon.core import Dimension, Scale, Unit
42
42
  from ucon.quantity import Number, Ratio
43
43
 
44
44
 
@@ -49,7 +49,5 @@ __all__ = [
49
49
  'Ratio',
50
50
  'Scale',
51
51
  'Unit',
52
- 'UnitFactor',
53
- 'UnitProduct',
54
52
  'units',
55
53
  ]
@@ -240,7 +240,8 @@ 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):
243
+ if isinstance(other, Unit) and not isinstance(other, UnitProduct):
244
+ # Unit no longer has scale attribute - always safe to apply
244
245
  return UnitProduct({UnitFactor(unit=other, scale=self): 1})
245
246
 
246
247
  # --- Case 2: other cases are NOT handled here -----------------
@@ -342,6 +343,8 @@ class Unit:
342
343
  Unit * Unit -> UnitProduct
343
344
  Unit * UnitProduct -> UnitProduct
344
345
  """
346
+ from ucon.core import UnitProduct # local import to avoid circulars
347
+
345
348
  if isinstance(other, UnitProduct):
346
349
  # let UnitProduct handle merging
347
350
  return other.__rmul__(self)
@@ -353,13 +356,12 @@ class Unit:
353
356
 
354
357
  def __truediv__(self, other):
355
358
  """
356
- Unit / Unit or Unit / UnitProduct => UnitProduct
359
+ Unit / Unit:
360
+ - If same unit => dimensionless Unit()
361
+ - If denominator is dimensionless => self
362
+ - Else => UnitProduct
357
363
  """
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)
364
+ from ucon.core import UnitProduct # local import
363
365
 
364
366
  if not isinstance(other, Unit):
365
367
  return NotImplemented
@@ -383,13 +385,13 @@ class Unit:
383
385
  """
384
386
  Unit ** n => UnitProduct with that exponent.
385
387
  """
388
+ from ucon.core import UnitProduct # local import
389
+
386
390
  return UnitProduct({self: power})
387
391
 
388
392
  # ----------------- equality & hashing -----------------
389
393
 
390
394
  def __eq__(self, other):
391
- if isinstance(other, UnitProduct):
392
- return other.__eq__(self)
393
395
  if not isinstance(other, Unit):
394
396
  return NotImplemented
395
397
  return (
@@ -496,7 +498,7 @@ class UnitFactor:
496
498
  return NotImplemented
497
499
 
498
500
 
499
- class UnitProduct:
501
+ class UnitProduct(Unit):
500
502
  """
501
503
  Represents a product or quotient of Units.
502
504
 
@@ -525,7 +527,8 @@ class UnitProduct:
525
527
  encountered UnitFactor (keeps user-intent scale).
526
528
  """
527
529
 
528
- self.name = ""
530
+ # UnitProduct always starts dimensionless
531
+ super().__init__(name="", dimension=Dimension.none)
529
532
  self.aliases = ()
530
533
 
531
534
  merged: dict[UnitFactor, float] = {}
@@ -703,16 +706,6 @@ class UnitProduct:
703
706
  result *= factor.scale.value.evaluated ** power
704
707
  return result
705
708
 
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
-
716
709
  # ------------- Algebra ---------------------------------------------------
717
710
 
718
711
  def __mul__(self, other):
@@ -806,7 +799,7 @@ class UnitProduct:
806
799
  return f"<{self.__class__.__name__} {self.shorthand}>"
807
800
 
808
801
  def __eq__(self, other):
809
- if isinstance(other, Unit):
802
+ if isinstance(other, Unit) and not isinstance(other, UnitProduct):
810
803
  # Only equal to a plain Unit if we have exactly that unit^1
811
804
  # Here, the tuple comparison will invoke UnitFactor.__eq__(Unit)
812
805
  # 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: Union[Unit, UnitProduct] = units.none
46
+ unit: Unit = 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.5
3
+ Version: 0.3.5rc1
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 via `UnitFactor` and `UnitProduct`
59
- - Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, etc.)
58
+ - Scale-aware arithmetic and conversions
59
+ - Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, ect.)
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,22 +70,20 @@ 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.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). |
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²`). |
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
- | **`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.). |
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.). | |
83
81
 
84
82
  ### Under the Hood
85
83
 
86
84
  `ucon` models unit math through a hierarchy where each layer builds on the last:
87
85
 
88
- <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/f24134c362829dc72e7dff18bfcaa24b9be01b54/ucon.data-model_v035.png align="center" alt="ucon Data Model" width=600/>
86
+ <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/0c704737a52b9e4a87cda5c839e9aa40f7e5bb48/ucon.data-model_v035.png align="center" alt="ucon Data Model" width=600/>
89
87
 
90
88
  ## Why `ucon`?
91
89
 
@@ -93,22 +91,24 @@ Python already has mature libraries for handling units and physical quantities
93
91
 
94
92
  | Library | Focus | Limitation |
95
93
  | --------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
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. |
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. |
97
95
  | **SymPy** | Symbolic algebra and simplification of unit expressions | Excellent for symbolic reasoning, but not designed for runtime validation, conversion, or serialization. |
98
96
  | **Unum** | Unit-aware arithmetic and unit propagation | Tracks units through arithmetic but lacks explicit dimensional algebra, conversion taxonomy, or runtime introspection. |
99
97
 
100
98
  Together, these tools can _use_ units, but none can explicitly represent and verify the relationships between units and dimensions.
101
99
 
102
- That's the gap `ucon` fills.
100
+ Thats the gap `ucon` fills.
103
101
 
104
102
  It treats units, dimensions, and scales as first-class objects and builds a composable algebra around them.
105
103
  This allows you to:
106
104
  - Represent dimensional meaning explicitly (`Dimension`, `Vector`);
107
105
  - 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 doesn't 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 doesnt just track names: it enforces physics:
112
112
  ```python
113
113
  from ucon import Number, units
114
114
 
@@ -136,8 +136,7 @@ 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
140
- from ucon.quantity import Ratio
139
+ from ucon import Number, Scale, Units, Ratio
141
140
 
142
141
  # Two milliliters of bromine
143
142
  mL = Scale.milli * units.liter
@@ -150,36 +149,27 @@ bromine_density = Ratio(
150
149
  )
151
150
 
152
151
  # Multiply to find mass
153
- grams_bromine = bromine_density.evaluate() * two_mL_bromine
154
- print(grams_bromine) # <6.238 g>
152
+ grams_bromine = two_mL_bromine * bromine_density
153
+ print(grams_bromine) # <6.238 gram>
155
154
  ```
156
155
 
157
- Scale prefixes compose naturally:
158
- ```python
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'
156
+ Scale conversion is automatic and precise:
164
157
 
165
- # Scale arithmetic
166
- print(km.fold_scale()) # 1000.0
167
- print(mg.fold_scale()) # 0.001
158
+ ```python
159
+ grams_bromine.to(Scale.milli) # <6238.0 milligram>
160
+ grams_bromine.to(Scale.kibi) # <0.006091796875 kibigram>
168
161
  ```
169
162
 
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
-
173
163
  ---
174
164
 
175
165
  ## Roadmap Highlights
176
166
 
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 |
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 |
183
173
 
184
174
  See full roadmap: [ROADMAP.md](./ROADMAP.md)
185
175
 
@@ -192,13 +182,13 @@ Ensure `nox` is installed.
192
182
  ```
193
183
  pip install -r requirements.txt
194
184
  ```
195
- Then run the full test suite (against all supported python versions) before committing:
185
+ Then run the full test suite (agains all supported python versions) before committing:
196
186
 
197
187
  ```bash
198
188
  nox -s test
199
189
  ```
200
190
  ---
201
191
 
202
- > "If it can be measured, it can be represented.
192
+ > If it can be measured, it can be represented.
203
193
  If it can be represented, it can be validated.
204
- If it can be validated, it can be trusted."
194
+ 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
File without changes
File without changes