ucon 0.3.2rc6__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/__init__.py +0 -0
- tests/ucon/test_core.py +535 -0
- tests/ucon/test_dimension.py +206 -0
- tests/ucon/test_unit.py +143 -0
- tests/ucon/test_units.py +21 -0
- ucon/__init__.py +49 -0
- ucon/core.py +353 -0
- ucon/dimension.py +172 -0
- ucon/unit.py +92 -0
- ucon/units.py +84 -0
- ucon-0.3.2rc6.dist-info/METADATA +219 -0
- ucon-0.3.2rc6.dist-info/RECORD +15 -0
- ucon-0.3.2rc6.dist-info/WHEEL +5 -0
- ucon-0.3.2rc6.dist-info/licenses/LICENSE +21 -0
- ucon-0.3.2rc6.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from ucon.dimension import Vector, Dimension
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TestVector(unittest.TestCase):
|
|
6
|
+
|
|
7
|
+
def test_vector_iteration_and_length(self):
|
|
8
|
+
v = Vector(1, 0, 0, 0, 0, 0, 0)
|
|
9
|
+
self.assertEqual(tuple(v), (1, 0, 0, 0, 0, 0, 0))
|
|
10
|
+
self.assertEqual(len(v), 7) # always 7 components
|
|
11
|
+
|
|
12
|
+
def test_vector_addition(self):
|
|
13
|
+
v1 = Vector(1, 0, 0, 0, 0, 0, 0)
|
|
14
|
+
v2 = Vector(0, 2, 0, 0, 0, 0, 0)
|
|
15
|
+
result = v1 + v2
|
|
16
|
+
self.assertEqual(result, Vector(1, 2, 0, 0, 0, 0, 0))
|
|
17
|
+
|
|
18
|
+
def test_vector_subtraction(self):
|
|
19
|
+
v1 = Vector(2, 1, 0, 0, 0, 0, 0)
|
|
20
|
+
v2 = Vector(1, 1, 0, 0, 0, 0, 0)
|
|
21
|
+
self.assertEqual(v1 - v2, Vector(1, 0, 0, 0, 0, 0, 0))
|
|
22
|
+
|
|
23
|
+
def test_vector_equality_and_hash(self):
|
|
24
|
+
v1 = Vector(1, 0, 0, 0, 0, 0, 0)
|
|
25
|
+
v2 = Vector(1, 0, 0, 0, 0, 0, 0)
|
|
26
|
+
v3 = Vector(0, 1, 0, 0, 0, 0, 0)
|
|
27
|
+
self.assertTrue(v1 == v2)
|
|
28
|
+
self.assertFalse(v1 == v3)
|
|
29
|
+
self.assertEqual(hash(v1), hash(v2))
|
|
30
|
+
self.assertNotEqual(hash(v1), hash(v3))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TestDimension(unittest.TestCase):
|
|
34
|
+
|
|
35
|
+
def test_basic_dimensions_are_unique(self):
|
|
36
|
+
seen = set()
|
|
37
|
+
for dim in Dimension:
|
|
38
|
+
self.assertNotIn(dim.value, seen, f'Duplicate vector found for {dim.name}')
|
|
39
|
+
seen.add(dim.value)
|
|
40
|
+
|
|
41
|
+
def test_multiplication_adds_exponents(self):
|
|
42
|
+
self.assertEqual(
|
|
43
|
+
Dimension.mass * Dimension.acceleration,
|
|
44
|
+
Dimension.force,
|
|
45
|
+
)
|
|
46
|
+
self.assertEqual(
|
|
47
|
+
Dimension.length * Dimension.length,
|
|
48
|
+
Dimension.area,
|
|
49
|
+
)
|
|
50
|
+
self.assertEqual(
|
|
51
|
+
Dimension.length * Dimension.length * Dimension.length,
|
|
52
|
+
Dimension.volume,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def test_division_subtracts_exponents(self):
|
|
56
|
+
self.assertEqual(
|
|
57
|
+
Dimension.length / Dimension.time,
|
|
58
|
+
Dimension.velocity,
|
|
59
|
+
)
|
|
60
|
+
self.assertEqual(
|
|
61
|
+
Dimension.force / Dimension.area,
|
|
62
|
+
Dimension.pressure,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def test_none_dimension_behaves_neutrally(self):
|
|
66
|
+
base = Dimension.mass
|
|
67
|
+
self.assertEqual(base * Dimension.none, base)
|
|
68
|
+
self.assertEqual(base / Dimension.none, base)
|
|
69
|
+
self.assertEqual(Dimension.none * base, base)
|
|
70
|
+
with self.assertRaises(ValueError) as exc:
|
|
71
|
+
Dimension.none / base
|
|
72
|
+
assert type(exc.exception) == ValueError
|
|
73
|
+
assert str(exc.exception).endswith('is not a valid Dimension')
|
|
74
|
+
|
|
75
|
+
def test_hash_and_equality_consistency(self):
|
|
76
|
+
d1 = Dimension.mass
|
|
77
|
+
d2 = Dimension.mass
|
|
78
|
+
d3 = Dimension.length
|
|
79
|
+
self.assertEqual(d1, d2)
|
|
80
|
+
self.assertNotEqual(d1, d3)
|
|
81
|
+
self.assertEqual(hash(d1), hash(d2))
|
|
82
|
+
self.assertNotEqual(hash(d1), hash(d3))
|
|
83
|
+
|
|
84
|
+
def test_composite_quantities_examples(self):
|
|
85
|
+
# Energy = Force * Length
|
|
86
|
+
self.assertEqual(
|
|
87
|
+
Dimension.force * Dimension.length,
|
|
88
|
+
Dimension.energy,
|
|
89
|
+
)
|
|
90
|
+
# Power = Energy / Time
|
|
91
|
+
self.assertEqual(
|
|
92
|
+
Dimension.energy / Dimension.time,
|
|
93
|
+
Dimension.power,
|
|
94
|
+
)
|
|
95
|
+
# Pressure = Force / Area
|
|
96
|
+
self.assertEqual(
|
|
97
|
+
Dimension.force / Dimension.area,
|
|
98
|
+
Dimension.pressure,
|
|
99
|
+
)
|
|
100
|
+
# Charge = Current * Time
|
|
101
|
+
self.assertEqual(
|
|
102
|
+
Dimension.current * Dimension.time,
|
|
103
|
+
Dimension.charge,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def test_vector_equality_reflects_dimension_equality(self):
|
|
107
|
+
self.assertEqual(Dimension.mass.value, Dimension.mass.value)
|
|
108
|
+
self.assertNotEqual(Dimension.mass.value, Dimension.time.value)
|
|
109
|
+
self.assertEqual(Dimension.mass, Dimension.mass)
|
|
110
|
+
self.assertNotEqual(Dimension.mass, Dimension.time)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TestVectorEdgeCases(unittest.TestCase):
|
|
114
|
+
|
|
115
|
+
def test_zero_vector_equality_and_additivity(self):
|
|
116
|
+
zero = Vector()
|
|
117
|
+
self.assertEqual(zero, Vector(0, 0, 0, 0, 0, 0, 0))
|
|
118
|
+
# Adding or subtracting zero should yield same vector
|
|
119
|
+
v = Vector(1, 2, 3, 4, 5, 6, 7)
|
|
120
|
+
self.assertEqual(v + zero, v)
|
|
121
|
+
self.assertEqual(v - zero, v)
|
|
122
|
+
|
|
123
|
+
def test_vector_with_negative_exponents(self):
|
|
124
|
+
v1 = Vector(1, -2, 3, 0, 0, 0, 0)
|
|
125
|
+
v2 = Vector(-1, 2, -3, 0, 0, 0, 0)
|
|
126
|
+
result = v1 + v2
|
|
127
|
+
self.assertEqual(result, Vector(0, 0, 0, 0, 0, 0, 0))
|
|
128
|
+
self.assertEqual(v1 - v1, Vector()) # perfect cancellation
|
|
129
|
+
|
|
130
|
+
def test_vector_equality_with_non_vector(self):
|
|
131
|
+
v = Vector()
|
|
132
|
+
with self.assertRaises(AssertionError):
|
|
133
|
+
v == "not a vector"
|
|
134
|
+
with self.assertRaises(AssertionError):
|
|
135
|
+
v == None
|
|
136
|
+
|
|
137
|
+
def test_hash_consistency_for_equal_vectors(self):
|
|
138
|
+
v1 = Vector(1, 0, 0, 0, 0, 0, 0)
|
|
139
|
+
v2 = Vector(1, 0, 0, 0, 0, 0, 0)
|
|
140
|
+
self.assertEqual(hash(v1), hash(v2))
|
|
141
|
+
self.assertEqual(len({v1, v2}), 1)
|
|
142
|
+
|
|
143
|
+
def test_iter_length_order_consistency(self):
|
|
144
|
+
v = Vector(1, 2, 3, 4, 5, 6, 7)
|
|
145
|
+
components = list(v)
|
|
146
|
+
self.assertEqual(len(components), len(v))
|
|
147
|
+
# Ensure order of iteration is fixed (T→L→M→I→Θ→J→N)
|
|
148
|
+
self.assertEqual(components, [1, 2, 3, 4, 5, 6, 7])
|
|
149
|
+
|
|
150
|
+
def test_vector_arithmetic_does_not_mutate_operands(self):
|
|
151
|
+
v1 = Vector(1, 0, 0, 0, 0, 0, 0)
|
|
152
|
+
v2 = Vector(0, 1, 0, 0, 0, 0, 0)
|
|
153
|
+
_ = v1 + v2
|
|
154
|
+
self.assertEqual(v1, Vector(1, 0, 0, 0, 0, 0, 0))
|
|
155
|
+
self.assertEqual(v2, Vector(0, 1, 0, 0, 0, 0, 0))
|
|
156
|
+
|
|
157
|
+
def test_invalid_addition_type_raises(self):
|
|
158
|
+
v = Vector(1, 0, 0, 0, 0, 0, 0)
|
|
159
|
+
with self.assertRaises(TypeError):
|
|
160
|
+
_ = v + "length"
|
|
161
|
+
with self.assertRaises(TypeError):
|
|
162
|
+
_ = v - 5
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class TestDimensionEdgeCases(unittest.TestCase):
|
|
166
|
+
|
|
167
|
+
def test_invalid_multiplication_type(self):
|
|
168
|
+
with self.assertRaises(TypeError):
|
|
169
|
+
Dimension.length * 5
|
|
170
|
+
with self.assertRaises(TypeError):
|
|
171
|
+
"mass" * Dimension.time
|
|
172
|
+
|
|
173
|
+
def test_invalid_division_type(self):
|
|
174
|
+
with self.assertRaises(TypeError):
|
|
175
|
+
Dimension.time / "length"
|
|
176
|
+
with self.assertRaises(TypeError):
|
|
177
|
+
5 / Dimension.mass
|
|
178
|
+
|
|
179
|
+
def test_equality_with_non_dimension(self):
|
|
180
|
+
with self.assertRaises(TypeError):
|
|
181
|
+
Dimension.mass == "mass"
|
|
182
|
+
|
|
183
|
+
def test_enum_uniqueness_and_hash(self):
|
|
184
|
+
# Hashes should be unique per distinct dimension
|
|
185
|
+
hashes = {hash(d) for d in Dimension}
|
|
186
|
+
self.assertEqual(len(hashes), len(Dimension))
|
|
187
|
+
# All Dimension.value entries must be distinct Vectors
|
|
188
|
+
values = [d.value for d in Dimension]
|
|
189
|
+
self.assertEqual(len(values), len(set(values)))
|
|
190
|
+
|
|
191
|
+
def test_combined_chained_operations(self):
|
|
192
|
+
# (mass * acceleration) / area = pressure
|
|
193
|
+
result = (Dimension.mass * Dimension.acceleration) / Dimension.area
|
|
194
|
+
self.assertEqual(result, Dimension.pressure)
|
|
195
|
+
|
|
196
|
+
def test_dimension_round_trip_equality(self):
|
|
197
|
+
# Multiplying and dividing by the same dimension returns self
|
|
198
|
+
d = Dimension.energy
|
|
199
|
+
self.assertEqual((d * Dimension.none) / Dimension.none, d)
|
|
200
|
+
self.assertEqual(d / Dimension.none, d)
|
|
201
|
+
self.assertEqual(Dimension.none * d, d)
|
|
202
|
+
|
|
203
|
+
def test_enum_is_hashable_and_iterable(self):
|
|
204
|
+
seen = {d for d in Dimension}
|
|
205
|
+
self.assertIn(Dimension.mass, seen)
|
|
206
|
+
self.assertEqual(len(seen), len(Dimension))
|
tests/ucon/test_unit.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from unittest import TestCase
|
|
4
|
+
|
|
5
|
+
from ucon.dimension import Dimension
|
|
6
|
+
from ucon.unit import Unit
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestUnit(TestCase):
|
|
10
|
+
|
|
11
|
+
unit_name = 'second'
|
|
12
|
+
unit_type = 'time'
|
|
13
|
+
unit_aliases = ('seconds', 'secs', 's', 'S')
|
|
14
|
+
unit = Unit(*unit_aliases, name=unit_name, dimension=Dimension.time)
|
|
15
|
+
|
|
16
|
+
def test___repr__(self):
|
|
17
|
+
self.assertEqual(f'<{self.unit_type} | {self.unit_name}>', str(self.unit))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestUnitEdgeCases(TestCase):
|
|
21
|
+
|
|
22
|
+
# --- Initialization & representation -----------------------------------
|
|
23
|
+
|
|
24
|
+
def test_default_unit_is_dimensionless(self):
|
|
25
|
+
u = Unit()
|
|
26
|
+
self.assertEqual(u.dimension, Dimension.none)
|
|
27
|
+
self.assertEqual(u.name, '')
|
|
28
|
+
self.assertEqual(u.aliases, ())
|
|
29
|
+
self.assertEqual(u.shorthand, '')
|
|
30
|
+
self.assertEqual(repr(u), '<none>')
|
|
31
|
+
|
|
32
|
+
def test_unit_with_aliases_and_name(self):
|
|
33
|
+
u = Unit('m', 'M', name='meter', dimension=Dimension.length)
|
|
34
|
+
self.assertEqual(u.shorthand, 'm')
|
|
35
|
+
self.assertIn('m', u.aliases)
|
|
36
|
+
self.assertIn('M', u.aliases)
|
|
37
|
+
self.assertIn('length', repr(u))
|
|
38
|
+
self.assertIn('meter', repr(u))
|
|
39
|
+
|
|
40
|
+
def test_hash_and_equality_consistency(self):
|
|
41
|
+
u1 = Unit('m', name='meter', dimension=Dimension.length)
|
|
42
|
+
u2 = Unit('m', name='meter', dimension=Dimension.length)
|
|
43
|
+
u3 = Unit('s', name='second', dimension=Dimension.time)
|
|
44
|
+
self.assertEqual(u1, u2)
|
|
45
|
+
self.assertEqual(hash(u1), hash(u2))
|
|
46
|
+
self.assertNotEqual(u1, u3)
|
|
47
|
+
self.assertNotEqual(hash(u1), hash(u3))
|
|
48
|
+
|
|
49
|
+
def test_units_with_same_name_but_different_dimension_not_equal(self):
|
|
50
|
+
u1 = Unit(name='amp', dimension=Dimension.current)
|
|
51
|
+
u2 = Unit(name='amp', dimension=Dimension.time)
|
|
52
|
+
self.assertNotEqual(u1, u2)
|
|
53
|
+
|
|
54
|
+
# --- generate_name edge cases -----------------------------------------
|
|
55
|
+
|
|
56
|
+
def test_generate_name_both_have_shorthand(self):
|
|
57
|
+
u1 = Unit('m', name='meter', dimension=Dimension.length)
|
|
58
|
+
u2 = Unit('s', name='second', dimension=Dimension.time)
|
|
59
|
+
result = u1.generate_name(u2, '*')
|
|
60
|
+
self.assertEqual(result, '(m*s)')
|
|
61
|
+
|
|
62
|
+
def test_generate_name_missing_left_shorthand(self):
|
|
63
|
+
u1 = Unit(name='unitless', dimension=Dimension.none)
|
|
64
|
+
u2 = Unit('s', name='second', dimension=Dimension.time)
|
|
65
|
+
self.assertEqual(u1.generate_name(u2, '/'), 'second')
|
|
66
|
+
|
|
67
|
+
def test_generate_name_missing_right_shorthand(self):
|
|
68
|
+
u1 = Unit('m', name='meter', dimension=Dimension.length)
|
|
69
|
+
u2 = Unit(name='none', dimension=Dimension.none)
|
|
70
|
+
self.assertEqual(u1.generate_name(u2, '*'), 'meter')
|
|
71
|
+
|
|
72
|
+
def test_generate_name_no_aliases_on_either_side(self):
|
|
73
|
+
u1 = Unit(name='foo', dimension=Dimension.length)
|
|
74
|
+
u2 = Unit(name='bar', dimension=Dimension.time)
|
|
75
|
+
self.assertEqual(u1.generate_name(u2, '*'), '(foo*bar)')
|
|
76
|
+
|
|
77
|
+
# --- arithmetic behavior ----------------------------------------------
|
|
78
|
+
|
|
79
|
+
def test_multiplication_produces_composite_unit(self):
|
|
80
|
+
m = Unit('m', name='meter', dimension=Dimension.length)
|
|
81
|
+
s = Unit('s', name='second', dimension=Dimension.time)
|
|
82
|
+
v = m / s
|
|
83
|
+
self.assertIsInstance(v, Unit)
|
|
84
|
+
self.assertEqual(v.dimension, Dimension.velocity)
|
|
85
|
+
self.assertIn('/', v.name)
|
|
86
|
+
|
|
87
|
+
def test_division_with_dimensionless_denominator_returns_self(self):
|
|
88
|
+
m = Unit('m', name='meter', dimension=Dimension.length)
|
|
89
|
+
none = Unit(name='none', dimension=Dimension.none)
|
|
90
|
+
result = m / none
|
|
91
|
+
self.assertEqual(result, m)
|
|
92
|
+
|
|
93
|
+
def test_division_of_identical_units_returns_dimensionless(self):
|
|
94
|
+
m1 = Unit('m', name='meter', dimension=Dimension.length)
|
|
95
|
+
m2 = Unit('m', name='meter', dimension=Dimension.length)
|
|
96
|
+
result = m1 / m2
|
|
97
|
+
self.assertEqual(result.dimension, Dimension.none)
|
|
98
|
+
self.assertEqual(result.name, '')
|
|
99
|
+
|
|
100
|
+
def test_multiplying_with_dimensionless_returns_self(self):
|
|
101
|
+
m = Unit('m', name='meter', dimension=Dimension.length)
|
|
102
|
+
none = Unit(name='none', dimension=Dimension.none)
|
|
103
|
+
result = m * none
|
|
104
|
+
self.assertEqual(result.dimension, Dimension.length)
|
|
105
|
+
self.assertIn('m', result.name)
|
|
106
|
+
|
|
107
|
+
def test_invalid_dimension_combinations_raise_value_error(self):
|
|
108
|
+
m = Unit('m', name='meter', dimension=Dimension.length)
|
|
109
|
+
c = Unit('C', name='coulomb', dimension=Dimension.charge)
|
|
110
|
+
# The result of dividing these is undefined (no such Dimension)
|
|
111
|
+
with self.assertRaises(ValueError):
|
|
112
|
+
_ = m / c
|
|
113
|
+
with self.assertRaises(ValueError):
|
|
114
|
+
_ = c * m
|
|
115
|
+
|
|
116
|
+
# --- equality, hashing, immutability ----------------------------------
|
|
117
|
+
|
|
118
|
+
def test_equality_with_non_unit(self):
|
|
119
|
+
with self.assertRaises(TypeError):
|
|
120
|
+
Unit('m', name='meter', dimension=Dimension.length) == 'meter'
|
|
121
|
+
|
|
122
|
+
def test_hash_stability_in_collections(self):
|
|
123
|
+
m1 = Unit('m', name='meter', dimension=Dimension.length)
|
|
124
|
+
s = set([m1])
|
|
125
|
+
self.assertIn(Unit('m', name='meter', dimension=Dimension.length), s)
|
|
126
|
+
|
|
127
|
+
def test_operations_do_not_mutate_operands(self):
|
|
128
|
+
m = Unit('m', name='meter', dimension=Dimension.length)
|
|
129
|
+
s = Unit('s', name='second', dimension=Dimension.time)
|
|
130
|
+
_ = m / s
|
|
131
|
+
self.assertEqual(m.dimension, Dimension.length)
|
|
132
|
+
self.assertEqual(s.dimension, Dimension.time)
|
|
133
|
+
|
|
134
|
+
# --- operator edge cases ----------------------------------------------
|
|
135
|
+
|
|
136
|
+
def test_generate_name_handles_empty_names_and_aliases(self):
|
|
137
|
+
a = Unit()
|
|
138
|
+
b = Unit()
|
|
139
|
+
self.assertEqual(a.generate_name(b, '*'), '')
|
|
140
|
+
|
|
141
|
+
def test_repr_contains_dimension_name_even_without_name(self):
|
|
142
|
+
u = Unit(dimension=Dimension.force)
|
|
143
|
+
self.assertIn('force', repr(u))
|
tests/ucon/test_units.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from unittest import TestCase
|
|
4
|
+
|
|
5
|
+
from ucon import units
|
|
6
|
+
from ucon.dimension import Dimension
|
|
7
|
+
from ucon.unit import Unit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestUnits(TestCase):
|
|
11
|
+
|
|
12
|
+
def test_has_expected_basic_units(self):
|
|
13
|
+
expected_basic_units = {'none', 'volt', 'liter', 'gram', 'second', 'kelvin', 'mole', 'coulomb'}
|
|
14
|
+
missing = {name for name in expected_basic_units if not units.have(name)}
|
|
15
|
+
assert not missing, f"Missing expected units: {missing}"
|
|
16
|
+
|
|
17
|
+
def test___truediv__(self):
|
|
18
|
+
self.assertEqual(units.none, units.gram / units.gram)
|
|
19
|
+
self.assertEqual(units.gram, units.gram / units.none)
|
|
20
|
+
|
|
21
|
+
self.assertEqual(Unit(name='(g/L)', dimension=Dimension.density), units.gram / units.liter)
|
ucon/__init__.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ucon
|
|
3
|
+
====
|
|
4
|
+
|
|
5
|
+
*ucon* (Unit Conversion & Dimensional Analysis) is a lightweight,
|
|
6
|
+
introspective library for reasoning about physical quantities.
|
|
7
|
+
|
|
8
|
+
Unlike conventional unit libraries that focus purely on arithmetic convenience,
|
|
9
|
+
*ucon* models the **semantics** of measurement-—exposing the algebra of
|
|
10
|
+
dimensions and the structure of compound units.
|
|
11
|
+
|
|
12
|
+
Overview
|
|
13
|
+
--------
|
|
14
|
+
*ucon* is organized into a small set of composable modules:
|
|
15
|
+
|
|
16
|
+
- :mod:`ucon.dimension` — defines the algebra of physical dimensions using
|
|
17
|
+
exponent vectors. Provides the foundation for all dimensional reasoning.
|
|
18
|
+
- :mod:`ucon.unit` — defines the :class:`Unit` abstraction, representing a
|
|
19
|
+
measurable quantity with an associated dimension, factor, and offset.
|
|
20
|
+
- :mod:`ucon.core` — implements numeric handling via :class:`Number`,
|
|
21
|
+
:class:`Scale`, and :class:`Ratio`, along with unified conversion logic.
|
|
22
|
+
- :mod:`ucon.units` — declares canonical SI base and derived units for immediate use.
|
|
23
|
+
|
|
24
|
+
Design Philosophy
|
|
25
|
+
-----------------
|
|
26
|
+
*ucon* treats unit conversion not as a lookup problem but as an **algebra**:
|
|
27
|
+
|
|
28
|
+
- **Dimensional Algebra** — all physical quantities are represented as
|
|
29
|
+
exponent vectors over the seven SI bases, ensuring strict composability.
|
|
30
|
+
- **Explicit Semantics** — units, dimensions, and scales are first-class
|
|
31
|
+
objects, not strings or tokens.
|
|
32
|
+
- **Unified Conversion Model** — all conversions are expressed through one
|
|
33
|
+
data-driven framework that is generalizable to arbitrary unit systems.
|
|
34
|
+
"""
|
|
35
|
+
from ucon import units
|
|
36
|
+
from ucon.unit import Unit
|
|
37
|
+
from ucon.core import Exponent, Number, Scale, Ratio
|
|
38
|
+
from ucon.dimension import Dimension
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
'Exponent',
|
|
43
|
+
'Dimension',
|
|
44
|
+
'Number',
|
|
45
|
+
'Ratio',
|
|
46
|
+
'Scale',
|
|
47
|
+
'Unit',
|
|
48
|
+
'units',
|
|
49
|
+
]
|