ucon 0.3.5rc2__py3-none-any.whl → 0.4.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.
- tests/ucon/conversion/__init__.py +0 -0
- tests/ucon/conversion/test_graph.py +409 -0
- tests/ucon/conversion/test_map.py +409 -0
- tests/ucon/test_algebra.py +34 -34
- tests/ucon/test_core.py +25 -26
- tests/ucon/test_default_graph_conversions.py +443 -0
- tests/ucon/test_quantity.py +246 -61
- ucon/__init__.py +6 -2
- ucon/algebra.py +9 -5
- ucon/core.py +366 -53
- ucon/graph.py +423 -0
- ucon/maps.py +161 -0
- ucon/quantity.py +7 -186
- ucon/units.py +79 -31
- {ucon-0.3.5rc2.dist-info → ucon-0.4.1.dist-info}/METADATA +28 -10
- ucon-0.4.1.dist-info/RECORD +22 -0
- ucon-0.3.5rc2.dist-info/RECORD +0 -16
- {ucon-0.3.5rc2.dist-info → ucon-0.4.1.dist-info}/WHEEL +0 -0
- {ucon-0.3.5rc2.dist-info → ucon-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {ucon-0.3.5rc2.dist-info → ucon-0.4.1.dist-info}/licenses/NOTICE +0 -0
- {ucon-0.3.5rc2.dist-info → ucon-0.4.1.dist-info}/top_level.txt +0 -0
tests/ucon/test_quantity.py
CHANGED
|
@@ -19,38 +19,38 @@ class TestNumber(unittest.TestCase):
|
|
|
19
19
|
self.assertEqual(ratio.numerator, self.number)
|
|
20
20
|
self.assertEqual(ratio.denominator, Number())
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
decagram =
|
|
25
|
-
kibigram = UnitFactor(dimension=Dimension.mass, name='gram', scale=Scale.kibi)
|
|
26
|
-
|
|
22
|
+
def test_simplify_scaled_unit(self):
|
|
23
|
+
"""Test simplify() removes scale prefix and adjusts quantity."""
|
|
24
|
+
decagram = Scale.deca * units.gram
|
|
27
25
|
ten_decagrams = Number(unit=decagram, quantity=10)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
self.assertEqual(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
kg =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
26
|
+
result = ten_decagrams.simplify()
|
|
27
|
+
# 10 decagrams = 100 grams
|
|
28
|
+
self.assertAlmostEqual(result.quantity, 100.0, places=10)
|
|
29
|
+
# Unit should be base gram (Scale.one)
|
|
30
|
+
self.assertEqual(result.unit.shorthand, "g")
|
|
31
|
+
|
|
32
|
+
def test_to_converts_between_units(self):
|
|
33
|
+
"""Test Number.to() converts between compatible units."""
|
|
34
|
+
# 1 gram to kilogram
|
|
35
|
+
kg = Scale.kilo * units.gram
|
|
36
|
+
result = self.number.to(kg)
|
|
37
|
+
self.assertAlmostEqual(result.quantity, 0.001, places=10)
|
|
38
|
+
|
|
39
|
+
# 1 gram to milligram
|
|
40
|
+
mg = Scale.milli * units.gram
|
|
41
|
+
result = self.number.to(mg)
|
|
42
|
+
self.assertAlmostEqual(result.quantity, 1000.0, places=10)
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
# 1 kilogram to gram
|
|
45
|
+
one_kg = Number(unit=kg, quantity=1)
|
|
46
|
+
result = one_kg.to(units.gram)
|
|
47
|
+
self.assertAlmostEqual(result.quantity, 1000.0, places=10)
|
|
48
48
|
|
|
49
|
-
@unittest.skip("TODO: revamp: Unit.scale is deprecated.")
|
|
50
49
|
def test___repr__(self):
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
self.assertIn(self.number.
|
|
50
|
+
"""Test Number repr contains quantity and unit shorthand."""
|
|
51
|
+
repr_str = repr(self.number)
|
|
52
|
+
self.assertIn(str(self.number.quantity), repr_str)
|
|
53
|
+
self.assertIn(self.number.unit.shorthand, repr_str)
|
|
54
54
|
|
|
55
55
|
def test___truediv__(self):
|
|
56
56
|
dal = Scale.deca * units.gram
|
|
@@ -154,32 +154,32 @@ class TestNumberEdgeCases(unittest.TestCase):
|
|
|
154
154
|
assert evaluated.unit == units.gram
|
|
155
155
|
assert abs(evaluated.quantity - 6.238) < 1e-12
|
|
156
156
|
|
|
157
|
-
@unittest.skip("TODO: revamp: Unit.scale is deprecated.")
|
|
158
157
|
def test_default_number_is_dimensionless_one(self):
|
|
158
|
+
"""Default Number() is dimensionless with quantity=1."""
|
|
159
159
|
n = Number()
|
|
160
160
|
self.assertEqual(n.unit, units.none)
|
|
161
|
-
self.assertEqual(n.unit.scale, Scale.one)
|
|
162
161
|
self.assertEqual(n.quantity, 1)
|
|
163
162
|
self.assertAlmostEqual(n.value, 1.0)
|
|
164
163
|
self.assertIn("1", repr(n))
|
|
165
164
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
n = Number(quantity=
|
|
170
|
-
converted = n.to(
|
|
171
|
-
|
|
172
|
-
self.
|
|
165
|
+
def test_to_different_scale_changes_quantity(self):
|
|
166
|
+
"""Converting to a different scale changes the quantity."""
|
|
167
|
+
km = Scale.kilo * units.meter
|
|
168
|
+
n = Number(quantity=5, unit=km) # 5 km
|
|
169
|
+
converted = n.to(units.meter) # convert to meters
|
|
170
|
+
# quantity changes: 5 km = 5000 m
|
|
171
|
+
self.assertNotEqual(n.quantity, converted.quantity)
|
|
172
|
+
self.assertAlmostEqual(converted.quantity, 5000.0, places=10)
|
|
173
173
|
|
|
174
|
-
@unittest.skip("TODO: revamp: Unit.scale is deprecated.")
|
|
175
|
-
@unittest.skip("Requires ConversionGraph implementation")
|
|
176
174
|
def test_simplify_uses_value_as_quantity(self):
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
"""Simplify converts scaled quantity to base scale quantity."""
|
|
176
|
+
km = Scale.kilo * units.meter
|
|
177
|
+
n = Number(quantity=2, unit=km) # 2 km
|
|
179
178
|
simplified = n.simplify()
|
|
180
|
-
|
|
181
|
-
self.
|
|
182
|
-
|
|
179
|
+
# simplified.quantity should be the canonical magnitude (2 * 1000 = 2000)
|
|
180
|
+
self.assertAlmostEqual(simplified.quantity, 2000.0, places=10)
|
|
181
|
+
# canonical magnitude (physical quantity) is preserved
|
|
182
|
+
self.assertAlmostEqual(simplified._canonical_magnitude, n._canonical_magnitude, places=10)
|
|
183
183
|
|
|
184
184
|
def test_multiplication_combines_units_and_quantities(self):
|
|
185
185
|
n1 = Number(unit=units.joule, quantity=2)
|
|
@@ -188,14 +188,13 @@ class TestNumberEdgeCases(unittest.TestCase):
|
|
|
188
188
|
self.assertEqual(result.quantity, 6)
|
|
189
189
|
self.assertEqual(result.unit.dimension, Dimension.energy * Dimension.time)
|
|
190
190
|
|
|
191
|
-
@unittest.skip("TODO: revamp: Unit.scale is deprecated.")
|
|
192
|
-
@unittest.skip("Requires ConversionGraph implementation")
|
|
193
191
|
def test_division_combines_units_scales_and_quantities(self):
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
"""Division creates composite unit with preserved scales."""
|
|
193
|
+
km = Scale.kilo * units.meter
|
|
194
|
+
n1 = Number(unit=km, quantity=1000) # 1000 km
|
|
196
195
|
n2 = Number(unit=units.second, quantity=2)
|
|
197
196
|
|
|
198
|
-
result = n1 / n2
|
|
197
|
+
result = n1 / n2 # should yield <500 km/s>
|
|
199
198
|
|
|
200
199
|
cu = result.unit
|
|
201
200
|
self.assertIsInstance(cu, UnitProduct)
|
|
@@ -206,18 +205,13 @@ class TestNumberEdgeCases(unittest.TestCase):
|
|
|
206
205
|
# --- dimension check ---
|
|
207
206
|
self.assertEqual(cu.dimension, Dimension.velocity)
|
|
208
207
|
|
|
209
|
-
# --- scale check: km/s should have a kilo-scaled meter in the numerator ---
|
|
210
|
-
# find the meter-like unit in the components
|
|
211
|
-
meter_like = next(u for u, exp in cu.components.items() if u.dimension == Dimension.length)
|
|
212
|
-
self.assertEqual(meter_like.scale, Scale.kilo)
|
|
213
|
-
self.assertEqual(cu.components[meter_like], 1) # exponent = 1 in numerator
|
|
214
|
-
|
|
215
208
|
# --- symbolic shorthand ---
|
|
216
209
|
self.assertEqual(cu.shorthand, "km/s")
|
|
217
210
|
|
|
218
|
-
# ---
|
|
219
|
-
|
|
220
|
-
|
|
211
|
+
# --- convert to base units (m/s) ---
|
|
212
|
+
m_per_s = units.meter / units.second
|
|
213
|
+
canonical = result.to(m_per_s)
|
|
214
|
+
self.assertAlmostEqual(canonical.quantity, 500000, places=5)
|
|
221
215
|
self.assertEqual(canonical.unit.shorthand, "m/s")
|
|
222
216
|
|
|
223
217
|
def test_equality_with_non_number_raises_value_error(self):
|
|
@@ -232,7 +226,7 @@ class TestNumberEdgeCases(unittest.TestCase):
|
|
|
232
226
|
self.assertTrue(r == Number())
|
|
233
227
|
|
|
234
228
|
def test_repr_includes_scale_and_unit(self):
|
|
235
|
-
kV = Scale.kilo * Unit(
|
|
229
|
+
kV = Scale.kilo * Unit(name='volt', dimension=Dimension.voltage, aliases=('V',))
|
|
236
230
|
n = Number(unit=kV, quantity=5)
|
|
237
231
|
rep = repr(n)
|
|
238
232
|
self.assertIn("kV", rep)
|
|
@@ -269,7 +263,7 @@ class TestRatio(unittest.TestCase):
|
|
|
269
263
|
self.assertEqual(self.one_half * self.three_halves, self.three_fourths)
|
|
270
264
|
|
|
271
265
|
def test___mul__(self):
|
|
272
|
-
mL = Scale.milli * Unit(
|
|
266
|
+
mL = Scale.milli * Unit(name='liter', dimension=Dimension.volume, aliases=('L',))
|
|
273
267
|
n1 = Number(unit=units.gram, quantity=3.119)
|
|
274
268
|
n2 = Number(unit=mL)
|
|
275
269
|
bromine_density = Ratio(n1, n2)
|
|
@@ -289,7 +283,7 @@ class TestRatio(unittest.TestCase):
|
|
|
289
283
|
|
|
290
284
|
# How many Wh from 20 kJ?
|
|
291
285
|
twenty_kilojoules = Number(
|
|
292
|
-
unit=Scale.kilo * Unit(
|
|
286
|
+
unit=Scale.kilo * Unit(name='joule', dimension=Dimension.energy, aliases=('J',)),
|
|
293
287
|
quantity=20
|
|
294
288
|
)
|
|
295
289
|
ratio = twenty_kilojoules.as_ratio() / seconds_per_hour
|
|
@@ -368,3 +362,194 @@ class TestRatioEdgeCases(unittest.TestCase):
|
|
|
368
362
|
r = Ratio(n1, n2)
|
|
369
363
|
rep = repr(r)
|
|
370
364
|
self.assertIn("/", rep)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class TestCallableUnits(unittest.TestCase):
|
|
368
|
+
"""Tests for the callable unit syntax: unit(quantity) -> Number."""
|
|
369
|
+
|
|
370
|
+
def test_unit_callable_returns_number(self):
|
|
371
|
+
result = units.meter(5)
|
|
372
|
+
self.assertIsInstance(result, Number)
|
|
373
|
+
self.assertEqual(result.quantity, 5)
|
|
374
|
+
|
|
375
|
+
def test_unit_callable_shorthand(self):
|
|
376
|
+
result = units.meter(5)
|
|
377
|
+
self.assertIn("m", result.unit.shorthand)
|
|
378
|
+
|
|
379
|
+
def test_unit_product_callable_returns_number(self):
|
|
380
|
+
velocity = units.meter / units.second
|
|
381
|
+
result = velocity(10)
|
|
382
|
+
self.assertIsInstance(result, Number)
|
|
383
|
+
self.assertEqual(result.quantity, 10)
|
|
384
|
+
self.assertEqual(result.unit.dimension, Dimension.velocity)
|
|
385
|
+
|
|
386
|
+
def test_scaled_unit_callable_returns_number(self):
|
|
387
|
+
km = Scale.kilo * units.meter
|
|
388
|
+
result = km(5)
|
|
389
|
+
self.assertIsInstance(result, Number)
|
|
390
|
+
self.assertEqual(result.quantity, 5)
|
|
391
|
+
self.assertIn("km", result.unit.shorthand)
|
|
392
|
+
|
|
393
|
+
def test_composite_scaled_unit_callable(self):
|
|
394
|
+
mph = units.mile / units.hour
|
|
395
|
+
result = mph(60)
|
|
396
|
+
self.assertIsInstance(result, Number)
|
|
397
|
+
self.assertEqual(result.quantity, 60)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class TestScaledUnitConversion(unittest.TestCase):
|
|
401
|
+
"""Tests for conversions involving scaled units.
|
|
402
|
+
|
|
403
|
+
Regression tests for bug where scale was applied twice during conversion.
|
|
404
|
+
"""
|
|
405
|
+
|
|
406
|
+
def test_km_to_mile_conversion(self):
|
|
407
|
+
"""5 km should be approximately 3.10686 miles."""
|
|
408
|
+
km = Scale.kilo * units.meter
|
|
409
|
+
result = km(5).to(units.mile)
|
|
410
|
+
# 5 km = 5000 m = 5000 / 1609.34 miles ≈ 3.10686
|
|
411
|
+
self.assertAlmostEqual(result.quantity, 3.10686, places=4)
|
|
412
|
+
|
|
413
|
+
def test_km_to_meter_conversion(self):
|
|
414
|
+
"""1 km should be 1000 meters."""
|
|
415
|
+
km = Scale.kilo * units.meter
|
|
416
|
+
result = km(1).to(units.meter)
|
|
417
|
+
self.assertAlmostEqual(result.quantity, 1000.0, places=6)
|
|
418
|
+
|
|
419
|
+
def test_meter_to_mm_conversion(self):
|
|
420
|
+
"""1 meter should be 1000 millimeters."""
|
|
421
|
+
mm = Scale.milli * units.meter
|
|
422
|
+
result = units.meter(1).to(mm)
|
|
423
|
+
self.assertAlmostEqual(result.quantity, 1000.0, places=6)
|
|
424
|
+
|
|
425
|
+
def test_mm_to_inch_conversion(self):
|
|
426
|
+
"""25.4 mm should be approximately 1 inch."""
|
|
427
|
+
mm = Scale.milli * units.meter
|
|
428
|
+
result = mm(25.4).to(units.inch)
|
|
429
|
+
self.assertAlmostEqual(result.quantity, 1.0, places=4)
|
|
430
|
+
|
|
431
|
+
def test_scaled_velocity_conversion(self):
|
|
432
|
+
"""1 km/h should be approximately 0.27778 m/s."""
|
|
433
|
+
km_per_h = (Scale.kilo * units.meter) / units.hour
|
|
434
|
+
m_per_s = units.meter / units.second
|
|
435
|
+
result = km_per_h(1).to(m_per_s)
|
|
436
|
+
# 1 km/h = 1000m / 3600s = 0.27778 m/s
|
|
437
|
+
self.assertAlmostEqual(result.quantity, 0.27778, places=4)
|
|
438
|
+
|
|
439
|
+
def test_mph_to_m_per_s_conversion(self):
|
|
440
|
+
"""60 mph should be approximately 26.8224 m/s."""
|
|
441
|
+
mph = units.mile / units.hour
|
|
442
|
+
m_per_s = units.meter / units.second
|
|
443
|
+
result = mph(60).to(m_per_s)
|
|
444
|
+
# 60 mph = 60 * 1609.34 / 3600 m/s ≈ 26.8224
|
|
445
|
+
self.assertAlmostEqual(result.quantity, 26.8224, places=2)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
class TestNumberSimplify(unittest.TestCase):
|
|
449
|
+
"""Tests for Number.simplify() method."""
|
|
450
|
+
|
|
451
|
+
def test_simplify_kilo_prefix(self):
|
|
452
|
+
"""5 km simplifies to 5000 m."""
|
|
453
|
+
km = Scale.kilo * units.meter
|
|
454
|
+
result = km(5).simplify()
|
|
455
|
+
self.assertAlmostEqual(result.quantity, 5000.0, places=10)
|
|
456
|
+
self.assertEqual(result.unit.shorthand, "m")
|
|
457
|
+
|
|
458
|
+
def test_simplify_milli_prefix(self):
|
|
459
|
+
"""500 mg simplifies to 0.5 g."""
|
|
460
|
+
mg = Scale.milli * units.gram
|
|
461
|
+
result = mg(500).simplify()
|
|
462
|
+
self.assertAlmostEqual(result.quantity, 0.5, places=10)
|
|
463
|
+
self.assertEqual(result.unit.shorthand, "g")
|
|
464
|
+
|
|
465
|
+
def test_simplify_binary_prefix(self):
|
|
466
|
+
"""2 kibibytes simplifies to 2048 bytes."""
|
|
467
|
+
kibibyte = Scale.kibi * units.byte
|
|
468
|
+
result = kibibyte(2).simplify()
|
|
469
|
+
self.assertAlmostEqual(result.quantity, 2048.0, places=10)
|
|
470
|
+
self.assertEqual(result.unit.shorthand, "B")
|
|
471
|
+
|
|
472
|
+
def test_simplify_composite_unit(self):
|
|
473
|
+
"""1 km/h simplifies to base scales."""
|
|
474
|
+
km_per_h = (Scale.kilo * units.meter) / units.hour
|
|
475
|
+
result = km_per_h(1).simplify()
|
|
476
|
+
# 1 km/h = 1000 m / 1 h (hour stays hour since it's base unit)
|
|
477
|
+
self.assertAlmostEqual(result.quantity, 1000.0, places=10)
|
|
478
|
+
self.assertEqual(result.unit.shorthand, "m/h")
|
|
479
|
+
|
|
480
|
+
def test_simplify_plain_unit_unchanged(self):
|
|
481
|
+
"""Plain unit without scale returns equivalent Number."""
|
|
482
|
+
result = units.meter(5).simplify()
|
|
483
|
+
self.assertAlmostEqual(result.quantity, 5.0, places=10)
|
|
484
|
+
self.assertEqual(result.unit.shorthand, "m")
|
|
485
|
+
|
|
486
|
+
def test_simplify_preserves_dimension(self):
|
|
487
|
+
"""Simplified Number has same dimension."""
|
|
488
|
+
km = Scale.kilo * units.meter
|
|
489
|
+
original = km(5)
|
|
490
|
+
simplified = original.simplify()
|
|
491
|
+
self.assertEqual(original.unit.dimension, simplified.unit.dimension)
|
|
492
|
+
|
|
493
|
+
def test_simplify_idempotent(self):
|
|
494
|
+
"""Simplifying twice gives same result."""
|
|
495
|
+
km = Scale.kilo * units.meter
|
|
496
|
+
result1 = km(5).simplify()
|
|
497
|
+
result2 = result1.simplify()
|
|
498
|
+
self.assertAlmostEqual(result1.quantity, result2.quantity, places=10)
|
|
499
|
+
self.assertEqual(result1.unit.shorthand, result2.unit.shorthand)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
class TestInformationDimension(unittest.TestCase):
|
|
503
|
+
"""Tests for Dimension.information and information units (bit, byte)."""
|
|
504
|
+
|
|
505
|
+
def test_dimension_information_exists(self):
|
|
506
|
+
"""Dimension.information should be a valid dimension."""
|
|
507
|
+
self.assertEqual(Dimension.information.name, 'information')
|
|
508
|
+
self.assertNotEqual(Dimension.information, Dimension.none)
|
|
509
|
+
|
|
510
|
+
def test_bit_unit_exists(self):
|
|
511
|
+
"""units.bit should have Dimension.information."""
|
|
512
|
+
self.assertEqual(units.bit.dimension, Dimension.information)
|
|
513
|
+
self.assertIn('b', units.bit.aliases)
|
|
514
|
+
|
|
515
|
+
def test_byte_unit_exists(self):
|
|
516
|
+
"""units.byte should have Dimension.information."""
|
|
517
|
+
self.assertEqual(units.byte.dimension, Dimension.information)
|
|
518
|
+
self.assertIn('B', units.byte.aliases)
|
|
519
|
+
|
|
520
|
+
def test_byte_to_bit_conversion(self):
|
|
521
|
+
"""1 byte should be 8 bits."""
|
|
522
|
+
result = units.byte(1).to(units.bit)
|
|
523
|
+
self.assertAlmostEqual(result.quantity, 8.0, places=10)
|
|
524
|
+
|
|
525
|
+
def test_bit_to_byte_conversion(self):
|
|
526
|
+
"""8 bits should be 1 byte."""
|
|
527
|
+
result = units.bit(8).to(units.byte)
|
|
528
|
+
self.assertAlmostEqual(result.quantity, 1.0, places=10)
|
|
529
|
+
|
|
530
|
+
def test_kibibyte_simplify(self):
|
|
531
|
+
"""1 kibibyte simplifies to 1024 bytes."""
|
|
532
|
+
kibibyte = Scale.kibi * units.byte
|
|
533
|
+
result = kibibyte(1).simplify()
|
|
534
|
+
self.assertAlmostEqual(result.quantity, 1024.0, places=10)
|
|
535
|
+
self.assertEqual(result.unit.shorthand, "B")
|
|
536
|
+
|
|
537
|
+
def test_kilobyte_simplify(self):
|
|
538
|
+
"""1 kilobyte simplifies to 1000 bytes."""
|
|
539
|
+
kilobyte = Scale.kilo * units.byte
|
|
540
|
+
result = kilobyte(1).simplify()
|
|
541
|
+
self.assertAlmostEqual(result.quantity, 1000.0, places=10)
|
|
542
|
+
self.assertEqual(result.unit.shorthand, "B")
|
|
543
|
+
|
|
544
|
+
def test_data_rate_dimension(self):
|
|
545
|
+
"""bytes/second should have information/time dimension."""
|
|
546
|
+
data_rate = units.byte / units.second
|
|
547
|
+
expected_dim = Dimension.information / Dimension.time
|
|
548
|
+
self.assertEqual(data_rate.dimension, expected_dim)
|
|
549
|
+
|
|
550
|
+
def test_information_orthogonal_to_physical(self):
|
|
551
|
+
"""Information dimension should be orthogonal to physical dimensions."""
|
|
552
|
+
# byte * meter should have both information and length
|
|
553
|
+
composite = units.byte * units.meter
|
|
554
|
+
self.assertNotEqual(composite.dimension, Dimension.information)
|
|
555
|
+
self.assertNotEqual(composite.dimension, Dimension.length)
|
ucon/__init__.py
CHANGED
|
@@ -38,8 +38,8 @@ Design Philosophy
|
|
|
38
38
|
"""
|
|
39
39
|
from ucon import units
|
|
40
40
|
from ucon.algebra import Exponent
|
|
41
|
-
from ucon.core import Dimension, Scale, Unit
|
|
42
|
-
from ucon.
|
|
41
|
+
from ucon.core import Dimension, Scale, Unit, UnitFactor, UnitProduct, Number, Ratio
|
|
42
|
+
from ucon.graph import get_default_graph, using_graph
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
__all__ = [
|
|
@@ -49,5 +49,9 @@ __all__ = [
|
|
|
49
49
|
'Ratio',
|
|
50
50
|
'Scale',
|
|
51
51
|
'Unit',
|
|
52
|
+
'UnitFactor',
|
|
53
|
+
'UnitProduct',
|
|
54
|
+
'get_default_graph',
|
|
55
|
+
'using_graph',
|
|
52
56
|
'units',
|
|
53
57
|
]
|
ucon/algebra.py
CHANGED
|
@@ -34,18 +34,20 @@ class Vector:
|
|
|
34
34
|
"""
|
|
35
35
|
Represents the **exponent vector** of a physical quantity.
|
|
36
36
|
|
|
37
|
-
Each component corresponds to the power of a base dimension in the SI system
|
|
37
|
+
Each component corresponds to the power of a base dimension in the SI system
|
|
38
|
+
plus information (B) as an orthogonal non-SI dimension:
|
|
38
39
|
time (T), length (L), mass (M), current (I), temperature (Θ),
|
|
39
|
-
luminous intensity (J),
|
|
40
|
+
luminous intensity (J), amount of substance (N), and information (B).
|
|
40
41
|
|
|
41
42
|
Arithmetic operations correspond to dimensional composition:
|
|
42
43
|
- Addition (`+`) → multiplication of quantities
|
|
43
44
|
- Subtraction (`-`) → division of quantities
|
|
44
45
|
|
|
45
46
|
e.g.
|
|
46
|
-
Vector(T=1, L=0, M=0, I=0, Θ=0, J=0, N=0) => "time"
|
|
47
|
-
Vector(T=0, L=2, M=0, I=0, Θ=0, J=0, N=0) => "area"
|
|
48
|
-
Vector(T=-2, L=1, M=1, I=0, Θ=0, J=0, N=0) => "force"
|
|
47
|
+
Vector(T=1, L=0, M=0, I=0, Θ=0, J=0, N=0, B=0) => "time"
|
|
48
|
+
Vector(T=0, L=2, M=0, I=0, Θ=0, J=0, N=0, B=0) => "area"
|
|
49
|
+
Vector(T=-2, L=1, M=1, I=0, Θ=0, J=0, N=0, B=0) => "force"
|
|
50
|
+
Vector(T=0, L=0, M=0, I=0, Θ=0, J=0, N=0, B=1) => "information"
|
|
49
51
|
"""
|
|
50
52
|
T: int = 0 # time
|
|
51
53
|
L: int = 0 # length
|
|
@@ -54,6 +56,7 @@ class Vector:
|
|
|
54
56
|
Θ: int = 0 # temperature
|
|
55
57
|
J: int = 0 # luminous intensity
|
|
56
58
|
N: int = 0 # amount of substance
|
|
59
|
+
B: int = 0 # information (bits)
|
|
57
60
|
|
|
58
61
|
def __iter__(self) -> Iterator[int]:
|
|
59
62
|
yield self.T
|
|
@@ -63,6 +66,7 @@ class Vector:
|
|
|
63
66
|
yield self.Θ
|
|
64
67
|
yield self.J
|
|
65
68
|
yield self.N
|
|
69
|
+
yield self.B
|
|
66
70
|
|
|
67
71
|
def __len__(self) -> int:
|
|
68
72
|
return sum(tuple(1 for x in self))
|