ucon 0.3.5rc1__py3-none-any.whl → 0.4.0__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.
File without changes
@@ -0,0 +1,175 @@
1
+ # © 2026 The Radiativity Company
2
+ # Licensed under the Apache License, Version 2.0
3
+ # See the LICENSE file for details.
4
+
5
+ import unittest
6
+
7
+ from ucon import units
8
+ from ucon.core import Dimension, Scale, Unit, UnitFactor, UnitProduct
9
+ from ucon.graph import (
10
+ ConversionGraph,
11
+ DimensionMismatch,
12
+ ConversionNotFound,
13
+ CyclicInconsistency,
14
+ )
15
+ from ucon.maps import LinearMap, AffineMap
16
+
17
+
18
+ class TestConversionGraphEdgeManagement(unittest.TestCase):
19
+
20
+ def setUp(self):
21
+ self.graph = ConversionGraph()
22
+ self.meter = units.meter
23
+ self.foot = Unit(name='foot', dimension=Dimension.length, aliases=('ft',))
24
+ self.inch = Unit(name='inch', dimension=Dimension.length, aliases=('in',))
25
+ self.gram = units.gram
26
+
27
+ def test_add_and_retrieve_edge(self):
28
+ self.graph.add_edge(src=self.meter, dst=self.foot, map=LinearMap(3.28084))
29
+ m = self.graph.convert(src=self.meter, dst=self.foot)
30
+ self.assertAlmostEqual(m(1), 3.28084, places=4)
31
+
32
+ def test_inverse_auto_stored(self):
33
+ self.graph.add_edge(src=self.meter, dst=self.foot, map=LinearMap(3.28084))
34
+ m = self.graph.convert(src=self.foot, dst=self.meter)
35
+ self.assertAlmostEqual(m(1), 0.3048, places=4)
36
+
37
+ def test_dimension_mismatch_rejected(self):
38
+ with self.assertRaises(DimensionMismatch):
39
+ self.graph.add_edge(src=self.meter, dst=self.gram, map=LinearMap(1))
40
+
41
+ def test_cyclic_consistency_check(self):
42
+ self.graph.add_edge(src=self.meter, dst=self.foot, map=LinearMap(3.28084))
43
+ # Adding inconsistent reverse should raise
44
+ with self.assertRaises(CyclicInconsistency):
45
+ self.graph.add_edge(src=self.foot, dst=self.meter, map=LinearMap(0.5)) # wrong!
46
+
47
+
48
+ class TestConversionGraphUnitConversion(unittest.TestCase):
49
+
50
+ def setUp(self):
51
+ self.graph = ConversionGraph()
52
+ self.meter = units.meter
53
+ self.foot = Unit(name='foot', dimension=Dimension.length, aliases=('ft',))
54
+ self.inch = Unit(name='inch', dimension=Dimension.length, aliases=('in',))
55
+ self.yard = Unit(name='yard', dimension=Dimension.length, aliases=('yd',))
56
+
57
+ def test_identity_conversion(self):
58
+ m = self.graph.convert(src=self.meter, dst=self.meter)
59
+ self.assertTrue(m.is_identity())
60
+
61
+ def test_direct_edge(self):
62
+ self.graph.add_edge(src=self.meter, dst=self.foot, map=LinearMap(3.28084))
63
+ m = self.graph.convert(src=self.meter, dst=self.foot)
64
+ self.assertAlmostEqual(m(1), 3.28084, places=4)
65
+
66
+ def test_composed_path(self):
67
+ self.graph.add_edge(src=self.meter, dst=self.foot, map=LinearMap(3.28084))
68
+ self.graph.add_edge(src=self.foot, dst=self.inch, map=LinearMap(12))
69
+ m = self.graph.convert(src=self.meter, dst=self.inch)
70
+ self.assertAlmostEqual(m(1), 39.37008, places=3)
71
+
72
+ def test_multi_hop_path(self):
73
+ self.graph.add_edge(src=self.meter, dst=self.foot, map=LinearMap(3.28084))
74
+ self.graph.add_edge(src=self.foot, dst=self.inch, map=LinearMap(12))
75
+ self.graph.add_edge(src=self.inch, dst=self.yard, map=LinearMap(1/36))
76
+ m = self.graph.convert(src=self.meter, dst=self.yard)
77
+ self.assertAlmostEqual(m(1), 1.0936, places=3)
78
+
79
+ def test_no_path_raises(self):
80
+ mile = Unit(name='mile', dimension=Dimension.length, aliases=('mi',))
81
+ with self.assertRaises(ConversionNotFound):
82
+ self.graph.convert(src=self.meter, dst=mile)
83
+
84
+ def test_dimension_mismatch_on_convert(self):
85
+ with self.assertRaises(DimensionMismatch):
86
+ self.graph.convert(src=self.meter, dst=units.second)
87
+
88
+
89
+ class TestConversionGraphProductConversion(unittest.TestCase):
90
+
91
+ def setUp(self):
92
+ self.graph = ConversionGraph()
93
+ self.meter = units.meter
94
+ self.second = units.second
95
+ self.mile = Unit(name='mile', dimension=Dimension.length, aliases=('mi',))
96
+ self.hour = Unit(name='hour', dimension=Dimension.time, aliases=('h',))
97
+
98
+ # Register unit conversions
99
+ self.graph.add_edge(src=self.meter, dst=self.mile, map=LinearMap(0.000621371))
100
+ self.graph.add_edge(src=self.second, dst=self.hour, map=LinearMap(1/3600))
101
+
102
+ def test_factorwise_velocity_conversion(self):
103
+ m_per_s = UnitProduct({
104
+ UnitFactor(self.meter, Scale.one): 1,
105
+ UnitFactor(self.second, Scale.one): -1,
106
+ })
107
+ mi_per_hr = UnitProduct({
108
+ UnitFactor(self.mile, Scale.one): 1,
109
+ UnitFactor(self.hour, Scale.one): -1,
110
+ })
111
+
112
+ m = self.graph.convert(src=m_per_s, dst=mi_per_hr)
113
+ # 1 m/s = 2.23694 mi/h
114
+ self.assertAlmostEqual(m(1), 2.237, places=2)
115
+
116
+ def test_scale_ratio_in_factorwise(self):
117
+ km = UnitProduct({UnitFactor(self.meter, Scale.kilo): 1})
118
+ m = UnitProduct({UnitFactor(self.meter, Scale.one): 1})
119
+
120
+ conversion = self.graph.convert(src=km, dst=m)
121
+ self.assertAlmostEqual(conversion(1), 1000, places=6)
122
+
123
+ def test_direct_product_edge(self):
124
+ # Define joule and watt_hour as UnitProducts
125
+ g = units.gram
126
+ joule = UnitProduct({
127
+ UnitFactor(g, Scale.one): 1,
128
+ UnitFactor(self.meter, Scale.one): 2,
129
+ UnitFactor(self.second, Scale.one): -2,
130
+ })
131
+ watt = UnitProduct({
132
+ UnitFactor(g, Scale.one): 1,
133
+ UnitFactor(self.meter, Scale.one): 2,
134
+ UnitFactor(self.second, Scale.one): -3,
135
+ })
136
+ watt_hour = watt * UnitProduct({UnitFactor(self.hour, Scale.one): 1})
137
+
138
+ # Register direct edge
139
+ self.graph.add_edge(src=joule, dst=watt_hour, map=LinearMap(1/3600))
140
+
141
+ m = self.graph.convert(src=joule, dst=watt_hour)
142
+ self.assertAlmostEqual(m(7200), 2.0, places=6)
143
+
144
+ def test_product_identity(self):
145
+ m_per_s = UnitProduct({
146
+ UnitFactor(self.meter, Scale.one): 1,
147
+ UnitFactor(self.second, Scale.one): -1,
148
+ })
149
+ m = self.graph.convert(src=m_per_s, dst=m_per_s)
150
+ self.assertTrue(m.is_identity())
151
+
152
+
153
+ class TestConversionGraphTemperature(unittest.TestCase):
154
+
155
+ def setUp(self):
156
+ self.graph = ConversionGraph()
157
+ self.celsius = Unit(name='celsius', dimension=Dimension.temperature, aliases=('°C',))
158
+ self.kelvin = Unit(name='kelvin', dimension=Dimension.temperature, aliases=('K',))
159
+ self.fahrenheit = Unit(name='fahrenheit', dimension=Dimension.temperature, aliases=('°F',))
160
+
161
+ def test_celsius_to_kelvin(self):
162
+ self.graph.add_edge(src=self.celsius, dst=self.kelvin, map=AffineMap(1, 273.15))
163
+ m = self.graph.convert(src=self.celsius, dst=self.kelvin)
164
+ self.assertAlmostEqual(m(0), 273.15, places=2)
165
+ self.assertAlmostEqual(m(100), 373.15, places=2)
166
+
167
+ def test_celsius_to_fahrenheit_via_kelvin(self):
168
+ # C → K: K = C + 273.15
169
+ self.graph.add_edge(src=self.celsius, dst=self.kelvin, map=AffineMap(1, 273.15))
170
+ # F → K: K = (F - 32) * 5/9 + 273.15 = 5/9 * F + 255.372
171
+ self.graph.add_edge(src=self.fahrenheit, dst=self.kelvin, map=AffineMap(5/9, 255.372))
172
+
173
+ m = self.graph.convert(src=self.celsius, dst=self.fahrenheit)
174
+ self.assertAlmostEqual(m(0), 32, places=0)
175
+ self.assertAlmostEqual(m(100), 212, places=0)
@@ -0,0 +1,163 @@
1
+ # © 2025 The Radiativity Company
2
+ # Licensed under the Apache License, Version 2.0
3
+ # See the LICENSE file for details.
4
+
5
+ import unittest
6
+
7
+ from ucon.maps import AffineMap, ComposedMap, LinearMap, Map
8
+
9
+
10
+ class TestLinearMap(unittest.TestCase):
11
+
12
+ def test_apply(self):
13
+ m = LinearMap(39.37)
14
+ self.assertAlmostEqual(m(1.0), 39.37)
15
+ self.assertAlmostEqual(m(0.0), 0.0)
16
+ self.assertAlmostEqual(m(2.5), 98.425)
17
+
18
+ def test_inverse(self):
19
+ m = LinearMap(39.37)
20
+ inv = m.inverse()
21
+ self.assertIsInstance(inv, LinearMap)
22
+ self.assertAlmostEqual(inv.a, 1.0 / 39.37)
23
+
24
+ def test_inverse_zero_raises(self):
25
+ m = LinearMap(0)
26
+ with self.assertRaises(ZeroDivisionError):
27
+ m.inverse()
28
+
29
+ def test_round_trip(self):
30
+ m = LinearMap(39.37)
31
+ for x in [0.0, 1.0, -5.5, 1000.0]:
32
+ self.assertAlmostEqual(m.inverse()(m(x)), x, places=10)
33
+
34
+ def test_compose_closed(self):
35
+ f = LinearMap(39.37)
36
+ g = LinearMap(1.0 / 12.0)
37
+ composed = f @ g
38
+ self.assertIsInstance(composed, LinearMap)
39
+ self.assertAlmostEqual(composed.a, 39.37 / 12.0)
40
+
41
+ def test_compose_apply(self):
42
+ f = LinearMap(2.0)
43
+ g = LinearMap(3.0)
44
+ # (f @ g)(x) = f(g(x)) = 2 * (3 * x) = 6x
45
+ self.assertAlmostEqual((f @ g)(5.0), 30.0)
46
+
47
+ def test_identity(self):
48
+ ident = LinearMap.identity()
49
+ self.assertAlmostEqual(ident(42.0), 42.0)
50
+ m = LinearMap(7.0)
51
+ self.assertEqual(m @ ident, m)
52
+ self.assertEqual(ident @ m, m)
53
+
54
+ def test_invertible(self):
55
+ self.assertTrue(LinearMap(5.0).invertible)
56
+ self.assertFalse(LinearMap(0).invertible)
57
+
58
+ def test_eq(self):
59
+ self.assertEqual(LinearMap(3.0), LinearMap(3.0))
60
+ self.assertNotEqual(LinearMap(3.0), LinearMap(4.0))
61
+
62
+ def test_repr(self):
63
+ self.assertIn("39.37", repr(LinearMap(39.37)))
64
+
65
+ def test_matmul_non_map_returns_not_implemented(self):
66
+ m = LinearMap(2.0)
67
+ result = m.__matmul__(42)
68
+ self.assertIs(result, NotImplemented)
69
+
70
+
71
+ class TestAffineMap(unittest.TestCase):
72
+
73
+ def test_apply(self):
74
+ # Celsius to Fahrenheit: F = 1.8 * C + 32
75
+ c_to_f = AffineMap(1.8, 32.0)
76
+ self.assertAlmostEqual(c_to_f(0.0), 32.0)
77
+ self.assertAlmostEqual(c_to_f(100.0), 212.0)
78
+ self.assertAlmostEqual(c_to_f(-40.0), -40.0)
79
+
80
+ def test_inverse(self):
81
+ c_to_f = AffineMap(1.8, 32.0)
82
+ f_to_c = c_to_f.inverse()
83
+ self.assertIsInstance(f_to_c, AffineMap)
84
+ self.assertAlmostEqual(f_to_c(32.0), 0.0)
85
+ self.assertAlmostEqual(f_to_c(212.0), 100.0)
86
+
87
+ def test_inverse_zero_raises(self):
88
+ m = AffineMap(0, 5.0)
89
+ with self.assertRaises(ZeroDivisionError):
90
+ m.inverse()
91
+
92
+ def test_round_trip(self):
93
+ m = AffineMap(1.8, 32.0)
94
+ for x in [0.0, 100.0, -40.0, 37.5]:
95
+ self.assertAlmostEqual(m.inverse()(m(x)), x, places=10)
96
+
97
+ def test_compose_closed(self):
98
+ f = AffineMap(2.0, 3.0)
99
+ g = AffineMap(4.0, 5.0)
100
+ composed = f @ g
101
+ self.assertIsInstance(composed, AffineMap)
102
+ # f(g(x)) = 2*(4x+5)+3 = 8x+13
103
+ self.assertAlmostEqual(composed.a, 8.0)
104
+ self.assertAlmostEqual(composed.b, 13.0)
105
+
106
+ def test_compose_apply(self):
107
+ f = AffineMap(2.0, 3.0)
108
+ g = AffineMap(4.0, 5.0)
109
+ for x in [0.0, 1.0, -2.0]:
110
+ self.assertAlmostEqual((f @ g)(x), f(g(x)), places=10)
111
+
112
+ def test_invertible(self):
113
+ self.assertTrue(AffineMap(1.8, 32.0).invertible)
114
+ self.assertFalse(AffineMap(0, 32.0).invertible)
115
+
116
+ def test_eq(self):
117
+ self.assertEqual(AffineMap(1.8, 32.0), AffineMap(1.8, 32.0))
118
+ self.assertNotEqual(AffineMap(1.8, 32.0), AffineMap(1.8, 0.0))
119
+
120
+ def test_repr(self):
121
+ r = repr(AffineMap(1.8, 32.0))
122
+ self.assertIn("1.8", r)
123
+ self.assertIn("32.0", r)
124
+
125
+
126
+ class TestComposedMap(unittest.TestCase):
127
+
128
+ def test_heterogeneous_composition(self):
129
+ # LinearMap @ AffineMap now returns AffineMap (closed composition)
130
+ # Use ComposedMap directly to test the fallback behavior
131
+ lin = LinearMap(2.0)
132
+ aff = AffineMap(3.0, 1.0)
133
+ composed = ComposedMap(lin, aff)
134
+ # lin(aff(x)) = 2 * (3x + 1) = 6x + 2
135
+ self.assertAlmostEqual(composed(0.0), 2.0)
136
+ self.assertAlmostEqual(composed(1.0), 8.0)
137
+
138
+ def test_inverse(self):
139
+ composed = ComposedMap(LinearMap(2.0), AffineMap(3.0, 1.0))
140
+ for x in [0.0, 1.0, -3.0, 10.0]:
141
+ self.assertAlmostEqual(composed.inverse()(composed(x)), x, places=10)
142
+
143
+ def test_invertible(self):
144
+ self.assertTrue(ComposedMap(LinearMap(2.0), AffineMap(3.0, 1.0)).invertible)
145
+ self.assertFalse(ComposedMap(LinearMap(0), AffineMap(3.0, 1.0)).invertible)
146
+
147
+ def test_non_invertible_raises(self):
148
+ composed = ComposedMap(LinearMap(0), AffineMap(3.0, 1.0))
149
+ with self.assertRaises(ValueError):
150
+ composed.inverse()
151
+
152
+ def test_repr(self):
153
+ composed = ComposedMap(LinearMap(2.0), AffineMap(3.0, 1.0))
154
+ r = repr(composed)
155
+ self.assertIn("LinearMap", r)
156
+ self.assertIn("AffineMap", r)
157
+
158
+
159
+ class TestMapABC(unittest.TestCase):
160
+
161
+ def test_cannot_instantiate(self):
162
+ with self.assertRaises(TypeError):
163
+ Map()
@@ -11,41 +11,41 @@ from ucon.algebra import Exponent, Vector
11
11
  class TestVector(TestCase):
12
12
 
13
13
  def test_vector_iteration_and_length(self):
14
- v = Vector(1, 0, 0, 0, 0, 0, 0)
15
- self.assertEqual(tuple(v), (1, 0, 0, 0, 0, 0, 0))
16
- self.assertEqual(len(v), 7) # always 7 components
14
+ v = Vector(1, 0, 0, 0, 0, 0, 0, 0)
15
+ self.assertEqual(tuple(v), (1, 0, 0, 0, 0, 0, 0, 0))
16
+ self.assertEqual(len(v), 8) # 7 SI + 1 information
17
17
 
18
18
  def test_vector_addition(self):
19
- v1 = Vector(1, 0, 0, 0, 0, 0, 0)
20
- v2 = Vector(0, 2, 0, 0, 0, 0, 0)
19
+ v1 = Vector(1, 0, 0, 0, 0, 0, 0, 0)
20
+ v2 = Vector(0, 2, 0, 0, 0, 0, 0, 0)
21
21
  result = v1 + v2
22
- self.assertEqual(result, Vector(1, 2, 0, 0, 0, 0, 0))
22
+ self.assertEqual(result, Vector(1, 2, 0, 0, 0, 0, 0, 0))
23
23
 
24
24
  def test_vector_subtraction(self):
25
- v1 = Vector(2, 1, 0, 0, 0, 0, 0)
26
- v2 = Vector(1, 1, 0, 0, 0, 0, 0)
27
- self.assertEqual(v1 - v2, Vector(1, 0, 0, 0, 0, 0, 0))
25
+ v1 = Vector(2, 1, 0, 0, 0, 0, 0, 0)
26
+ v2 = Vector(1, 1, 0, 0, 0, 0, 0, 0)
27
+ self.assertEqual(v1 - v2, Vector(1, 0, 0, 0, 0, 0, 0, 0))
28
28
 
29
29
  def test_vector_scalar_multiplication_by_integer(self):
30
- v = Vector(1, -2, 0, 0, 0, 0, 3)
30
+ v = Vector(1, -2, 0, 0, 0, 0, 3, 0)
31
31
  scaled = v * 2
32
- self.assertEqual(scaled, Vector(2, -4, 0, 0, 0, 0, 6))
33
- self.assertEqual(v, Vector(1, -2, 0, 0, 0, 0, 3)) # original unchanged
32
+ self.assertEqual(scaled, Vector(2, -4, 0, 0, 0, 0, 6, 0))
33
+ self.assertEqual(v, Vector(1, -2, 0, 0, 0, 0, 3, 0)) # original unchanged
34
34
 
35
35
  def test_vector_scalar_multiplication_by_float(self):
36
- v = Vector(0, 1, 0, 0, 0, 0, 0)
36
+ v = Vector(0, 1, 0, 0, 0, 0, 0, 0)
37
37
  scaled = v * 0.5
38
- self.assertEqual(scaled, Vector(0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0))
38
+ self.assertEqual(scaled, Vector(0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0))
39
39
 
40
40
  def test_vector_scalar_multiplication_by_zero(self):
41
- v = Vector(1, 2, 3, 4, 5, 6, 7)
41
+ v = Vector(1, 2, 3, 4, 5, 6, 7, 8)
42
42
  zeroed = v * 0
43
- self.assertEqual(zeroed, Vector(0, 0, 0, 0, 0, 0, 0))
43
+ self.assertEqual(zeroed, Vector(0, 0, 0, 0, 0, 0, 0, 0))
44
44
 
45
45
  def test_vector_equality_and_hash(self):
46
- v1 = Vector(1, 0, 0, 0, 0, 0, 0)
47
- v2 = Vector(1, 0, 0, 0, 0, 0, 0)
48
- v3 = Vector(0, 1, 0, 0, 0, 0, 0)
46
+ v1 = Vector(1, 0, 0, 0, 0, 0, 0, 0)
47
+ v2 = Vector(1, 0, 0, 0, 0, 0, 0, 0)
48
+ v3 = Vector(0, 1, 0, 0, 0, 0, 0, 0)
49
49
  self.assertTrue(v1 == v2)
50
50
  self.assertFalse(v1 == v3)
51
51
  self.assertEqual(hash(v1), hash(v2))
@@ -56,17 +56,17 @@ class TestVectorEdgeCases(TestCase):
56
56
 
57
57
  def test_zero_vector_equality_and_additivity(self):
58
58
  zero = Vector()
59
- self.assertEqual(zero, Vector(0, 0, 0, 0, 0, 0, 0))
59
+ self.assertEqual(zero, Vector(0, 0, 0, 0, 0, 0, 0, 0))
60
60
  # Adding or subtracting zero should yield same vector
61
- v = Vector(1, 2, 3, 4, 5, 6, 7)
61
+ v = Vector(1, 2, 3, 4, 5, 6, 7, 8)
62
62
  self.assertEqual(v + zero, v)
63
63
  self.assertEqual(v - zero, v)
64
64
 
65
65
  def test_vector_with_negative_exponents(self):
66
- v1 = Vector(1, -2, 3, 0, 0, 0, 0)
67
- v2 = Vector(-1, 2, -3, 0, 0, 0, 0)
66
+ v1 = Vector(1, -2, 3, 0, 0, 0, 0, 0)
67
+ v2 = Vector(-1, 2, -3, 0, 0, 0, 0, 0)
68
68
  result = v1 + v2
69
- self.assertEqual(result, Vector(0, 0, 0, 0, 0, 0, 0))
69
+ self.assertEqual(result, Vector(0, 0, 0, 0, 0, 0, 0, 0))
70
70
  self.assertEqual(v1 - v1, Vector()) # perfect cancellation
71
71
 
72
72
  def test_vector_equality_with_non_vector(self):
@@ -77,27 +77,27 @@ class TestVectorEdgeCases(TestCase):
77
77
  v == None
78
78
 
79
79
  def test_hash_consistency_for_equal_vectors(self):
80
- v1 = Vector(1, 0, 0, 0, 0, 0, 0)
81
- v2 = Vector(1, 0, 0, 0, 0, 0, 0)
80
+ v1 = Vector(1, 0, 0, 0, 0, 0, 0, 0)
81
+ v2 = Vector(1, 0, 0, 0, 0, 0, 0, 0)
82
82
  self.assertEqual(hash(v1), hash(v2))
83
83
  self.assertEqual(len({v1, v2}), 1)
84
84
 
85
85
  def test_iter_length_order_consistency(self):
86
- v = Vector(1, 2, 3, 4, 5, 6, 7)
86
+ v = Vector(1, 2, 3, 4, 5, 6, 7, 8)
87
87
  components = list(v)
88
88
  self.assertEqual(len(components), len(v))
89
- # Ensure order of iteration is fixed (T→L→M→I→Θ→J→N)
90
- self.assertEqual(components, [1, 2, 3, 4, 5, 6, 7])
89
+ # Ensure order of iteration is fixed (T→L→M→I→Θ→J→N→B)
90
+ self.assertEqual(components, [1, 2, 3, 4, 5, 6, 7, 8])
91
91
 
92
92
  def test_vector_arithmetic_does_not_mutate_operands(self):
93
- v1 = Vector(1, 0, 0, 0, 0, 0, 0)
94
- v2 = Vector(0, 1, 0, 0, 0, 0, 0)
93
+ v1 = Vector(1, 0, 0, 0, 0, 0, 0, 0)
94
+ v2 = Vector(0, 1, 0, 0, 0, 0, 0, 0)
95
95
  _ = v1 + v2
96
- self.assertEqual(v1, Vector(1, 0, 0, 0, 0, 0, 0))
97
- self.assertEqual(v2, Vector(0, 1, 0, 0, 0, 0, 0))
96
+ self.assertEqual(v1, Vector(1, 0, 0, 0, 0, 0, 0, 0))
97
+ self.assertEqual(v2, Vector(0, 1, 0, 0, 0, 0, 0, 0))
98
98
 
99
99
  def test_invalid_addition_type_raises(self):
100
- v = Vector(1, 0, 0, 0, 0, 0, 0)
100
+ v = Vector(1, 0, 0, 0, 0, 0, 0, 0)
101
101
  with self.assertRaises(TypeError):
102
102
  _ = v + "length"
103
103
  with self.assertRaises(TypeError):
tests/ucon/test_core.py CHANGED
@@ -451,7 +451,7 @@ class TestUnit(unittest.TestCase):
451
451
  unit_name = 'second'
452
452
  unit_type = 'time'
453
453
  unit_aliases = ('seconds', 'secs', 's', 'S')
454
- unit = Unit(*unit_aliases, name=unit_name, dimension=Dimension.time)
454
+ unit = Unit(name=unit_name, dimension=Dimension.time, aliases=unit_aliases)
455
455
 
456
456
  def test___repr__(self):
457
457
  self.assertEqual(f'<Unit {self.unit_aliases[0]}>', str(self.unit))
@@ -464,12 +464,12 @@ class TestUnit(unittest.TestCase):
464
464
 
465
465
  def test_unit_equality_alias_normalization(self):
466
466
  # ('',) should normalize to () under _norm
467
- u1 = Unit("", name="x", dimension=Dimension.length)
467
+ u1 = Unit(name="x", dimension=Dimension.length, aliases=("",))
468
468
  u2 = Unit(name="x", dimension=Dimension.length)
469
469
  self.assertEqual(u1, u2)
470
470
 
471
471
  def test_unit_invalid_eq_type(self):
472
- self.assertFalse(Unit("m", dimension=Dimension.length) == "meter")
472
+ self.assertFalse(Unit(name="meter", dimension=Dimension.length, aliases=("m",)) == "meter")
473
473
 
474
474
 
475
475
  class TestUnitProduct(unittest.TestCase):
@@ -544,7 +544,7 @@ class TestUnitEdgeCases(unittest.TestCase):
544
544
  self.assertEqual(repr(u), '<Unit>')
545
545
 
546
546
  def test_unit_with_aliases_and_name(self):
547
- u = Unit('m', 'M', name='meter', dimension=Dimension.length)
547
+ u = Unit(name='meter', dimension=Dimension.length, aliases=('m', 'M'))
548
548
  self.assertEqual(u.shorthand, 'm')
549
549
  self.assertIn('m', u.aliases)
550
550
  self.assertIn('M', u.aliases)
@@ -553,9 +553,9 @@ class TestUnitEdgeCases(unittest.TestCase):
553
553
  self.assertIn('<Unit m>', repr(u))
554
554
 
555
555
  def test_hash_and_equality_consistency(self):
556
- u1 = Unit('m', name='meter', dimension=Dimension.length)
557
- u2 = Unit('m', name='meter', dimension=Dimension.length)
558
- u3 = Unit('s', name='second', dimension=Dimension.time)
556
+ u1 = Unit(name='meter', dimension=Dimension.length, aliases=('m',))
557
+ u2 = Unit(name='meter', dimension=Dimension.length, aliases=('m',))
558
+ u3 = Unit(name='second', dimension=Dimension.time, aliases=('s',))
559
559
  self.assertEqual(u1, u2)
560
560
  self.assertEqual(hash(u1), hash(u2))
561
561
  self.assertNotEqual(u1, u3)
@@ -569,36 +569,36 @@ class TestUnitEdgeCases(unittest.TestCase):
569
569
  # --- arithmetic behavior ----------------------------------------------
570
570
 
571
571
  def test_multiplication_produces_composite_unit(self):
572
- m = Unit('m', name='meter', dimension=Dimension.length)
573
- s = Unit('s', name='second', dimension=Dimension.time)
572
+ m = Unit(name='meter', dimension=Dimension.length, aliases=('m',))
573
+ s = Unit(name='second', dimension=Dimension.time, aliases=('s',))
574
574
  v = m / s
575
575
  self.assertIsInstance(v, UnitProduct)
576
576
  self.assertEqual(v.dimension, Dimension.velocity)
577
577
  self.assertIn('/', repr(v))
578
578
 
579
579
  def test_division_with_dimensionless_denominator_returns_self(self):
580
- m = Unit('m', name='meter', dimension=Dimension.length)
580
+ m = Unit(name='meter', dimension=Dimension.length, aliases=('m',))
581
581
  none = Unit(name='none', dimension=Dimension.none)
582
582
  result = m / none
583
583
  self.assertEqual(result, m)
584
584
 
585
585
  def test_division_of_identical_units_returns_dimensionless(self):
586
- m1 = Unit('m', name='meter', dimension=Dimension.length)
587
- m2 = Unit('m', name='meter', dimension=Dimension.length)
586
+ m1 = Unit(name='meter', dimension=Dimension.length, aliases=('m',))
587
+ m2 = Unit(name='meter', dimension=Dimension.length, aliases=('m',))
588
588
  result = m1 / m2
589
589
  self.assertEqual(result.dimension, Dimension.none)
590
590
  self.assertEqual(result.name, '')
591
591
 
592
592
  def test_multiplying_with_dimensionless_returns_self(self):
593
- m = Unit('m', name='meter', dimension=Dimension.length)
593
+ m = Unit(name='meter', dimension=Dimension.length, aliases=('m',))
594
594
  none = Unit(name='none', dimension=Dimension.none)
595
595
  result = m * none
596
596
  self.assertEqual(result.dimension, Dimension.length)
597
597
  self.assertEqual('m', result.shorthand)
598
598
 
599
599
  def test_invalid_dimension_combinations_raise_value_error(self):
600
- m = Unit('m', name='meter', dimension=Dimension.length)
601
- c = Unit('C', name='coulomb', dimension=Dimension.charge)
600
+ m = Unit(name='meter', dimension=Dimension.length, aliases=('m',))
601
+ c = Unit(name='coulomb', dimension=Dimension.charge, aliases=('C',))
602
602
  # The result of combination gives CompositeUnit
603
603
  self.assertIsInstance(m / c, UnitProduct)
604
604
  self.assertIsInstance(m * c, UnitProduct)
@@ -606,16 +606,16 @@ class TestUnitEdgeCases(unittest.TestCase):
606
606
  # --- equality, hashing, immutability ----------------------------------
607
607
 
608
608
  def test_equality_with_non_unit(self):
609
- self.assertFalse(Unit('m', name='meter', dimension=Dimension.length) == 'meter')
609
+ self.assertFalse(Unit(name='meter', dimension=Dimension.length, aliases=('m',)) == 'meter')
610
610
 
611
611
  def test_hash_stability_in_collections(self):
612
- m1 = Unit('m', name='meter', dimension=Dimension.length)
612
+ m1 = Unit(name='meter', dimension=Dimension.length, aliases=('m',))
613
613
  s = set([m1])
614
- self.assertIn(Unit('m', name='meter', dimension=Dimension.length), s)
614
+ self.assertIn(Unit(name='meter', dimension=Dimension.length, aliases=('m',)), s)
615
615
 
616
616
  def test_operations_do_not_mutate_operands(self):
617
- m = Unit('m', name='meter', dimension=Dimension.length)
618
- s = Unit('s', name='second', dimension=Dimension.time)
617
+ m = Unit(name='meter', dimension=Dimension.length, aliases=('m',))
618
+ s = Unit(name='second', dimension=Dimension.time, aliases=('s',))
619
619
  _ = m / s
620
620
  self.assertEqual(m.dimension, Dimension.length)
621
621
  self.assertEqual(s.dimension, Dimension.time)