ucon 0.4.1__tar.gz → 0.4.2__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.
Files changed (45) hide show
  1. {ucon-0.4.1 → ucon-0.4.2}/PKG-INFO +1 -1
  2. {ucon-0.4.1 → ucon-0.4.2}/tests/ucon/test_quantity.py +60 -0
  3. {ucon-0.4.1 → ucon-0.4.2}/ucon/core.py +18 -4
  4. {ucon-0.4.1 → ucon-0.4.2}/ucon.egg-info/PKG-INFO +1 -1
  5. {ucon-0.4.1 → ucon-0.4.2}/.github/workflows/publish.yaml +0 -0
  6. {ucon-0.4.1 → ucon-0.4.2}/.github/workflows/tests.yaml +0 -0
  7. {ucon-0.4.1 → ucon-0.4.2}/.gitignore +0 -0
  8. {ucon-0.4.1 → ucon-0.4.2}/LICENSE +0 -0
  9. {ucon-0.4.1 → ucon-0.4.2}/NOTICE +0 -0
  10. {ucon-0.4.1 → ucon-0.4.2}/README.md +0 -0
  11. {ucon-0.4.1 → ucon-0.4.2}/ROADMAP.md +0 -0
  12. {ucon-0.4.1 → ucon-0.4.2}/docs/decisions/composable-unit-algebra.md +0 -0
  13. {ucon-0.4.1 → ucon-0.4.2}/docs/decisions/composite-units.md +0 -0
  14. {ucon-0.4.1 → ucon-0.4.2}/docs/decisions/unit-algebra-naming.md +0 -0
  15. {ucon-0.4.1 → ucon-0.4.2}/docs/decisions/unity-distance-metric-for-nearest-scale.md +0 -0
  16. {ucon-0.4.1 → ucon-0.4.2}/docs/explainers/exponent-scale-relationship.md +0 -0
  17. {ucon-0.4.1 → ucon-0.4.2}/docs/explainers/type-operation-matrix.md +0 -0
  18. {ucon-0.4.1 → ucon-0.4.2}/docs/explainers/why-algebraic-closure-matters.md +0 -0
  19. {ucon-0.4.1 → ucon-0.4.2}/docs/explainers/why-type-safety-matters.md +0 -0
  20. {ucon-0.4.1 → ucon-0.4.2}/docs/proposals/interface-unifying-the-value-layer.md +0 -0
  21. {ucon-0.4.1 → ucon-0.4.2}/docs/proposals/project_unified-algebraic-core.md +0 -0
  22. {ucon-0.4.1 → ucon-0.4.2}/docs/proposals/support-for-fractional-exponents.md +0 -0
  23. {ucon-0.4.1 → ucon-0.4.2}/docs/proposals/unified-unit-presentation.md +0 -0
  24. {ucon-0.4.1 → ucon-0.4.2}/noxfile.py +0 -0
  25. {ucon-0.4.1 → ucon-0.4.2}/requirements.txt +0 -0
  26. {ucon-0.4.1 → ucon-0.4.2}/setup.cfg +0 -0
  27. {ucon-0.4.1 → ucon-0.4.2}/setup.py +0 -0
  28. {ucon-0.4.1 → ucon-0.4.2}/tests/__init__.py +0 -0
  29. {ucon-0.4.1 → ucon-0.4.2}/tests/ucon/__init__.py +0 -0
  30. {ucon-0.4.1 → ucon-0.4.2}/tests/ucon/conversion/__init__.py +0 -0
  31. {ucon-0.4.1 → ucon-0.4.2}/tests/ucon/conversion/test_graph.py +0 -0
  32. {ucon-0.4.1 → ucon-0.4.2}/tests/ucon/conversion/test_map.py +0 -0
  33. {ucon-0.4.1 → ucon-0.4.2}/tests/ucon/test_algebra.py +0 -0
  34. {ucon-0.4.1 → ucon-0.4.2}/tests/ucon/test_core.py +0 -0
  35. {ucon-0.4.1 → ucon-0.4.2}/tests/ucon/test_default_graph_conversions.py +0 -0
  36. {ucon-0.4.1 → ucon-0.4.2}/tests/ucon/test_units.py +0 -0
  37. {ucon-0.4.1 → ucon-0.4.2}/ucon/__init__.py +0 -0
  38. {ucon-0.4.1 → ucon-0.4.2}/ucon/algebra.py +0 -0
  39. {ucon-0.4.1 → ucon-0.4.2}/ucon/graph.py +0 -0
  40. {ucon-0.4.1 → ucon-0.4.2}/ucon/maps.py +0 -0
  41. {ucon-0.4.1 → ucon-0.4.2}/ucon/quantity.py +0 -0
  42. {ucon-0.4.1 → ucon-0.4.2}/ucon/units.py +0 -0
  43. {ucon-0.4.1 → ucon-0.4.2}/ucon.egg-info/SOURCES.txt +0 -0
  44. {ucon-0.4.1 → ucon-0.4.2}/ucon.egg-info/dependency_links.txt +0 -0
  45. {ucon-0.4.1 → ucon-0.4.2}/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.4.1
3
+ Version: 0.4.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
@@ -364,6 +364,66 @@ class TestRatioEdgeCases(unittest.TestCase):
364
364
  self.assertIn("/", rep)
365
365
 
366
366
 
367
+ class TestRatioExponentScaling(unittest.TestCase):
368
+ """Tests for Ratio.evaluate() using Exponent-based scaling.
369
+
370
+ Ensures Ratio.evaluate() behaves consistently with Number.__truediv__
371
+ when units cancel to dimensionless results.
372
+ """
373
+
374
+ def test_evaluate_dimensionless_with_different_scales(self):
375
+ """Ratio of same unit with different scales should fold scales."""
376
+ kg = Scale.kilo * units.gram
377
+ # 500 g / 1 kg = 0.5 (dimensionless)
378
+ ratio = Ratio(units.gram(500), kg(1))
379
+ result = ratio.evaluate()
380
+ self.assertAlmostEqual(result.quantity, 0.5, places=10)
381
+ self.assertEqual(result.unit.dimension, Dimension.none)
382
+
383
+ def test_evaluate_matches_number_truediv(self):
384
+ """Ratio.evaluate() should match Number.__truediv__ for dimensionless."""
385
+ kg = Scale.kilo * units.gram
386
+ num = units.gram(500)
387
+ den = kg(1)
388
+
389
+ ratio_result = Ratio(num, den).evaluate()
390
+ truediv_result = num / den
391
+
392
+ self.assertAlmostEqual(ratio_result.quantity, truediv_result.quantity, places=10)
393
+ self.assertEqual(ratio_result.unit.dimension, truediv_result.unit.dimension)
394
+
395
+ def test_evaluate_cross_base_scaling(self):
396
+ """Binary and decimal prefixes should combine correctly."""
397
+ kibigram = Scale.kibi * units.gram # 1024 g
398
+ kg = Scale.kilo * units.gram # 1000 g
399
+ # 1 kibigram / 1 kg = 1024/1000 = 1.024
400
+ ratio = Ratio(kibigram(1), kg(1))
401
+ result = ratio.evaluate()
402
+ self.assertAlmostEqual(result.quantity, 1.024, places=10)
403
+ self.assertEqual(result.unit.dimension, Dimension.none)
404
+
405
+ def test_evaluate_dimensionful_preserves_scales(self):
406
+ """Non-cancelling units should preserve symbolic scales."""
407
+ km = Scale.kilo * units.meter
408
+ # 100 km / 2 h = 50 km/h (scales preserved, not folded)
409
+ ratio = Ratio(km(100), units.hour(2))
410
+ result = ratio.evaluate()
411
+ self.assertAlmostEqual(result.quantity, 50.0, places=10)
412
+ self.assertEqual(result.unit.dimension, Dimension.velocity)
413
+ self.assertIn("km", result.unit.shorthand)
414
+
415
+ def test_evaluate_complex_composition(self):
416
+ """Composed ratios should maintain scale semantics."""
417
+ mL = Scale.milli * units.liter
418
+ # Density: 3.119 g/mL
419
+ density = Ratio(units.gram(3.119), mL(1))
420
+ # Volume: 2 mL
421
+ volume = Ratio(mL(2), Number())
422
+ # Mass = density * volume
423
+ result = (density * volume).evaluate()
424
+ self.assertAlmostEqual(result.quantity, 6.238, places=3)
425
+
426
+
367
427
  class TestCallableUnits(unittest.TestCase):
368
428
  """Tests for the callable unit syntax: unit(quantity) -> Number."""
369
429
 
@@ -1091,13 +1091,27 @@ class Ratio:
1091
1091
  return Ratio(numerator=self.denominator, denominator=self.numerator)
1092
1092
 
1093
1093
  def evaluate(self) -> "Number":
1094
- # Pure arithmetic, no scale normalization.
1095
- numeric = self.numerator.quantity / self.denominator.quantity
1094
+ """Evaluate the ratio to a Number.
1095
+
1096
+ Uses Exponent-derived arithmetic for scale handling:
1097
+ - If the result is dimensionless (units cancel), scales are folded
1098
+ into the magnitude using _canonical_magnitude.
1099
+ - If the result is dimensionful, raw quantities are divided and
1100
+ unit scales are preserved symbolically.
1096
1101
 
1097
- # Pure unit division, with UnitFactor preservation.
1102
+ This matches the behavior of Number.__truediv__ for consistency.
1103
+ """
1104
+ # Symbolic quotient in the unit algebra
1098
1105
  unit = self.numerator.unit / self.denominator.unit
1099
1106
 
1100
- # DO NOT normalize, DO NOT fold scale.
1107
+ # Dimensionless result: fold all scale factors into magnitude
1108
+ if not unit.dimension:
1109
+ num = self.numerator._canonical_magnitude
1110
+ den = self.denominator._canonical_magnitude
1111
+ return Number(quantity=num / den, unit=_none)
1112
+
1113
+ # Dimensionful result: preserve user's chosen scales symbolically
1114
+ numeric = self.numerator.quantity / self.denominator.quantity
1101
1115
  return Number(quantity=numeric, unit=unit)
1102
1116
 
1103
1117
  def __mul__(self, another_ratio: 'Ratio') -> 'Ratio':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ucon
3
- Version: 0.4.1
3
+ Version: 0.4.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
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