Skip to content

Commit

Permalink
Added no-op conversions to UnitConversion (units are changed but valu…
Browse files Browse the repository at this point in the history
…e stays the same, ex. $->USD)
  • Loading branch information
Ken Kundert authored and Ken Kundert committed Nov 22, 2017
1 parent 1872588 commit b2dbcfa
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 27 deletions.
30 changes: 15 additions & 15 deletions doc/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -567,8 +567,8 @@ It demonstrates some of the features of *UnitConversion*.
# download latest asset prices from cryptocompare.com
currencies = dict(
fsyms = 'BTC,ETH,BCH,ZEC',
tsyms = 'ETH,USD',
fsyms = 'BTC,ETH,BCH,ZEC', # from symbols
tsyms = 'ETH,USD', # to symbols
)
url_args = '&'.join(f'{k}={v}' for k, v in currencies.items())
base_url = f'https://min-api.cryptocompare.com/data/pricemulti'
Expand Down Expand Up @@ -604,24 +604,24 @@ It demonstrates some of the features of *UnitConversion*.
1 ZEC = {zec2usd.convert()}
Holdings:
{btc:>7q} = {btc:q$}
{eth:>7q} = {eth:q$}
{bch:>7q} = {bch:q$}
{zec:>7q} = {zec:q$}
{btc:>7qBTC} = {btc:q$} {100*btc.scale('$')/total:.0f}%
{eth:>7qETH} = {eth:q$} {100*eth.scale('$')/total:.0f}%
{bch:>7qBCH} = {bch:q$} {100*bch.scale('$')/total:.0f}%
{zec:>7qZEC} = {zec:q$} {100*zec.scale('$')/total:.0f}%
Total = {total:q}
''').strip())
The output of the script looks like this::

Current Prices:
1 BTC = $7.15k or Ξ24
1 ETH = $299 or Ƀ41.7m
1 BCH = $604
1 ZEC = $231
1 BTC = $8.22k or Ξ22.4
1 ETH = $366 or Ƀ44.6m
1 BCH = $1.26k
1 ZEC = $310

Holdings:
Ƀ100 = $715k
Ξ100 = $29.9k
100 BCH = $60.4k
100 ZEC = $23.1k
Total = $829k
100 BTC = $822k 81%
100 ETH = $36.6k 4%
100 BCH = $126k 12%
100 ZEC = $31k 3%
Total = $1.02M
7 changes: 4 additions & 3 deletions doc/user.rst
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,10 @@ This unit conversion says, when converting units of 'm' to either 'pc' or

Notice that the return value of *UnitConversion* was not used. It is enough to
simply create the *UnitConversion* for it to be available to *Quantity*. So, it
is normal to not capture the return value. However, there are two things you can
do with the return value. First you can convert it to a string to get
a description of the relationship. This is largely used as a sanity check:
is normal to not capture the return value of *UnitConversion*. However, there
are two things you can do with the return value. First you can convert it to
a string to get a description of the relationship. This is largely used as
a sanity check:

.. code-block:: python
Expand Down
48 changes: 39 additions & 9 deletions quantiphy.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ class UnitConversion(object):
object itself is normally discarded). Once created, it is automatically
employed by :class:`Quantity` when a conversion is requested with the given
units. A forward conversion is performed if the from and to units match, and
a reversion conversion is performed if they are swapped.
a reversion conversion is performed if they are swapped. A no-op conversion
is performed when converting one from- unit to another or one to-unit to
another.
:arg to_units:
A collection of units. If given as a single string it is split.
Expand Down Expand Up @@ -122,6 +124,12 @@ class UnitConversion(object):
*new_value* = (*given_value* - *intercept*)/*slope*
**No-Op Conversion**:
The following conversion is applied if the given units are among
the *to_units* and the desired units are among the *from_units*:
*new_value* = *given_value*
Example::
>>> from quantiphy import Quantity, UnitConversion
Expand All @@ -145,23 +153,44 @@ class UnitConversion(object):
>>> print(d_ac.render(scale='m'))
41.344e15 m
>>> d_ac = Quantity(1.339848, units='pc')
>>> print(f'{d_ac:qparsec}')
1.3398 parsec
"""
def __init__(self, to_units, from_units, slope=1, intercept=0):
self.to_units = to_units.split() if is_str(to_units) else to_units
self.from_units = from_units.split() if is_str(from_units) else from_units
self.slope = slope
self.intercept = intercept

# add to known unit conversion
for to in self.to_units:
for frm in self.from_units:
_unit_conversions[(to, frm)] = self._forward
_unit_conversions[(frm, to)] = self._reverse

# add no-op converters to allow a from-units to be converted to another
for u1 in self.from_units:
for u2 in self.from_units:
if u1 != u2:
_unit_conversions[(u1, u2)] = self._no_op

# add no-op converters to allow a to-units to be converted to another
for u1 in self.to_units:
for u2 in self.to_units:
if u1 != u2:
_unit_conversions[(u1, u2)] = self._no_op

def _forward(self, value):
return value*self.slope + self.intercept

def _reverse(self, value):
return (value - self.intercept)/self.slope

def _no_op(self, value):
return value

def convert(self, value=1, from_units=None, to_units=None):
"""Convert value to quantity with new units.
Expand Down Expand Up @@ -778,16 +807,16 @@ def set_prefs(cls, **kwargs):
setting is False, the radix is still striped if the number has a
scale factor. By default this is True.
Use strip_radix=False when generating output that will be read by a
parser that distinguishes between integers and reals based on the
Set strip_radix to False when generating output that will be read by
a parser that distinguishes between integers and reals based on the
presence of a decimal point.
:arg bool strip_zeros:
When rendering, strip off any unneeded zeros from the number. By
default this is True.
Use strip_zeros=False when you would like to indicated the precision
of your numbers based on the number of digits shown.
Set strip_zeros to False when you would like to indicated the
precision of your numbers based on the number of digits shown.
:arg str unity_sf:
The output scale factor for unity, generally '' or '_'. The default
Expand Down Expand Up @@ -817,7 +846,7 @@ def set_prefs(cls, **kwargs):
kwargs['known_units'] = kwargs['known_units'].split()
for k, v in kwargs.items():
if k not in DEFAULTS.keys():
raise KeyError(k)
raise KeyError(k)
if v is None:
try:
del cls._preferences[k]
Expand Down Expand Up @@ -862,11 +891,13 @@ class ContextManager:
def __init__(self, cls, kwargs):
self.cls = cls
self.kwargs = kwargs

def __enter__(self):
cls = self.cls
cls._initialize_preferences()
cls._preferences = cls._preferences.new_child()
cls.set_prefs(**self.kwargs)

def __exit__(self, *args):
self.cls._preferences = self.cls._preferences.parents

Expand Down Expand Up @@ -980,7 +1011,7 @@ def _initialize_recognizers(cls):
mantissa = named_regex(
'mant',
r'(?:{od}\.?{rd})|(?:{rd}\.?{od})'.format(
rd = required_digits, od = optional_digits
rd=required_digits, od=optional_digits
), # leading or trailing digits are optional, but not both
)
exponent = named_regex('exp', '[eE][-+]?[0-9]+')
Expand Down Expand Up @@ -1494,7 +1525,7 @@ def render(
mantissa = sign + mantissa[0:(shift+1)] + '.' + mantissa[(shift+1):]

# remove trailing decimal point
if sf or self.strip_radix: # could also add 'or units'
if sf or self.strip_radix: # could also add 'or units'
mantissa = mantissa.rstrip('.')
elif self.strip_zeros:
# a trailing radix is not very attractive, so add a zero except if
Expand Down Expand Up @@ -1745,7 +1776,6 @@ def extract(cls, text):
8.9247 GHz
"""
import keyword
quantities = {}
for line in text.splitlines():
match = re.match(cls.get_pref('assign_rec'), line)
Expand Down

0 comments on commit b2dbcfa

Please sign in to comment.