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.
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/PKG-INFO +42 -32
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/README.md +41 -31
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ROADMAP.md +97 -89
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon/core.py +22 -15
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon/quantity.py +1 -1
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon.egg-info/PKG-INFO +42 -32
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/.github/workflows/publish.yaml +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/.github/workflows/tests.yaml +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/.gitignore +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/LICENSE +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/NOTICE +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/decisions/composable-unit-algebra.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/decisions/composite-units.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/decisions/unit-algebra-naming.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/decisions/unity-distance-metric-for-nearest-scale.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/explainers/type-operation-matrix.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/explainers/why-algebraic-closure-matters.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/explainers/why-type-safety-matters.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/proposals/interface-unifying-the-value-layer.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/proposals/project_unified-algebraic-core.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/proposals/support-for-fractional-exponents.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/docs/proposals/unified-unit-presentation.md +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/noxfile.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/requirements.txt +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/setup.cfg +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/setup.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/tests/__init__.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/tests/ucon/__init__.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/tests/ucon/test_algebra.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/tests/ucon/test_core.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/tests/ucon/test_quantity.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/tests/ucon/test_units.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon/__init__.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon/algebra.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon/units.py +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon.egg-info/SOURCES.txt +0 -0
- {ucon-0.3.5rc1 → ucon-0.3.5rc2}/ucon.egg-info/dependency_links.txt +0 -0
- {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.
|
|
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
|
|
59
|
-
- Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`,
|
|
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.
|
|
74
|
-
| **`
|
|
75
|
-
| **`
|
|
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
|
-
| **`
|
|
78
|
-
| **`
|
|
79
|
-
| **`
|
|
80
|
-
| **`
|
|
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/
|
|
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 isn
|
|
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
|
-
That
|
|
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 doesn
|
|
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,
|
|
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 =
|
|
153
|
-
print(grams_bromine) # <6.238
|
|
153
|
+
grams_bromine = bromine_density.evaluate() * two_mL_bromine
|
|
154
|
+
print(grams_bromine) # <6.238 g>
|
|
154
155
|
```
|
|
155
156
|
|
|
156
|
-
Scale
|
|
157
|
-
|
|
157
|
+
Scale prefixes compose naturally:
|
|
158
158
|
```python
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
|
170
|
-
| [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System |
|
|
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 (
|
|
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
|
-
>
|
|
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
|
|
22
|
-
- Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`,
|
|
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.
|
|
37
|
-
| **`
|
|
38
|
-
| **`
|
|
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
|
-
| **`
|
|
41
|
-
| **`
|
|
42
|
-
| **`
|
|
43
|
-
| **`
|
|
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/
|
|
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 isn
|
|
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
|
-
That
|
|
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 doesn
|
|
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,
|
|
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 =
|
|
116
|
-
print(grams_bromine) # <6.238
|
|
116
|
+
grams_bromine = bromine_density.evaluate() * two_mL_bromine
|
|
117
|
+
print(grams_bromine) # <6.238 g>
|
|
117
118
|
```
|
|
118
119
|
|
|
119
|
-
Scale
|
|
120
|
-
|
|
120
|
+
Scale prefixes compose naturally:
|
|
121
121
|
```python
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
|
133
|
-
| [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System |
|
|
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 (
|
|
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
|
-
>
|
|
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.
|
|
7
|
+
## 🪜 Current Version: **v0.3.5**
|
|
8
8
|
|
|
9
9
|
Stable baseline for:
|
|
10
|
-
- `ucon.core` (`
|
|
11
|
-
- `ucon.
|
|
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
|
-
##
|
|
17
|
+
## ✅ v0.3.x — Dimensional Algebra (Complete)
|
|
18
18
|
|
|
19
19
|
### 🔹 Summary
|
|
20
|
-
> Introduces
|
|
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
|
-
- [
|
|
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
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
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 `
|
|
53
|
-
- [ ] Add support for `standard`, `linear`, and `affine` conversion types
|
|
54
|
-
- [ ] Implement
|
|
55
|
-
- [ ]
|
|
56
|
-
- [ ]
|
|
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
|
-
-
|
|
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
|
|
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 |
|
|
196
|
-
|
|
197
|
-
| **0.3.
|
|
198
|
-
| **0.4.0** | Conversion Engine |
|
|
199
|
-
| **0.5.0** | Unit Systems & Registries | Extensible registry system |
|
|
200
|
-
| **0.6.0** | Nonlinear Conversions | Logarithmic / exponential families |
|
|
201
|
-
| **0.7.0** | Testing & API Polish | Coverage, ergonomics, stability |
|
|
202
|
-
| **0.8.0** |
|
|
203
|
-
| **0.9.x** | Documentation & RC | Freeze API, publish docs, RCs |
|
|
204
|
-
| **1.0.0** | Stable Release | Publish production-ready core |
|
|
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
|
-
>
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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)
|
|
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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ucon
|
|
3
|
-
Version: 0.3.
|
|
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
|
|
59
|
-
- Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`,
|
|
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.
|
|
74
|
-
| **`
|
|
75
|
-
| **`
|
|
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
|
-
| **`
|
|
78
|
-
| **`
|
|
79
|
-
| **`
|
|
80
|
-
| **`
|
|
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/
|
|
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 isn
|
|
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
|
-
That
|
|
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 doesn
|
|
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,
|
|
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 =
|
|
153
|
-
print(grams_bromine) # <6.238
|
|
153
|
+
grams_bromine = bromine_density.evaluate() * two_mL_bromine
|
|
154
|
+
print(grams_bromine) # <6.238 g>
|
|
154
155
|
```
|
|
155
156
|
|
|
156
|
-
Scale
|
|
157
|
-
|
|
157
|
+
Scale prefixes compose naturally:
|
|
158
158
|
```python
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
|
170
|
-
| [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System |
|
|
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 (
|
|
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
|
-
>
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|