ucon 0.5.2__py3-none-any.whl → 0.6.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/__init__.py +4 -1
- ucon/core.py +7 -2
- ucon/graph.py +8 -6
- ucon/maps.py +116 -0
- ucon/mcp/__init__.py +8 -0
- ucon/mcp/server.py +250 -0
- ucon/pydantic.py +199 -0
- ucon/units.py +261 -12
- {ucon-0.5.2.dist-info → ucon-0.6.1.dist-info}/METADATA +119 -98
- ucon-0.6.1.dist-info/RECORD +17 -0
- ucon-0.6.1.dist-info/entry_points.txt +2 -0
- ucon-0.6.1.dist-info/top_level.txt +1 -0
- tests/ucon/__init__.py +0 -3
- tests/ucon/conversion/__init__.py +0 -0
- tests/ucon/conversion/test_graph.py +0 -409
- tests/ucon/conversion/test_map.py +0 -409
- tests/ucon/test_algebra.py +0 -239
- tests/ucon/test_basis_transform.py +0 -521
- tests/ucon/test_core.py +0 -827
- tests/ucon/test_default_graph_conversions.py +0 -443
- tests/ucon/test_dimensionless_units.py +0 -248
- tests/ucon/test_graph_basis_transform.py +0 -263
- tests/ucon/test_quantity.py +0 -615
- tests/ucon/test_rebased_unit.py +0 -184
- tests/ucon/test_uncertainty.py +0 -264
- tests/ucon/test_unit_system.py +0 -174
- tests/ucon/test_units.py +0 -25
- tests/ucon/test_vector_fraction.py +0 -185
- ucon-0.5.2.dist-info/RECORD +0 -29
- ucon-0.5.2.dist-info/top_level.txt +0 -2
- {ucon-0.5.2.dist-info → ucon-0.6.1.dist-info}/WHEEL +0 -0
- {ucon-0.5.2.dist-info → ucon-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {ucon-0.5.2.dist-info → ucon-0.6.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,521 +0,0 @@
|
|
|
1
|
-
# (c) 2026 The Radiativity Company
|
|
2
|
-
# Licensed under the Apache License, Version 2.0
|
|
3
|
-
# See the LICENSE file for details.
|
|
4
|
-
|
|
5
|
-
"""
|
|
6
|
-
Tests for BasisTransform.
|
|
7
|
-
|
|
8
|
-
Verifies matrix-based dimensional basis transformations,
|
|
9
|
-
invertibility detection, and exact Fraction arithmetic.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
import unittest
|
|
13
|
-
from fractions import Fraction
|
|
14
|
-
|
|
15
|
-
from ucon.core import (
|
|
16
|
-
BasisTransform,
|
|
17
|
-
Dimension,
|
|
18
|
-
NonInvertibleTransform,
|
|
19
|
-
Unit,
|
|
20
|
-
UnitSystem,
|
|
21
|
-
)
|
|
22
|
-
from ucon import units
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class TestBasisTransformConstruction(unittest.TestCase):
|
|
26
|
-
"""Test BasisTransform construction and validation."""
|
|
27
|
-
|
|
28
|
-
def setUp(self):
|
|
29
|
-
self.si = UnitSystem(
|
|
30
|
-
name="SI",
|
|
31
|
-
bases={
|
|
32
|
-
Dimension.length: units.meter,
|
|
33
|
-
Dimension.mass: units.kilogram,
|
|
34
|
-
Dimension.time: units.second,
|
|
35
|
-
}
|
|
36
|
-
)
|
|
37
|
-
self.custom = UnitSystem(
|
|
38
|
-
name="Custom",
|
|
39
|
-
bases={
|
|
40
|
-
Dimension.length: units.foot,
|
|
41
|
-
Dimension.mass: units.pound,
|
|
42
|
-
Dimension.time: units.second,
|
|
43
|
-
}
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
def test_valid_construction(self):
|
|
47
|
-
bt = BasisTransform(
|
|
48
|
-
src=self.custom,
|
|
49
|
-
dst=self.si,
|
|
50
|
-
src_dimensions=(Dimension.length,),
|
|
51
|
-
dst_dimensions=(Dimension.length,),
|
|
52
|
-
matrix=((1,),),
|
|
53
|
-
)
|
|
54
|
-
self.assertEqual(bt.src, self.custom)
|
|
55
|
-
self.assertEqual(bt.dst, self.si)
|
|
56
|
-
|
|
57
|
-
def test_matrix_converted_to_fraction(self):
|
|
58
|
-
bt = BasisTransform(
|
|
59
|
-
src=self.custom,
|
|
60
|
-
dst=self.si,
|
|
61
|
-
src_dimensions=(Dimension.length, Dimension.mass),
|
|
62
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
63
|
-
matrix=((1, 0), (0, 1)),
|
|
64
|
-
)
|
|
65
|
-
self.assertIsInstance(bt.matrix[0][0], Fraction)
|
|
66
|
-
self.assertIsInstance(bt.matrix[1][1], Fraction)
|
|
67
|
-
|
|
68
|
-
def test_dimension_count_mismatch_rows(self):
|
|
69
|
-
with self.assertRaises(ValueError) as ctx:
|
|
70
|
-
BasisTransform(
|
|
71
|
-
src=self.custom,
|
|
72
|
-
dst=self.si,
|
|
73
|
-
src_dimensions=(Dimension.length,),
|
|
74
|
-
dst_dimensions=(Dimension.length, Dimension.mass), # 2 dims
|
|
75
|
-
matrix=((1,),), # only 1 row
|
|
76
|
-
)
|
|
77
|
-
self.assertIn("row", str(ctx.exception).lower())
|
|
78
|
-
|
|
79
|
-
def test_dimension_count_mismatch_cols(self):
|
|
80
|
-
with self.assertRaises(ValueError) as ctx:
|
|
81
|
-
BasisTransform(
|
|
82
|
-
src=self.custom,
|
|
83
|
-
dst=self.si,
|
|
84
|
-
src_dimensions=(Dimension.length, Dimension.mass), # 2 dims
|
|
85
|
-
dst_dimensions=(Dimension.length,),
|
|
86
|
-
matrix=((1,),), # only 1 column
|
|
87
|
-
)
|
|
88
|
-
self.assertIn("column", str(ctx.exception).lower())
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
class TestBasisTransformSquareMatrix(unittest.TestCase):
|
|
92
|
-
"""Test BasisTransform with square matrices."""
|
|
93
|
-
|
|
94
|
-
def setUp(self):
|
|
95
|
-
self.si = UnitSystem(
|
|
96
|
-
name="SI",
|
|
97
|
-
bases={
|
|
98
|
-
Dimension.length: units.meter,
|
|
99
|
-
Dimension.mass: units.kilogram,
|
|
100
|
-
Dimension.time: units.second,
|
|
101
|
-
}
|
|
102
|
-
)
|
|
103
|
-
self.custom = UnitSystem(
|
|
104
|
-
name="Custom",
|
|
105
|
-
bases={
|
|
106
|
-
Dimension.length: units.foot,
|
|
107
|
-
Dimension.mass: units.pound,
|
|
108
|
-
Dimension.time: units.second,
|
|
109
|
-
}
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
def test_1x1_identity_transform(self):
|
|
113
|
-
bt = BasisTransform(
|
|
114
|
-
src=self.custom,
|
|
115
|
-
dst=self.si,
|
|
116
|
-
src_dimensions=(Dimension.length,),
|
|
117
|
-
dst_dimensions=(Dimension.length,),
|
|
118
|
-
matrix=((1,),),
|
|
119
|
-
)
|
|
120
|
-
self.assertTrue(bt.is_square)
|
|
121
|
-
self.assertTrue(bt.is_invertible)
|
|
122
|
-
|
|
123
|
-
def test_2x2_identity_transform(self):
|
|
124
|
-
bt = BasisTransform(
|
|
125
|
-
src=self.custom,
|
|
126
|
-
dst=self.si,
|
|
127
|
-
src_dimensions=(Dimension.length, Dimension.mass),
|
|
128
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
129
|
-
matrix=((1, 0), (0, 1)),
|
|
130
|
-
)
|
|
131
|
-
self.assertTrue(bt.is_square)
|
|
132
|
-
self.assertTrue(bt.is_invertible)
|
|
133
|
-
|
|
134
|
-
def test_2x2_singular_not_invertible(self):
|
|
135
|
-
bt = BasisTransform(
|
|
136
|
-
src=self.custom,
|
|
137
|
-
dst=self.si,
|
|
138
|
-
src_dimensions=(Dimension.length, Dimension.mass),
|
|
139
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
140
|
-
matrix=((1, 2), (2, 4)), # Rows are linearly dependent
|
|
141
|
-
)
|
|
142
|
-
self.assertTrue(bt.is_square)
|
|
143
|
-
self.assertFalse(bt.is_invertible)
|
|
144
|
-
|
|
145
|
-
def test_3x3_invertible(self):
|
|
146
|
-
bt = BasisTransform(
|
|
147
|
-
src=self.custom,
|
|
148
|
-
dst=self.si,
|
|
149
|
-
src_dimensions=(Dimension.length, Dimension.mass, Dimension.time),
|
|
150
|
-
dst_dimensions=(Dimension.length, Dimension.mass, Dimension.time),
|
|
151
|
-
matrix=(
|
|
152
|
-
(1, 0, 0),
|
|
153
|
-
(0, 1, 0),
|
|
154
|
-
(0, 0, 1),
|
|
155
|
-
),
|
|
156
|
-
)
|
|
157
|
-
self.assertTrue(bt.is_square)
|
|
158
|
-
self.assertTrue(bt.is_invertible)
|
|
159
|
-
|
|
160
|
-
def test_determinant_2x2(self):
|
|
161
|
-
bt = BasisTransform(
|
|
162
|
-
src=self.custom,
|
|
163
|
-
dst=self.si,
|
|
164
|
-
src_dimensions=(Dimension.length, Dimension.mass),
|
|
165
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
166
|
-
matrix=((2, 3), (1, 4)), # det = 2*4 - 3*1 = 5
|
|
167
|
-
)
|
|
168
|
-
# Access internal _determinant method for testing
|
|
169
|
-
self.assertEqual(bt._determinant(), Fraction(5))
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
class TestBasisTransformInverse(unittest.TestCase):
|
|
173
|
-
"""Test BasisTransform inverse computation."""
|
|
174
|
-
|
|
175
|
-
def setUp(self):
|
|
176
|
-
self.si = UnitSystem(
|
|
177
|
-
name="SI",
|
|
178
|
-
bases={
|
|
179
|
-
Dimension.length: units.meter,
|
|
180
|
-
Dimension.mass: units.kilogram,
|
|
181
|
-
Dimension.time: units.second,
|
|
182
|
-
}
|
|
183
|
-
)
|
|
184
|
-
self.custom = UnitSystem(
|
|
185
|
-
name="Custom",
|
|
186
|
-
bases={
|
|
187
|
-
Dimension.length: units.foot,
|
|
188
|
-
Dimension.mass: units.pound,
|
|
189
|
-
Dimension.time: units.second,
|
|
190
|
-
}
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
def test_1x1_inverse(self):
|
|
194
|
-
bt = BasisTransform(
|
|
195
|
-
src=self.custom,
|
|
196
|
-
dst=self.si,
|
|
197
|
-
src_dimensions=(Dimension.length,),
|
|
198
|
-
dst_dimensions=(Dimension.length,),
|
|
199
|
-
matrix=((2,),), # scale by 2
|
|
200
|
-
)
|
|
201
|
-
inv = bt.inverse()
|
|
202
|
-
self.assertEqual(inv.matrix, ((Fraction(1, 2),),))
|
|
203
|
-
self.assertEqual(inv.src, self.si)
|
|
204
|
-
self.assertEqual(inv.dst, self.custom)
|
|
205
|
-
|
|
206
|
-
def test_2x2_inverse(self):
|
|
207
|
-
bt = BasisTransform(
|
|
208
|
-
src=self.custom,
|
|
209
|
-
dst=self.si,
|
|
210
|
-
src_dimensions=(Dimension.length, Dimension.mass),
|
|
211
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
212
|
-
matrix=((1, 0), (0, 1)),
|
|
213
|
-
)
|
|
214
|
-
inv = bt.inverse()
|
|
215
|
-
self.assertEqual(inv.matrix, ((Fraction(1), Fraction(0)), (Fraction(0), Fraction(1))))
|
|
216
|
-
|
|
217
|
-
def test_inverse_of_inverse_equals_original(self):
|
|
218
|
-
bt = BasisTransform(
|
|
219
|
-
src=self.custom,
|
|
220
|
-
dst=self.si,
|
|
221
|
-
src_dimensions=(Dimension.length, Dimension.mass),
|
|
222
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
223
|
-
matrix=((2, 1), (1, 1)), # det = 2*1 - 1*1 = 1
|
|
224
|
-
)
|
|
225
|
-
inv = bt.inverse()
|
|
226
|
-
inv_inv = inv.inverse()
|
|
227
|
-
self.assertEqual(bt.matrix, inv_inv.matrix)
|
|
228
|
-
|
|
229
|
-
def test_non_invertible_raises(self):
|
|
230
|
-
bt = BasisTransform(
|
|
231
|
-
src=self.custom,
|
|
232
|
-
dst=self.si,
|
|
233
|
-
src_dimensions=(Dimension.length, Dimension.mass),
|
|
234
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
235
|
-
matrix=((1, 2), (2, 4)), # Singular
|
|
236
|
-
)
|
|
237
|
-
with self.assertRaises(NonInvertibleTransform):
|
|
238
|
-
bt.inverse()
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
class TestBasisTransformNonSquare(unittest.TestCase):
|
|
242
|
-
"""Test BasisTransform with non-square (surjective) matrices."""
|
|
243
|
-
|
|
244
|
-
def setUp(self):
|
|
245
|
-
self.si = UnitSystem(
|
|
246
|
-
name="SI",
|
|
247
|
-
bases={
|
|
248
|
-
Dimension.length: units.meter,
|
|
249
|
-
Dimension.mass: units.kilogram,
|
|
250
|
-
Dimension.time: units.second,
|
|
251
|
-
}
|
|
252
|
-
)
|
|
253
|
-
self.reduced = UnitSystem(
|
|
254
|
-
name="Reduced",
|
|
255
|
-
bases={
|
|
256
|
-
Dimension.length: units.meter,
|
|
257
|
-
Dimension.mass: units.kilogram,
|
|
258
|
-
}
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
def test_2x3_not_square(self):
|
|
262
|
-
bt = BasisTransform(
|
|
263
|
-
src=self.si,
|
|
264
|
-
dst=self.reduced,
|
|
265
|
-
src_dimensions=(Dimension.length, Dimension.mass, Dimension.time),
|
|
266
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
267
|
-
matrix=(
|
|
268
|
-
(1, 0, 0),
|
|
269
|
-
(0, 1, 0),
|
|
270
|
-
),
|
|
271
|
-
)
|
|
272
|
-
self.assertFalse(bt.is_square)
|
|
273
|
-
self.assertFalse(bt.is_invertible)
|
|
274
|
-
|
|
275
|
-
def test_non_square_inverse_raises(self):
|
|
276
|
-
bt = BasisTransform(
|
|
277
|
-
src=self.si,
|
|
278
|
-
dst=self.reduced,
|
|
279
|
-
src_dimensions=(Dimension.length, Dimension.mass, Dimension.time),
|
|
280
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
281
|
-
matrix=(
|
|
282
|
-
(1, 0, 0),
|
|
283
|
-
(0, 1, 0),
|
|
284
|
-
),
|
|
285
|
-
)
|
|
286
|
-
with self.assertRaises(NonInvertibleTransform):
|
|
287
|
-
bt.inverse()
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
class TestBasisTransformTransform(unittest.TestCase):
|
|
291
|
-
"""Test the transform() method for vector transformation."""
|
|
292
|
-
|
|
293
|
-
def setUp(self):
|
|
294
|
-
self.si = UnitSystem(
|
|
295
|
-
name="SI",
|
|
296
|
-
bases={
|
|
297
|
-
Dimension.length: units.meter,
|
|
298
|
-
Dimension.mass: units.kilogram,
|
|
299
|
-
Dimension.time: units.second,
|
|
300
|
-
}
|
|
301
|
-
)
|
|
302
|
-
self.custom = UnitSystem(
|
|
303
|
-
name="Custom",
|
|
304
|
-
bases={
|
|
305
|
-
Dimension.length: units.foot,
|
|
306
|
-
Dimension.mass: units.pound,
|
|
307
|
-
Dimension.time: units.second,
|
|
308
|
-
}
|
|
309
|
-
)
|
|
310
|
-
|
|
311
|
-
def test_identity_transform_vector(self):
|
|
312
|
-
bt = BasisTransform(
|
|
313
|
-
src=self.custom,
|
|
314
|
-
dst=self.si,
|
|
315
|
-
src_dimensions=(Dimension.length,),
|
|
316
|
-
dst_dimensions=(Dimension.length,),
|
|
317
|
-
matrix=((1,),),
|
|
318
|
-
)
|
|
319
|
-
# Transform the length dimension vector
|
|
320
|
-
src_vector = Dimension.length.value
|
|
321
|
-
result = bt.transform(src_vector)
|
|
322
|
-
self.assertEqual(result, Dimension.length.value)
|
|
323
|
-
|
|
324
|
-
def test_scaling_transform_vector(self):
|
|
325
|
-
bt = BasisTransform(
|
|
326
|
-
src=self.custom,
|
|
327
|
-
dst=self.si,
|
|
328
|
-
src_dimensions=(Dimension.length,),
|
|
329
|
-
dst_dimensions=(Dimension.length,),
|
|
330
|
-
matrix=((2,),),
|
|
331
|
-
)
|
|
332
|
-
src_vector = Dimension.length.value
|
|
333
|
-
result = bt.transform(src_vector)
|
|
334
|
-
# Length component should be doubled
|
|
335
|
-
self.assertEqual(result.L, Fraction(2))
|
|
336
|
-
|
|
337
|
-
def test_mixed_transform_vector(self):
|
|
338
|
-
# Transform where mass includes contributions from multiple src dimensions
|
|
339
|
-
bt = BasisTransform(
|
|
340
|
-
src=self.custom,
|
|
341
|
-
dst=self.si,
|
|
342
|
-
src_dimensions=(Dimension.length, Dimension.mass),
|
|
343
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
344
|
-
matrix=(
|
|
345
|
-
(1, 0),
|
|
346
|
-
(1, 1), # dst_mass = src_length + src_mass
|
|
347
|
-
),
|
|
348
|
-
)
|
|
349
|
-
# Input: pure length (L=1, M=0)
|
|
350
|
-
src_vector = Dimension.length.value
|
|
351
|
-
result = bt.transform(src_vector)
|
|
352
|
-
self.assertEqual(result.L, Fraction(1))
|
|
353
|
-
self.assertEqual(result.M, Fraction(1)) # Contribution from length
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
class TestBasisTransformValidateEdge(unittest.TestCase):
|
|
357
|
-
"""Test validate_edge() for cross-basis edge validation."""
|
|
358
|
-
|
|
359
|
-
def setUp(self):
|
|
360
|
-
self.si = UnitSystem(
|
|
361
|
-
name="SI",
|
|
362
|
-
bases={
|
|
363
|
-
Dimension.length: units.meter,
|
|
364
|
-
Dimension.mass: units.kilogram,
|
|
365
|
-
Dimension.time: units.second,
|
|
366
|
-
}
|
|
367
|
-
)
|
|
368
|
-
self.custom = UnitSystem(
|
|
369
|
-
name="Custom",
|
|
370
|
-
bases={
|
|
371
|
-
Dimension.length: units.foot,
|
|
372
|
-
Dimension.mass: units.pound,
|
|
373
|
-
Dimension.time: units.second,
|
|
374
|
-
}
|
|
375
|
-
)
|
|
376
|
-
|
|
377
|
-
def test_valid_edge_same_dimension(self):
|
|
378
|
-
bt = BasisTransform(
|
|
379
|
-
src=self.custom,
|
|
380
|
-
dst=self.si,
|
|
381
|
-
src_dimensions=(Dimension.length,),
|
|
382
|
-
dst_dimensions=(Dimension.length,),
|
|
383
|
-
matrix=((1,),),
|
|
384
|
-
)
|
|
385
|
-
# foot (length) -> meter (length) should be valid
|
|
386
|
-
self.assertTrue(bt.validate_edge(units.foot, units.meter))
|
|
387
|
-
|
|
388
|
-
def test_invalid_edge_different_dimension(self):
|
|
389
|
-
bt = BasisTransform(
|
|
390
|
-
src=self.custom,
|
|
391
|
-
dst=self.si,
|
|
392
|
-
src_dimensions=(Dimension.length,),
|
|
393
|
-
dst_dimensions=(Dimension.length,),
|
|
394
|
-
matrix=((1,),),
|
|
395
|
-
)
|
|
396
|
-
# foot (length) -> kilogram (mass) should be invalid
|
|
397
|
-
self.assertFalse(bt.validate_edge(units.foot, units.kilogram))
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
class TestBasisTransformFractionArithmetic(unittest.TestCase):
|
|
401
|
-
"""Test that Fraction arithmetic is exact throughout."""
|
|
402
|
-
|
|
403
|
-
def setUp(self):
|
|
404
|
-
self.si = UnitSystem(
|
|
405
|
-
name="SI",
|
|
406
|
-
bases={
|
|
407
|
-
Dimension.length: units.meter,
|
|
408
|
-
Dimension.mass: units.kilogram,
|
|
409
|
-
Dimension.time: units.second,
|
|
410
|
-
}
|
|
411
|
-
)
|
|
412
|
-
self.custom = UnitSystem(
|
|
413
|
-
name="Custom",
|
|
414
|
-
bases={
|
|
415
|
-
Dimension.length: units.foot,
|
|
416
|
-
Dimension.mass: units.pound,
|
|
417
|
-
Dimension.time: units.second,
|
|
418
|
-
}
|
|
419
|
-
)
|
|
420
|
-
|
|
421
|
-
def test_fraction_matrix_entries(self):
|
|
422
|
-
bt = BasisTransform(
|
|
423
|
-
src=self.custom,
|
|
424
|
-
dst=self.si,
|
|
425
|
-
src_dimensions=(Dimension.length, Dimension.mass),
|
|
426
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
427
|
-
matrix=(
|
|
428
|
-
(Fraction(1, 3), Fraction(2, 3)),
|
|
429
|
-
(Fraction(1, 2), Fraction(1, 2)),
|
|
430
|
-
),
|
|
431
|
-
)
|
|
432
|
-
self.assertEqual(bt.matrix[0][0], Fraction(1, 3))
|
|
433
|
-
self.assertEqual(bt.matrix[0][1], Fraction(2, 3))
|
|
434
|
-
|
|
435
|
-
def test_inverse_preserves_fractions(self):
|
|
436
|
-
bt = BasisTransform(
|
|
437
|
-
src=self.custom,
|
|
438
|
-
dst=self.si,
|
|
439
|
-
src_dimensions=(Dimension.length, Dimension.mass),
|
|
440
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
441
|
-
matrix=((3, 0), (0, 2)),
|
|
442
|
-
)
|
|
443
|
-
inv = bt.inverse()
|
|
444
|
-
self.assertEqual(inv.matrix[0][0], Fraction(1, 3))
|
|
445
|
-
self.assertEqual(inv.matrix[1][1], Fraction(1, 2))
|
|
446
|
-
|
|
447
|
-
def test_no_floating_point_drift(self):
|
|
448
|
-
# 1/3 inverse should give exactly 3, not 2.9999...
|
|
449
|
-
bt = BasisTransform(
|
|
450
|
-
src=self.custom,
|
|
451
|
-
dst=self.si,
|
|
452
|
-
src_dimensions=(Dimension.length,),
|
|
453
|
-
dst_dimensions=(Dimension.length,),
|
|
454
|
-
matrix=((Fraction(1, 3),),),
|
|
455
|
-
)
|
|
456
|
-
inv = bt.inverse()
|
|
457
|
-
self.assertEqual(inv.matrix[0][0], Fraction(3))
|
|
458
|
-
# Double inverse should be exactly original
|
|
459
|
-
inv_inv = inv.inverse()
|
|
460
|
-
self.assertEqual(inv_inv.matrix[0][0], Fraction(1, 3))
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
class TestBasisTransformHashEquality(unittest.TestCase):
|
|
464
|
-
"""Test BasisTransform equality and hashing."""
|
|
465
|
-
|
|
466
|
-
def setUp(self):
|
|
467
|
-
self.si = UnitSystem(
|
|
468
|
-
name="SI",
|
|
469
|
-
bases={
|
|
470
|
-
Dimension.length: units.meter,
|
|
471
|
-
Dimension.mass: units.kilogram,
|
|
472
|
-
Dimension.time: units.second,
|
|
473
|
-
}
|
|
474
|
-
)
|
|
475
|
-
self.custom = UnitSystem(
|
|
476
|
-
name="Custom",
|
|
477
|
-
bases={
|
|
478
|
-
Dimension.length: units.foot,
|
|
479
|
-
Dimension.mass: units.pound,
|
|
480
|
-
Dimension.time: units.second,
|
|
481
|
-
}
|
|
482
|
-
)
|
|
483
|
-
|
|
484
|
-
def test_equal_transforms(self):
|
|
485
|
-
bt1 = BasisTransform(
|
|
486
|
-
src=self.custom,
|
|
487
|
-
dst=self.si,
|
|
488
|
-
src_dimensions=(Dimension.length,),
|
|
489
|
-
dst_dimensions=(Dimension.length,),
|
|
490
|
-
matrix=((1,),),
|
|
491
|
-
)
|
|
492
|
-
bt2 = BasisTransform(
|
|
493
|
-
src=self.custom,
|
|
494
|
-
dst=self.si,
|
|
495
|
-
src_dimensions=(Dimension.length,),
|
|
496
|
-
dst_dimensions=(Dimension.length,),
|
|
497
|
-
matrix=((1,),),
|
|
498
|
-
)
|
|
499
|
-
self.assertEqual(bt1, bt2)
|
|
500
|
-
|
|
501
|
-
def test_hashable(self):
|
|
502
|
-
bt1 = BasisTransform(
|
|
503
|
-
src=self.custom,
|
|
504
|
-
dst=self.si,
|
|
505
|
-
src_dimensions=(Dimension.length,),
|
|
506
|
-
dst_dimensions=(Dimension.length,),
|
|
507
|
-
matrix=((1,),),
|
|
508
|
-
)
|
|
509
|
-
bt2 = BasisTransform(
|
|
510
|
-
src=self.custom,
|
|
511
|
-
dst=self.si,
|
|
512
|
-
src_dimensions=(Dimension.length,),
|
|
513
|
-
dst_dimensions=(Dimension.length,),
|
|
514
|
-
matrix=((1,),),
|
|
515
|
-
)
|
|
516
|
-
self.assertEqual(hash(bt1), hash(bt2))
|
|
517
|
-
self.assertEqual(len({bt1, bt2}), 1)
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
if __name__ == "__main__":
|
|
521
|
-
unittest.main()
|