Skip to content

Commit

Permalink
Added preferred_units preference
Browse files Browse the repository at this point in the history
  • Loading branch information
Ken Kundert authored and Ken Kundert committed Dec 31, 2022
1 parent 590996d commit 57091ab
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 53 deletions.
3 changes: 2 additions & 1 deletion doc/releases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ Latest development release
- Added quantity functions: :func:`as_real`, :func:`as_tuple`, :func:`render`,
:func:`fixed`, and :func:`binary`.
- Fixed rendering of full precision numbers in :meth:`Quantity.fixed()`.
- Added “cover” option to *strip_radix* preference.
- Added *preferred_units* :class:`Quantity` preference.
- Added “cover” option to *strip_radix* :class:`Quantity` preference.
- Added type hints.


Expand Down
16 changes: 13 additions & 3 deletions doc/user.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1619,9 +1619,19 @@ conversions (conversions with extra parameters) and dynamic conversions
>>> print(Quantity('100', scale='dB'))
40 dB
Notice that the these scaling functions differ from those describe previously in
that the only take one argument and return one value. The units are not
included in either then argument list or the return value.
One thing to be aware of with affine conversions like °C to °F: they are
suitable for converting absolute temperatures but not temperature differences.
One way around this is to add another conversion specifically for differences:

.. code-block:: python
>>> dC_F = UnitConversion('ΔC Δ°C', 'ΔF Δ°F', 5/9)
>>> print(Quantity('100 Δ°C', scale='Δ°F'))
180 Δ°F
Notice that the scaling functions used here differ from those described
previously in that they only take one argument and return one value. The units
are not included in either then argument list or the return value.

Also notice that the return value of *UnitConversion* was not used in the
examples above. It is enough to simply create the *UnitConversion* for it to be
Expand Down
51 changes: 40 additions & 11 deletions quantiphy/quantiphy.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,8 @@ def add_constant(value, alias=None, unit_systems=None):
output_sf = 'TGMkmunpfa',
plus = '+',
prec = 4,
preferred_units = {},
_preferred_units = {}, # transposed version of preferred_units
radix = '.',
reltol = 1e-6,
show_commas = False,
Expand Down Expand Up @@ -924,6 +926,14 @@ def set_prefs(cls, **kwargs):
Default precision in digits where 0 corresponds to 1 digit. Must
be nonnegative. This precision is used when the full precision is
not required. Default is 4.
:type prec: int or str
:arg dict preferred_units:
A dictionary that is used when looking up the preferred units when
rendering. For example, if *preferred_units* contains the entry:
{“Ω”: “Ohms Ohm ohms ohm”}, then when rendering a quantity with
units “Ohms”, “Ohm”, “ohms”, or “ohm”, the units are rendered as
“Ω”.
:arg float reltol:
Relative tolerance, used by :meth:`Quantity.is_close()` when
Expand Down Expand Up @@ -1033,8 +1043,33 @@ def set_prefs(cls, **kwargs):
"""
# code {{{4
cls._initialize_preferences()

# preprocess specific preferences
# split known_units
if isinstance(kwargs.get('known_units'), str):
kwargs['known_units'] = kwargs['known_units'].split()

# split preferred_units
if 'preferred_units' in kwargs:
_preferred_units = {}
for preferred_unit, undesired in kwargs['preferred_units'].items():
for each in undesired.split():
_preferred_units[each] = preferred_unit
kwargs['_preferred_units'] = _preferred_units

# check for unknown output scale factors
if kwargs.get('output_sf'):
unknown_sf = set(kwargs['output_sf']) - set(MAPPINGS.keys())
if unknown_sf:
raise UnknownScaleFactor(
*sorted(unknown_sf),
combined = ", ".join(sorted(unknown_sf)),
culprit = "output_sf"
)

# no need to check the input scale factors here
# they are checked when rebuilding recognizers

for k, v in kwargs.items():
if k not in DEFAULTS.keys():
raise UnknownPreference(k)
Expand All @@ -1050,16 +1085,10 @@ def set_prefs(cls, **kwargs):
cls._preferences[k] = DEFAULTS[k]
else:
cls._preferences[k] = v

if 'input_sf' in kwargs:
cls._initialize_recognizers()
if 'output_sf' in kwargs:
unknown_sf = set(cls.get_pref('output_sf')) - set(MAPPINGS.keys())
if unknown_sf:
raise UnknownScaleFactor(
*sorted(unknown_sf),
combined = ", ".join(sorted(unknown_sf)),
culprit = "output_sf"
)


# get preference {{{3
@classmethod
Expand Down Expand Up @@ -1945,7 +1974,7 @@ def render(
strip_zeros = self.strip_zeros if strip_zeros is None else strip_zeros
strip_radix = self.strip_radix if strip_radix is None else strip_radix
negligible = self.negligible if negligible is None else negligible
units = self.units if show_units else ''
units = self._preferred_units.get(self.units, self.units) if show_units else ''
if prec is None:
prec = self.prec

Expand Down Expand Up @@ -2174,7 +2203,7 @@ def fixed(
show_commas = self.show_commas if show_commas is None else show_commas
strip_zeros = self.strip_zeros if strip_zeros is None else strip_zeros
strip_radix = self.strip_radix if strip_radix is None else strip_radix
units = self.units if show_units else ''
units = self._preferred_units.get(self.units, self.units) if show_units else ''
if prec is None:
prec = self.prec

Expand Down Expand Up @@ -2327,7 +2356,7 @@ def binary(
show_units = self.show_units if show_units is None else show_units
strip_zeros = self.strip_zeros if strip_zeros is None else strip_zeros
strip_radix = self.strip_radix if strip_radix is None else strip_radix
units = self.units if show_units else ''
units = self._preferred_units.get(self.units, self.units) if show_units else ''
if prec is None:
prec = self.prec
elif prec == 'full':
Expand Down
63 changes: 49 additions & 14 deletions quantiphy/quantiphy.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Callable, Sequence
from typing import Any, Callable, Dict, List, Sequence

class QuantiPhyError(Exception):
args: tuple
Expand Down Expand Up @@ -76,7 +76,42 @@ class Quantity(float):
...

@classmethod
def set_prefs(cls, **kwargs) -> None:
def set_prefs(
cls,
abstol: float = ...,
accept_binary: bool = ...,
assign_rec: str = ...,
comma: str = ...,
form: str = ...,
full_prec: int = ...,
ignore_sf: bool = ...,
inf: str = ...,
input_sf: str = ...,
keep_components: bool = ...,
known_units: str | List[str] = ...,
label_fmt: str = ...,
label_fmt_full: str = ...,
map_sf: dict | Callable = ...,
minus: str = ...,
nan: str = ...,
negligible: float | Dict[str, float] = ...,
number_fmt: str | Callable = ...,
output_sf: str = ...,
plus: str = ...,
prec: int | str = ...,
preferred_units: Dict[str, str] = ...,
radix: str = ...,
reltol: float = ...,
show_commas: bool = ...,
show_desc: bool = ...,
show_label: bool | str = ...,
show_units: bool | str = ...,
spacer: str = ...,
strip_radix: bool | str = ...,
strip_zeros: bool = ...,
tight_units: List[str] = ...,
unity_sf: str = ...,
) -> None:
...

@classmethod
Expand Down Expand Up @@ -117,8 +152,8 @@ class Quantity(float):
self,
form: str = ...,
show_units: bool = ...,
prec: int = ...,
show_label: bool = ...,
prec: int | str = ...,
show_label: bool | str = ...,
strip_zeros:bool = ...,
strip_radix: bool = ...,
scale: str | float | tuple[float | Quantity, str] | Callable = ...,
Expand All @@ -129,8 +164,8 @@ class Quantity(float):
def fixed(
self,
show_units: bool = ...,
prec: int = ...,
show_label: bool = ...,
prec: int | str = ...,
show_label: bool | str = ...,
show_commas: bool = ...,
strip_zeros: bool = ...,
strip_radix: bool = ...,
Expand All @@ -141,8 +176,8 @@ class Quantity(float):
def binary(
self,
show_units: bool = ...,
prec: int = ...,
show_label: bool = ...,
prec: int | str = ...,
show_label: bool | str = ...,
strip_zeros: bool = ...,
strip_radix: bool = ...,
scale: str | float | tuple[float | Quantity, str] | Callable = ...,
Expand Down Expand Up @@ -271,8 +306,8 @@ def render(
units: str,
form: str = ...,
show_units: bool = ...,
prec: int = ...,
show_label: bool = ...,
prec: int | str = ...,
show_label: bool | str = ...,
strip_zeros:bool = ...,
strip_radix: bool = ...,
scale: str | float | tuple[float | Quantity, str] | Callable = ...,
Expand All @@ -284,8 +319,8 @@ def fixed(
value: float,
units: str,
show_units: bool = ...,
prec: int = ...,
show_label: bool = ...,
prec: int | str = ...,
show_label: bool | str = ...,
show_commas: bool = ...,
strip_zeros: bool = ...,
strip_radix: bool = ...,
Expand All @@ -297,8 +332,8 @@ def binary(
value: float,
units: str,
show_units: bool = ...,
prec: int = ...,
show_label: bool = ...,
prec: int | str = ...,
show_label: bool | str = ...,
strip_zeros: bool = ...,
strip_radix: bool = ...,
scale: str | float | tuple[float | Quantity, str] | Callable = ...,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_doctests.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def test_manual():
Quantity.reset_prefs()
expected_test_count = {
'../doc/index.rst': 31,
'../doc/user.rst': 448,
'../doc/user.rst': 450,
'../doc/api.rst': 0,
'../doc/examples.rst': 36,
'../doc/accessories.rst': 12,
Expand Down
91 changes: 91 additions & 0 deletions tests/test_preferred_units.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from quantiphy import (
as_tuple,
Quantity,
QuantiPhyError,
UnitConversion,
UnknownConversion,
)
from pytest import approx, fixture, raises

@fixture
def initialize_preferences():
Quantity.reset_prefs()

def test_rendering(initialize_preferences):
preferred_units = {
"Ω": "Ohm ohm Ohms ohms Ω", # the capital Omega is preferred over unicode ohm symbol
"℧": "Mho mho Mhos mhos Ʊ", # the unicode inverted-omega is preferred over capital upisilon
}
with Quantity.prefs(preferred_units=preferred_units, known_units="Mho mho Mhos mhos"):

assert Quantity.get_pref("preferred_units") == preferred_units

q = Quantity('50 Ohm')
assert q.units == 'Ohm'
assert str(q) == '50 Ω'
assert q.render() == '50 Ω'
assert q.fixed() == '50 Ω'
assert q.binary() == '50 Ω'

q = Quantity('50 ohm')
assert q.units == 'ohm'
assert str(q) == '50 Ω'
assert q.render() == '50 Ω'
assert q.fixed() == '50 Ω'
assert q.binary() == '50 Ω'

q = Quantity('50 Ohms')
assert q.units == 'Ohms'
assert str(q) == '50 Ω'
assert q.render() == '50 Ω'
assert q.fixed() == '50 Ω'
assert q.binary() == '50 Ω'

q = Quantity('50 ohms')
assert q.units == 'ohms'
assert str(q) == '50 Ω'
assert q.render() == '50 Ω'
assert q.fixed() == '50 Ω'
assert q.binary() == '50 Ω'

q = Quantity('50 Ω')
assert q.units == 'Ω'
assert str(q) == '50 Ω'
assert q.render() == '50 Ω'
assert q.fixed() == '50 Ω'
assert q.binary() == '50 Ω'

q = Quantity('50 Mho')
assert q.units == 'Mho'
assert str(q) == '50 ℧'
assert q.render() == '50 ℧'
assert q.fixed() == '50 ℧'
assert q.binary() == '50 ℧'

q = Quantity('50 mho')
assert q.units == 'mho'
assert str(q) == '50 ℧'
assert q.render() == '50 ℧'
assert q.fixed() == '50 ℧'
assert q.binary() == '50 ℧'

q = Quantity('50 Mhos')
assert q.units == 'Mhos'
assert str(q) == '50 ℧'
assert q.render() == '50 ℧'
assert q.fixed() == '50 ℧'
assert q.binary() == '50 ℧'

q = Quantity('50 mhos')
assert q.units == 'mhos'
assert str(q) == '50 ℧'
assert q.render() == '50 ℧'
assert q.fixed() == '50 ℧'
assert q.binary() == '50 ℧'

q = Quantity('50 Ʊ')
assert q.units == 'Ʊ'
assert str(q) == '50 ℧'
assert q.render() == '50 ℧'
assert q.fixed() == '50 ℧'
assert q.binary() == '50 ℧'
2 changes: 1 addition & 1 deletion tests/test_quantity_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def test_fixed():
assert fixed(1e6, 'g', scale='g', show_commas=True) == '1,000,000 g'
assert fixed(1e6, 'g', scale='kg', show_commas=True) == '1,000 kg'

def test_fixed():
def test_binary():
UnitConversion('b', 'B', 8)
assert binary(2, 'B') == '2 B'
assert binary(2, 'B', scale='b') == '16 b'
Expand Down

0 comments on commit 57091ab

Please sign in to comment.