ucon 0.3.3rc1__py3-none-any.whl → 0.3.4__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/test_algebra.py +237 -0
- tests/ucon/test_core.py +455 -364
- tests/ucon/test_quantity.py +363 -0
- tests/ucon/test_units.py +5 -3
- ucon/__init__.py +3 -3
- ucon/algebra.py +212 -0
- ucon/core.py +691 -286
- ucon/quantity.py +249 -0
- ucon/units.py +1 -2
- {ucon-0.3.3rc1.dist-info → ucon-0.3.4.dist-info}/METADATA +6 -5
- ucon-0.3.4.dist-info/RECORD +15 -0
- tests/ucon/test_dimension.py +0 -206
- tests/ucon/test_unit.py +0 -143
- ucon/dimension.py +0 -172
- ucon/unit.py +0 -92
- ucon-0.3.3rc1.dist-info/RECORD +0 -15
- {ucon-0.3.3rc1.dist-info → ucon-0.3.4.dist-info}/WHEEL +0 -0
- {ucon-0.3.3rc1.dist-info → ucon-0.3.4.dist-info}/licenses/LICENSE +0 -0
- {ucon-0.3.3rc1.dist-info → ucon-0.3.4.dist-info}/top_level.txt +0 -0
ucon/dimension.py
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
ucon.dimension
|
|
3
|
-
===============
|
|
4
|
-
|
|
5
|
-
Defines the algebra of **physical dimensions**--the foundation of all unit
|
|
6
|
-
relationships and dimensional analysis in *ucon*.
|
|
7
|
-
|
|
8
|
-
Each :class:`Dimension` represents a physical quantity (time, mass, length, etc.)
|
|
9
|
-
expressed as a 7-element exponent vector following the SI base system:
|
|
10
|
-
|
|
11
|
-
(T, L, M, I, Θ, J, N) :: (s * m * kg * A * K * cd * mol)
|
|
12
|
-
time, length, mass, current, temperature, luminous intensity, substance
|
|
13
|
-
|
|
14
|
-
Derived dimensions are expressed as algebraic sums or differences of these base
|
|
15
|
-
vectors (e.g., `velocity = length / time`, `force = mass * acceleration`).
|
|
16
|
-
|
|
17
|
-
Classes
|
|
18
|
-
-------
|
|
19
|
-
- :class:`Vector` — Represents the exponent vector of a physical quantity.
|
|
20
|
-
- :class:`Dimension` — Enum of known physical quantities, each with a `Vector`
|
|
21
|
-
value and operator overloads for dimensional algebra.
|
|
22
|
-
"""
|
|
23
|
-
from dataclasses import dataclass
|
|
24
|
-
from enum import Enum
|
|
25
|
-
from functools import partial, reduce
|
|
26
|
-
from operator import __sub__ as subtraction
|
|
27
|
-
from typing import Callable, Iterable, Iterator
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
diff: Callable[[Iterable], int] = partial(reduce, subtraction)
|
|
31
|
-
|
|
32
|
-
@dataclass
|
|
33
|
-
class Vector:
|
|
34
|
-
"""
|
|
35
|
-
Represents the **exponent vector** of a physical quantity.
|
|
36
|
-
|
|
37
|
-
Each component corresponds to the power of a base dimension in the SI system:
|
|
38
|
-
time (T), length (L), mass (M), current (I), temperature (Θ),
|
|
39
|
-
luminous intensity (J), and amount of substance (N).
|
|
40
|
-
|
|
41
|
-
Arithmetic operations correspond to dimensional composition:
|
|
42
|
-
- Addition (`+`) → multiplication of quantities
|
|
43
|
-
- Subtraction (`-`) → division of quantities
|
|
44
|
-
|
|
45
|
-
e.g.
|
|
46
|
-
Vector(T=1, L=0, M=0, I=0, Θ=0, J=0, N=0) => "time"
|
|
47
|
-
Vector(T=0, L=2, M=0, I=0, Θ=0, J=0, N=0) => "area"
|
|
48
|
-
Vector(T=-2, L=1, M=1, I=0, Θ=0, J=0, N=0) => "force"
|
|
49
|
-
"""
|
|
50
|
-
T: int = 0 # time
|
|
51
|
-
L: int = 0 # length
|
|
52
|
-
M: int = 0 # mass
|
|
53
|
-
I: int = 0 # current
|
|
54
|
-
Θ: int = 0 # temperature
|
|
55
|
-
J: int = 0 # luminous intensity
|
|
56
|
-
N: int = 0 # amount of substance
|
|
57
|
-
|
|
58
|
-
def __iter__(self) -> Iterator[int]:
|
|
59
|
-
yield self.T
|
|
60
|
-
yield self.L
|
|
61
|
-
yield self.M
|
|
62
|
-
yield self.I
|
|
63
|
-
yield self.Θ
|
|
64
|
-
yield self.J
|
|
65
|
-
yield self.N
|
|
66
|
-
|
|
67
|
-
def __len__(self) -> int:
|
|
68
|
-
return sum(tuple(1 for x in self))
|
|
69
|
-
|
|
70
|
-
def __add__(self, vector: 'Vector') -> 'Vector':
|
|
71
|
-
"""
|
|
72
|
-
Addition, here, comes from the multiplication of base quantities
|
|
73
|
-
|
|
74
|
-
e.g. F = m * a
|
|
75
|
-
F =
|
|
76
|
-
(s^-2 * m^1 * kg * A * K * cd * mol) +
|
|
77
|
-
(s * m * kg^1 * A * K * cd * mol)
|
|
78
|
-
"""
|
|
79
|
-
values = tuple(sum(pair) for pair in zip(tuple(self), tuple(vector)))
|
|
80
|
-
return Vector(*values)
|
|
81
|
-
|
|
82
|
-
def __sub__(self, vector: 'Vector') -> 'Vector':
|
|
83
|
-
"""
|
|
84
|
-
Subtraction, here, comes from the division of base quantities
|
|
85
|
-
"""
|
|
86
|
-
values = tuple(diff(pair) for pair in zip(tuple(self), tuple(vector)))
|
|
87
|
-
return Vector(*values)
|
|
88
|
-
|
|
89
|
-
def __eq__(self, vector: 'Vector') -> bool:
|
|
90
|
-
assert isinstance(vector, Vector), "Can only compare Vector to another Vector"
|
|
91
|
-
return tuple(self) == tuple(vector)
|
|
92
|
-
|
|
93
|
-
def __hash__(self) -> int:
|
|
94
|
-
# Hash based on the string because tuples have been shown to collide
|
|
95
|
-
# Not the most performant, but effective
|
|
96
|
-
return hash(str(tuple(self)))
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class Dimension(Enum):
|
|
100
|
-
"""
|
|
101
|
-
Represents a **physical dimension** defined by a :class:`Vector`.
|
|
102
|
-
|
|
103
|
-
Each dimension corresponds to a distinct combination of base exponents.
|
|
104
|
-
Dimensions are algebraically composable via multiplication and division:
|
|
105
|
-
|
|
106
|
-
>>> Dimension.length / Dimension.time
|
|
107
|
-
<Dimension.velocity: Vector(T=-1, L=1, M=0, I=0, Θ=0, J=0, N=0)>
|
|
108
|
-
|
|
109
|
-
This algebra forms the foundation for unit compatibility and conversion.
|
|
110
|
-
"""
|
|
111
|
-
none = Vector()
|
|
112
|
-
|
|
113
|
-
# -- BASIS ---------------------------------------
|
|
114
|
-
time = Vector(1, 0, 0, 0, 0, 0, 0)
|
|
115
|
-
length = Vector(0, 1, 0, 0, 0, 0, 0)
|
|
116
|
-
mass = Vector(0, 0, 1, 0, 0, 0, 0)
|
|
117
|
-
current = Vector(0, 0, 0, 1, 0, 0, 0)
|
|
118
|
-
temperature = Vector(0, 0, 0, 0, 1, 0, 0)
|
|
119
|
-
luminous_intensity = Vector(0, 0, 0, 0, 0, 1, 0)
|
|
120
|
-
amount_of_substance = Vector(0, 0, 0, 0, 0, 0, 1)
|
|
121
|
-
# ------------------------------------------------
|
|
122
|
-
|
|
123
|
-
acceleration = Vector(-2, 1, 0, 0, 0, 0, 0)
|
|
124
|
-
angular_momentum = Vector(-1, 2, 1, 0, 0, 0, 0)
|
|
125
|
-
area = Vector(0, 2, 0, 0, 0, 0, 0)
|
|
126
|
-
capacitance = Vector(4, -2, -1, 2, 0, 0, 0)
|
|
127
|
-
charge = Vector(1, 0, 0, 1, 0, 0, 0)
|
|
128
|
-
conductance = Vector(3, -2, -1, 2, 0, 0, 0)
|
|
129
|
-
conductivity = Vector(3, -3, -1, 2, 0, 0, 0)
|
|
130
|
-
density = Vector(0, -3, 1, 0, 0, 0, 0)
|
|
131
|
-
electric_field_strength = Vector(-3, 1, 1, -1, 0, 0, 0)
|
|
132
|
-
energy = Vector(-2, 2, 1, 0, 0, 0, 0)
|
|
133
|
-
entropy = Vector(-2, 2, 1, 0, -1, 0, 0)
|
|
134
|
-
force = Vector(-2, 1, 1, 0, 0, 0, 0)
|
|
135
|
-
frequency = Vector(-1, 0, 0, 0, 0, 0, 0)
|
|
136
|
-
gravitation = Vector(-2, 3, -1, 0, 0, 0, 0)
|
|
137
|
-
illuminance = Vector(0, -2, 0, 0, 0, 1, 0)
|
|
138
|
-
inductance = Vector(-2, 2, 1, -2, 0, 0, 0)
|
|
139
|
-
magnetic_flux = Vector(-2, 2, 1, -1, 0, 0, 0)
|
|
140
|
-
magnetic_flux_density = Vector(-2, 0, 1, -1, 0, 0, 0)
|
|
141
|
-
magnetic_permeability = Vector(-2, 1, 1, -2, 0, 0, 0)
|
|
142
|
-
molar_mass = Vector(0, 0, 1, 0, 0, 0, -1)
|
|
143
|
-
molar_volume = Vector(0, 3, 0, 0, 0, 0, -1)
|
|
144
|
-
momentum = Vector(-1, 1, 1, 0, 0, 0, 0)
|
|
145
|
-
permittivity = Vector(4, -3, -1, 2, 0, 0, 0)
|
|
146
|
-
power = Vector(-3, 2, 1, 0, 0, 0, 0)
|
|
147
|
-
pressure = Vector(-2, -1, 1, 0, 0, 0, 0)
|
|
148
|
-
resistance = Vector(-3, 2, 1, -2, 0, 0, 0)
|
|
149
|
-
resistivity = Vector(-3, 3, 1, -2, 0, 0, 0)
|
|
150
|
-
specific_heat_capacity = Vector(-2, 2, 0, 0, -1, 0, 0)
|
|
151
|
-
thermal_conductivity = Vector(-3, 1, 1, 0, -1, 0, 0)
|
|
152
|
-
velocity = Vector(-1, 1, 0, 0, 0, 0, 0)
|
|
153
|
-
voltage = Vector(-3, 2, 1, -1, 0, 0, 0)
|
|
154
|
-
volume = Vector(0, 3, 0, 0, 0, 0, 0)
|
|
155
|
-
|
|
156
|
-
def __truediv__(self, dimension: 'Dimension') -> 'Dimension':
|
|
157
|
-
if not isinstance(dimension, Dimension):
|
|
158
|
-
raise TypeError(f"Cannot divide Dimension by non-Dimension type: {type(dimension)}")
|
|
159
|
-
return Dimension(self.value - dimension.value)
|
|
160
|
-
|
|
161
|
-
def __mul__(self, dimension: 'Dimension') -> 'Dimension':
|
|
162
|
-
if not isinstance(dimension, Dimension):
|
|
163
|
-
raise TypeError(f"Cannot multiply Dimension by non-Dimension type: {type(dimension)}")
|
|
164
|
-
return Dimension(self.value + dimension.value)
|
|
165
|
-
|
|
166
|
-
def __eq__(self, dimension) -> bool:
|
|
167
|
-
if not isinstance(dimension, Dimension):
|
|
168
|
-
raise TypeError(f"Cannot compare Dimension with non-Dimension type: {type(dimension)}")
|
|
169
|
-
return self.value == dimension.value
|
|
170
|
-
|
|
171
|
-
def __hash__(self) -> int:
|
|
172
|
-
return hash(self.value)
|
ucon/unit.py
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
ucon.unit
|
|
3
|
-
==========
|
|
4
|
-
|
|
5
|
-
Defines the **Unit** abstraction — the symbolic and algebraic representation of
|
|
6
|
-
a measurable quantity associated with a :class:`ucon.dimension.Dimension`.
|
|
7
|
-
|
|
8
|
-
A :class:`Unit` pairs a human-readable name and aliases with its underlying
|
|
9
|
-
dimension.
|
|
10
|
-
|
|
11
|
-
Units are composable:
|
|
12
|
-
|
|
13
|
-
>>> from ucon import units
|
|
14
|
-
>>> units.meter / units.second
|
|
15
|
-
<velocity | (m/s)>
|
|
16
|
-
|
|
17
|
-
They can be multiplied or divided to form compound units, and their dimensional
|
|
18
|
-
relationships are preserved algebraically.
|
|
19
|
-
"""
|
|
20
|
-
from ucon.dimension import Dimension
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class Unit:
|
|
24
|
-
"""
|
|
25
|
-
Represents a **unit of measure** associated with a :class:`Dimension`.
|
|
26
|
-
|
|
27
|
-
Parameters
|
|
28
|
-
----------
|
|
29
|
-
*aliases : str
|
|
30
|
-
Optional shorthand symbols (e.g., "m", "sec").
|
|
31
|
-
name : str
|
|
32
|
-
Canonical name of the unit (e.g., "meter").
|
|
33
|
-
dimension : Dimension
|
|
34
|
-
The physical dimension this unit represents.
|
|
35
|
-
|
|
36
|
-
Notes
|
|
37
|
-
-----
|
|
38
|
-
Units participate in algebraic operations that produce new compound units:
|
|
39
|
-
|
|
40
|
-
>>> density = units.gram / units.liter
|
|
41
|
-
>>> density.dimension
|
|
42
|
-
<Dimension.density: Vector(T=0, L=-3, M=1, I=0, Θ=0, J=0, N=0)>
|
|
43
|
-
|
|
44
|
-
The combination rules follow the same algebra as :class:`Dimension`.
|
|
45
|
-
"""
|
|
46
|
-
def __init__(self, *aliases: str, name: str = '', dimension: Dimension = Dimension.none):
|
|
47
|
-
self.dimension = dimension
|
|
48
|
-
self.name = name
|
|
49
|
-
self.aliases = aliases
|
|
50
|
-
self.shorthand = aliases[0] if aliases else self.name
|
|
51
|
-
|
|
52
|
-
def __repr__(self):
|
|
53
|
-
addendum = f' | {self.name}' if self.name else ''
|
|
54
|
-
return f'<{self.dimension.name}{addendum}>'
|
|
55
|
-
|
|
56
|
-
# TODO -- limit `operator` param choices
|
|
57
|
-
def generate_name(self, unit: 'Unit', operator: str):
|
|
58
|
-
if (self.dimension is Dimension.none) and not (unit.dimension is Dimension.none):
|
|
59
|
-
return unit.name
|
|
60
|
-
if not (self.dimension is Dimension.none) and (unit.dimension is Dimension.none):
|
|
61
|
-
return self.name
|
|
62
|
-
|
|
63
|
-
if not self.shorthand and not unit.shorthand:
|
|
64
|
-
name = ''
|
|
65
|
-
elif self.shorthand and not unit.shorthand:
|
|
66
|
-
name = f'({self.shorthand}{operator}?)'
|
|
67
|
-
elif not self.shorthand and unit.shorthand:
|
|
68
|
-
name = f'(?{operator}{unit.shorthand})'
|
|
69
|
-
else:
|
|
70
|
-
name = f'({self.shorthand}{operator}{unit.shorthand})'
|
|
71
|
-
return name
|
|
72
|
-
|
|
73
|
-
def __truediv__(self, unit: 'Unit') -> 'Unit':
|
|
74
|
-
# TODO -- define __eq__ for simplification, here
|
|
75
|
-
if (self.name == unit.name) and (self.dimension == unit.dimension):
|
|
76
|
-
return Unit()
|
|
77
|
-
|
|
78
|
-
if (unit.dimension is Dimension.none):
|
|
79
|
-
return self
|
|
80
|
-
|
|
81
|
-
return Unit(name=self.generate_name(unit, '/'), dimension=self.dimension / unit.dimension)
|
|
82
|
-
|
|
83
|
-
def __mul__(self, unit: 'Unit') -> 'Unit':
|
|
84
|
-
return Unit(name=self.generate_name(unit, '*'), dimension=self.dimension * unit.dimension)
|
|
85
|
-
|
|
86
|
-
def __eq__(self, unit: 'Unit') -> bool:
|
|
87
|
-
if not isinstance(unit, Unit):
|
|
88
|
-
raise TypeError(f'Cannot compare Unit to non-Unit type: {type(unit)}')
|
|
89
|
-
return (self.name == unit.name) and (self.dimension == unit.dimension)
|
|
90
|
-
|
|
91
|
-
def __hash__(self) -> int:
|
|
92
|
-
return hash(tuple([self.name, self.dimension,]))
|
ucon-0.3.3rc1.dist-info/RECORD
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
tests/ucon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
tests/ucon/test_core.py,sha256=a48nfTqg-WJ_jzjVT6KtsBb6E8oPUZaqCmscKDzlG_g,22871
|
|
3
|
-
tests/ucon/test_dimension.py,sha256=JyA9ySFvohs2l6oK77ehCQ7QvvFVqB_9t0iC7CUErjw,7296
|
|
4
|
-
tests/ucon/test_unit.py,sha256=vEPOeSxFBqcRBAUczCN9KPo_dTmLk4LQExPSt6UGVa4,5712
|
|
5
|
-
tests/ucon/test_units.py,sha256=248JZbo8RVvG_q3T0IhKG43vxM4F_2Xgf4_RjGZNsFM,704
|
|
6
|
-
ucon/__init__.py,sha256=ZWWLodIiG17OgCfoAm532wpwmJzdRXlUGX3w6OBxFeQ,1743
|
|
7
|
-
ucon/core.py,sha256=QI0aayUm0rgggdD7_zvdrmV26dbEARCJ6Yj5gn5PitI,13729
|
|
8
|
-
ucon/dimension.py,sha256=uUP05bPE8r15oFeD36DrclNIfBsugV7uFhvtJRYy4qI,6598
|
|
9
|
-
ucon/unit.py,sha256=KxOBcQNxciljGskhZCfktLhRF5u-rWgrTg565Flo3eI,3213
|
|
10
|
-
ucon/units.py,sha256=e1j7skYMghlMZi7l94EAgxq4_lNRDC7FcSooJoE_U50,3689
|
|
11
|
-
ucon-0.3.3rc1.dist-info/licenses/LICENSE,sha256=-Djjiq2wM8Cc6fzTsdMbr_T2_uaX6Yorxcemr3GGkqc,1072
|
|
12
|
-
ucon-0.3.3rc1.dist-info/METADATA,sha256=j9qlRr2LbkyV4WbRFcMjfMSI_hOEujAHb5iBGHO-g0Y,10606
|
|
13
|
-
ucon-0.3.3rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
-
ucon-0.3.3rc1.dist-info/top_level.txt,sha256=zZYRJiQrVUtN32ziJD2YEq7ClSvDmVYHYy5ArRAZGxI,11
|
|
15
|
-
ucon-0.3.3rc1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|