ucon 0.5.2__py3-none-any.whl → 0.6.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.
- ucon/__init__.py +4 -1
- ucon/core.py +7 -2
- ucon/mcp/__init__.py +8 -0
- ucon/mcp/server.py +250 -0
- ucon/pydantic.py +199 -0
- ucon/units.py +259 -11
- {ucon-0.5.2.dist-info → ucon-0.6.0.dist-info}/METADATA +73 -97
- ucon-0.6.0.dist-info/RECORD +17 -0
- ucon-0.6.0.dist-info/entry_points.txt +2 -0
- ucon-0.6.0.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.0.dist-info}/WHEEL +0 -0
- {ucon-0.5.2.dist-info → ucon-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {ucon-0.5.2.dist-info → ucon-0.6.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,263 +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 ConversionGraph integration with BasisTransform.
|
|
7
|
-
|
|
8
|
-
Verifies cross-basis edge handling, RebasedUnit creation,
|
|
9
|
-
and conversion paths that span dimensional bases.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
import unittest
|
|
13
|
-
from fractions import Fraction
|
|
14
|
-
|
|
15
|
-
from ucon.core import (
|
|
16
|
-
BasisTransform,
|
|
17
|
-
Dimension,
|
|
18
|
-
RebasedUnit,
|
|
19
|
-
Unit,
|
|
20
|
-
UnitSystem,
|
|
21
|
-
)
|
|
22
|
-
from ucon.graph import ConversionGraph, DimensionMismatch
|
|
23
|
-
from ucon.maps import LinearMap
|
|
24
|
-
from ucon import units
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class TestGraphAddEdgeWithBasisTransform(unittest.TestCase):
|
|
28
|
-
"""Test add_edge with basis_transform parameter."""
|
|
29
|
-
|
|
30
|
-
def setUp(self):
|
|
31
|
-
self.si = UnitSystem(
|
|
32
|
-
name="SI",
|
|
33
|
-
bases={
|
|
34
|
-
Dimension.length: units.meter,
|
|
35
|
-
Dimension.mass: units.kilogram,
|
|
36
|
-
Dimension.time: units.second,
|
|
37
|
-
}
|
|
38
|
-
)
|
|
39
|
-
self.imperial = UnitSystem(
|
|
40
|
-
name="Imperial",
|
|
41
|
-
bases={
|
|
42
|
-
Dimension.length: units.foot,
|
|
43
|
-
Dimension.mass: units.pound,
|
|
44
|
-
Dimension.time: units.second,
|
|
45
|
-
}
|
|
46
|
-
)
|
|
47
|
-
# Simple 1:1 transform for length
|
|
48
|
-
self.bt = BasisTransform(
|
|
49
|
-
src=self.imperial,
|
|
50
|
-
dst=self.si,
|
|
51
|
-
src_dimensions=(Dimension.length,),
|
|
52
|
-
dst_dimensions=(Dimension.length,),
|
|
53
|
-
matrix=((1,),),
|
|
54
|
-
)
|
|
55
|
-
self.graph = ConversionGraph()
|
|
56
|
-
|
|
57
|
-
def test_add_edge_with_basis_transform(self):
|
|
58
|
-
# foot -> meter with basis transform
|
|
59
|
-
self.graph.add_edge(
|
|
60
|
-
src=units.foot,
|
|
61
|
-
dst=units.meter,
|
|
62
|
-
map=LinearMap(0.3048),
|
|
63
|
-
basis_transform=self.bt,
|
|
64
|
-
)
|
|
65
|
-
# Verify the rebased unit was created
|
|
66
|
-
self.assertIn(units.foot, self.graph._rebased)
|
|
67
|
-
rebased = self.graph._rebased[units.foot]
|
|
68
|
-
self.assertIsInstance(rebased, RebasedUnit)
|
|
69
|
-
self.assertEqual(rebased.original, units.foot)
|
|
70
|
-
self.assertEqual(rebased.rebased_dimension, Dimension.length)
|
|
71
|
-
|
|
72
|
-
def test_add_edge_without_basis_transform_requires_same_dimension(self):
|
|
73
|
-
# foot and meter both have Dimension.length, so this should work
|
|
74
|
-
self.graph.add_edge(
|
|
75
|
-
src=units.foot,
|
|
76
|
-
dst=units.meter,
|
|
77
|
-
map=LinearMap(0.3048),
|
|
78
|
-
)
|
|
79
|
-
# Verify no rebased unit was created (normal edge)
|
|
80
|
-
self.assertNotIn(units.foot, self.graph._rebased)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class TestGraphConvertWithBasisTransform(unittest.TestCase):
|
|
84
|
-
"""Test convert() with cross-basis edges."""
|
|
85
|
-
|
|
86
|
-
def setUp(self):
|
|
87
|
-
self.si = UnitSystem(
|
|
88
|
-
name="SI",
|
|
89
|
-
bases={
|
|
90
|
-
Dimension.length: units.meter,
|
|
91
|
-
Dimension.mass: units.kilogram,
|
|
92
|
-
Dimension.time: units.second,
|
|
93
|
-
}
|
|
94
|
-
)
|
|
95
|
-
self.imperial = UnitSystem(
|
|
96
|
-
name="Imperial",
|
|
97
|
-
bases={
|
|
98
|
-
Dimension.length: units.foot,
|
|
99
|
-
Dimension.mass: units.pound,
|
|
100
|
-
Dimension.time: units.second,
|
|
101
|
-
}
|
|
102
|
-
)
|
|
103
|
-
self.bt = BasisTransform(
|
|
104
|
-
src=self.imperial,
|
|
105
|
-
dst=self.si,
|
|
106
|
-
src_dimensions=(Dimension.length,),
|
|
107
|
-
dst_dimensions=(Dimension.length,),
|
|
108
|
-
matrix=((1,),),
|
|
109
|
-
)
|
|
110
|
-
self.graph = ConversionGraph()
|
|
111
|
-
# Add edge with basis transform
|
|
112
|
-
self.graph.add_edge(
|
|
113
|
-
src=units.foot,
|
|
114
|
-
dst=units.meter,
|
|
115
|
-
map=LinearMap(0.3048),
|
|
116
|
-
basis_transform=self.bt,
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
def test_convert_uses_rebased_path(self):
|
|
120
|
-
# Convert via the rebased edge
|
|
121
|
-
map = self.graph.convert(src=units.foot, dst=units.meter)
|
|
122
|
-
# 1 foot = 0.3048 meters
|
|
123
|
-
self.assertAlmostEqual(map(1), 0.3048, places=5)
|
|
124
|
-
|
|
125
|
-
def test_convert_inverse_works(self):
|
|
126
|
-
# The inverse edge should also be available
|
|
127
|
-
map = self.graph.convert(src=units.meter, dst=units.foot)
|
|
128
|
-
# 1 meter ≈ 3.28084 feet
|
|
129
|
-
self.assertAlmostEqual(map(1), 1/0.3048, places=5)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
class TestGraphConnectSystems(unittest.TestCase):
|
|
133
|
-
"""Test connect_systems convenience method."""
|
|
134
|
-
|
|
135
|
-
def setUp(self):
|
|
136
|
-
self.si = UnitSystem(
|
|
137
|
-
name="SI",
|
|
138
|
-
bases={
|
|
139
|
-
Dimension.length: units.meter,
|
|
140
|
-
Dimension.mass: units.kilogram,
|
|
141
|
-
Dimension.time: units.second,
|
|
142
|
-
}
|
|
143
|
-
)
|
|
144
|
-
self.imperial = UnitSystem(
|
|
145
|
-
name="Imperial",
|
|
146
|
-
bases={
|
|
147
|
-
Dimension.length: units.foot,
|
|
148
|
-
Dimension.mass: units.pound,
|
|
149
|
-
Dimension.time: units.second,
|
|
150
|
-
}
|
|
151
|
-
)
|
|
152
|
-
self.bt = BasisTransform(
|
|
153
|
-
src=self.imperial,
|
|
154
|
-
dst=self.si,
|
|
155
|
-
src_dimensions=(Dimension.length, Dimension.mass),
|
|
156
|
-
dst_dimensions=(Dimension.length, Dimension.mass),
|
|
157
|
-
matrix=((1, 0), (0, 1)),
|
|
158
|
-
)
|
|
159
|
-
self.graph = ConversionGraph()
|
|
160
|
-
|
|
161
|
-
def test_connect_systems_bulk_adds_edges(self):
|
|
162
|
-
self.graph.connect_systems(
|
|
163
|
-
basis_transform=self.bt,
|
|
164
|
-
edges={
|
|
165
|
-
(units.foot, units.meter): LinearMap(0.3048),
|
|
166
|
-
(units.pound, units.kilogram): LinearMap(0.453592),
|
|
167
|
-
}
|
|
168
|
-
)
|
|
169
|
-
# Both should be converted
|
|
170
|
-
length_map = self.graph.convert(src=units.foot, dst=units.meter)
|
|
171
|
-
self.assertAlmostEqual(length_map(1), 0.3048, places=5)
|
|
172
|
-
|
|
173
|
-
mass_map = self.graph.convert(src=units.pound, dst=units.kilogram)
|
|
174
|
-
self.assertAlmostEqual(mass_map(1), 0.453592, places=5)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
class TestGraphListTransforms(unittest.TestCase):
|
|
178
|
-
"""Test introspection methods for transforms."""
|
|
179
|
-
|
|
180
|
-
def setUp(self):
|
|
181
|
-
self.si = UnitSystem(
|
|
182
|
-
name="SI",
|
|
183
|
-
bases={
|
|
184
|
-
Dimension.length: units.meter,
|
|
185
|
-
}
|
|
186
|
-
)
|
|
187
|
-
self.imperial = UnitSystem(
|
|
188
|
-
name="Imperial",
|
|
189
|
-
bases={
|
|
190
|
-
Dimension.length: units.foot,
|
|
191
|
-
}
|
|
192
|
-
)
|
|
193
|
-
self.bt = BasisTransform(
|
|
194
|
-
src=self.imperial,
|
|
195
|
-
dst=self.si,
|
|
196
|
-
src_dimensions=(Dimension.length,),
|
|
197
|
-
dst_dimensions=(Dimension.length,),
|
|
198
|
-
matrix=((1,),),
|
|
199
|
-
)
|
|
200
|
-
self.graph = ConversionGraph()
|
|
201
|
-
self.graph.add_edge(
|
|
202
|
-
src=units.foot,
|
|
203
|
-
dst=units.meter,
|
|
204
|
-
map=LinearMap(0.3048),
|
|
205
|
-
basis_transform=self.bt,
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
def test_list_rebased_units(self):
|
|
209
|
-
rebased = self.graph.list_rebased_units()
|
|
210
|
-
self.assertEqual(len(rebased), 1)
|
|
211
|
-
self.assertIn(units.foot, rebased)
|
|
212
|
-
self.assertIsInstance(rebased[units.foot], RebasedUnit)
|
|
213
|
-
|
|
214
|
-
def test_list_transforms(self):
|
|
215
|
-
transforms = self.graph.list_transforms()
|
|
216
|
-
self.assertEqual(len(transforms), 1)
|
|
217
|
-
self.assertEqual(transforms[0], self.bt)
|
|
218
|
-
|
|
219
|
-
def test_edges_for_transform(self):
|
|
220
|
-
edges = self.graph.edges_for_transform(self.bt)
|
|
221
|
-
self.assertEqual(len(edges), 1)
|
|
222
|
-
self.assertEqual(edges[0], (units.foot, units.meter))
|
|
223
|
-
|
|
224
|
-
def test_list_transforms_multiple(self):
|
|
225
|
-
# Add another transform
|
|
226
|
-
custom = UnitSystem(
|
|
227
|
-
name="Custom",
|
|
228
|
-
bases={Dimension.mass: units.pound}
|
|
229
|
-
)
|
|
230
|
-
bt2 = BasisTransform(
|
|
231
|
-
src=custom,
|
|
232
|
-
dst=self.si,
|
|
233
|
-
src_dimensions=(Dimension.mass,),
|
|
234
|
-
dst_dimensions=(Dimension.mass,),
|
|
235
|
-
matrix=((1,),),
|
|
236
|
-
)
|
|
237
|
-
# Need to add a mass base to SI for this test
|
|
238
|
-
si_with_mass = UnitSystem(
|
|
239
|
-
name="SI",
|
|
240
|
-
bases={
|
|
241
|
-
Dimension.length: units.meter,
|
|
242
|
-
Dimension.mass: units.kilogram,
|
|
243
|
-
}
|
|
244
|
-
)
|
|
245
|
-
bt2 = BasisTransform(
|
|
246
|
-
src=custom,
|
|
247
|
-
dst=si_with_mass,
|
|
248
|
-
src_dimensions=(Dimension.mass,),
|
|
249
|
-
dst_dimensions=(Dimension.mass,),
|
|
250
|
-
matrix=((1,),),
|
|
251
|
-
)
|
|
252
|
-
self.graph.add_edge(
|
|
253
|
-
src=units.pound,
|
|
254
|
-
dst=units.kilogram,
|
|
255
|
-
map=LinearMap(0.453592),
|
|
256
|
-
basis_transform=bt2,
|
|
257
|
-
)
|
|
258
|
-
transforms = self.graph.list_transforms()
|
|
259
|
-
self.assertEqual(len(transforms), 2)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if __name__ == "__main__":
|
|
263
|
-
unittest.main()
|