u 0.1__tar.gz
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.
- u-0.1/.gitignore +10 -0
- u-0.1/.python-version +1 -0
- u-0.1/.vscode/settings.json +7 -0
- u-0.1/PKG-INFO +37 -0
- u-0.1/README.md +28 -0
- u-0.1/__init__.py +18 -0
- u-0.1/pyproject.toml +22 -0
- u-0.1/requirements-dev.lock +19 -0
- u-0.1/requirements.lock +12 -0
- u-0.1/tests/test_conversions.py +38 -0
- u-0.1/tests/test_math.py +48 -0
- u-0.1/u/__init__.py +5 -0
- u-0.1/u/_utils.py +21 -0
- u-0.1/u/prefixes.py +83 -0
- u-0.1/u/quantities/__init__.py +14 -0
- u-0.1/u/quantities/acceleration.py +18 -0
- u-0.1/u/quantities/amount_of_substance.py +16 -0
- u-0.1/u/quantities/angle.py +24 -0
- u-0.1/u/quantities/area.py +17 -0
- u-0.1/u/quantities/data_transfer_speed.py +24 -0
- u-0.1/u/quantities/data_volume.py +44 -0
- u-0.1/u/quantities/distance.py +38 -0
- u-0.1/u/quantities/duration.py +32 -0
- u-0.1/u/quantities/electric_charge.py +17 -0
- u-0.1/u/quantities/electric_current.py +16 -0
- u-0.1/u/quantities/frequency.py +12 -0
- u-0.1/u/quantities/luminous_intensity.py +11 -0
- u-0.1/u/quantities/mass.py +37 -0
- u-0.1/u/quantities/one.py +14 -0
- u-0.1/u/quantities/pixels.py +11 -0
- u-0.1/u/quantities/speed.py +12 -0
- u-0.1/u/quantities/temperature.py +13 -0
- u-0.1/u/quantity_and_unit.py +157 -0
u-0.1/.gitignore
ADDED
u-0.1/.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12.2
|
u-0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: u
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: Statically typed units
|
|
5
|
+
Author-email: Aran-Fey <rawing7@gmail.com>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Requires-Dist: typing-extensions
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# u
|
|
11
|
+
|
|
12
|
+
Statically typed units.
|
|
13
|
+
|
|
14
|
+
## Quickstart
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
# Type safe assignments
|
|
18
|
+
duration: u.Duration = u.seconds(5) # Ok
|
|
19
|
+
distance: u.Distance = u.amperes(5) # Type checking error
|
|
20
|
+
|
|
21
|
+
# Type safe math
|
|
22
|
+
print(u.seconds(120) + u.minutes(3)) # Ok
|
|
23
|
+
print(u.seconds(120) + u.amperes(3)) # Type checking error
|
|
24
|
+
|
|
25
|
+
# Type safe compound units (with some caveats)
|
|
26
|
+
speed: u.Div[u.Distance, u.Duration] = u.km(5) / u.hours(1) # Ok
|
|
27
|
+
|
|
28
|
+
# Reusable prefixes
|
|
29
|
+
print(u.bytes(1000) == u.mega(u.bytes)(1))
|
|
30
|
+
|
|
31
|
+
# Define your own Quantities and Units
|
|
32
|
+
class Tastiness(u.Quantity):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
mmm = u.Unit[Tastiness](symbol='mmm', multiplier=1)
|
|
36
|
+
yum = u.Unit[Tastiness]('yum', 10)
|
|
37
|
+
```
|
u-0.1/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# u
|
|
2
|
+
|
|
3
|
+
Statically typed units.
|
|
4
|
+
|
|
5
|
+
## Quickstart
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
# Type safe assignments
|
|
9
|
+
duration: u.Duration = u.seconds(5) # Ok
|
|
10
|
+
distance: u.Distance = u.amperes(5) # Type checking error
|
|
11
|
+
|
|
12
|
+
# Type safe math
|
|
13
|
+
print(u.seconds(120) + u.minutes(3)) # Ok
|
|
14
|
+
print(u.seconds(120) + u.amperes(3)) # Type checking error
|
|
15
|
+
|
|
16
|
+
# Type safe compound units (with some caveats)
|
|
17
|
+
speed: u.Div[u.Distance, u.Duration] = u.km(5) / u.hours(1) # Ok
|
|
18
|
+
|
|
19
|
+
# Reusable prefixes
|
|
20
|
+
print(u.bytes(1000) == u.mega(u.bytes)(1))
|
|
21
|
+
|
|
22
|
+
# Define your own Quantities and Units
|
|
23
|
+
class Tastiness(u.Quantity):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
mmm = u.Unit[Tastiness](symbol='mmm', multiplier=1)
|
|
27
|
+
yum = u.Unit[Tastiness]('yum', 10)
|
|
28
|
+
```
|
u-0.1/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
# fake module that resides in my lib folder and
|
|
3
|
+
# imports the actual implementation
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
here = Path(__file__).absolute().parent
|
|
8
|
+
name = here.stem
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
sys.path.insert(0, str(here))
|
|
13
|
+
del sys.modules[name]
|
|
14
|
+
module = __import__(name)
|
|
15
|
+
del sys.path[0]
|
|
16
|
+
|
|
17
|
+
del Path, here, name, sys
|
|
18
|
+
globals().update(module.__dict__)
|
u-0.1/pyproject.toml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "u"
|
|
3
|
+
description = "Statically typed units"
|
|
4
|
+
authors = [{ name = "Aran-Fey", email = "rawing7@gmail.com" }]
|
|
5
|
+
dependencies = ["typing-extensions"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">= 3.8"
|
|
8
|
+
dynamic = ["version"]
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["hatchling"]
|
|
12
|
+
build-backend = "hatchling.build"
|
|
13
|
+
|
|
14
|
+
[tool.hatch.version]
|
|
15
|
+
path = "u/__init__.py"
|
|
16
|
+
|
|
17
|
+
[tool.rye]
|
|
18
|
+
managed = true
|
|
19
|
+
dev-dependencies = ["pytest"]
|
|
20
|
+
|
|
21
|
+
[tool.hatch.build.targets.wheel]
|
|
22
|
+
packages = ["u"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# generated by rye
|
|
2
|
+
# use `rye lock` or `rye sync` to update this lockfile
|
|
3
|
+
#
|
|
4
|
+
# last locked with the following flags:
|
|
5
|
+
# pre: false
|
|
6
|
+
# features: []
|
|
7
|
+
# all-features: false
|
|
8
|
+
# with-sources: false
|
|
9
|
+
|
|
10
|
+
-e file:.
|
|
11
|
+
iniconfig==2.0.0
|
|
12
|
+
# via pytest
|
|
13
|
+
packaging==24.0
|
|
14
|
+
# via pytest
|
|
15
|
+
pluggy==1.5.0
|
|
16
|
+
# via pytest
|
|
17
|
+
pytest==8.1.1
|
|
18
|
+
typing-extensions==4.11.0
|
|
19
|
+
# via u
|
u-0.1/requirements.lock
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
import u
|
|
4
|
+
from u import Quantity, Unit
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.parametrize(
|
|
8
|
+
"value, unit, expected_result",
|
|
9
|
+
[
|
|
10
|
+
(u.meters(7_000), u.kilometers, 7),
|
|
11
|
+
(u.kilometers(3.5), u.meters, 3_500),
|
|
12
|
+
# (u.celsius(10), u.kelvin, 283.15),
|
|
13
|
+
# (u.kelvin(100), u.celsius, -173.15),
|
|
14
|
+
# (u.kelvin(0), u.fahrenheit, -459.67),
|
|
15
|
+
# (u.fahrenheit(50), u.kelvin, 283.15),
|
|
16
|
+
(u.meters_per_second(1234), u.kilometers / u.seconds, 1.234),
|
|
17
|
+
(u.meters_per_second(1), u.meters / u.minutes, 60),
|
|
18
|
+
(u.kilometers_per_hour(90), u.meters_per_second, 25),
|
|
19
|
+
(u.meters_per_second(30), u.kilometers_per_hour, 108),
|
|
20
|
+
(u.square_meters(1_000_000), u.square_kilometers, 1),
|
|
21
|
+
],
|
|
22
|
+
)
|
|
23
|
+
def test_to_number(value: Quantity, unit: Unit, expected_result: float):
|
|
24
|
+
result = value.to_number(unit)
|
|
25
|
+
assert result == pytest.approx(expected_result)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.mark.parametrize(
|
|
29
|
+
"value, expected_result",
|
|
30
|
+
[
|
|
31
|
+
(u.meters(7), "7m"),
|
|
32
|
+
(u.kilometers(3.5), "3.5km"),
|
|
33
|
+
(u.meters_per_second(5), "5m/s"),
|
|
34
|
+
(u.square_meters(3), "3m²"),
|
|
35
|
+
],
|
|
36
|
+
)
|
|
37
|
+
def test_to_string(value: Quantity, expected_result: str):
|
|
38
|
+
assert str(value) == expected_result
|
u-0.1/tests/test_math.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
import u
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.mark.parametrize(
|
|
7
|
+
"result, expected_result",
|
|
8
|
+
[
|
|
9
|
+
(u.seconds(10) + u.seconds(5), u.seconds(15)),
|
|
10
|
+
(u.seconds(10) + u.seconds(50), u.minutes(1)),
|
|
11
|
+
(u.hours(0.25) + u.hours(0.5), u.minutes(45)),
|
|
12
|
+
],
|
|
13
|
+
)
|
|
14
|
+
def test_addition(result: u.Quantity, expected_result: u.Quantity):
|
|
15
|
+
assert result == expected_result
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.parametrize(
|
|
19
|
+
"result, expected_result",
|
|
20
|
+
[
|
|
21
|
+
(u.seconds(10) - u.seconds(3), u.seconds(7)),
|
|
22
|
+
(u.seconds(70) - u.seconds(10), u.minutes(1)),
|
|
23
|
+
(u.hours(0.75) - u.hours(0.5), u.minutes(15)),
|
|
24
|
+
],
|
|
25
|
+
)
|
|
26
|
+
def test_subtraction(result: u.Quantity, expected_result: u.Quantity):
|
|
27
|
+
assert result == expected_result
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.mark.parametrize(
|
|
31
|
+
"result, expected_result",
|
|
32
|
+
[
|
|
33
|
+
(u.meters(10) * u.meters(5), u.square_meters(50)),
|
|
34
|
+
(u.meters(10) * u.meters(5), u.square_meters(50)),
|
|
35
|
+
],
|
|
36
|
+
)
|
|
37
|
+
def test_multiplication(result: u.Quantity, expected_result: u.Quantity):
|
|
38
|
+
assert result == expected_result
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.parametrize(
|
|
42
|
+
"result, expected_result",
|
|
43
|
+
[
|
|
44
|
+
(u.meters(10) / u.seconds(5), u.meters_per_second(2)),
|
|
45
|
+
],
|
|
46
|
+
)
|
|
47
|
+
def test_division(result: u.Quantity, expected_result: u.Quantity):
|
|
48
|
+
assert result == expected_result
|
u-0.1/u/__init__.py
ADDED
u-0.1/u/_utils.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
from typing import Callable, TypeVar
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
C = TypeVar("C", bound=Callable)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def cached(func: C) -> C:
|
|
9
|
+
cache = {}
|
|
10
|
+
|
|
11
|
+
@functools.wraps(func)
|
|
12
|
+
def wrapper(*args):
|
|
13
|
+
try:
|
|
14
|
+
return cache[args]
|
|
15
|
+
except KeyError:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
result = cache[args] = func(*args)
|
|
19
|
+
return result
|
|
20
|
+
|
|
21
|
+
return wrapper # type: ignore
|
u-0.1/u/prefixes.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from ._utils import cached
|
|
2
|
+
from .quantity_and_unit import Unit, Q
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"Prefix",
|
|
6
|
+
"atto",
|
|
7
|
+
"femto",
|
|
8
|
+
"pico",
|
|
9
|
+
"nano",
|
|
10
|
+
"micro",
|
|
11
|
+
"milli",
|
|
12
|
+
"centi",
|
|
13
|
+
"deci",
|
|
14
|
+
"deka",
|
|
15
|
+
"hecto",
|
|
16
|
+
"kilo",
|
|
17
|
+
"mega",
|
|
18
|
+
"giga",
|
|
19
|
+
"tera",
|
|
20
|
+
"peta",
|
|
21
|
+
"exa",
|
|
22
|
+
"zetta",
|
|
23
|
+
"yotta",
|
|
24
|
+
"kibi",
|
|
25
|
+
"mebi",
|
|
26
|
+
"gibi",
|
|
27
|
+
"tebi",
|
|
28
|
+
"pebi",
|
|
29
|
+
"exbi",
|
|
30
|
+
"zebi",
|
|
31
|
+
"yobi",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Prefix:
|
|
36
|
+
def __init__(self, symbol: str, multiplier: float):
|
|
37
|
+
self.symbol = symbol
|
|
38
|
+
self.multiplier = multiplier
|
|
39
|
+
|
|
40
|
+
@cached
|
|
41
|
+
def __call__(self, unit: Unit[Q]) -> Unit[Q]:
|
|
42
|
+
return Unit(
|
|
43
|
+
self.symbol + unit.symbol,
|
|
44
|
+
self.multiplier * unit.multiplier,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def __repr__(self) -> str:
|
|
48
|
+
return f"<Prefix {self.symbol}>"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
quecto = Prefix("q", 1e-30)
|
|
52
|
+
ronto = Prefix("r", 1e-27)
|
|
53
|
+
yocto = Prefix("y", 1e-24)
|
|
54
|
+
zepto = Prefix("z", 1e-21)
|
|
55
|
+
atto = Prefix("a", 1e-18)
|
|
56
|
+
femto = Prefix("f", 1e-15)
|
|
57
|
+
pico = Prefix("p", 1e-12)
|
|
58
|
+
nano = Prefix("n", 1e-9)
|
|
59
|
+
micro = Prefix("µ", 1e-6)
|
|
60
|
+
milli = Prefix("m", 1e-3)
|
|
61
|
+
centi = Prefix("c", 1e-2)
|
|
62
|
+
deci = Prefix("d", 1e-1)
|
|
63
|
+
deka = Prefix("da", 1e1)
|
|
64
|
+
hecto = Prefix("h", 1e2)
|
|
65
|
+
kilo = Prefix("k", 1e3)
|
|
66
|
+
mega = Prefix("M", 1e6)
|
|
67
|
+
giga = Prefix("G", 1e9)
|
|
68
|
+
tera = Prefix("T", 1e12)
|
|
69
|
+
peta = Prefix("P", 1e15)
|
|
70
|
+
exa = Prefix("E", 1e18)
|
|
71
|
+
zetta = Prefix("Z", 1e21)
|
|
72
|
+
yotta = Prefix("Y", 1e24)
|
|
73
|
+
ronna = Prefix("R", 1e27)
|
|
74
|
+
quetta = Prefix("Q", 1e30)
|
|
75
|
+
|
|
76
|
+
kibi = Prefix("ki", 2**10)
|
|
77
|
+
mebi = Prefix("Mi", 2**20)
|
|
78
|
+
gibi = Prefix("Gi", 2**30)
|
|
79
|
+
tebi = Prefix("Ti", 2**40)
|
|
80
|
+
pebi = Prefix("Pi", 2**50)
|
|
81
|
+
exbi = Prefix("Ei", 2**60)
|
|
82
|
+
zebi = Prefix("Zi", 2**70)
|
|
83
|
+
yobi = Prefix("Yi", 2**80)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .acceleration import *
|
|
2
|
+
from .amount_of_substance import *
|
|
3
|
+
from .area import *
|
|
4
|
+
from .data_volume import *
|
|
5
|
+
from .distance import *
|
|
6
|
+
from .duration import *
|
|
7
|
+
from .electric_current import *
|
|
8
|
+
from .data_volume import *
|
|
9
|
+
from .frequency import *
|
|
10
|
+
from .luminous_intensity import *
|
|
11
|
+
from .mass import *
|
|
12
|
+
from .one import *
|
|
13
|
+
from .speed import *
|
|
14
|
+
from .temperature import *
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from ..quantity_and_unit import Div
|
|
2
|
+
from .duration import Duration, second, hour
|
|
3
|
+
from .speed import Speed, meters_per_second, kilometers_per_hour
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# fmt: off
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Acceleration",
|
|
9
|
+
"meters_per_second_squared", "mps2",
|
|
10
|
+
"kilometers_per_hour_squared", "kph2",
|
|
11
|
+
]
|
|
12
|
+
# fmt: on
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Acceleration = Div[Speed, Duration]
|
|
16
|
+
|
|
17
|
+
meters_per_second_squared = mps2 = meters_per_second / second
|
|
18
|
+
kilometers_per_hour_squared = kph2 = kilometers_per_hour / hour
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from ..quantity_and_unit import Quantity, Unit
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# fmt: off
|
|
5
|
+
__all__ = [
|
|
6
|
+
"AmountOfSubstance",
|
|
7
|
+
"moles", "mole", "mol",
|
|
8
|
+
]
|
|
9
|
+
# fmt: on
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AmountOfSubstance(Quantity):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
moles = mole = mol = Unit[AmountOfSubstance]("mol", 1)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from math import pi
|
|
2
|
+
|
|
3
|
+
from ..quantity_and_unit import Quantity, Unit
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# fmt: off
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Angle",
|
|
9
|
+
"radians", "radian", "rad",
|
|
10
|
+
"degrees", "degree", "deg",
|
|
11
|
+
"gradians", "gradian", "gons", "gon",
|
|
12
|
+
"turns", "turn", "tr",
|
|
13
|
+
]
|
|
14
|
+
# fmt: on
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Angle(Quantity):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
radians = radian = rad = Unit[Angle]("rad", 1)
|
|
22
|
+
degrees = degree = deg = Unit[Angle]("°", 2 * pi / 360)
|
|
23
|
+
gradians = gradian = gons = gon = Unit[Angle]("ᵍ", 2 * pi / 400)
|
|
24
|
+
turns = turn = tr = Unit[Angle]("tr", 2 * pi)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ..quantity_and_unit import Mul
|
|
2
|
+
from .distance import Distance, meter, kilometer
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# fmt: off
|
|
6
|
+
__all__ = [
|
|
7
|
+
"Area",
|
|
8
|
+
"square_meters", "square_meter", "m2",
|
|
9
|
+
"square_kilometers", "square_kilometer", "km2",
|
|
10
|
+
]
|
|
11
|
+
# fmt: on
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
Area = Mul[Distance, Distance]
|
|
15
|
+
|
|
16
|
+
square_meters = square_meter = m2 = meter * meter
|
|
17
|
+
square_kilometers = square_kilometer = km2 = kilometer * kilometer
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from ..quantity_and_unit import Div
|
|
2
|
+
from .data_volume import DataVolume, bytes, kilobytes, megabytes, gigabytes, terabytes
|
|
3
|
+
from .duration import Duration, second
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# fmt: off
|
|
7
|
+
__all__ = [
|
|
8
|
+
"DataTransferSpeed",
|
|
9
|
+
"bytes_per_second", "Bps",
|
|
10
|
+
"kilobytes_per_second", "Bps",
|
|
11
|
+
"megabytes_per_second", "MBps",
|
|
12
|
+
"gigabytes_per_second", "GBps",
|
|
13
|
+
"terabytes_per_second", "TBps",
|
|
14
|
+
]
|
|
15
|
+
# fmt: on
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DataTransferSpeed = Div[DataVolume, Duration]
|
|
19
|
+
|
|
20
|
+
bytes_per_second = Bps = bytes / second
|
|
21
|
+
kilobytes_per_second = KBps = kilobytes / second
|
|
22
|
+
megabytes_per_second = MBps = megabytes / second
|
|
23
|
+
gigabytes_per_second = GBps = gigabytes / second
|
|
24
|
+
terabytes_per_second = TBps = terabytes / second
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# fmt: off
|
|
2
|
+
from ..prefixes import (
|
|
3
|
+
kilo, mega, giga, tera, peta, exa, zetta, yotta,
|
|
4
|
+
kibi, mebi, gibi, tebi, pebi, exbi, zebi, yobi,
|
|
5
|
+
)
|
|
6
|
+
# fmt: on
|
|
7
|
+
from ..quantity_and_unit import Quantity, Unit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# fmt: off
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DataVolume",
|
|
13
|
+
"bytes", "byte", "B",
|
|
14
|
+
]
|
|
15
|
+
# fmt: on
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DataVolume(Quantity):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# TODO: Bits don't really fit in here. I initially wanted to define a byte as 8 bits, but that's not
|
|
23
|
+
# true - 8 bits is an octet. Bytes don't have a fixed amount of bits.
|
|
24
|
+
# bits = bit = b = Unit[FileSize]('b', 1)
|
|
25
|
+
|
|
26
|
+
bytes = byte = B = Unit[DataVolume]("B", 1)
|
|
27
|
+
|
|
28
|
+
kilobytes = kilobyte = kB = kilo(bytes)
|
|
29
|
+
megabytes = megabyte = MB = mega(bytes)
|
|
30
|
+
gigabytes = gigabyte = GB = giga(bytes)
|
|
31
|
+
terabytes = terabyte = TB = tera(bytes)
|
|
32
|
+
petabytes = petabyte = PB = peta(bytes)
|
|
33
|
+
exabytes = exabyte = EB = exa(bytes)
|
|
34
|
+
zettabytes = zettabyte = ZB = zetta(bytes)
|
|
35
|
+
yottabytes = yottabyte = YB = yotta(bytes)
|
|
36
|
+
|
|
37
|
+
kibibytes = kibibyte = kiB = kibi(bytes)
|
|
38
|
+
mebibytes = mebibyte = MiB = mebi(bytes)
|
|
39
|
+
gibibytes = gibibyte = GiB = gibi(bytes)
|
|
40
|
+
tebibytes = tebibyte = TiB = tebi(bytes)
|
|
41
|
+
pebibytes = pebibyte = PiB = pebi(bytes)
|
|
42
|
+
exbibytes = exbibyte = EiB = exbi(bytes)
|
|
43
|
+
zebibytes = zebibyte = ZiB = zebi(bytes)
|
|
44
|
+
yobibytes = yobibyte = YiB = yobi(bytes)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from ..prefixes import nano, micro, milli, centi, deci, kilo
|
|
2
|
+
from ..quantity_and_unit import Quantity, Unit
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# fmt: off
|
|
6
|
+
__all__ = [
|
|
7
|
+
"Distance",
|
|
8
|
+
"nanometers", "nanometer", "nm",
|
|
9
|
+
"micrometers", "micrometer", "μm",
|
|
10
|
+
"millimeters", "millimeter", "mm",
|
|
11
|
+
"centimeters", "centimeter", "cm",
|
|
12
|
+
"decimeters", "decimeter", "dm",
|
|
13
|
+
"meters", "meter", "m",
|
|
14
|
+
"kilometers", "kilometer", "km",
|
|
15
|
+
"light_seconds", "light_second", "ls",
|
|
16
|
+
"light_years", "light_year", "ly",
|
|
17
|
+
"astronomical_units", "astronomical_unit", "au",
|
|
18
|
+
"parsecs", "parsec", "pc",
|
|
19
|
+
]
|
|
20
|
+
# fmt: on
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Distance(Quantity):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
meters = meter = m = Unit[Distance]("m", 1)
|
|
28
|
+
light_seconds = light_second = ls = Unit[Distance]("ls", 299_792_458)
|
|
29
|
+
light_years = light_year = ly = Unit[Distance]("ly", 9_460_730_472_580_800)
|
|
30
|
+
astronomical_units = astronomical_unit = au = Unit[Distance]("au", 149_597_870_700)
|
|
31
|
+
parsecs = parsec = pc = Unit[Distance]("pc", 3.085_677_581_491_367_3e16)
|
|
32
|
+
|
|
33
|
+
nanometers = nanometer = nm = nano(meter)
|
|
34
|
+
micrometers = micrometer = μm = micro(meter)
|
|
35
|
+
millimeters = millimeter = mm = milli(meter)
|
|
36
|
+
centimeters = centimeter = cm = centi(meter)
|
|
37
|
+
decimeters = decimeter = dm = deci(meter)
|
|
38
|
+
kilometers = kilometer = km = kilo(meter)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from ..quantity_and_unit import Quantity, Unit
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# fmt: off
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Duration",
|
|
7
|
+
"seconds", "second", "sec", "s",
|
|
8
|
+
"minutes", "minute", "min",
|
|
9
|
+
"hours", "hour", "h",
|
|
10
|
+
"days", "day", "d",
|
|
11
|
+
"weeks", "week", "wk", "w",
|
|
12
|
+
"years", "year", "yr", "y",
|
|
13
|
+
"decades", "decade", "dec",
|
|
14
|
+
"centuries", "century", "cent", "c",
|
|
15
|
+
"millenia", "millenium", "mil", "ka", "ky",
|
|
16
|
+
]
|
|
17
|
+
# fmt: on
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Duration(Quantity):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
seconds = second = sec = s = Unit[Duration]("s", 1)
|
|
25
|
+
minutes = minute = min = Unit[Duration]("min", 60)
|
|
26
|
+
hours = hour = h = Unit[Duration]("h", 3_600)
|
|
27
|
+
days = day = d = Unit[Duration]("d", 86_400)
|
|
28
|
+
weeks = week = wk = w = Unit[Duration]("wk", 86_400)
|
|
29
|
+
years = year = yr = y = Unit[Duration]("yr", 31_557_600)
|
|
30
|
+
decades = decade = dec = Unit[Duration]("dec", 315_576_000)
|
|
31
|
+
centuries = century = cent = c = Unit[Duration]("cent", 3_155_760_000)
|
|
32
|
+
millenia = millenium = mil = ka = ky = Unit[Duration]("mil", 31_557_600_000)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ..quantity_and_unit import Mul
|
|
2
|
+
from .duration import Duration, seconds
|
|
3
|
+
from .electric_current import ElectricCurrent, amperes
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# fmt: off
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ElectricCharge",
|
|
9
|
+
"coulombs", "coulomb", "C",
|
|
10
|
+
]
|
|
11
|
+
# fmt: on
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
ElectricCharge = Mul[Duration, ElectricCurrent]
|
|
15
|
+
|
|
16
|
+
coulombs = coulomb = C = seconds * amperes
|
|
17
|
+
coulombs.symbol = "C"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from ..quantity_and_unit import Quantity, Unit
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# fmt: off
|
|
5
|
+
__all__ = [
|
|
6
|
+
"ElectricCurrent",
|
|
7
|
+
"amperes", "ampere", "A",
|
|
8
|
+
]
|
|
9
|
+
# fmt: on
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ElectricCurrent(Quantity):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
amperes = ampere = A = Unit[ElectricCurrent]("A", 1)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from ..quantity_and_unit import Div
|
|
2
|
+
from .duration import Duration, seconds
|
|
3
|
+
from .one import One, one
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
__all__ = ["Frequency", "hertz", "hertzes", "Hz"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Frequency = Div[One, Duration]
|
|
10
|
+
|
|
11
|
+
hertz = hertzes = Hz = one / seconds
|
|
12
|
+
hertz.symbol = "Hz"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from ..prefixes import nano, micro, milli, centi, deci, kilo, mega
|
|
2
|
+
from ..quantity_and_unit import Quantity, Unit
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# fmt: off
|
|
6
|
+
__all__ = [
|
|
7
|
+
"Mass",
|
|
8
|
+
"nanograms", "nanogram", "ng",
|
|
9
|
+
"micrograms", "microgram", "μg",
|
|
10
|
+
"milligrams", "milligram", "mg",
|
|
11
|
+
"centigrams", "centigram", "cg",
|
|
12
|
+
"decigrams", "decigram", "dg",
|
|
13
|
+
"grams", "gram", "g",
|
|
14
|
+
"kilograms", "kilogram", "kg",
|
|
15
|
+
"tons", "ton", "t",
|
|
16
|
+
"kilotons", "kiloton", "kt",
|
|
17
|
+
"megatons", "megaton", "Mt",
|
|
18
|
+
]
|
|
19
|
+
# fmt: on
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Mass(Quantity):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
grams = gram = g = Unit[Mass]("g", 1)
|
|
27
|
+
tons = ton = t = Unit[Mass]("t", 1_000_000)
|
|
28
|
+
|
|
29
|
+
nanograms = nanogram = ng = nano(grams)
|
|
30
|
+
micrograms = microgram = μg = micro(grams)
|
|
31
|
+
milligrams = milligram = mg = milli(grams)
|
|
32
|
+
centigrams = centigram = cg = centi(grams)
|
|
33
|
+
decigrams = decigram = dg = deci(grams)
|
|
34
|
+
kilograms = kilogram = kg = kilo(grams)
|
|
35
|
+
|
|
36
|
+
kilotons = kiloton = kt = kilo(tons)
|
|
37
|
+
megatons = megaton = Mt = mega(tons)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from ..quantity_and_unit import Quantity, Unit
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
__all__ = ["One", "ones", "one"]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class One(Quantity):
|
|
8
|
+
"""
|
|
9
|
+
Represents the absence of a quantity. Used to create "inverted" quantities like `Frequency =
|
|
10
|
+
Div[One, Duration]`.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
ones = one = Unit[One]("1", 1)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from ..quantity_and_unit import Div
|
|
2
|
+
from .duration import Duration, seconds, hours
|
|
3
|
+
from .distance import Distance, meters, kilometers
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
__all__ = ["Speed", "meters_per_second", "mps", "kilometers_per_hour", "kph"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Speed = Div[Distance, Duration]
|
|
10
|
+
|
|
11
|
+
meters_per_second = mps = meters / seconds
|
|
12
|
+
kilometers_per_hour = kph = kilometers / hours
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ..quantity_and_unit import Quantity, Unit
|
|
2
|
+
|
|
3
|
+
__all__ = ["Temperature", "kelvins", "kelvin", "K"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Temperature(Quantity):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# FIXME: What happens if you add two temperatures?
|
|
11
|
+
kelvins = kelvin = K = Unit[Temperature]("K", 1)
|
|
12
|
+
# celsius = C = Unit[Temperature]("°C", offset=273.15)
|
|
13
|
+
# fahrenheit = F = Unit[Temperature]("°F", formula=((BASE_UNIT - 273.15) * 1.8) + 32)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from typing import Generic, TypeVar, Union
|
|
5
|
+
from typing_extensions import Self
|
|
6
|
+
|
|
7
|
+
from ._utils import cached
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
__all__ = ["Quantity", "Mul", "Div", "Unit"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Quantity:
|
|
14
|
+
"""
|
|
15
|
+
Represents a measurable quantity, like mass or acceleration.
|
|
16
|
+
|
|
17
|
+
FIXME: Correct docstring
|
|
18
|
+
|
|
19
|
+
In order to be able to represent compound quantities (like speed, which is distance divided by
|
|
20
|
+
time), this is a generic class that accepts an arbitrary number of type arguments. Each type
|
|
21
|
+
argument represents either a multiplication or, through the use of `Inv`, a division.
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Distance = Quantity[DISTANCE] # just distance
|
|
27
|
+
Area = Quantity[DISTANCE, DISTANCE] # distance squared
|
|
28
|
+
Speed = Quantity[DISTANCE, Inv[DURATION]] # distance divided by time
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Unfortunately this isn't ideal: Because type checkers care about the order of the arguments,
|
|
32
|
+
they treat `Quantity[A, B]` as different from `Quantity[B, A]`.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, value: float, unit: Unit[Self]):
|
|
36
|
+
self._value = value
|
|
37
|
+
self.unit = unit
|
|
38
|
+
|
|
39
|
+
def to(self, unit: Unit[Self]) -> Self:
|
|
40
|
+
return Quantity(self.to_number(unit), unit) # type: ignore
|
|
41
|
+
|
|
42
|
+
def to_number(self, unit: Unit[Self]) -> float:
|
|
43
|
+
return self._value * (self.unit.multiplier / unit.multiplier)
|
|
44
|
+
|
|
45
|
+
def __eq__(self, quantity: object, /) -> bool:
|
|
46
|
+
if not isinstance(quantity, __class__):
|
|
47
|
+
return NotImplemented
|
|
48
|
+
|
|
49
|
+
num = self.to_number(quantity.unit) # type: ignore
|
|
50
|
+
return math.isclose(num, quantity._value)
|
|
51
|
+
|
|
52
|
+
def __lt__(self, quantity: object, /) -> bool:
|
|
53
|
+
if not isinstance(quantity, __class__):
|
|
54
|
+
return NotImplemented
|
|
55
|
+
|
|
56
|
+
num = self.to_number(quantity.unit) # type: ignore
|
|
57
|
+
return num < quantity._value and not math.isclose(num, quantity._value)
|
|
58
|
+
|
|
59
|
+
def __le__(self, quantity: object, /) -> bool:
|
|
60
|
+
if not isinstance(quantity, __class__):
|
|
61
|
+
return NotImplemented
|
|
62
|
+
|
|
63
|
+
num = self.to_number(quantity.unit) # type: ignore
|
|
64
|
+
return num < quantity._value or math.isclose(num, quantity._value)
|
|
65
|
+
|
|
66
|
+
def __gt__(self, quantity: object, /) -> bool:
|
|
67
|
+
if not isinstance(quantity, __class__):
|
|
68
|
+
return NotImplemented
|
|
69
|
+
|
|
70
|
+
num = self.to_number(quantity.unit) # type: ignore
|
|
71
|
+
return num > quantity._value and not math.isclose(num, quantity._value)
|
|
72
|
+
|
|
73
|
+
def __ge__(self, quantity: object, /) -> bool:
|
|
74
|
+
if not isinstance(quantity, __class__):
|
|
75
|
+
return NotImplemented
|
|
76
|
+
|
|
77
|
+
num = self.to_number(quantity.unit) # type: ignore
|
|
78
|
+
return num > quantity._value or math.isclose(num, quantity._value)
|
|
79
|
+
|
|
80
|
+
def __add__(self, quantity: Self) -> Self:
|
|
81
|
+
return Quantity(
|
|
82
|
+
self._value + quantity.to_number(self.unit),
|
|
83
|
+
self.unit, # type: ignore
|
|
84
|
+
) # type: ignore
|
|
85
|
+
|
|
86
|
+
def __sub__(self, quantity: Self) -> Self:
|
|
87
|
+
return Quantity(
|
|
88
|
+
self._value - quantity.to_number(self.unit),
|
|
89
|
+
self.unit, # type: ignore
|
|
90
|
+
) # type: ignore
|
|
91
|
+
|
|
92
|
+
def __mul__(self, quantity: Quantity) -> Self:
|
|
93
|
+
return Quantity(
|
|
94
|
+
self._value * quantity._value,
|
|
95
|
+
self.unit * quantity.unit, # type: ignore
|
|
96
|
+
) # type: ignore
|
|
97
|
+
|
|
98
|
+
def __truediv__(self, quantity: Quantity) -> Self:
|
|
99
|
+
return Quantity(
|
|
100
|
+
self._value / quantity._value,
|
|
101
|
+
self.unit / quantity.unit, # type: ignore
|
|
102
|
+
) # type: ignore
|
|
103
|
+
|
|
104
|
+
def __repr__(self) -> str:
|
|
105
|
+
return f"{self._value}{self.unit.symbol}"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
Q = TypeVar("Q", bound=Quantity)
|
|
109
|
+
Q2 = TypeVar("Q2", bound=Quantity)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class _Mul(Generic[Q, Q2], Quantity): ...
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Make multiplication commutative, otherwise type checkers would complain every time you do a
|
|
116
|
+
# multiplication in the "wrong" order. For example:
|
|
117
|
+
#
|
|
118
|
+
# electric_charge: Mul[Duration, ElectricCurrent] = ampere(1) * second(1)
|
|
119
|
+
Mul = Union[_Mul[Q, Q2], _Mul[Q2, Q]]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class Div(Generic[Q, Q2], Quantity): ...
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Unit(Generic[Q]):
|
|
126
|
+
"""
|
|
127
|
+
Represents a unit of measurement for a certain `Quantity`.
|
|
128
|
+
|
|
129
|
+
To create your own unit, simply create an instance of this class. Make sure to pass the
|
|
130
|
+
corresponding `Quantity` as a type argument as seen here:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
seconds = Unit[Duration]("s", 1)
|
|
134
|
+
minutes = Unit[Duration]("min", 60)
|
|
135
|
+
```
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def __init__(self, symbol: str, multiplier: float):
|
|
139
|
+
self.symbol = symbol
|
|
140
|
+
self.multiplier = multiplier
|
|
141
|
+
|
|
142
|
+
@cached
|
|
143
|
+
def __mul__(self, other: Unit[Q2]) -> Unit[Mul[Q, Q2]]:
|
|
144
|
+
return Unit(
|
|
145
|
+
f"{self.symbol}*{other.symbol}",
|
|
146
|
+
self.multiplier * other.multiplier,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
@cached
|
|
150
|
+
def __truediv__(self, other: Unit[Q2]) -> Unit[Div[Q, Q2]]:
|
|
151
|
+
return Unit(f"{self.symbol}/{other.symbol}", self.multiplier / other.multiplier)
|
|
152
|
+
|
|
153
|
+
def __call__(self, value: float) -> Q:
|
|
154
|
+
return Quantity(value, self) # type: ignore
|
|
155
|
+
|
|
156
|
+
def __rmul__(self, value: float) -> Q:
|
|
157
|
+
return Quantity(value, self) # type: ignore
|