ucon 0.3.5rc1__py3-none-any.whl → 0.3.5rc2__py3-none-any.whl
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/core.py +22 -15
- ucon/quantity.py +1 -1
- {ucon-0.3.5rc1.dist-info → ucon-0.3.5rc2.dist-info}/METADATA +42 -32
- {ucon-0.3.5rc1.dist-info → ucon-0.3.5rc2.dist-info}/RECORD +8 -8
- {ucon-0.3.5rc1.dist-info → ucon-0.3.5rc2.dist-info}/WHEEL +0 -0
- {ucon-0.3.5rc1.dist-info → ucon-0.3.5rc2.dist-info}/licenses/LICENSE +0 -0
- {ucon-0.3.5rc1.dist-info → ucon-0.3.5rc2.dist-info}/licenses/NOTICE +0 -0
- {ucon-0.3.5rc1.dist-info → ucon-0.3.5rc2.dist-info}/top_level.txt +0 -0
ucon/core.py
CHANGED
|
@@ -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.
|
ucon/quantity.py
CHANGED
|
@@ -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."
|
|
@@ -5,12 +5,12 @@ tests/ucon/test_quantity.py,sha256=YLV78_t4AkZcJeEGu-lBvIXNLhTV_MLkTbIKV67rs4Y,1
|
|
|
5
5
|
tests/ucon/test_units.py,sha256=SILymDtDNDyxEhkYQubrfkakKCMexwEwjyHfhrkDrMI,869
|
|
6
6
|
ucon/__init__.py,sha256=vJ6A2BU6s2_3vW4fExhn3VEjbn8yrvgF2hYBfGrKPrY,1865
|
|
7
7
|
ucon/algebra.py,sha256=ZVF8B2kUOeSN20R-lTHJNJDxe_Zv7s8kJ70F2JOSYxk,7262
|
|
8
|
-
ucon/core.py,sha256=
|
|
9
|
-
ucon/quantity.py,sha256=
|
|
8
|
+
ucon/core.py,sha256=Dx8HNwjGpF-RFlO_2Cz1BZikRFeLcYlMy7hscw106vo,29825
|
|
9
|
+
ucon/quantity.py,sha256=skXge9RU-5dW1ULCV6kiE4jk-rMVzXpBa4hV4mVr_Eo,7310
|
|
10
10
|
ucon/units.py,sha256=-CShNMLr9t7f3pyYsfmZv3wMCZU4lEnoe8r_9YQWjxA,3783
|
|
11
|
-
ucon-0.3.
|
|
12
|
-
ucon-0.3.
|
|
13
|
-
ucon-0.3.
|
|
14
|
-
ucon-0.3.
|
|
15
|
-
ucon-0.3.
|
|
16
|
-
ucon-0.3.
|
|
11
|
+
ucon-0.3.5rc2.dist-info/licenses/LICENSE,sha256=LtimSYBSw1L_X6n1-VEdZRdwuROzPumrMUNX21asFuI,11356
|
|
12
|
+
ucon-0.3.5rc2.dist-info/licenses/NOTICE,sha256=bh4fBOItio3kM4hSNYhqfFpcaAvOoixjD7Du8im-sYA,1079
|
|
13
|
+
ucon-0.3.5rc2.dist-info/METADATA,sha256=I6f-KMRaLq9rm4q5OxcFLlK5bKOPvKlWMZHnNpTL_d8,11432
|
|
14
|
+
ucon-0.3.5rc2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
15
|
+
ucon-0.3.5rc2.dist-info/top_level.txt,sha256=zZYRJiQrVUtN32ziJD2YEq7ClSvDmVYHYy5ArRAZGxI,11
|
|
16
|
+
ucon-0.3.5rc2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|