egauge-python 0.9.8__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.
- egauge/ctid/__init__.py +7 -0
- egauge/ctid/bit_stuffer.py +65 -0
- egauge/ctid/ctid.py +967 -0
- egauge/ctid/encoder.py +436 -0
- egauge/ctid/intel_hex_encoder.py +98 -0
- egauge/ctid/waveform.py +299 -0
- egauge/examples/data/test-ctid-decoder.raw +0 -0
- egauge/examples/test_capture.py +77 -0
- egauge/examples/test_common.py +26 -0
- egauge/examples/test_ctid.py +89 -0
- egauge/examples/test_ctid_decoder.py +93 -0
- egauge/examples/test_local.py +201 -0
- egauge/examples/test_register.py +104 -0
- egauge/loggers.py +72 -0
- egauge/pyside/__init__.py +0 -0
- egauge/pyside/ansi2html.py +112 -0
- egauge/pyside/terminal.py +295 -0
- egauge/webapi/__init__.py +34 -0
- egauge/webapi/auth.py +364 -0
- egauge/webapi/cloud/__init__.py +30 -0
- egauge/webapi/cloud/credentials.py +86 -0
- egauge/webapi/cloud/credentials_dialog.py +58 -0
- egauge/webapi/cloud/gui/credentials_dialog.py +100 -0
- egauge/webapi/cloud/serial_number.py +276 -0
- egauge/webapi/device/__init__.py +38 -0
- egauge/webapi/device/capture.py +453 -0
- egauge/webapi/device/ctid_info.py +553 -0
- egauge/webapi/device/device.py +349 -0
- egauge/webapi/device/local.py +268 -0
- egauge/webapi/device/physical_quantity.py +439 -0
- egauge/webapi/device/physical_units.py +473 -0
- egauge/webapi/device/register.py +338 -0
- egauge/webapi/device/register_row.py +145 -0
- egauge/webapi/device/register_type.py +851 -0
- egauge/webapi/device/slop.py +334 -0
- egauge/webapi/device/virtual_register.py +353 -0
- egauge/webapi/error.py +34 -0
- egauge/webapi/json_api.py +332 -0
- egauge_python-0.9.8.dist-info/METADATA +148 -0
- egauge_python-0.9.8.dist-info/RECORD +44 -0
- egauge_python-0.9.8.dist-info/WHEEL +5 -0
- egauge_python-0.9.8.dist-info/entry_points.txt +2 -0
- egauge_python-0.9.8.dist-info/licenses/LICENSE +22 -0
- egauge_python-0.9.8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2022, 2024 eGauge Systems LLC
|
|
3
|
+
# 1644 Conestoga St, Suite 2
|
|
4
|
+
# Boulder, CO 80301
|
|
5
|
+
# voice: 720-545-9767
|
|
6
|
+
# email: davidm@egauge.net
|
|
7
|
+
#
|
|
8
|
+
# All rights reserved.
|
|
9
|
+
#
|
|
10
|
+
# MIT License
|
|
11
|
+
#
|
|
12
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
# in the Software without restriction, including without limitation the rights
|
|
15
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
# furnished to do so, subject to the following conditions:
|
|
18
|
+
#
|
|
19
|
+
# The above copyright notice and this permission notice shall be included in
|
|
20
|
+
# all copies or substantial portions of the Software.
|
|
21
|
+
#
|
|
22
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
28
|
+
# THE SOFTWARE.
|
|
29
|
+
#
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from typing import Callable
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Error(Exception):
|
|
35
|
+
"""Base class for any error raised by this module."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class PhysicalValue:
|
|
40
|
+
"""A physical value has a unit attached to its numeric value."""
|
|
41
|
+
|
|
42
|
+
value: float
|
|
43
|
+
unit: str
|
|
44
|
+
|
|
45
|
+
def __str__(self):
|
|
46
|
+
return f"{self.value}{self.unit}"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class PhysicalUnitConversion:
|
|
51
|
+
"""An object of this class handles conversion from one unit to another
|
|
52
|
+
and back."""
|
|
53
|
+
|
|
54
|
+
#
|
|
55
|
+
# True if the conversion requires specifying the time parameters
|
|
56
|
+
# to `calc` and `inverse`. This is necessary, for example to
|
|
57
|
+
# convert from C*s (celsius-seconds) to F*s (Fahrenheit-seconds).
|
|
58
|
+
#
|
|
59
|
+
time_dependent: bool
|
|
60
|
+
#
|
|
61
|
+
# Given a primary unit value, calculate the equivalent value in
|
|
62
|
+
# this unit.
|
|
63
|
+
#
|
|
64
|
+
# First argument is the primary-unit value to convert. Second
|
|
65
|
+
# argument is is the number of seconds over which the value was
|
|
66
|
+
# measured. This argument is only needed if `time_dependent` is
|
|
67
|
+
# True.
|
|
68
|
+
calc: Callable[[float, float | None], float]
|
|
69
|
+
#
|
|
70
|
+
# Given a value in this unit, convert it back to a primary-unit value.
|
|
71
|
+
#
|
|
72
|
+
# First argument is the value to convert. Second argument is the
|
|
73
|
+
# number of seconds over which the value was measured. This
|
|
74
|
+
# argument is only needed if `time_dependent` is True.
|
|
75
|
+
inverse: Callable[[float, float | None], float]
|
|
76
|
+
#
|
|
77
|
+
# Given a primary-unit change (difference) in value, calculate the
|
|
78
|
+
# equivalent difference in this unit. This is defined only if the
|
|
79
|
+
# formula is not the same as `calc`.
|
|
80
|
+
#
|
|
81
|
+
# The first argument is the measured difference to convert. The
|
|
82
|
+
# second argument is the number of seconds over which the
|
|
83
|
+
# difference was measured. This argument is only needed if
|
|
84
|
+
# `time_dependent` is True.
|
|
85
|
+
diff_calc: Callable[[float, float | None], float] | None = None
|
|
86
|
+
#
|
|
87
|
+
# Given a difference in this unit, convert it back to a difference
|
|
88
|
+
# in the primary unit. This is defined only if the formula is not
|
|
89
|
+
# the same as `inverse`.
|
|
90
|
+
#
|
|
91
|
+
# First argument is the difference to convert. The second
|
|
92
|
+
# argument is the number of seconds over which the difference was
|
|
93
|
+
# measured. This argument is only needed if `time_dependent` is
|
|
94
|
+
# True.
|
|
95
|
+
diff_inverse: Callable[[float, float | None], float] | None = None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class PhysicalUnit:
|
|
99
|
+
"""Each physical unit object has a unit name and may have one or more
|
|
100
|
+
scaled version of this unit."""
|
|
101
|
+
|
|
102
|
+
def __init__(self, name: str, description: str | None):
|
|
103
|
+
self.name = name
|
|
104
|
+
self.description = description
|
|
105
|
+
# Dictionary of scaled units or None if there aren't any. The
|
|
106
|
+
# index is the name of the scaled unit, the value is the scale
|
|
107
|
+
# factor to use when converting to that scaled unit.
|
|
108
|
+
self.scaled: dict[str, float] | None = None
|
|
109
|
+
# Documentation for a particular scaled unit or None if there
|
|
110
|
+
# aren't any such documentation strings.
|
|
111
|
+
self.scaled_description: dict[str, str] | None = None
|
|
112
|
+
|
|
113
|
+
def add_scaled(
|
|
114
|
+
self, name: str, factor: float, description: str | None = None
|
|
115
|
+
):
|
|
116
|
+
"""Add a scaled version of the unit (e.g., for 'A' (ampere), this
|
|
117
|
+
might be 'mA' (milli ampere) or 'kA' (kilo ampere).
|
|
118
|
+
|
|
119
|
+
Required arguments:
|
|
120
|
+
|
|
121
|
+
name -- The name of the scaled unit.
|
|
122
|
+
|
|
123
|
+
factor -- The number by which to multiply a value by to get
|
|
124
|
+
the equivalent value in the scaled unit.
|
|
125
|
+
|
|
126
|
+
Keyword arguments:
|
|
127
|
+
|
|
128
|
+
description -- Optional description of the scaled unit
|
|
129
|
+
(default None).
|
|
130
|
+
|
|
131
|
+
"""
|
|
132
|
+
if not self.scaled:
|
|
133
|
+
self.scaled = {}
|
|
134
|
+
if name in self.scaled:
|
|
135
|
+
raise Error("Scaled unit name already exists.", name)
|
|
136
|
+
self.scaled[name] = factor
|
|
137
|
+
if description is not None:
|
|
138
|
+
if not self.scaled_description:
|
|
139
|
+
self.scaled_description = {}
|
|
140
|
+
self.scaled_description[name] = description
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class PrimaryUnit(PhysicalUnit):
|
|
144
|
+
"""Each primary unit object is a physical unit that may have zero or
|
|
145
|
+
more alternate units."""
|
|
146
|
+
|
|
147
|
+
def __init__(self, name: str, description: str | None = None):
|
|
148
|
+
super().__init__(name, description)
|
|
149
|
+
# Dictionary of alternate units available for this primary
|
|
150
|
+
# unit. The index is the name of the alternate unit, the
|
|
151
|
+
# value is an alternate unit object.
|
|
152
|
+
self.alternates: dict[str, "AlternateUnit"] | None = None
|
|
153
|
+
|
|
154
|
+
def add_alternate(self, alternate: "AlternateUnit"):
|
|
155
|
+
"""Add an alternate unit to this primary unit."""
|
|
156
|
+
if not self.alternates:
|
|
157
|
+
self.alternates = {}
|
|
158
|
+
if alternate.name in self.alternates:
|
|
159
|
+
raise Error("Alternate unit name already exists.", alternate.name)
|
|
160
|
+
self.alternates[alternate.name] = alternate
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class AlternateUnit(PhysicalUnit):
|
|
164
|
+
"""Each alternate unit is a physical unit which has a parent that this
|
|
165
|
+
the primary unit and conversion functions to convert values
|
|
166
|
+
between the primary unit and the alternate unit."""
|
|
167
|
+
|
|
168
|
+
def __init__(
|
|
169
|
+
self,
|
|
170
|
+
name: str,
|
|
171
|
+
primary: PrimaryUnit,
|
|
172
|
+
conversion: PhysicalUnitConversion,
|
|
173
|
+
description: str | None = None,
|
|
174
|
+
):
|
|
175
|
+
"""Create an alternate unit object.
|
|
176
|
+
|
|
177
|
+
Required arguments:
|
|
178
|
+
|
|
179
|
+
name -- The name of the alternate unit.
|
|
180
|
+
|
|
181
|
+
primary -- The primary unit of the alternate unit.
|
|
182
|
+
|
|
183
|
+
conversion --- Physical unit conversion object describing the
|
|
184
|
+
conversion between the primary and alternate units.
|
|
185
|
+
|
|
186
|
+
Keyword arguments:
|
|
187
|
+
|
|
188
|
+
description -- Optional description of the alternate unit
|
|
189
|
+
(default None).
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
super().__init__(name, description)
|
|
193
|
+
self.primary = primary
|
|
194
|
+
self.conversion = conversion
|
|
195
|
+
primary.add_alternate(self)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class PhysicalUnits:
|
|
199
|
+
"""An object of this class defines a set of physical units and methods
|
|
200
|
+
to convert between them."""
|
|
201
|
+
|
|
202
|
+
def __init__(self):
|
|
203
|
+
# Map from any recognized unit to its PhysicalUnit. For
|
|
204
|
+
# scaled units, this maps to the primary or alternate unit
|
|
205
|
+
# that defines the scaled unit.
|
|
206
|
+
self._map: dict[str, PhysicalUnit] = {}
|
|
207
|
+
|
|
208
|
+
def add(self, unit: PhysicalUnit):
|
|
209
|
+
"""Add a physical unit.
|
|
210
|
+
|
|
211
|
+
Raises Error if the unit's name is already known.
|
|
212
|
+
|
|
213
|
+
Required argument:
|
|
214
|
+
|
|
215
|
+
unit -- The physical unit to add.
|
|
216
|
+
|
|
217
|
+
"""
|
|
218
|
+
if unit.name in self._map:
|
|
219
|
+
raise Error("Unit name already exists.", unit.name)
|
|
220
|
+
self._map[unit.name] = unit
|
|
221
|
+
|
|
222
|
+
def add_scaled(
|
|
223
|
+
self,
|
|
224
|
+
unit: PhysicalUnit,
|
|
225
|
+
name: str,
|
|
226
|
+
factor: float,
|
|
227
|
+
description: str | None = None,
|
|
228
|
+
):
|
|
229
|
+
"""Add a scaled version of a unit.
|
|
230
|
+
|
|
231
|
+
Raises an Error if the scaled unit's name is already known.
|
|
232
|
+
|
|
233
|
+
Required arguments:
|
|
234
|
+
|
|
235
|
+
name -- The name of the scaled unit.
|
|
236
|
+
|
|
237
|
+
factor -- The number by which to multiply a value by to get
|
|
238
|
+
the equivalent value in the scaled unit.
|
|
239
|
+
|
|
240
|
+
Keyword arguments:
|
|
241
|
+
|
|
242
|
+
description -- Optional description of the scaled unit
|
|
243
|
+
(default None).
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
unit.add_scaled(name, factor, description)
|
|
247
|
+
if name in self._map:
|
|
248
|
+
raise Error("Scaled unit already exists.", name)
|
|
249
|
+
self._map[name] = unit
|
|
250
|
+
|
|
251
|
+
def convert(
|
|
252
|
+
self,
|
|
253
|
+
x: float,
|
|
254
|
+
from_unit: str,
|
|
255
|
+
to_unit: str,
|
|
256
|
+
is_diff: bool = False,
|
|
257
|
+
t: float | None = None,
|
|
258
|
+
) -> float | None:
|
|
259
|
+
"""Convert a value from a source unit to a destination unit.
|
|
260
|
+
|
|
261
|
+
Returns the value in the destination unit or `None` if the
|
|
262
|
+
source or destination unit is unknown or if the conversion
|
|
263
|
+
from the source unit to the destination unit is not possible.
|
|
264
|
+
|
|
265
|
+
Required arguments:
|
|
266
|
+
|
|
267
|
+
x -- The value to convert.
|
|
268
|
+
|
|
269
|
+
from_unit -- The unit of `x`
|
|
270
|
+
|
|
271
|
+
to_unit -- The destination unit to convert `x` to.
|
|
272
|
+
|
|
273
|
+
Keyword arguments:
|
|
274
|
+
|
|
275
|
+
is_diff -- Indicates that the value `x` is a difference (i.e.,
|
|
276
|
+
a relative value) rather than an absolute value (default
|
|
277
|
+
`False`).
|
|
278
|
+
|
|
279
|
+
t -- Indicates whether the conversion is time-dependent
|
|
280
|
+
(default None). If None, the conversion is independent of
|
|
281
|
+
time. If a number, the conversion is time-dependent and
|
|
282
|
+
`t` must the duration over which the value was measured.
|
|
283
|
+
|
|
284
|
+
"""
|
|
285
|
+
if from_unit == to_unit:
|
|
286
|
+
return x
|
|
287
|
+
|
|
288
|
+
pu = self._map.get(from_unit)
|
|
289
|
+
if not isinstance(pu, PrimaryUnit):
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
if pu.scaled and from_unit in pu.scaled:
|
|
293
|
+
# convert the value to the unscaled unit:
|
|
294
|
+
x /= pu.scaled[from_unit]
|
|
295
|
+
from_unit = pu.name
|
|
296
|
+
if from_unit == to_unit:
|
|
297
|
+
return x
|
|
298
|
+
|
|
299
|
+
if isinstance(pu, AlternateUnit):
|
|
300
|
+
# convert from alternate unit to primary unit:
|
|
301
|
+
c = pu.conversion
|
|
302
|
+
if is_diff and c.diff_inverse:
|
|
303
|
+
x = c.diff_inverse(x, t)
|
|
304
|
+
else:
|
|
305
|
+
x = c.inverse(x, t)
|
|
306
|
+
pu = pu.primary
|
|
307
|
+
|
|
308
|
+
primary = pu
|
|
309
|
+
from_unit = primary.name
|
|
310
|
+
if from_unit == to_unit:
|
|
311
|
+
return x
|
|
312
|
+
|
|
313
|
+
# convert from primary unit to target unit:
|
|
314
|
+
pu = self._map.get(to_unit)
|
|
315
|
+
if not pu:
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
# verify that we can convert between the two units:
|
|
319
|
+
if isinstance(pu, AlternateUnit):
|
|
320
|
+
if pu.primary != primary:
|
|
321
|
+
return None
|
|
322
|
+
elif pu != primary:
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
valid_unit = False
|
|
326
|
+
if primary.alternates:
|
|
327
|
+
au = primary.alternates.get(pu.name)
|
|
328
|
+
if au:
|
|
329
|
+
# convert to alternate unit:
|
|
330
|
+
pu = au
|
|
331
|
+
c = au.conversion
|
|
332
|
+
if is_diff and c.diff_calc:
|
|
333
|
+
x = c.diff_calc(x, t)
|
|
334
|
+
else:
|
|
335
|
+
x = c.calc(x, t)
|
|
336
|
+
valid_unit = True
|
|
337
|
+
|
|
338
|
+
if pu.scaled and to_unit in pu.scaled:
|
|
339
|
+
# apply scale factor:
|
|
340
|
+
x *= pu.scaled[to_unit]
|
|
341
|
+
valid_unit = True
|
|
342
|
+
return x if valid_unit else None
|
|
343
|
+
|
|
344
|
+
def auto_scale(self, pv: PhysicalValue) -> PhysicalValue:
|
|
345
|
+
"""Automatically scale a physical value such that its absolute
|
|
346
|
+
value is either zero or in the range from 1..1000.
|
|
347
|
+
|
|
348
|
+
The method returns the scaled value or the original value if
|
|
349
|
+
scaling is not possible for any reason.
|
|
350
|
+
|
|
351
|
+
Required arguments:
|
|
352
|
+
|
|
353
|
+
pv -- The physical value to auto-scale.
|
|
354
|
+
|
|
355
|
+
"""
|
|
356
|
+
pu = self._map.get(pv.unit)
|
|
357
|
+
if not pu or _well_scaled(pv.value):
|
|
358
|
+
return pv
|
|
359
|
+
|
|
360
|
+
if pv.unit != pu.name:
|
|
361
|
+
# convert to unscaled unit:
|
|
362
|
+
val = self.convert(pv.value, pv.unit, pu.name)
|
|
363
|
+
|
|
364
|
+
if not isinstance(val, float):
|
|
365
|
+
return pv # conversion is not possible
|
|
366
|
+
|
|
367
|
+
pv.value = val
|
|
368
|
+
pv.unit = pu.name
|
|
369
|
+
if _well_scaled(pv.value):
|
|
370
|
+
return pv
|
|
371
|
+
|
|
372
|
+
best = pv
|
|
373
|
+
if pu.scaled:
|
|
374
|
+
for scaled_unit, factor in pu.scaled.items():
|
|
375
|
+
value = pv.value * factor
|
|
376
|
+
if abs(value) >= 1.0:
|
|
377
|
+
best = PhysicalValue(value, scaled_unit)
|
|
378
|
+
if _well_scaled(value):
|
|
379
|
+
break
|
|
380
|
+
return best
|
|
381
|
+
|
|
382
|
+
def primary_unit(self, unit: str) -> str | None:
|
|
383
|
+
"""Return the name of the primary unit of a unit name.
|
|
384
|
+
|
|
385
|
+
This method returns the primary unit name or `None` if the
|
|
386
|
+
unit name is not known.
|
|
387
|
+
|
|
388
|
+
Required arguments:
|
|
389
|
+
|
|
390
|
+
unit -- The unit name for which to return the primary unit.
|
|
391
|
+
|
|
392
|
+
"""
|
|
393
|
+
primary = self._primary(unit)
|
|
394
|
+
if primary is None:
|
|
395
|
+
return None
|
|
396
|
+
return primary.name
|
|
397
|
+
|
|
398
|
+
def alternate_units(self, unit: str) -> list[str]:
|
|
399
|
+
"""Return a list of available alternate units of a given unit.
|
|
400
|
+
|
|
401
|
+
In the returned list, the primary unit is listed first, the
|
|
402
|
+
rest is sorted alphabetically by unit name. If the specified
|
|
403
|
+
unit is not known, an empty list is returned.
|
|
404
|
+
|
|
405
|
+
Required arguments:
|
|
406
|
+
|
|
407
|
+
unit -- The unit for which to find the alternate units. This
|
|
408
|
+
does not need to be a primary unit. The specified unit is
|
|
409
|
+
also included in the returned list.
|
|
410
|
+
|
|
411
|
+
"""
|
|
412
|
+
primary = self._primary(unit)
|
|
413
|
+
available = []
|
|
414
|
+
if primary is not None:
|
|
415
|
+
if primary.alternates:
|
|
416
|
+
for alt in primary.alternates.keys():
|
|
417
|
+
available.append(alt)
|
|
418
|
+
available.sort()
|
|
419
|
+
available.insert(0, primary.name)
|
|
420
|
+
return available
|
|
421
|
+
|
|
422
|
+
def scaled_units(self, unit: str) -> list[str]:
|
|
423
|
+
"""Return a list of available scaled units.
|
|
424
|
+
|
|
425
|
+
The returned list is sorted by increasing scale factor.
|
|
426
|
+
|
|
427
|
+
unit -- The primary unit for which to find the available
|
|
428
|
+
scaled units. This unit is is not included in the
|
|
429
|
+
returned list.
|
|
430
|
+
|
|
431
|
+
"""
|
|
432
|
+
pu = self._map.get(unit)
|
|
433
|
+
if not isinstance(pu, PrimaryUnit) or pu.scaled is None:
|
|
434
|
+
return []
|
|
435
|
+
|
|
436
|
+
available: list[tuple[str, float]] = []
|
|
437
|
+
for name, factor in pu.scaled.items():
|
|
438
|
+
available.append((name, factor))
|
|
439
|
+
available.sort(key=lambda el: el[1])
|
|
440
|
+
return [el[0] for el in available]
|
|
441
|
+
|
|
442
|
+
def _primary(self, unit: str) -> PrimaryUnit | None:
|
|
443
|
+
"""Get the primary unit object for a unit.
|
|
444
|
+
|
|
445
|
+
Returns the primary unit object or `None` if the unit name is
|
|
446
|
+
not known.
|
|
447
|
+
|
|
448
|
+
Required arguments:
|
|
449
|
+
|
|
450
|
+
unit -- The unit name whose primary unit to get.
|
|
451
|
+
|
|
452
|
+
"""
|
|
453
|
+
pu = self._map.get(unit)
|
|
454
|
+
if not isinstance(pu, PrimaryUnit):
|
|
455
|
+
return None
|
|
456
|
+
if isinstance(pu, AlternateUnit):
|
|
457
|
+
pu = pu.primary
|
|
458
|
+
return pu
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _well_scaled(x: float) -> bool:
|
|
462
|
+
"""Check if number is "well scaled".
|
|
463
|
+
|
|
464
|
+
The number 0 is always well-scaled. All other numbers must have
|
|
465
|
+
an absolute value in the range from 1 to 1000 (exclusive) for this
|
|
466
|
+
to be the case.
|
|
467
|
+
|
|
468
|
+
Required arguments:
|
|
469
|
+
|
|
470
|
+
x -- The number to check for well-scaledness.
|
|
471
|
+
|
|
472
|
+
"""
|
|
473
|
+
return x == 0 or (1.0 <= abs(x) < 1000)
|