ucon 0.5.1__py3-none-any.whl → 0.5.2__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.
- tests/ucon/test_basis_transform.py +521 -0
- tests/ucon/test_graph_basis_transform.py +263 -0
- tests/ucon/test_rebased_unit.py +184 -0
- tests/ucon/test_unit_system.py +174 -0
- tests/ucon/test_vector_fraction.py +185 -0
- ucon/__init__.py +20 -2
- ucon/algebra.py +36 -14
- ucon/core.py +407 -0
- ucon/graph.py +167 -10
- ucon/units.py +28 -1
- {ucon-0.5.1.dist-info → ucon-0.5.2.dist-info}/METADATA +84 -3
- {ucon-0.5.1.dist-info → ucon-0.5.2.dist-info}/RECORD +16 -11
- {ucon-0.5.1.dist-info → ucon-0.5.2.dist-info}/WHEEL +0 -0
- {ucon-0.5.1.dist-info → ucon-0.5.2.dist-info}/licenses/LICENSE +0 -0
- {ucon-0.5.1.dist-info → ucon-0.5.2.dist-info}/licenses/NOTICE +0 -0
- {ucon-0.5.1.dist-info → ucon-0.5.2.dist-info}/top_level.txt +0 -0
ucon/units.py
CHANGED
|
@@ -28,7 +28,7 @@ Notes
|
|
|
28
28
|
The design allows for future extensibility: users can register their own units,
|
|
29
29
|
systems, or aliases dynamically, without modifying the core definitions.
|
|
30
30
|
"""
|
|
31
|
-
from ucon.core import Dimension, Unit
|
|
31
|
+
from ucon.core import Dimension, Unit, UnitSystem
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
none = Unit()
|
|
@@ -49,6 +49,7 @@ joule_per_kelvin = Unit(name='joule_per_kelvin', dimension=Dimension.entropy, al
|
|
|
49
49
|
kelvin = Unit(name='kelvin', dimension=Dimension.temperature, aliases=('K',))
|
|
50
50
|
kilogram = Unit(name='kilogram', dimension=Dimension.mass, aliases=('kg',))
|
|
51
51
|
liter = Unit(name='liter', dimension=Dimension.volume, aliases=('L', 'l'))
|
|
52
|
+
candela = Unit(name='candela', dimension=Dimension.luminous_intensity, aliases=('cd',))
|
|
52
53
|
lumen = Unit(name='lumen', dimension=Dimension.luminous_intensity, aliases=('lm',))
|
|
53
54
|
lux = Unit(name='lux', dimension=Dimension.illuminance, aliases=('lx',))
|
|
54
55
|
meter = Unit(name='meter', dimension=Dimension.length, aliases=('m',))
|
|
@@ -141,6 +142,32 @@ basis_point = Unit(name='basis_point', dimension=Dimension.ratio, aliases=('bp',
|
|
|
141
142
|
webers = weber
|
|
142
143
|
|
|
143
144
|
|
|
145
|
+
# -- Predefined Unit Systems -----------------------------------------------
|
|
146
|
+
si = UnitSystem(
|
|
147
|
+
name="SI",
|
|
148
|
+
bases={
|
|
149
|
+
Dimension.length: meter,
|
|
150
|
+
Dimension.mass: kilogram,
|
|
151
|
+
Dimension.time: second,
|
|
152
|
+
Dimension.temperature: kelvin,
|
|
153
|
+
Dimension.current: ampere,
|
|
154
|
+
Dimension.amount_of_substance: mole,
|
|
155
|
+
Dimension.luminous_intensity: candela,
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
imperial = UnitSystem(
|
|
160
|
+
name="Imperial",
|
|
161
|
+
bases={
|
|
162
|
+
Dimension.length: foot,
|
|
163
|
+
Dimension.mass: pound,
|
|
164
|
+
Dimension.time: second,
|
|
165
|
+
Dimension.temperature: fahrenheit,
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
# --------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
|
|
144
171
|
def have(name: str) -> bool:
|
|
145
172
|
assert name, "Must provide a unit name to check"
|
|
146
173
|
assert isinstance(name, str), "Unit name must be a string"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ucon
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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
|
|
@@ -92,6 +92,9 @@ To best answer this question, we turn to an age-old technique ([dimensional anal
|
|
|
92
92
|
| **`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). |
|
|
93
93
|
| **`Map`** hierarchy | `ucon.maps` | Composable conversion morphisms: `LinearMap`, `AffineMap`, `ComposedMap`. | Defining conversion functions between units (e.g., meter→foot, celsius→kelvin). |
|
|
94
94
|
| **`ConversionGraph`** | `ucon.graph` | Registry of unit conversion edges with BFS path composition. | Converting between units via `Number.to(target)`; managing default and custom graphs. |
|
|
95
|
+
| **`UnitSystem`** | `ucon.core` | Named mapping from dimensions to base units (e.g., SI, Imperial). | Defining coherent unit systems; grouping base units by dimension. |
|
|
96
|
+
| **`BasisTransform`** | `ucon.core` | Matrix-based transformation between dimensional exponent spaces. | Converting between incompatible dimensional structures; exact arithmetic with `Fraction`. |
|
|
97
|
+
| **`RebasedUnit`** | `ucon.core` | A unit rebased to another system's dimension, preserving provenance. | Cross-basis conversions; tracking original unit through basis changes. |
|
|
95
98
|
| **`units` module** | `ucon.units` | Defines canonical unit instances (SI, imperial, information, and derived units). | Quick access to standard physical units (`units.meter`, `units.foot`, `units.byte`, etc.). |
|
|
96
99
|
|
|
97
100
|
### Under the Hood
|
|
@@ -235,6 +238,83 @@ length_ft = length.to(units.foot)
|
|
|
235
238
|
print(length_ft) # <4.048... ± 0.0164... ft>
|
|
236
239
|
```
|
|
237
240
|
|
|
241
|
+
Unit systems and basis transforms enable conversions between incompatible dimensional structures.
|
|
242
|
+
This goes beyond simple unit conversion (meter → foot) into structural transformation:
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from fractions import Fraction
|
|
246
|
+
from ucon import BasisTransform, Dimension, Unit, UnitSystem, units
|
|
247
|
+
from ucon.graph import ConversionGraph
|
|
248
|
+
from ucon.maps import LinearMap
|
|
249
|
+
|
|
250
|
+
# The realm of Valdris has three fundamental dimensions:
|
|
251
|
+
# - Aether (A): magical energy substrate
|
|
252
|
+
# - Resonance (R): vibrational frequency of magic
|
|
253
|
+
# - Substance (S): physical matter
|
|
254
|
+
#
|
|
255
|
+
# These combine into SI dimensions via a transformation matrix:
|
|
256
|
+
#
|
|
257
|
+
# | L | | 2 0 0 | | A |
|
|
258
|
+
# | M | = | 1 0 1 | × | R |
|
|
259
|
+
# | T | |-2 -1 0 | | S |
|
|
260
|
+
#
|
|
261
|
+
# Reading the columns:
|
|
262
|
+
# - 1 aether contributes: L², M, T⁻² (energy-like)
|
|
263
|
+
# - 1 resonance contributes: T⁻¹ (frequency-like)
|
|
264
|
+
# - 1 substance contributes: M (mass-like)
|
|
265
|
+
|
|
266
|
+
# Fantasy base units
|
|
267
|
+
mote = Unit(name='mote', dimension=Dimension.energy, aliases=('mt',))
|
|
268
|
+
chime = Unit(name='chime', dimension=Dimension.frequency, aliases=('ch',))
|
|
269
|
+
ite = Unit(name='ite', dimension=Dimension.mass, aliases=('it',))
|
|
270
|
+
|
|
271
|
+
valdris = UnitSystem(
|
|
272
|
+
name="Valdris",
|
|
273
|
+
bases={
|
|
274
|
+
Dimension.energy: mote,
|
|
275
|
+
Dimension.frequency: chime,
|
|
276
|
+
Dimension.mass: ite,
|
|
277
|
+
}
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# The basis transform encodes how Valdris dimensions compose into SI
|
|
281
|
+
valdris_to_si = BasisTransform(
|
|
282
|
+
src=valdris,
|
|
283
|
+
dst=units.si,
|
|
284
|
+
src_dimensions=(Dimension.energy, Dimension.frequency, Dimension.mass),
|
|
285
|
+
dst_dimensions=(Dimension.energy, Dimension.frequency, Dimension.mass),
|
|
286
|
+
matrix=(
|
|
287
|
+
(2, 0, 0), # energy: 2 × aether
|
|
288
|
+
(1, 0, 1), # frequency: aether + substance
|
|
289
|
+
(-2, -1, 0), # mass: -2×aether - resonance
|
|
290
|
+
),
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
# Physical calibration: how many SI units per fantasy unit
|
|
294
|
+
graph = ConversionGraph()
|
|
295
|
+
graph.connect_systems(
|
|
296
|
+
basis_transform=valdris_to_si,
|
|
297
|
+
edges={
|
|
298
|
+
(mote, units.joule): LinearMap(42), # 1 mote = 42 J
|
|
299
|
+
(chime, units.hertz): LinearMap(7), # 1 chime = 7 Hz
|
|
300
|
+
(ite, units.kilogram): LinearMap(Fraction(1, 2)), # 1 ite = 0.5 kg
|
|
301
|
+
}
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Game engine converts between physics systems
|
|
305
|
+
energy_map = graph.convert(src=mote, dst=units.joule)
|
|
306
|
+
energy_map(10) # 420 joules from 10 motes
|
|
307
|
+
|
|
308
|
+
# Inverse: display real-world values in game units
|
|
309
|
+
joule_to_mote = graph.convert(src=units.joule, dst=mote)
|
|
310
|
+
joule_to_mote(420) # 10 motes
|
|
311
|
+
|
|
312
|
+
# The transform is invertible with exact Fraction arithmetic
|
|
313
|
+
valdris_to_si.is_invertible # True
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
This enables fantasy game physics, or any field where the dimensional structure differs from SI.
|
|
317
|
+
|
|
238
318
|
---
|
|
239
319
|
|
|
240
320
|
## Roadmap Highlights
|
|
@@ -245,8 +325,9 @@ print(length_ft) # <4.048... ± 0.0164... ft>
|
|
|
245
325
|
| **0.4.x** | Conversion System | `ConversionGraph`, `Number.to()`, callable units | ✅ Complete |
|
|
246
326
|
| **0.5.0** | Dimensionless Units | Pseudo-dimensions for angle, solid angle, ratio | ✅ Complete |
|
|
247
327
|
| **0.5.x** | Uncertainty | Propagation through arithmetic and conversions | ✅ Complete |
|
|
248
|
-
| **0.5.x** | Unit Systems | `
|
|
249
|
-
| **0.
|
|
328
|
+
| **0.5.x** | Unit Systems | `BasisTransform`, `UnitSystem`, cross-basis conversion | ✅ Complete |
|
|
329
|
+
| **0.6.x** | Pydantic Integration | Type-safe quantity validation | ⏳ Planned |
|
|
330
|
+
| **0.7.x** | NumPy Arrays | Vectorized conversion and arithmetic | ⏳ Planned |
|
|
250
331
|
|
|
251
332
|
See full roadmap: [ROADMAP.md](./ROADMAP.md)
|
|
252
333
|
|
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
tests/ucon/__init__.py,sha256=9BAHYTs27Ed3VgqiMUH4XtVttAmOPgK0Zvj-dUNo7D8,119
|
|
2
2
|
tests/ucon/test_algebra.py,sha256=NnoQOSMW8NJlOTnbr3M_5epvnDPXpVTgO21L2LcytRY,8503
|
|
3
|
+
tests/ucon/test_basis_transform.py,sha256=P7ccr9wgDPCwCmsj4dceu3V0A72qFbTavjzm9kB3xP8,16710
|
|
3
4
|
tests/ucon/test_core.py,sha256=bmwSRWPlhwossy5NJ9rcPWujFmzBBPOeZzPAzN1acFg,32631
|
|
4
5
|
tests/ucon/test_default_graph_conversions.py,sha256=rkcDcSV1_kZeuPf4ModHDpgfkOPZS32xcKq7KPDRN-0,15760
|
|
5
6
|
tests/ucon/test_dimensionless_units.py,sha256=K6BrIPOFL9IO_ksR8t_oJUXmjTgqBUzMdgaV-hZc52w,8410
|
|
7
|
+
tests/ucon/test_graph_basis_transform.py,sha256=5-WglJaR1N_mJlqR6i8NuxLJ_FASqb5a8WoO_177smU,8249
|
|
6
8
|
tests/ucon/test_quantity.py,sha256=md5nbmy0u2cFBdqNeu-ROhoj29vYrIlGm_AjlmCttgc,24519
|
|
9
|
+
tests/ucon/test_rebased_unit.py,sha256=n2mksEYSJ8UJJXXwlgaLKg3THaf7_LKzWB7kwjoaXEU,5150
|
|
7
10
|
tests/ucon/test_uncertainty.py,sha256=KkJw2dJR0EToxPpBN24x735jr9fv6a2myxjvhOH4MPU,9649
|
|
11
|
+
tests/ucon/test_unit_system.py,sha256=gRc3fMvo9ded1tBUQWLKpcbpBepMAb7gPu8XvFzQZaM,5860
|
|
8
12
|
tests/ucon/test_units.py,sha256=SILymDtDNDyxEhkYQubrfkakKCMexwEwjyHfhrkDrMI,869
|
|
13
|
+
tests/ucon/test_vector_fraction.py,sha256=fTgxlV9aSP15sA4gATTXBzIDbtKXwWqG1Ip0o-V2B4Y,6369
|
|
9
14
|
tests/ucon/conversion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
15
|
tests/ucon/conversion/test_graph.py,sha256=fs0aP6qNf8eE1uI7SoGSCW2XAkHYb7T9aaI-kzmO02c,16955
|
|
11
16
|
tests/ucon/conversion/test_map.py,sha256=DVFQ3xwp16Nuy9EtZRjKlWbkXfRUcM1mOzFrS4HhOaw,13886
|
|
12
|
-
ucon/__init__.py,sha256=
|
|
13
|
-
ucon/algebra.py,sha256=
|
|
14
|
-
ucon/core.py,sha256=
|
|
15
|
-
ucon/graph.py,sha256=
|
|
17
|
+
ucon/__init__.py,sha256=M_sijIUYPvU87Jtnq_O2X7TS4x9RW1LHZJ8bbm3gfk0,2255
|
|
18
|
+
ucon/algebra.py,sha256=6QrPyD23L93XSrnIORcYEx2CLDv4WDcrh6H_hxeeOus,8668
|
|
19
|
+
ucon/core.py,sha256=j2Xw73-Xuh0CkaUYEv5ljsxjt-XdthiH-EbqUBgG1a8,63139
|
|
20
|
+
ucon/graph.py,sha256=Ec0Q2QiAGUm2RaxrKnpFHtwpNvTf4PYbvo62BWtGJG8,21159
|
|
16
21
|
ucon/maps.py,sha256=tWP4ayYCEazJzf81EP1_fmtADhg18D1eHldudAMEY0U,5460
|
|
17
22
|
ucon/quantity.py,sha256=GBxZ_96nocx-8F-usNWGbPvWHRhRgdZzqfH9Sx69iC4,465
|
|
18
|
-
ucon/units.py,sha256=
|
|
19
|
-
ucon-0.5.
|
|
20
|
-
ucon-0.5.
|
|
21
|
-
ucon-0.5.
|
|
22
|
-
ucon-0.5.
|
|
23
|
-
ucon-0.5.
|
|
24
|
-
ucon-0.5.
|
|
23
|
+
ucon/units.py,sha256=MWCJhicK6jQb71fREyW_HSfGFKL8KEQej2JnySL_MjE,8285
|
|
24
|
+
ucon-0.5.2.dist-info/licenses/LICENSE,sha256=LtimSYBSw1L_X6n1-VEdZRdwuROzPumrMUNX21asFuI,11356
|
|
25
|
+
ucon-0.5.2.dist-info/licenses/NOTICE,sha256=bh4fBOItio3kM4hSNYhqfFpcaAvOoixjD7Du8im-sYA,1079
|
|
26
|
+
ucon-0.5.2.dist-info/METADATA,sha256=CTRgYp67awjnjy7ZzKL2opd81T2RK1feQ6AXMnXmSj4,17200
|
|
27
|
+
ucon-0.5.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
28
|
+
ucon-0.5.2.dist-info/top_level.txt,sha256=zZYRJiQrVUtN32ziJD2YEq7ClSvDmVYHYy5ArRAZGxI,11
|
|
29
|
+
ucon-0.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|