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.
@@ -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