frequenz-quantities 1.0.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.
- frequenz/quantities/__init__.py +112 -0
- frequenz/quantities/_apparent_power.py +234 -0
- frequenz/quantities/_current.py +131 -0
- frequenz/quantities/_energy.py +188 -0
- frequenz/quantities/_frequency.py +115 -0
- frequenz/quantities/_percentage.py +66 -0
- frequenz/quantities/_power.py +244 -0
- frequenz/quantities/_quantity.py +541 -0
- frequenz/quantities/_reactive_power.py +235 -0
- frequenz/quantities/_temperature.py +39 -0
- frequenz/quantities/_voltage.py +148 -0
- frequenz/quantities/conftest.py +13 -0
- frequenz/quantities/experimental/__init__.py +11 -0
- frequenz/quantities/experimental/marshmallow.py +271 -0
- frequenz/quantities/py.typed +0 -0
- frequenz_quantities-1.0.0.dist-info/LICENSE +21 -0
- frequenz_quantities-1.0.0.dist-info/METADATA +103 -0
- frequenz_quantities-1.0.0.dist-info/RECORD +20 -0
- frequenz_quantities-1.0.0.dist-info/WHEEL +5 -0
- frequenz_quantities-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# License: MIT
|
|
2
|
+
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH
|
|
3
|
+
|
|
4
|
+
"""Types for holding quantities with units."""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from datetime import timedelta
|
|
8
|
+
from typing import Self
|
|
9
|
+
|
|
10
|
+
from ._quantity import NoDefaultConstructible, Quantity
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Frequency(
|
|
14
|
+
Quantity,
|
|
15
|
+
metaclass=NoDefaultConstructible,
|
|
16
|
+
exponent_unit_map={0: "Hz", 3: "kHz", 6: "MHz", 9: "GHz"},
|
|
17
|
+
):
|
|
18
|
+
"""A frequency quantity.
|
|
19
|
+
|
|
20
|
+
Objects of this type are wrappers around `float` values and are immutable.
|
|
21
|
+
|
|
22
|
+
The constructors accept a single `float` value, the `as_*()` methods return a
|
|
23
|
+
`float` value, and each of the arithmetic operators supported by this type are
|
|
24
|
+
actually implemented using floating-point arithmetic.
|
|
25
|
+
|
|
26
|
+
So all considerations about floating-point arithmetic apply to this type as well.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_hertz(cls, hertz: float) -> Self:
|
|
31
|
+
"""Initialize a new frequency quantity.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
hertz: The frequency in hertz.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
A new frequency quantity.
|
|
38
|
+
"""
|
|
39
|
+
return cls._new(hertz)
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_kilohertz(cls, kilohertz: float) -> Self:
|
|
43
|
+
"""Initialize a new frequency quantity.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
kilohertz: The frequency in kilohertz.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
A new frequency quantity.
|
|
50
|
+
"""
|
|
51
|
+
return cls._new(kilohertz, exponent=3)
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def from_megahertz(cls, megahertz: float) -> Self:
|
|
55
|
+
"""Initialize a new frequency quantity.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
megahertz: The frequency in megahertz.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
A new frequency quantity.
|
|
62
|
+
"""
|
|
63
|
+
return cls._new(megahertz, exponent=6)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_gigahertz(cls, gigahertz: float) -> Self:
|
|
67
|
+
"""Initialize a new frequency quantity.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
gigahertz: The frequency in gigahertz.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
A new frequency quantity.
|
|
74
|
+
"""
|
|
75
|
+
return cls._new(gigahertz, exponent=9)
|
|
76
|
+
|
|
77
|
+
def as_hertz(self) -> float:
|
|
78
|
+
"""Return the frequency in hertz.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
The frequency in hertz.
|
|
82
|
+
"""
|
|
83
|
+
return self._base_value
|
|
84
|
+
|
|
85
|
+
def as_kilohertz(self) -> float:
|
|
86
|
+
"""Return the frequency in kilohertz.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The frequency in kilohertz.
|
|
90
|
+
"""
|
|
91
|
+
return self._base_value / 1e3
|
|
92
|
+
|
|
93
|
+
def as_megahertz(self) -> float:
|
|
94
|
+
"""Return the frequency in megahertz.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The frequency in megahertz.
|
|
98
|
+
"""
|
|
99
|
+
return self._base_value / 1e6
|
|
100
|
+
|
|
101
|
+
def as_gigahertz(self) -> float:
|
|
102
|
+
"""Return the frequency in gigahertz.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The frequency in gigahertz.
|
|
106
|
+
"""
|
|
107
|
+
return self._base_value / 1e9
|
|
108
|
+
|
|
109
|
+
def period(self) -> timedelta:
|
|
110
|
+
"""Return the period of the frequency.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
The period of the frequency.
|
|
114
|
+
"""
|
|
115
|
+
return timedelta(seconds=1.0 / self._base_value)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# License: MIT
|
|
2
|
+
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH
|
|
3
|
+
|
|
4
|
+
"""Types for holding quantities with units."""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from typing import Self
|
|
8
|
+
|
|
9
|
+
from ._quantity import NoDefaultConstructible, Quantity
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Percentage(
|
|
13
|
+
Quantity,
|
|
14
|
+
metaclass=NoDefaultConstructible,
|
|
15
|
+
exponent_unit_map={0: "%"},
|
|
16
|
+
):
|
|
17
|
+
"""A percentage quantity.
|
|
18
|
+
|
|
19
|
+
Objects of this type are wrappers around `float` values and are immutable.
|
|
20
|
+
|
|
21
|
+
The constructors accept a single `float` value, the `as_*()` methods return a
|
|
22
|
+
`float` value, and each of the arithmetic operators supported by this type are
|
|
23
|
+
actually implemented using floating-point arithmetic.
|
|
24
|
+
|
|
25
|
+
So all considerations about floating-point arithmetic apply to this type as well.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_percent(cls, percent: float) -> Self:
|
|
30
|
+
"""Initialize a new percentage quantity from a percent value.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
percent: The percent value, normally in the 0.0-100.0 range.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
A new percentage quantity.
|
|
37
|
+
"""
|
|
38
|
+
return cls._new(percent)
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_fraction(cls, fraction: float) -> Self:
|
|
42
|
+
"""Initialize a new percentage quantity from a fraction.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
fraction: The fraction, normally in the 0.0-1.0 range.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A new percentage quantity.
|
|
49
|
+
"""
|
|
50
|
+
return cls._new(fraction * 100)
|
|
51
|
+
|
|
52
|
+
def as_percent(self) -> float:
|
|
53
|
+
"""Return this quantity as a percentage.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
This quantity as a percentage.
|
|
57
|
+
"""
|
|
58
|
+
return self._base_value
|
|
59
|
+
|
|
60
|
+
def as_fraction(self) -> float:
|
|
61
|
+
"""Return this quantity as a fraction.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
This quantity as a fraction.
|
|
65
|
+
"""
|
|
66
|
+
return self._base_value / 100
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# License: MIT
|
|
2
|
+
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH
|
|
3
|
+
|
|
4
|
+
"""Types for holding quantities with units."""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import timedelta
|
|
10
|
+
from typing import TYPE_CHECKING, Self, overload
|
|
11
|
+
|
|
12
|
+
from ._quantity import NoDefaultConstructible, Quantity
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ._current import Current
|
|
16
|
+
from ._energy import Energy
|
|
17
|
+
from ._percentage import Percentage
|
|
18
|
+
from ._voltage import Voltage
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Power(
|
|
22
|
+
Quantity,
|
|
23
|
+
metaclass=NoDefaultConstructible,
|
|
24
|
+
exponent_unit_map={
|
|
25
|
+
-3: "mW",
|
|
26
|
+
0: "W",
|
|
27
|
+
3: "kW",
|
|
28
|
+
6: "MW",
|
|
29
|
+
},
|
|
30
|
+
):
|
|
31
|
+
"""A power quantity.
|
|
32
|
+
|
|
33
|
+
Objects of this type are wrappers around `float` values and are immutable.
|
|
34
|
+
|
|
35
|
+
The constructors accept a single `float` value, the `as_*()` methods return a
|
|
36
|
+
`float` value, and each of the arithmetic operators supported by this type are
|
|
37
|
+
actually implemented using floating-point arithmetic.
|
|
38
|
+
|
|
39
|
+
So all considerations about floating-point arithmetic apply to this type as well.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def from_watts(cls, watts: float) -> Self:
|
|
44
|
+
"""Initialize a new power quantity.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
watts: The power in watts.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
A new power quantity.
|
|
51
|
+
"""
|
|
52
|
+
return cls._new(watts)
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_milliwatts(cls, milliwatts: float) -> Self:
|
|
56
|
+
"""Initialize a new power quantity.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
milliwatts: The power in milliwatts.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
A new power quantity.
|
|
63
|
+
"""
|
|
64
|
+
return cls._new(milliwatts, exponent=-3)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def from_kilowatts(cls, kilowatts: float) -> Self:
|
|
68
|
+
"""Initialize a new power quantity.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
kilowatts: The power in kilowatts.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
A new power quantity.
|
|
75
|
+
"""
|
|
76
|
+
return cls._new(kilowatts, exponent=3)
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_megawatts(cls, megawatts: float) -> Self:
|
|
80
|
+
"""Initialize a new power quantity.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
megawatts: The power in megawatts.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
A new power quantity.
|
|
87
|
+
"""
|
|
88
|
+
return cls._new(megawatts, exponent=6)
|
|
89
|
+
|
|
90
|
+
def as_watts(self) -> float:
|
|
91
|
+
"""Return the power in watts.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
The power in watts.
|
|
95
|
+
"""
|
|
96
|
+
return self._base_value
|
|
97
|
+
|
|
98
|
+
def as_kilowatts(self) -> float:
|
|
99
|
+
"""Return the power in kilowatts.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
The power in kilowatts.
|
|
103
|
+
"""
|
|
104
|
+
return self._base_value / 1e3
|
|
105
|
+
|
|
106
|
+
def as_megawatts(self) -> float:
|
|
107
|
+
"""Return the power in megawatts.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
The power in megawatts.
|
|
111
|
+
"""
|
|
112
|
+
return self._base_value / 1e6
|
|
113
|
+
|
|
114
|
+
# We need the ignore here because otherwise mypy will give this error:
|
|
115
|
+
# > Overloaded operator methods can't have wider argument types in overrides
|
|
116
|
+
# The problem seems to be when the other type implements an **incompatible**
|
|
117
|
+
# __rmul__ method, which is not the case here, so we should be safe.
|
|
118
|
+
# Please see this example:
|
|
119
|
+
# https://github.com/python/mypy/blob/c26f1297d4f19d2d1124a30efc97caebb8c28616/test-data/unit/check-overloading.test#L4738C1-L4769C55
|
|
120
|
+
# And a discussion in a mypy issue here:
|
|
121
|
+
# https://github.com/python/mypy/issues/4985#issuecomment-389692396
|
|
122
|
+
@overload # type: ignore[override]
|
|
123
|
+
def __mul__(self, scalar: float, /) -> Self:
|
|
124
|
+
"""Scale this power by a scalar.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
scalar: The scalar by which to scale this power.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
The scaled power.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
@overload
|
|
134
|
+
def __mul__(self, percent: Percentage, /) -> Self:
|
|
135
|
+
"""Scale this power by a percentage.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
percent: The percentage by which to scale this power.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
The scaled power.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
@overload
|
|
145
|
+
def __mul__(self, other: timedelta, /) -> Energy:
|
|
146
|
+
"""Return an energy from multiplying this power by the given duration.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
other: The duration to multiply by.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
The calculated energy.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __mul__(self, other: float | Percentage | timedelta, /) -> Self | Energy:
|
|
156
|
+
"""Return a power or energy from multiplying this power by the given value.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
other: The scalar, percentage or duration to multiply by.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
A power or energy.
|
|
163
|
+
"""
|
|
164
|
+
from ._energy import Energy # pylint: disable=import-outside-toplevel
|
|
165
|
+
from ._percentage import Percentage # pylint: disable=import-outside-toplevel
|
|
166
|
+
|
|
167
|
+
match other:
|
|
168
|
+
case float() | Percentage():
|
|
169
|
+
return super().__mul__(other)
|
|
170
|
+
case timedelta():
|
|
171
|
+
return Energy._new(self._base_value * other.total_seconds() / 3600.0)
|
|
172
|
+
case _:
|
|
173
|
+
return NotImplemented
|
|
174
|
+
|
|
175
|
+
# See the comment for Power.__mul__ for why we need the ignore here.
|
|
176
|
+
@overload # type: ignore[override]
|
|
177
|
+
def __truediv__(self, other: float, /) -> Self:
|
|
178
|
+
"""Divide this power by a scalar.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
other: The scalar to divide this power by.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
The divided power.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
@overload
|
|
188
|
+
def __truediv__(self, other: Self, /) -> float:
|
|
189
|
+
"""Return the ratio of this power to another.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
other: The other power.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
The ratio of this power to another.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
@overload
|
|
199
|
+
def __truediv__(self, current: Current, /) -> Voltage:
|
|
200
|
+
"""Return a voltage from dividing this power by the given current.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
current: The current to divide by.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
A voltage from dividing this power by the a current.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
@overload
|
|
210
|
+
def __truediv__(self, voltage: Voltage, /) -> Current:
|
|
211
|
+
"""Return a current from dividing this power by the given voltage.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
voltage: The voltage to divide by.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
A current from dividing this power by a voltage.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
def __truediv__(
|
|
221
|
+
self, other: float | Self | Current | Voltage, /
|
|
222
|
+
) -> Self | float | Voltage | Current:
|
|
223
|
+
"""Return a current or voltage from dividing this power by the given value.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
other: The scalar, power, current or voltage to divide by.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
A current or voltage from dividing this power by the given value.
|
|
230
|
+
"""
|
|
231
|
+
from ._current import Current # pylint: disable=import-outside-toplevel
|
|
232
|
+
from ._voltage import Voltage # pylint: disable=import-outside-toplevel
|
|
233
|
+
|
|
234
|
+
match other:
|
|
235
|
+
case float():
|
|
236
|
+
return super().__truediv__(other)
|
|
237
|
+
case Power():
|
|
238
|
+
return self._base_value / other._base_value
|
|
239
|
+
case Current():
|
|
240
|
+
return Voltage._new(self._base_value / other._base_value)
|
|
241
|
+
case Voltage():
|
|
242
|
+
return Current._new(self._base_value / other._base_value)
|
|
243
|
+
case _:
|
|
244
|
+
return NotImplemented
|