ucon 0.3.1__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/unit.py ADDED
@@ -0,0 +1,92 @@
1
+ """
2
+ ucon.unit
3
+ ==========
4
+
5
+ Defines the **Unit** abstraction — the symbolic and algebraic representation of
6
+ a measurable quantity associated with a :class:`ucon.dimension.Dimension`.
7
+
8
+ A :class:`Unit` pairs a human-readable name and aliases with its underlying
9
+ dimension.
10
+
11
+ Units are composable:
12
+
13
+ >>> from ucon import units
14
+ >>> units.meter / units.second
15
+ <velocity | (m/s)>
16
+
17
+ They can be multiplied or divided to form compound units, and their dimensional
18
+ relationships are preserved algebraically.
19
+ """
20
+ from ucon.dimension import Dimension
21
+
22
+
23
+ class Unit:
24
+ """
25
+ Represents a **unit of measure** associated with a :class:`Dimension`.
26
+
27
+ Parameters
28
+ ----------
29
+ *aliases : str
30
+ Optional shorthand symbols (e.g., "m", "sec").
31
+ name : str
32
+ Canonical name of the unit (e.g., "meter").
33
+ dimension : Dimension
34
+ The physical dimension this unit represents.
35
+
36
+ Notes
37
+ -----
38
+ Units participate in algebraic operations that produce new compound units:
39
+
40
+ >>> density = units.gram / units.liter
41
+ >>> density.dimension
42
+ <Dimension.density: Vector(T=0, L=-3, M=1, I=0, Θ=0, J=0, N=0)>
43
+
44
+ The combination rules follow the same algebra as :class:`Dimension`.
45
+ """
46
+ def __init__(self, *aliases: str, name: str = '', dimension: Dimension = Dimension.none):
47
+ self.dimension = dimension
48
+ self.name = name
49
+ self.aliases = aliases
50
+ self.shorthand = aliases[0] if aliases else self.name
51
+
52
+ def __repr__(self):
53
+ addendum = f' | {self.name}' if self.name else ''
54
+ return f'<{self.dimension.name}{addendum}>'
55
+
56
+ # TODO -- limit `operator` param choices
57
+ def generate_name(self, unit: 'Unit', operator: str):
58
+ if (self.dimension is Dimension.none) and not (unit.dimension is Dimension.none):
59
+ return unit.name
60
+ if not (self.dimension is Dimension.none) and (unit.dimension is Dimension.none):
61
+ return self.name
62
+
63
+ if not self.shorthand and not unit.shorthand:
64
+ name = ''
65
+ elif self.shorthand and not unit.shorthand:
66
+ name = f'({self.shorthand}{operator}?)'
67
+ elif not self.shorthand and unit.shorthand:
68
+ name = f'(?{operator}{unit.shorthand})'
69
+ else:
70
+ name = f'({self.shorthand}{operator}{unit.shorthand})'
71
+ return name
72
+
73
+ def __truediv__(self, unit: 'Unit') -> 'Unit':
74
+ # TODO -- define __eq__ for simplification, here
75
+ if (self.name == unit.name) and (self.dimension == unit.dimension):
76
+ return Unit()
77
+
78
+ if (unit.dimension is Dimension.none):
79
+ return self
80
+
81
+ return Unit(name=self.generate_name(unit, '/'), dimension=self.dimension / unit.dimension)
82
+
83
+ def __mul__(self, unit: 'Unit') -> 'Unit':
84
+ return Unit(name=self.generate_name(unit, '*'), dimension=self.dimension * unit.dimension)
85
+
86
+ def __eq__(self, unit: 'Unit') -> bool:
87
+ if not isinstance(unit, Unit):
88
+ raise TypeError(f'Cannot compare Unit to non-Unit type: {type(unit)}')
89
+ return (self.name == unit.name) and (self.dimension == unit.dimension)
90
+
91
+ def __hash__(self) -> int:
92
+ return hash(tuple([self.name, self.dimension,]))
ucon/units.py ADDED
@@ -0,0 +1,84 @@
1
+ """
2
+ ucon.units
3
+ ===========
4
+
5
+ Defines and registers the canonical **unit set** for the *ucon* library.
6
+
7
+ This module exports the standard SI base and derived units, along with a few
8
+ common non-SI units. Each unit is a pre-constructed :class:`ucon.unit.Unit`
9
+ object associated with a :class:`ucon.dimension.Dimension`.
10
+
11
+ Example
12
+ -------
13
+ >>> from ucon import units
14
+ >>> units.meter.dimension
15
+ <Dimension.length>
16
+ >>> units.newton.dimension
17
+ <Dimension.force>
18
+
19
+ Includes convenience utilities such as :func:`have(name)` for unit membership
20
+ checks.
21
+
22
+ Notes
23
+ -----
24
+ The design allows for future extensibility: users can register their own units,
25
+ systems, or aliases dynamically, without modifying the core definitions.
26
+ """
27
+ from ucon.dimension import Dimension
28
+ from ucon.unit import Unit
29
+
30
+
31
+ none = Unit()
32
+
33
+
34
+ # -- International System of Units (SI) --------------------------------
35
+ ampere = Unit('I', 'amp', name='ampere', dimension=Dimension.current)
36
+ becquerel = Unit('Bq', name='becquerel', dimension=Dimension.frequency)
37
+ celsius = Unit('°C', name='celsius', dimension=Dimension.temperature)
38
+ coulomb = Unit('C', name='coulomb', dimension=Dimension.charge)
39
+ farad = Unit('F', name='farad', dimension=Dimension.capacitance)
40
+ gram = Unit('g', 'G', name='gram', dimension=Dimension.mass)
41
+ gray = Unit('Gy', name='gray', dimension=Dimension.energy)
42
+ henry = Unit('H', name='henry', dimension=Dimension.inductance)
43
+ hertz = Unit('Hz', name='hertz', dimension=Dimension.frequency)
44
+ hour = Unit('h', 'H', name='hour', dimension=Dimension.time)
45
+ joule = Unit('J', name='joule', dimension=Dimension.energy)
46
+ joule_per_kelvin = Unit('J/K', name='joule_per_kelvin', dimension=Dimension.entropy)
47
+ kelvin = Unit('K', name='kelvin', dimension=Dimension.temperature)
48
+ liter = Unit('L', 'l', name='liter', dimension=Dimension.volume)
49
+ lumen = Unit('lm', name='lumen', dimension=Dimension.luminous_intensity)
50
+ lux = Unit('lx', name='lux', dimension=Dimension.illuminance)
51
+ meter = Unit('m', 'M', name='meter', dimension=Dimension.length)
52
+ mole = Unit('mol', 'n', name='mole', dimension=Dimension.amount_of_substance)
53
+ newton = Unit('N', name='newton', dimension=Dimension.force)
54
+ ohm = Unit('Ω', name='ohm', dimension=Dimension.resistance)
55
+ pascal = Unit('Pa', name='pascal', dimension=Dimension.pressure)
56
+ radian = Unit('rad', name='radian', dimension=Dimension.none)
57
+ second = Unit('s', 'sec', name='second', dimension=Dimension.time)
58
+ sievert = Unit('Sv', name='sievert', dimension=Dimension.energy)
59
+ siemens = Unit('S', name='siemens', dimension=Dimension.conductance)
60
+ steradian = Unit('sr', name='steradian', dimension=Dimension.none)
61
+ tesla = Unit('T', name='tesla', dimension=Dimension.magnetic_flux_density)
62
+ volt = Unit('V', name='volt', dimension=Dimension.voltage)
63
+ watt = Unit('W', name='watt', dimension=Dimension.power)
64
+ webers = Unit('Wb', name='weber', dimension=Dimension.magnetic_flux)
65
+ webers_per_meter = Unit('Wb/m', name='webers_per_meter', dimension=Dimension.magnetic_permeability)
66
+ # ----------------------------------------------------------------------
67
+
68
+
69
+ def have(name: str) -> bool:
70
+ assert name, "Must provide a unit name to check"
71
+ assert isinstance(name, str), "Unit name must be a string"
72
+ target = name.lower()
73
+ for attr, val in globals().items():
74
+ if isinstance(val, Unit):
75
+ # match the variable name (e.g., "none", "meter")
76
+ if attr.lower() == target:
77
+ return True
78
+ # match the declared unit name
79
+ if val.name and val.name.lower() == target:
80
+ return True
81
+ # match any alias
82
+ if any((alias or "").lower() == target for alias in getattr(val, "aliases", ())):
83
+ return True
84
+ return False
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: ucon
3
+ Version: 0.3.1
4
+ Summary: a tool for dimensional analysis: a "Unit CONverter"
5
+ Home-page: https://github.com/withtwoemms/ucon
6
+ Author: Emmanuel I. Obi
7
+ Maintainer: Emmanuel I. Obi
8
+ Maintainer-email: withtwoemms@gmail.com
9
+ License: MIT
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Software Development :: Build Tools
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Dynamic: author
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: description-content-type
30
+ Dynamic: home-page
31
+ Dynamic: license
32
+ Dynamic: license-file
33
+ Dynamic: maintainer
34
+ Dynamic: maintainer-email
35
+ Dynamic: summary
36
+
37
+ <img src="https://gist.githubusercontent.com/withtwoemms/0cb9e6bc8df08f326771a89eeb790f8e/raw/dde6c7d3b8a7d79eb1006ace03fb834e044cdebc/ucon-logo.png" align="left" width="200" />
38
+
39
+ # ucon
40
+
41
+ > Pronounced: _yoo · cahn_
42
+
43
+ [![tests](https://github.com/withtwoemms/ucon/workflows/tests/badge.svg)](https://github.com/withtwoemms/ucon/actions?query=workflow%3Atests)
44
+ [![codecov](https://codecov.io/gh/withtwoemms/ucon/graph/badge.svg?token=BNONQTRJWG)](https://codecov.io/gh/withtwoemms/ucon)
45
+ [![publish](https://github.com/withtwoemms/ucon/workflows/publish/badge.svg)](https://github.com/withtwoemms/ucon/actions?query=workflow%3Apublish)
46
+
47
+ > A lightweight, **unit-aware computation library** for Python — built on first-principles.
48
+
49
+ ---
50
+
51
+ ## Overview
52
+
53
+ `ucon` helps Python understand the *physical meaning* of your numbers.
54
+ It combines **units**, **scales**, and **dimensions** into a composable algebra that supports:
55
+
56
+ - Dimensional analysis through `Number` and `Ratio`
57
+ - Scale-aware arithmetic and conversions
58
+ - Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, ect.)
59
+ - A clean foundation for physics, chemistry, data modeling, and beyond
60
+
61
+ Think of it as **`decimal.Decimal` for the physical world** — precise, predictable, and type-safe.
62
+
63
+ ## Introduction
64
+
65
+ The crux of this tiny library is to provide abstractions that simplify the answering of questions like:
66
+
67
+ > _"If given two milliliters of bromine (liquid Br<sub>2</sub>), how many grams of bromine does one have?"_
68
+
69
+ 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:
70
+ | Type | Defined In | Purpose | Typical Use Cases |
71
+ | ----------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
72
+ | **`Vector`** | `ucon.dimension` | 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). |
73
+ | **`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). |
74
+ | **`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²`). |
75
+ | **`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). |
76
+ | **`Exponent`** | `ucon.core` | Represents base-power pairs (e.g., 10³, 2¹⁰) used by `Scale`. | Performing arithmetic on powers and bases; normalizing scales across conversions. |
77
+ | **`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. |
78
+ | **`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). |
79
+ | **`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.). | |
80
+
81
+ ### Under the Hood
82
+
83
+ `ucon` models unit math through a hierarchy where each layer builds on the last:
84
+
85
+ <img src=https://gist.githubusercontent.com/withtwoemms/429d2ca1f979865aa80a2658bf9efa32/raw/f3518d37445301950026fc9ffd1bd062768005fe/ucon.data-model.png align="center" alt="ucon Data Model" width=600/>
86
+
87
+ ## Why `ucon`?
88
+
89
+ Python already has mature libraries for handling units and physical quantities — Pint, SymPy, and Unum — each solving part of the same problem from different angles:
90
+
91
+ | Library | Focus | Limitation |
92
+ | --------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
93
+ | **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
+ | **SymPy** | Symbolic algebra and simplification of unit expressions | Excellent for symbolic reasoning, but not designed for runtime validation, conversion, or serialization. |
95
+ | **Unum** | Unit-aware arithmetic and unit propagation | Tracks units through arithmetic but lacks explicit dimensional algebra, conversion taxonomy, or runtime introspection. |
96
+
97
+ Together, these tools can _use_ units, but none can explicitly represent and verify the relationships between units and dimensions.
98
+
99
+ That’s the gap `ucon` fills.
100
+
101
+ It treats units, dimensions, and scales as first-class objects and builds a composable algebra around them.
102
+ This allows you to:
103
+ - Represent dimensional meaning explicitly (`Dimension`, `Vector`);
104
+ - Compose and compute with type-safe, introspectable quantities (`Unit`, `Number`);
105
+ - Perform reversible, declarative conversions (standard, linear, affine, nonlinear);
106
+ - Serialize and validate measurements with Pydantic integration;
107
+ - Extend the system with custom unit registries and conversion families.
108
+
109
+ Where Pint, Unum, and SymPy focus on _how_ to compute with units,
110
+ `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
+ ```python
112
+ from ucon import Number, units
113
+
114
+ length = Number(quantity=5, unit=units.meter)
115
+ time = Number(quantity=2, unit=units.second)
116
+
117
+ speed = length / time # ✅ valid: L / T = velocity
118
+ invalid = length + time # ❌ raises: incompatible dimensions
119
+ ```
120
+
121
+ ## Setup
122
+
123
+ Simple:
124
+ ```bash
125
+ pip install ucon
126
+ ```
127
+
128
+ ## Usage
129
+
130
+ This sort of dimensional analysis:
131
+ ```
132
+ 2 mL bromine | 3.119 g bromine
133
+ --------------x----------------- #=> 6.238 g bromine
134
+ 1 | 1 mL bromine
135
+ ```
136
+ becomes straightforward when you define a measurement:
137
+ ```python
138
+ from ucon import Number, Scale, Units, Ratio
139
+
140
+ # Two milliliters of bromine
141
+ two_mL_bromine = Number(unit=Units.liter, scale=Scale.milli, quantity=2)
142
+
143
+ # Density of bromine: 3.119 g/mL
144
+ bromine_density = Ratio(
145
+ numerator=Number(unit=Units.gram, quantity=3.119),
146
+ denominator=Number(unit=Units.liter, scale=Scale.milli),
147
+ )
148
+
149
+ # Multiply to find mass
150
+ grams_bromine = two_mL_bromine * bromine_density
151
+ print(grams_bromine) # <6.238 gram>
152
+ ```
153
+
154
+ Scale conversion is automatic and precise:
155
+
156
+ ```python
157
+ grams_bromine.to(Scale.milli) # <6238.0 milligram>
158
+ grams_bromine.to(Scale.kibi) # <0.006091796875 kibigram>
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Roadmap Highlights
164
+
165
+ | Version | Theme | Focus |
166
+ |----------|-------|--------|
167
+ | [**0.3.x**](https://github.com/withtwoemms/ucon/milestone/1) | Primitive Type Refinement | Unified algebraic foundation |
168
+ | [**0.4.x**](https://github.com/withtwoemms/ucon/milestone/2) | Conversion System | Linear & affine conversions |
169
+ | [**0.6.x**](https://github.com/withtwoemms/ucon/milestone/4) | Nonlinear / Specialized Units | Decibel, Percent, pH |
170
+ | [**0.8.x**](https://github.com/withtwoemms/ucon/milestone/6) | Pydantic Integration | Type-safe quantity validation |
171
+
172
+ See full roadmap: [ROADMAP.md](./ROADMAP.md)
173
+
174
+ ---
175
+
176
+ ## Contributing
177
+
178
+ Contributions, issues, and pull requests are welcome!
179
+ Ensure `nox` is installed.
180
+ ```
181
+ pip install -r requirements.txt
182
+ ```
183
+ Then run the full test suite (agains all supported python versions) before committing:
184
+
185
+ ```bash
186
+ nox -s test
187
+ ```
188
+ ---
189
+
190
+ > “If it can be measured, it can be represented.
191
+ If it can be represented, it can be validated.
192
+ If it can be validated, it can be trusted.”
@@ -0,0 +1,15 @@
1
+ tests/ucon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ tests/ucon/test_core.py,sha256=fD4c6K35RxCyaR5pHqBSr8DEHTqmMvYX9ZuVS6JdSwo,16408
3
+ tests/ucon/test_dimension.py,sha256=JyA9ySFvohs2l6oK77ehCQ7QvvFVqB_9t0iC7CUErjw,7296
4
+ tests/ucon/test_unit.py,sha256=vEPOeSxFBqcRBAUczCN9KPo_dTmLk4LQExPSt6UGVa4,5712
5
+ tests/ucon/test_units.py,sha256=248JZbo8RVvG_q3T0IhKG43vxM4F_2Xgf4_RjGZNsFM,704
6
+ ucon/__init__.py,sha256=ZWWLodIiG17OgCfoAm532wpwmJzdRXlUGX3w6OBxFeQ,1743
7
+ ucon/core.py,sha256=DRymayBlXfVZMSHIt_Hi-xe8GPs9WYHOCCJ1QDaKn7Q,10643
8
+ ucon/dimension.py,sha256=uUP05bPE8r15oFeD36DrclNIfBsugV7uFhvtJRYy4qI,6598
9
+ ucon/unit.py,sha256=KxOBcQNxciljGskhZCfktLhRF5u-rWgrTg565Flo3eI,3213
10
+ ucon/units.py,sha256=e1j7skYMghlMZi7l94EAgxq4_lNRDC7FcSooJoE_U50,3689
11
+ ucon-0.3.1.dist-info/licenses/LICENSE,sha256=-Djjiq2wM8Cc6fzTsdMbr_T2_uaX6Yorxcemr3GGkqc,1072
12
+ ucon-0.3.1.dist-info/METADATA,sha256=kchGc0Q4C3RSbQdT1WxGlFMEsRqp1ZNnptzy1VF4SQo,10603
13
+ ucon-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ ucon-0.3.1.dist-info/top_level.txt,sha256=zZYRJiQrVUtN32ziJD2YEq7ClSvDmVYHYy5ArRAZGxI,11
15
+ ucon-0.3.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Emmanuel I. Obi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ tests
2
+ ucon