"""
Simple numerical operations put together in a list, useful for
scaling or offsetting some incoming numerical value.
Examples:
Ops()(x)
returns the number unchanged
Ops('mul', 0.5)(x)
multiples x by 0.5
Ops('add', 0.1)(x)
adds 1 to x
Ops('sqrt')(x)
returns the square root of x
Ops('mul', 0.5, 'add', 1, 'sqrt')(x)
returns sqrt(1 + 0.5 * x)
"""
import math, operator
[docs]class Op:
def __init__(self, op, *values):
if len(values) > len(OPS):
raise KeyError('Too many arguments for ' + op)
ops = OPS[len(values)]
if op not in ops:
raise KeyError(OPS_ERROR.format(op, ', '.join(sorted(ops))))
self.op = ops[op]
self.opname = op
self.values = values
def __call__(self, x):
return self.op(x, *self.values)
def __str__(self):
values = ', '.join(str(v) for v in self.values)
return '%s(%s)' % self.opname, values
[docs]class Ops:
def __init__(self, *ops):
self.ops = tuple(_make_ops(ops))
def __call__(self, x):
for op in self.ops:
x = op(x)
return x
def __bool__(self):
return bool(self.ops)
def __str__(self):
return '->'.join(str(i) for i in self.ops)
def _make_ops(ops):
op, values = None, []
for o in ops:
if isinstance(o, str):
# We're starting a new operation
if op:
yield Op(op, *values)
op, values = o, []
else:
# We're in the middle of an operation
if not op:
raise ValueError('Value without op')
values.append(o)
if op:
yield Op(op, *values)
def _two_ops():
def add(ops, op):
f = getattr(operator, 'truediv' if op == 'div' else op)
ops[op] = f
if op not in COMMUTATIVE:
ops['r' + op] = lambda x, y: f(y, x)
ops = {}
for op in TWO_OPERATORS:
add(ops, op)
ops.update(max=max, min=min, gamma=ops['rpow'])
return ops
def _join(items, indent=' ', per_line=8):
lines = []
for i in range(0, len(items), 8):
lines.append(indent + ', '.join(items[i:i + 8]) + '\n')
return ''.join(lines)
TWO_OPERATORS = 'add', 'div', 'mod', 'mul', 'pow', 'sub'
COMMUTATIVE = 'add', 'mul'
ONE_OPERATORS = (
'acos',
'acosh',
'asin',
'asinh',
'atan',
'atanh',
'ceil',
'cos',
'cosh',
'degrees',
'erf',
'erfc',
'exp',
'expm1',
'fabs',
'floor',
'gamma',
'lgamma',
'log',
'log10',
'log1p',
'log2',
'radians',
'sin',
'sinh',
'sqrt',
'tan',
'tanh',
'trunc')
OPS = [{k: getattr(math, k) for k in ONE_OPERATORS}, _two_ops()]
OPS_ERROR = """\
Do not understand operator {0}.
One argument operators are:
%s
Two argument operators are:
%s
""" % tuple(_join(sorted(o)) for o in OPS)