From de02058ba94c9f71d3441ff7f0c9573740ad2fa5 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Fri, 9 Feb 2024 22:25:18 -0500 Subject: [PATCH 1/8] sympy integration via ufunc dispatch Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- csp/baselib.py | 190 +++++++++++++++++++++++++++++--------- csp/impl/wiring/edge.py | 158 ++++++++++++++++++++++++++++--- csp/tests/test_baselib.py | 65 ++++++++++++- csp/tests/test_engine.py | 1 + 4 files changed, 353 insertions(+), 61 deletions(-) diff --git a/csp/baselib.py b/csp/baselib.py index 0aac1f52..10260923 100644 --- a/csp/baselib.py +++ b/csp/baselib.py @@ -44,10 +44,28 @@ "min", "gate", "floordiv", + "mod", "pow", + "abs", "ln", + "log2", + "log10", "exp", - "abs", + "exp2", + "sqrt", + "erf", + "sin", + "cos", + "tan", + "arcsin", + "arccos", + "arctan", + "sinh", + "cosh", + "tanh", + "arcsinh", + "arccosh", + "arctanh", "unroll", "collect", "flatten", @@ -670,11 +688,44 @@ def or_(*inputs): # Math/comparison binary operators are supported in C++ only for (int,int) and # (float, float) arguments. For all other types, the Python implementation is used. -MATH_OPS = ["add", "sub", "multiply", "divide", "pow", "max", "min"] +MATH_OPS = [ + # binary + "add", + "sub", + "multiply", + "divide", + "pow", + "max", + "min", + "floordiv", + "mod", + # unary + "abs", + "ln", + "log2", + "log10", + "exp", + "exp2", + "sqrt", + "erf", + "sin", + "cos", + "tan", + "arcsin", + "arccos", + "arctan", + "sinh", + "cosh", + "tanh", + "arcsinh", + "arccosh", + "arctanh", +] COMP_OPS = ["eq", "ne", "lt", "gt", "le", "ge"] MATH_COMP_OPS_CPP = { + # binary math ("add", "float"): _cspbaselibimpl.add_f, ("add", "int"): _cspbaselibimpl.add_i, ("sub", "float"): _cspbaselibimpl.sub_f, @@ -689,6 +740,11 @@ def or_(*inputs): ("max", "int"): _cspbaselibimpl.max_i, ("min", "float"): _cspbaselibimpl.min_f, ("min", "int"): _cspbaselibimpl.min_i, + # unary math + ("abs", "float"): _cspbaselibimpl.abs, + ("ln", "float"): _cspbaselibimpl.ln, + ("exp", "float"): _cspbaselibimpl.exp, + # binary comparator ("eq", "float"): _cspbaselibimpl.eq_f, ("eq", "int"): _cspbaselibimpl.eq_i, ("ne", "float"): _cspbaselibimpl.ne_f, @@ -705,7 +761,7 @@ def or_(*inputs): @lru_cache(maxsize=512) -def define_op(name, op_lambda): +def define_binary_op(name, op_lambda): float_out_type, int_out_type, generic_out_type = [None] * 3 if name in COMP_OPS: float_out_type = bool @@ -722,12 +778,12 @@ def define_op(name, op_lambda): from csp.impl.wiring.node import _node_internal_use - @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP[(name, "float")], name=name) + @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP.get((name, "float"), None), name=name) def float_type(x: ts[float], y: ts[float]) -> ts[float_out_type]: if csp.valid(x, y): return op_lambda(x, y) - @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP[(name, "int")], name=name) + @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP.get((name, "int"), None), name=name) def int_type(x: ts[int], y: ts[int]) -> ts[int_out_type]: if csp.valid(x, y): return op_lambda(x, y) @@ -759,32 +815,98 @@ def comp(x: ts["T"], y: ts["U"]): return comp -# Math operators +@lru_cache(maxsize=512) +def define_unary_op(name, op_lambda): + float_out_type, int_out_type, generic_out_type = [None] * 3 + if name in COMP_OPS: + float_out_type = bool + int_out_type = bool + generic_out_type = bool + elif name in MATH_OPS: + float_out_type = float + if name in ("abs",): + int_out_type = int + generic_out_type = "T" + else: + int_out_type = float + generic_out_type = float -add = define_op("add", lambda x, y: x + y) -sub = define_op("sub", lambda x, y: x - y) -multiply = define_op("multiply", lambda x, y: x * y) -pow = define_op("pow", lambda x, y: x**y) -divide = define_op("divide", lambda x, y: x / y) -min = define_op("min", lambda x, y: x if x < y else y) -max = define_op("max", lambda x, y: x if x > y else y) + from csp.impl.wiring.node import _node_internal_use -# Comparison operators + @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP.get((name, "float"), None), name=name) + def float_type(x: ts[float]) -> ts[float_out_type]: + if csp.valid(x): + return op_lambda(x) -eq = define_op("eq", lambda x, y: x == y) -ne = define_op("ne", lambda x, y: x != y) -gt = define_op("gt", lambda x, y: x > y) -lt = define_op("lt", lambda x, y: x < y) -ge = define_op("ge", lambda x, y: x >= y) -le = define_op("le", lambda x, y: x <= y) + @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP.get((name, "int"), None), name=name) + def int_type(x: ts[int]) -> ts[int_out_type]: + if csp.valid(x): + return op_lambda(x) -# Other math ops + @_node_internal_use(name=name) + def numpy_type(x: ts["T"]) -> ts[np.ndarray]: + if csp.valid(x): + return op_lambda(x) + @_node_internal_use(name=name) + def generic_type(x: ts["T"]) -> ts[generic_out_type]: + if csp.valid(x): + return op_lambda(x) + + def comp(x: ts["T"]): + if x.tstype.typ in [Numpy1DArray[float], NumpyNDArray[float]]: + return numpy_type(x) + elif x.tstype.typ is float: + return float_type(x) + elif x.tstype.typ is int: + return int_type(x) + return generic_type(x) -@node -def floordiv(x: ts["T"], y: ts["T"]) -> ts["T"]: - if csp.ticked(x, y) and csp.valid(x, y): - return x // y + comp.__name__ = name + return comp + + +# Math operators +add = define_binary_op("add", lambda x, y: x + y) +sub = define_binary_op("sub", lambda x, y: x - y) +multiply = define_binary_op("multiply", lambda x, y: x * y) +divide = define_binary_op("divide", lambda x, y: x / y) +pow = define_binary_op("pow", lambda x, y: x**y) +min = define_binary_op("min", lambda x, y: x if x < y else y) +max = define_binary_op("max", lambda x, y: x if x > y else y) +floordiv = define_binary_op("floordiv", lambda x, y: x // y) +mod = define_binary_op("mod", lambda x, y: x % y) + +# Other math ops +_python_abs = abs +abs = define_unary_op("abs", lambda x: _python_abs(x)) +ln = define_unary_op("ln", lambda x: math.log(x)) +log2 = define_unary_op("log2", lambda x: math.log2(x)) +log10 = define_unary_op("log10", lambda x: math.log10(x)) +exp = define_unary_op("exp", lambda x: math.exp(x)) +exp2 = define_unary_op("exp2", lambda x: math.exp2(x)) +sqrt = define_unary_op("sqrt", lambda x: math.sqrt(x)) +erf = define_unary_op("erf", lambda x: math.erf(x)) +sin = define_unary_op("sin", lambda x: math.sin(x)) +cos = define_unary_op("cos", lambda x: math.cos(x)) +tan = define_unary_op("tan", lambda x: math.tan(x)) +arcsin = define_unary_op("arcsin", lambda x: math.asin(x)) +arccos = define_unary_op("arccos", lambda x: math.acos(x)) +arctan = define_unary_op("arctan", lambda x: math.atan(x)) +sinh = define_unary_op("sinh", lambda x: math.sinh(x)) +cosh = define_unary_op("cosh", lambda x: math.cosh(x)) +tanh = define_unary_op("tanh", lambda x: math.tanh(x)) +arcsinh = define_unary_op("arcsinh", lambda x: math.asinh(x)) +arccosh = define_unary_op("arccosh", lambda x: math.acosh(x)) +arctanh = define_unary_op("arctanh", lambda x: math.atanh(x)) + +# Comparison operators +eq = define_binary_op("eq", lambda x, y: x == y) +ne = define_binary_op("ne", lambda x, y: x != y) +gt = define_binary_op("gt", lambda x, y: x > y) +lt = define_binary_op("lt", lambda x, y: x < y) +ge = define_binary_op("ge", lambda x, y: x >= y) +le = define_binary_op("le", lambda x, y: x <= y) @node @@ -797,24 +919,6 @@ def accum(x: ts["T"], start: "~T" = 0) -> ts["T"]: return s_accum -@node(cppimpl=_cspbaselibimpl.ln) -def ln(x: ts[float]) -> ts[float]: - if csp.ticked(x): - return math.log(x) - - -@node(cppimpl=_cspbaselibimpl.exp) -def exp(x: ts[float]) -> ts[float]: - if csp.ticked(x): - return math.exp(x) - - -@node(cppimpl=_cspbaselibimpl.abs) -def abs(x: ts[float]) -> ts[float]: - if csp.ticked(x): - return abs(x) - - @node(cppimpl=_cspbaselibimpl.exprtk_impl) def _csp_exprtk_impl( expression_str: str, diff --git a/csp/impl/wiring/edge.py b/csp/impl/wiring/edge.py index d285fe2e..7a559420 100644 --- a/csp/impl/wiring/edge.py +++ b/csp/impl/wiring/edge.py @@ -1,3 +1,6 @@ +import numpy as np + + class Edge: __slots__ = ["tstype", "nodedef", "output_idx", "basket_idx"] @@ -13,7 +16,7 @@ def __repr__(self): def __bool__(self): raise ValueError("boolean evaluation of an edge is not supported") - def __wrap_method(self, other, method): + def __wrap_binary_method(self, other, method): import csp if isinstance(other, Edge): @@ -30,7 +33,7 @@ def __hash__(self): def __add__(self, other): import csp - return self.__wrap_method(other, csp.add) + return self.__wrap_binary_method(other, csp.add) def __radd__(self, other): return self.__add__(other) @@ -38,7 +41,7 @@ def __radd__(self, other): def __sub__(self, other): import csp - return self.__wrap_method(other, csp.sub) + return self.__wrap_binary_method(other, csp.sub) def __rsub__(self, other): import csp @@ -48,7 +51,7 @@ def __rsub__(self, other): def __mul__(self, other): import csp - return self.__wrap_method(other, csp.multiply) + return self.__wrap_binary_method(other, csp.multiply) def __rmul__(self, other): return self.__mul__(other) @@ -56,7 +59,7 @@ def __rmul__(self, other): def __truediv__(self, other): import csp - return self.__wrap_method(other, csp.divide) + return self.__wrap_binary_method(other, csp.divide) def __rtruediv__(self, other): import csp @@ -66,7 +69,7 @@ def __rtruediv__(self, other): def __floordiv__(self, other): import csp - return self.__wrap_method(other, csp.floordiv) + return self.__wrap_binary_method(other, csp.floordiv) def __rfloordiv__(self, other): import csp @@ -76,42 +79,47 @@ def __rfloordiv__(self, other): def __pow__(self, other): import csp - return self.__wrap_method(other, csp.pow) + return self.__wrap_binary_method(other, csp.pow) def __rpow__(self, other): import csp return csp.pow(csp.const(other), self) + def __mod__(self, other): + import csp + + return csp.mod(csp.const(other), self) + def __gt__(self, other): import csp - return self.__wrap_method(other, csp.gt) + return self.__wrap_binary_method(other, csp.gt) def __ge__(self, other): import csp - return self.__wrap_method(other, csp.ge) + return self.__wrap_binary_method(other, csp.ge) def __lt__(self, other): import csp - return self.__wrap_method(other, csp.lt) + return self.__wrap_binary_method(other, csp.lt) def __le__(self, other): import csp - return self.__wrap_method(other, csp.le) + return self.__wrap_binary_method(other, csp.le) def __eq__(self, other): import csp - return self.__wrap_method(other, csp.eq) + return self.__wrap_binary_method(other, csp.eq) def __ne__(self, other): import csp - return self.__wrap_method(other, csp.ne) + return self.__wrap_binary_method(other, csp.ne) def __invert__(self): import csp @@ -120,6 +128,130 @@ def __invert__(self): return csp.bitwise_not(self) raise TypeError(f"Cannot call invert with a ts[{self.tstype.typ.__name__}], not an integer type") + def abs(self): + import csp + + return csp.abs(self) + + def ln(self): + import csp + + return csp.ln(self) + + def log2(self): + import csp + + return csp.log2(self) + + def log10(self): + import csp + + return csp.log10(self) + + def exp(self): + import csp + + return csp.exp(self) + + def sin(self): + import csp + + return csp.sin(self) + + def cos(self): + import csp + + return csp.cos(self) + + def tan(self): + import csp + + return csp.tan(self) + + def arcsin(self): + import csp + + return csp.arcsin(self) + + def arccos(self): + import csp + + return csp.arccos(self) + + def arctan(self): + import csp + + return csp.arctan(self) + + def sqrt(self): + import csp + + return csp.sqrt(self) + + def erf(self): + import csp + + return csp.erf(self) + + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + if ufunc == np.add: + if isinstance(inputs[0], Edge): + return inputs[0].__add__(inputs[1]) + else: + return inputs[1].__add__(inputs[0]) + elif ufunc == np.subtract: + if isinstance(inputs[0], Edge): + return inputs[0].__sub__(inputs[1]) + else: + return inputs[1].__sub__(inputs[0]) + elif ufunc == np.multiply: + if isinstance(inputs[0], Edge): + return inputs[0].__mul__(inputs[1]) + else: + return inputs[1].__mul__(inputs[0]) + elif ufunc == np.divide: + if isinstance(inputs[0], Edge): + return inputs[0].__truediv__(inputs[1]) + else: + return inputs[1].__truediv__(inputs[0]) + elif ufunc == np.floor_divide: + if isinstance(inputs[0], Edge): + return inputs[0].__floordiv__(inputs[1]) + else: + return inputs[1].__floordiv__(inputs[0]) + elif ufunc == np.power: + return inputs[0].pow(inputs[1]) + elif ufunc == np.abs: + return inputs[0].abs() + elif ufunc == np.log: + return inputs[0].ln() + elif ufunc == np.log2: + return inputs[0].log2() + elif ufunc == np.log10: + return inputs[0].log10() + elif ufunc == np.exp: + return inputs[0].exp() + elif ufunc == np.exp2: + return inputs[0].exp2() + elif ufunc == np.sin: + return inputs[0].sin() + elif ufunc == np.cos: + return inputs[0].cos() + elif ufunc == np.tan: + return inputs[0].tan() + elif ufunc == np.arcsin: + return inputs[0].asin() + elif ufunc == np.arccos: + return inputs[0].acos() + elif ufunc == np.arctan: + return inputs[0].atan() + elif ufunc == np.sqrt: + return inputs[0].sqrt() + elif ufunc.__name__ == "erf": + # TODO can we use name for all? + return inputs[0].erf() + raise NotImplementedError("Not Implemented for type csp.Edge: {}".format(ufunc)) + def __getattr__(self, key): from csp.impl.struct import Struct diff --git a/csp/tests/test_baselib.py b/csp/tests/test_baselib.py index 972b3f3f..1b407cab 100644 --- a/csp/tests/test_baselib.py +++ b/csp/tests/test_baselib.py @@ -417,16 +417,16 @@ def test_exprtk(self): results[0], list(zip([start_time + timedelta(seconds=i) for i in range(5)], [0, 77, 154, 231, 308])) ) - def test_math_ops(self): + def test_math_binary_ops(self): OPS = { csp.add: lambda x, y: x + y, csp.sub: lambda x, y: x - y, csp.multiply: lambda x, y: x * y, csp.divide: lambda x, y: x / y, csp.pow: lambda x, y: x**y, - csp.floordiv: lambda x, y: x // y, csp.min: lambda x, y: min(x, y), csp.max: lambda x, y: max(x, y), + csp.floordiv: lambda x, y: x // y, } @csp.graph @@ -469,6 +469,63 @@ def graph(use_promotion: bool): [v[1] for v in results[op.__name__ + "-rev"]], [comp(y, x) for x, y in zip(xv, yv)], op.__name__ ) + def test_math_unary_ops(self): + OPS = { + csp.abs: lambda x: abs(x), + csp.ln: lambda x: math.log(x), + csp.log2: lambda x: math.log2(x), + csp.log10: lambda x: math.log10(x), + csp.exp: lambda x: math.exp(x), + csp.exp2: lambda x: math.exp2(x), + csp.sin: lambda x: math.sin(x), + csp.cos: lambda x: math.cos(x), + csp.tan: lambda x: math.tan(x), + csp.arctan: lambda x: math.atan(x), + csp.sinh: lambda x: math.sinh(x), + csp.cosh: lambda x: math.cosh(x), + csp.tanh: lambda x: math.tanh(x), + csp.arcsinh: lambda x: math.asinh(x), + csp.arccosh: lambda x: math.acosh(x), + csp.erf: lambda x: math.erf(x), + } + + @csp.graph + def graph(): + x = csp.count(csp.timer(timedelta(seconds=0.25))) + csp.add_graph_output("x", x) + + for op in OPS.keys(): + csp.add_graph_output(op.__name__, op(x)) + + st = datetime(2020, 1, 1) + results = csp.run(graph, starttime=st, endtime=st + timedelta(seconds=3)) + xv = [v[1] for v in results["x"]] + + for op, comp in OPS.items(): + self.assertEqual([v[1] for v in results[op.__name__]], [comp(x) for x in xv], op.__name__) + + def test_math_unary_ops_other_domain(self): + OPS = { + csp.arcsin: lambda x: math.asin(x), + csp.arccos: lambda x: math.acos(x), + csp.arctanh: lambda x: math.atanh(x), + } + + @csp.graph + def graph(): + x = 1 / (csp.count(csp.timer(timedelta(seconds=0.25))) * math.pi) + csp.add_graph_output("x", x) + + for op in OPS.keys(): + csp.add_graph_output(op.__name__, op(x)) + + st = datetime(2020, 1, 1) + results = csp.run(graph, starttime=st, endtime=st + timedelta(seconds=3)) + xv = [v[1] for v in results["x"]] + + for op, comp in OPS.items(): + self.assertEqual([v[1] for v in results[op.__name__]], [comp(x) for x in xv], op.__name__) + def test_comparisons(self): OPS = { csp.gt: lambda x, y: x > y, @@ -1096,9 +1153,6 @@ def graph(): other_nodes = { csp.drop_nans: lambda node: node(random_gen_nan(trigger1)), - csp.exp: lambda node: node(random_gen(trigger1, float) / 100), - csp.ln: lambda node: node(random_gen(trigger1, float)), - csp.abs: lambda node: node(random_gen(trigger1, float)), csp.cast_int_to_float: lambda node: node(random_gen(trigger1, int)), csp.bitwise_not: lambda node: node(random_gen(trigger1, int)), } @@ -1131,6 +1185,7 @@ def handle_collect(node, typ): assertEqual(node.__name__, str(typ), python, cpp) for node, apply in other_nodes.items(): + print(node.__name__) python = apply(node.python) cpp = apply(node) assertEqual(node.__name__, "", python, cpp) diff --git a/csp/tests/test_engine.py b/csp/tests/test_engine.py index 2b45c319..bd604f2c 100644 --- a/csp/tests/test_engine.py +++ b/csp/tests/test_engine.py @@ -488,6 +488,7 @@ def graph(): def test_bugreport_csp28(self): """bug where non-basket inputs after basket inputs were not being assigne dproperly in c++""" + @csp.node def buggy(basket: [ts[int]], x: ts[bool]) -> ts[bool]: if csp.ticked(x) and csp.valid(x): From 6b4f6e153e730f7941baeb0c9d6e155fe46f6acb Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:40:02 -0500 Subject: [PATCH 2/8] Update operator dispatch to include numpy variants. Allow for only a single argument to be a numpy type, with required modifications to max and min to support this. Add numpy specializations - due to numpy's ufunc dispatch already deferring to numpy functions when applicable, this does not have a huge effect. Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- csp/baselib.py | 41 ++++++++++++++--- csp/tests/test_baselib.py | 94 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 6 deletions(-) diff --git a/csp/baselib.py b/csp/baselib.py index 10260923..7068319a 100644 --- a/csp/baselib.py +++ b/csp/baselib.py @@ -230,6 +230,9 @@ def _print_ts(tag: str, x: ts["T"]): builtins.print("%s %s:%s" % (t, tag, x)) +_python_print = print + + def print(tag: str, x): return _print_ts(tag, _convert_ts_object_for_print(x)) @@ -738,12 +741,34 @@ def or_(*inputs): ("pow", "int"): _cspbaselibimpl.pow_i, ("max", "float"): _cspbaselibimpl.max_f, ("max", "int"): _cspbaselibimpl.max_i, + ("max", "np"): np.maximum, ("min", "float"): _cspbaselibimpl.min_f, ("min", "int"): _cspbaselibimpl.min_i, + ("min", "np"): np.minimum, # unary math ("abs", "float"): _cspbaselibimpl.abs, + ("abs", "np"): np.abs, ("ln", "float"): _cspbaselibimpl.ln, + ("ln", "np"): np.log, + ("log2", "np"): np.log2, + ("log10", "np"): np.log10, ("exp", "float"): _cspbaselibimpl.exp, + ("exp", "np"): np.exp, + ("exp2", "np"): np.exp2, + ("sqrt", "np"): np.sqrt, + # ("erf", "np"): np.erf, # erf is in scipy, worth it to import? + ("sin", "np"): np.sin, + ("cos", "np"): np.cos, + ("tan", "np"): np.tan, + ("arcsin", "np"): np.arcsin, + ("arccos", "np"): np.arccos, + ("arctan", "np"): np.arctan, + ("sinh", "np"): np.sinh, + ("cosh", "np"): np.cosh, + ("tanh", "np"): np.tanh, + ("arcsinh", "np"): np.arcsinh, + ("arccosh", "np"): np.arccosh, + ("arctanh", "np"): np.arctanh, # binary comparator ("eq", "float"): _cspbaselibimpl.eq_f, ("eq", "int"): _cspbaselibimpl.eq_i, @@ -788,10 +813,12 @@ def int_type(x: ts[int], y: ts[int]) -> ts[int_out_type]: if csp.valid(x, y): return op_lambda(x, y) + numpy_func = MATH_COMP_OPS_CPP.get((name, "np"), op_lambda) + @_node_internal_use(name=name) def numpy_type(x: ts["T"], y: ts["U"]) -> ts[np.ndarray]: if csp.valid(x, y): - return op_lambda(x, y) + return numpy_func(x, y) @_node_internal_use(name=name) def generic_type(x: ts["T"], y: ts["T"]) -> ts[generic_out_type]: @@ -799,9 +826,9 @@ def generic_type(x: ts["T"], y: ts["T"]) -> ts[generic_out_type]: return op_lambda(x, y) def comp(x: ts["T"], y: ts["U"]): - if x.tstype.typ in [Numpy1DArray[float], NumpyNDArray[float]] or y.tstype.typ in [ - Numpy1DArray[float], - NumpyNDArray[float], + if typing.get_origin(x.tstype.typ) in [Numpy1DArray, NumpyNDArray] or typing.get_origin(y.tstype.typ) in [ + Numpy1DArray, + NumpyNDArray, ]: return numpy_type(x, y) elif x.tstype.typ is float and y.tstype.typ is float: @@ -843,10 +870,12 @@ def int_type(x: ts[int]) -> ts[int_out_type]: if csp.valid(x): return op_lambda(x) + numpy_func = MATH_COMP_OPS_CPP.get((name, "np"), op_lambda) + @_node_internal_use(name=name) def numpy_type(x: ts["T"]) -> ts[np.ndarray]: if csp.valid(x): - return op_lambda(x) + return numpy_func(x) @_node_internal_use(name=name) def generic_type(x: ts["T"]) -> ts[generic_out_type]: @@ -854,7 +883,7 @@ def generic_type(x: ts["T"]) -> ts[generic_out_type]: return op_lambda(x) def comp(x: ts["T"]): - if x.tstype.typ in [Numpy1DArray[float], NumpyNDArray[float]]: + if typing.get_origin(x.tstype.typ) in [Numpy1DArray, NumpyNDArray]: return numpy_type(x) elif x.tstype.typ is float: return float_type(x) diff --git a/csp/tests/test_baselib.py b/csp/tests/test_baselib.py index 1b407cab..6551c472 100644 --- a/csp/tests/test_baselib.py +++ b/csp/tests/test_baselib.py @@ -469,6 +469,60 @@ def graph(use_promotion: bool): [v[1] for v in results[op.__name__ + "-rev"]], [comp(y, x) for x, y in zip(xv, yv)], op.__name__ ) + def test_math_binary_ops_numpy(self): + OPS = { + csp.add: lambda x, y: x + y, + csp.sub: lambda x, y: x - y, + csp.multiply: lambda x, y: x * y, + csp.divide: lambda x, y: x / y, + csp.pow: lambda x, y: x**y, + csp.min: lambda x, y: np.minimum(x, y), + csp.max: lambda x, y: np.maximum(x, y), + csp.floordiv: lambda x, y: x // y, + } + + @csp.graph + def graph(use_promotion: bool): + x = csp.count(csp.timer(timedelta(seconds=0.25))) + csp.const(np.random.randint(0, 10, (10,))) + if use_promotion: + y = 10 + y_edge = csp.const(y) + else: + y = csp.default( + csp.count(csp.timer(timedelta(seconds=1))), 1, delay=timedelta(seconds=0.25) + ) * csp.const(np.random.randint(1, 2, (10,))) + y_edge = y + + csp.add_graph_output("x", csp.merge(x, csp.sample(y_edge, x))) + csp.add_graph_output("y", csp.merge(y_edge, csp.sample(x, y_edge))) + + for op in OPS.keys(): + if use_promotion: + if op in [csp.min, csp.max]: + continue # can't type promote, it's not being called ON an edge + p_op = OPS[op] + csp.add_graph_output(op.__name__, p_op(x, y)) + csp.add_graph_output(op.__name__ + "-rev", p_op(y, x)) + else: + csp.add_graph_output(op.__name__, op(x, y)) + csp.add_graph_output(op.__name__ + "-rev", op(y, x)) + + for use_promotion in [False, True]: + st = datetime(2020, 1, 1) + results = csp.run(graph, use_promotion, starttime=st, endtime=st + timedelta(seconds=3)) + xv = [v[1] for v in results["x"]] + yv = [v[1] for v in results["y"]] + + for op, comp in OPS.items(): + if op in [csp.min, csp.max] and use_promotion: + continue + for i, (_, result) in enumerate(results[op.__name__]): + reference = comp(xv[i], yv[i]) + self.assertTrue((result == reference).all(), op.__name__) + for i, (_, result) in enumerate(results[op.__name__ + "-rev"]): + reference = comp(yv[i], xv[i]) + self.assertTrue((result == reference).all(), op.__name__) + def test_math_unary_ops(self): OPS = { csp.abs: lambda x: abs(x), @@ -504,6 +558,46 @@ def graph(): for op, comp in OPS.items(): self.assertEqual([v[1] for v in results[op.__name__]], [comp(x) for x in xv], op.__name__) + def test_math_unary_ops_numpy(self): + OPS = { + csp.abs: lambda x: np.abs(x), + csp.ln: lambda x: np.log(x), + csp.log2: lambda x: np.log2(x), + csp.log10: lambda x: np.log10(x), + csp.exp: lambda x: np.exp(x), + csp.exp2: lambda x: np.exp2(x), + csp.sin: lambda x: np.sin(x), + csp.cos: lambda x: np.cos(x), + csp.tan: lambda x: np.tan(x), + csp.arctan: lambda x: np.arctan(x), + csp.sinh: lambda x: np.sinh(x), + csp.cosh: lambda x: np.cosh(x), + csp.tanh: lambda x: np.tanh(x), + csp.arcsinh: lambda x: np.arcsinh(x), + csp.arccosh: lambda x: np.arccosh(x), + # csp.erf: lambda x: math.erf(x), + } + + @csp.graph + def graph(): + x = csp.count(csp.timer(timedelta(seconds=0.25))) + csp.const(np.random.randint(-10, 10, (10,))) + csp.add_graph_output("x", x) + + for op in OPS.keys(): + csp.add_graph_output(op.__name__, op(x)) + + st = datetime(2020, 1, 1) + results = csp.run(graph, starttime=st, endtime=st + timedelta(seconds=3)) + xv = [v[1] for v in results["x"]] + + for op, comp in OPS.items(): + for i, (_, result) in enumerate(results[op.__name__]): + reference = comp(xv[i]) + # drop nans + result = result[~np.isnan(result)] + reference = reference[~np.isnan(reference)] + self.assertTrue((result == reference).all(), op.__name__) + def test_math_unary_ops_other_domain(self): OPS = { csp.arcsin: lambda x: math.asin(x), From 6c10981ef59bbc695569c50de96a51369a4602c3 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Tue, 20 Feb 2024 00:04:03 -0500 Subject: [PATCH 3/8] Add more C++ implementations for new standard functions Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- cpp/csp/cppnodes/CMakeLists.txt | 2 +- cpp/csp/cppnodes/baselibimpl.cpp | 81 ++++++++++++++++++++++++++----- cpp/csp/python/cspbaselibimpl.cpp | 45 +++++++++++++++-- csp/baselib.py | 45 +++++++++++++++-- csp/impl/wiring/edge.py | 19 ++++++++ csp/tests/test_baselib.py | 5 +- 6 files changed, 175 insertions(+), 22 deletions(-) diff --git a/cpp/csp/cppnodes/CMakeLists.txt b/cpp/csp/cppnodes/CMakeLists.txt index 0e3f5d47..776c3ab1 100644 --- a/cpp/csp/cppnodes/CMakeLists.txt +++ b/cpp/csp/cppnodes/CMakeLists.txt @@ -1,5 +1,5 @@ add_library(baselibimpl STATIC baselibimpl.cpp) -target_link_libraries(baselibimpl csp_core csp_engine) +target_link_libraries(baselibimpl csp_core csp_engine m) add_library(basketlibimpl STATIC basketlibimpl.cpp) target_link_libraries(basketlibimpl baselibimpl csp_core csp_engine) diff --git a/cpp/csp/cppnodes/baselibimpl.cpp b/cpp/csp/cppnodes/baselibimpl.cpp index 952b3cb1..aae979af 100644 --- a/cpp/csp/cppnodes/baselibimpl.cpp +++ b/cpp/csp/cppnodes/baselibimpl.cpp @@ -580,18 +580,18 @@ Math operations // Unary operation -template +template DECLARE_CPPNODE( _unary_op ) { - TS_INPUT( T, x ); - TS_OUTPUT( T ); + TS_INPUT( ArgT, x ); + TS_OUTPUT( OutT ); //Expanded out INIT_CPPNODE without create call... CSP csp; const char * name() const override { return "_unary_op"; } public: - _STATIC_CREATE_METHOD( SINGLE_ARG( _unary_op ) ); + _STATIC_CREATE_METHOD( SINGLE_ARG( _unary_op ) ); _unary_op( csp::Engine * engine, const csp::CppNode::NodeDef & nodedef ) : csp::CppNode( engine, nodedef ) {} @@ -601,18 +601,73 @@ DECLARE_CPPNODE( _unary_op ) } }; -inline double _exp( double x ){ return std::exp(x ); } -inline double _ln( double x ){ return std::log( x ); } -inline double _abs( double x ){ return std::abs( x ); } +template inline T _abs( T x ){ return std::abs( x ); } +template inline double _ln( T x ){ return std::log( x ); } +template inline double _log2( T x ){ return std::log2( x ); } +template inline double _log10( T x ){ return std::log10( x ); } +template inline double _exp( T x ){ return std::exp( x ); } +template inline double _exp2( T x ){ return std::exp2( x ); } +template inline double _sqrt( T x ){ return std::sqrt( x ); } +template inline double _erf( T x ){ return std::erf( x ); } +template inline double _sin( T x ){ return std::sin( x ); } +template inline double _cos( T x ){ return std::cos( x ); } +template inline double _tan( T x ){ return std::tan( x ); } +template inline double _asin( T x ){ return std::asin( x ); } +template inline double _acos( T x ){ return std::acos( x ); } +template inline double _atan( T x ){ return std::atan( x ); } +template inline double _sinh( T x ){ return std::sinh( x ); } +template inline double _cosh( T x ){ return std::cosh( x ); } +template inline double _tanh( T x ){ return std::tanh( x ); } +template inline double _asinh( T x ){ return std::asinh( x ); } +template inline double _acosh( T x ){ return std::acosh( x ); } +template inline double _atanh( T x ){ return std::atanh( x ); } + inline bool _not_( bool x ){ return !x; } inline int64_t _bitwise_not(int64_t x) { return ~x; } -#define EXPORT_UNARY_OP( Type, Name ) EXPORT_TEMPLATE_CPPNODE( Name, SINGLE_ARG( _unary_op ) ) - EXPORT_UNARY_OP( double, ln ); - EXPORT_UNARY_OP( double, exp ); - EXPORT_UNARY_OP( double, abs ); - EXPORT_UNARY_OP( bool, not_ ); - EXPORT_UNARY_OP( int64_t, bitwise_not ); +#define EXPORT_UNARY_OP( Name, ArgType, OutType, Func ) EXPORT_TEMPLATE_CPPNODE( Name, SINGLE_ARG( _unary_op ) ) + EXPORT_UNARY_OP( abs_f, double, double, abs ); + EXPORT_UNARY_OP( abs_i, int64_t, int64_t, abs ); + EXPORT_UNARY_OP( ln_f, double, double, ln ); + EXPORT_UNARY_OP( ln_i, int64_t, double, ln ); + EXPORT_UNARY_OP( log2_f, double, double, log2 ); + EXPORT_UNARY_OP( log2_i, int64_t, double, log2 ); + EXPORT_UNARY_OP( log10_f, double, double, log10 ); + EXPORT_UNARY_OP( log10_i, int64_t, double, log10 ); + EXPORT_UNARY_OP( exp_f, double, double, exp ); + EXPORT_UNARY_OP( exp_i, int64_t, double, exp ); + EXPORT_UNARY_OP( exp2_f, double, double, exp2 ); + EXPORT_UNARY_OP( exp2_i, int64_t, double, exp2 ); + EXPORT_UNARY_OP( sqrt_f, double, double, sqrt ); + EXPORT_UNARY_OP( sqrt_i, int64_t, double, sqrt ); + EXPORT_UNARY_OP( erf_f, double, double, erf ); + EXPORT_UNARY_OP( erf_i, int64_t, double, erf ); + EXPORT_UNARY_OP( sin_f, double, double, sin ); + EXPORT_UNARY_OP( sin_i, int64_t, double, sin ); + EXPORT_UNARY_OP( cos_f, double, double, cos ); + EXPORT_UNARY_OP( cos_i, int64_t, double, cos ); + EXPORT_UNARY_OP( tan_f, double, double, tan ); + EXPORT_UNARY_OP( tan_i, int64_t, double, tan ); + EXPORT_UNARY_OP( asin_f, double, double, asin ); + EXPORT_UNARY_OP( asin_i, int64_t, double, asin ); + EXPORT_UNARY_OP( acos_f, double, double, acos ); + EXPORT_UNARY_OP( acos_i, int64_t, double, acos ); + EXPORT_UNARY_OP( atan_f, double, double, atan ); + EXPORT_UNARY_OP( atan_i, int64_t, double, atan ); + EXPORT_UNARY_OP( sinh_f, double, double, sinh ); + EXPORT_UNARY_OP( sinh_i, int64_t, double, sinh ); + EXPORT_UNARY_OP( cosh_f, double, double, cosh ); + EXPORT_UNARY_OP( cosh_i, int64_t, double, cosh ); + EXPORT_UNARY_OP( tanh_f, double, double, tanh ); + EXPORT_UNARY_OP( tanh_i, int64_t, double, tanh ); + EXPORT_UNARY_OP( asinh_f, double, double, asinh ); + EXPORT_UNARY_OP( asinh_i, int64_t, double, asinh ); + EXPORT_UNARY_OP( acosh_f, double, double, acosh ); + EXPORT_UNARY_OP( acosh_i, int64_t, double, acosh ); + EXPORT_UNARY_OP( atanh_f, double, double, atanh ); + EXPORT_UNARY_OP( atanh_i, int64_t, double, atanh ); + EXPORT_UNARY_OP( not_, bool, bool, not_ ); + EXPORT_UNARY_OP( bitwise_not, int64_t, int64_t, bitwise_not ); #undef EXPORT_UNARY_OP // Binary operation diff --git a/cpp/csp/python/cspbaselibimpl.cpp b/cpp/csp/python/cspbaselibimpl.cpp index 6f940c59..c392adbf 100644 --- a/cpp/csp/python/cspbaselibimpl.cpp +++ b/cpp/csp/python/cspbaselibimpl.cpp @@ -361,9 +361,48 @@ REGISTER_CPPNODE( csp::cppnodes, max_f ); REGISTER_CPPNODE( csp::cppnodes, max_i ); REGISTER_CPPNODE( csp::cppnodes, min_f ); REGISTER_CPPNODE( csp::cppnodes, min_i ); -REGISTER_CPPNODE( csp::cppnodes, ln ); -REGISTER_CPPNODE( csp::cppnodes, exp ); -REGISTER_CPPNODE( csp::cppnodes, abs ); +REGISTER_CPPNODE( csp::cppnodes, abs_f ); +REGISTER_CPPNODE( csp::cppnodes, abs_i ); +REGISTER_CPPNODE( csp::cppnodes, ln_f ); +REGISTER_CPPNODE( csp::cppnodes, ln_i ); +REGISTER_CPPNODE( csp::cppnodes, log2_f ); +REGISTER_CPPNODE( csp::cppnodes, log2_i ); +REGISTER_CPPNODE( csp::cppnodes, log10_f ); +REGISTER_CPPNODE( csp::cppnodes, log10_i ); +REGISTER_CPPNODE( csp::cppnodes, exp_f ); +REGISTER_CPPNODE( csp::cppnodes, exp_i ); +REGISTER_CPPNODE( csp::cppnodes, exp2_f ); +REGISTER_CPPNODE( csp::cppnodes, exp2_i ); +REGISTER_CPPNODE( csp::cppnodes, sqrt_f ); +REGISTER_CPPNODE( csp::cppnodes, sqrt_i ); +REGISTER_CPPNODE( csp::cppnodes, erf_f ); +REGISTER_CPPNODE( csp::cppnodes, erf_i ); +REGISTER_CPPNODE( csp::cppnodes, sin_f ); +REGISTER_CPPNODE( csp::cppnodes, sin_i ); +REGISTER_CPPNODE( csp::cppnodes, cos_f ); +REGISTER_CPPNODE( csp::cppnodes, cos_i ); +REGISTER_CPPNODE( csp::cppnodes, tan_f ); +REGISTER_CPPNODE( csp::cppnodes, tan_i ); +REGISTER_CPPNODE( csp::cppnodes, asin_f ); +REGISTER_CPPNODE( csp::cppnodes, asin_i ); +REGISTER_CPPNODE( csp::cppnodes, acos_f ); +REGISTER_CPPNODE( csp::cppnodes, acos_i ); +REGISTER_CPPNODE( csp::cppnodes, atan_f ); +REGISTER_CPPNODE( csp::cppnodes, atan_i ); +REGISTER_CPPNODE( csp::cppnodes, sinh_f ); +REGISTER_CPPNODE( csp::cppnodes, sinh_i ); +REGISTER_CPPNODE( csp::cppnodes, cosh_f ); +REGISTER_CPPNODE( csp::cppnodes, cosh_i ); +REGISTER_CPPNODE( csp::cppnodes, tanh_f ); +REGISTER_CPPNODE( csp::cppnodes, tanh_i ); +REGISTER_CPPNODE( csp::cppnodes, asinh_f ); +REGISTER_CPPNODE( csp::cppnodes, asinh_i ); +REGISTER_CPPNODE( csp::cppnodes, acosh_f ); +REGISTER_CPPNODE( csp::cppnodes, acosh_i ); +REGISTER_CPPNODE( csp::cppnodes, atanh_f ); +REGISTER_CPPNODE( csp::cppnodes, atanh_i ); + + REGISTER_CPPNODE( csp::cppnodes, bitwise_not ); // Comparisons diff --git a/csp/baselib.py b/csp/baselib.py index 7068319a..f0addf44 100644 --- a/csp/baselib.py +++ b/csp/baselib.py @@ -46,6 +46,7 @@ "floordiv", "mod", "pow", + "neg", "abs", "ln", "log2", @@ -746,28 +747,65 @@ def or_(*inputs): ("min", "int"): _cspbaselibimpl.min_i, ("min", "np"): np.minimum, # unary math - ("abs", "float"): _cspbaselibimpl.abs, + ("abs", "float"): _cspbaselibimpl.abs_f, + ("abs", "int"): _cspbaselibimpl.abs_i, ("abs", "np"): np.abs, - ("ln", "float"): _cspbaselibimpl.ln, + ("ln", "float"): _cspbaselibimpl.ln_f, + ("ln", "int"): _cspbaselibimpl.ln_i, ("ln", "np"): np.log, + ("log2", "float"): _cspbaselibimpl.log2_f, + ("log2", "int"): _cspbaselibimpl.log2_i, ("log2", "np"): np.log2, + ("log10", "float"): _cspbaselibimpl.log10_f, + ("log10", "int"): _cspbaselibimpl.log10_i, ("log10", "np"): np.log10, - ("exp", "float"): _cspbaselibimpl.exp, + ("exp", "float"): _cspbaselibimpl.exp_f, + ("exp", "int"): _cspbaselibimpl.exp_i, ("exp", "np"): np.exp, + ("exp2", "float"): _cspbaselibimpl.exp2_f, + ("exp2", "int"): _cspbaselibimpl.exp2_i, ("exp2", "np"): np.exp2, + ("sqrt", "float"): _cspbaselibimpl.sqrt_f, + ("sqrt", "int"): _cspbaselibimpl.sqrt_i, ("sqrt", "np"): np.sqrt, + ("erf", "float"): _cspbaselibimpl.erf_f, + ("erf", "int"): _cspbaselibimpl.erf_i, # ("erf", "np"): np.erf, # erf is in scipy, worth it to import? + ("sin", "float"): _cspbaselibimpl.sin_f, + ("sin", "int"): _cspbaselibimpl.sin_i, ("sin", "np"): np.sin, + ("cos", "float"): _cspbaselibimpl.cos_f, + ("cos", "int"): _cspbaselibimpl.cos_i, ("cos", "np"): np.cos, + ("tan", "float"): _cspbaselibimpl.tan_f, + ("tan", "int"): _cspbaselibimpl.tan_i, ("tan", "np"): np.tan, + ("arcsin", "float"): _cspbaselibimpl.asin_f, + ("arcsin", "int"): _cspbaselibimpl.asin_i, ("arcsin", "np"): np.arcsin, + ("arccos", "float"): _cspbaselibimpl.acos_f, + ("arccos", "int"): _cspbaselibimpl.acos_i, ("arccos", "np"): np.arccos, + ("arctan", "float"): _cspbaselibimpl.atan_f, + ("arctan", "int"): _cspbaselibimpl.atan_i, ("arctan", "np"): np.arctan, + ("sinh", "float"): _cspbaselibimpl.sinh_f, + ("sinh", "int"): _cspbaselibimpl.sinh_i, ("sinh", "np"): np.sinh, + ("cosh", "float"): _cspbaselibimpl.cosh_f, + ("cosh", "int"): _cspbaselibimpl.cosh_i, ("cosh", "np"): np.cosh, + ("tanh", "float"): _cspbaselibimpl.tanh_f, + ("tanh", "int"): _cspbaselibimpl.tanh_i, ("tanh", "np"): np.tanh, + ("arcsinh", "float"): _cspbaselibimpl.asinh_f, + ("arcsinh", "int"): _cspbaselibimpl.asinh_i, ("arcsinh", "np"): np.arcsinh, + ("arccosh", "float"): _cspbaselibimpl.acosh_f, + ("arccosh", "int"): _cspbaselibimpl.acosh_i, ("arccosh", "np"): np.arccosh, + ("arctanh", "float"): _cspbaselibimpl.atanh_f, + ("arctanh", "int"): _cspbaselibimpl.atanh_i, ("arctanh", "np"): np.arctanh, # binary comparator ("eq", "float"): _cspbaselibimpl.eq_f, @@ -905,6 +943,7 @@ def comp(x: ts["T"]): max = define_binary_op("max", lambda x, y: x if x > y else y) floordiv = define_binary_op("floordiv", lambda x, y: x // y) mod = define_binary_op("mod", lambda x, y: x % y) +neg = define_unary_op("mod", lambda x: -x) # Other math ops _python_abs = abs diff --git a/csp/impl/wiring/edge.py b/csp/impl/wiring/edge.py index 7a559420..7188e1b1 100644 --- a/csp/impl/wiring/edge.py +++ b/csp/impl/wiring/edge.py @@ -128,6 +128,25 @@ def __invert__(self): return csp.bitwise_not(self) raise TypeError(f"Cannot call invert with a ts[{self.tstype.typ.__name__}], not an integer type") + def __neg__(self): + import csp + + return csp.neg(self) + + # def __ceil__(self): + # def __floor__(self): + # def __round__(self): + # def __trunc__(self): + # def __lshift__(self): + # def __rshift__(self): + # def __pos__(self): + # def __xor__(self): + + def __abs__(self): + import csp + + return csp.abs(self) + def abs(self): import csp diff --git a/csp/tests/test_baselib.py b/csp/tests/test_baselib.py index 6551c472..8929d386 100644 --- a/csp/tests/test_baselib.py +++ b/csp/tests/test_baselib.py @@ -483,7 +483,7 @@ def test_math_binary_ops_numpy(self): @csp.graph def graph(use_promotion: bool): - x = csp.count(csp.timer(timedelta(seconds=0.25))) + csp.const(np.random.randint(0, 10, (10,))) + x = csp.count(csp.timer(timedelta(seconds=0.25))) + csp.const(np.random.rand(10)) if use_promotion: y = 10 y_edge = csp.const(y) @@ -525,6 +525,7 @@ def graph(use_promotion: bool): def test_math_unary_ops(self): OPS = { + csp.neg: lambda x: -x, csp.abs: lambda x: abs(x), csp.ln: lambda x: math.log(x), csp.log2: lambda x: math.log2(x), @@ -580,7 +581,7 @@ def test_math_unary_ops_numpy(self): @csp.graph def graph(): - x = csp.count(csp.timer(timedelta(seconds=0.25))) + csp.const(np.random.randint(-10, 10, (10,))) + x = csp.count(csp.timer(timedelta(seconds=0.25))) + csp.const(np.random.rand(10)) csp.add_graph_output("x", x) for op in OPS.keys(): From d016451e97941cee605aabca327ec22686ddb3cc Mon Sep 17 00:00:00 2001 From: Tim Paine Date: Tue, 20 Feb 2024 12:26:53 -0500 Subject: [PATCH 4/8] Breakout math functions into csp.math, tweak edge ufunc dispatch to use big dict Signed-off-by: Tim Paine --- cpp/csp/cppnodes/CMakeLists.txt | 4 +- csp/__init__.py | 1 + csp/baselib.py | 430 +++----------------------------- csp/impl/wiring/edge.py | 95 +++---- csp/math.py | 380 ++++++++++++++++++++++++++++ csp/tests/test_baselib.py | 277 -------------------- csp/tests/test_math.py | 287 +++++++++++++++++++++ 7 files changed, 738 insertions(+), 736 deletions(-) create mode 100644 csp/math.py create mode 100644 csp/tests/test_math.py diff --git a/cpp/csp/cppnodes/CMakeLists.txt b/cpp/csp/cppnodes/CMakeLists.txt index 776c3ab1..4c1e0116 100644 --- a/cpp/csp/cppnodes/CMakeLists.txt +++ b/cpp/csp/cppnodes/CMakeLists.txt @@ -1,12 +1,12 @@ add_library(baselibimpl STATIC baselibimpl.cpp) -target_link_libraries(baselibimpl csp_core csp_engine m) +target_link_libraries(baselibimpl csp_core csp_engine) add_library(basketlibimpl STATIC basketlibimpl.cpp) target_link_libraries(basketlibimpl baselibimpl csp_core csp_engine) add_library(statsimpl STATIC statsimpl.cpp) set_target_properties(statsimpl PROPERTIES PUBLIC_HEADER statsimpl.h) -target_link_libraries(statsimpl baselibimpl csp_core csp_engine) +target_link_libraries(statsimpl baselibimpl csp_core csp_engine) install(TARGETS baselibimpl basketlibimpl statsimpl PUBLIC_HEADER DESTINATION include/csp/cppnodes diff --git a/csp/__init__.py b/csp/__init__.py index 67d6bf7c..9dd3119c 100644 --- a/csp/__init__.py +++ b/csp/__init__.py @@ -27,6 +27,7 @@ run_on_thread, ) from csp.impl.wiring.context import clear_global_context, new_global_context +from csp.math import * from csp.showgraph import show_graph from . import cache_support, stats diff --git a/csp/baselib.py b/csp/baselib.py index f0addf44..9b37c940 100644 --- a/csp/baselib.py +++ b/csp/baselib.py @@ -8,7 +8,6 @@ import threading import typing from datetime import datetime, timedelta -from functools import lru_cache import csp from csp.impl.__cspimpl import _cspimpl @@ -18,91 +17,50 @@ from csp.impl.wiring import DelayedEdge, Edge, OutputsContainer, graph, input_adapter_def, node from csp.impl.wiring.delayed_node import DelayedNodeWrapperDef from csp.lib import _cspbaselibimpl -from csp.typing import Numpy1DArray, NumpyNDArray __all__ = [ - "get_basket_field", - "timer", - "const", - "print", - "log", + "DelayedCollect", + "DelayedDemultiplex", "LogSettings", - "firstN", + "accum", + "apply", + "cast_int_to_float", + "collect", + "const", "count", + "default", "delay", + "demultiplex", "diff", - "merge", - "sample", + "drop_dups", + "drop_nans", + "dynamic_cast", + "dynamic_collect", + "dynamic_demultiplex", + "exprtk", "filter", - "default", - "accum", - "add", - "sub", - "multiply", - "divide", - "max", - "min", - "gate", - "floordiv", - "mod", - "pow", - "neg", - "abs", - "ln", - "log2", - "log10", - "exp", - "exp2", - "sqrt", - "erf", - "sin", - "cos", - "tan", - "arcsin", - "arccos", - "arctan", - "sinh", - "cosh", - "tanh", - "arcsinh", - "arccosh", - "arctanh", - "unroll", - "collect", + "firstN", "flatten", + "gate", + "get_basket_field", + "log", + "merge", + "multiplex", + "null_ts", + "print", + "sample", + "schedule_on_engine_stop", "split", - "cast_int_to_float", - "drop_dups", - "drop_nans", - "apply", + "static_cast", "stop_engine", - "not_", - "bitwise_not", - "and_", - "or_", - "gt", - "ge", - "lt", - "le", - "eq", - "ne", - "exprtk", + "struct_collectts", "struct_field", "struct_fromts", - "struct_collectts", - "null_ts", - "multiplex", - "demultiplex", - "dynamic_demultiplex", - "dynamic_collect", - "wrap_feedback", - "schedule_on_engine_stop", + "timer", "times", "times_ns", - "static_cast", - "dynamic_cast", - "DelayedDemultiplex", - "DelayedCollect", + "unroll", + "wrap_feedback", ] T = typing.TypeVar("T") @@ -354,9 +312,6 @@ def firstN(x: ts["T"], N: int) -> ts["T"]: return x -_TypeVar = typing.TypeVar("T") - - @node(cppimpl=_cspbaselibimpl.count) def count(x: ts["T"]) -> ts[int]: """return count of ticks of input""" @@ -439,11 +394,6 @@ def cast_int_to_float(x: ts[int]) -> ts[float]: return x -@node(cppimpl=_cspbaselibimpl.bitwise_not) -def bitwise_not(x: ts[int]) -> ts[int]: - return ~x - - @node() def apply(x: ts["T"], f: object, result_type: "U") -> ts["U"]: """ @@ -657,326 +607,6 @@ def dynamic_collect(data: {ts["K"]: ts["V"]}) -> ts[{"K": "V"}]: return dict(data.tickeditems()) -# May want to move these into separate math lib -@node(cppimpl=_cspbaselibimpl.not_, name="not_") -def not_(x: ts[bool]) -> ts[bool]: - """boolean not""" - if csp.ticked(x): - return not x - - -@node -def andnode(x: [ts[bool]]) -> ts[bool]: - if csp.valid(x): - return all(x.validvalues()) - - -def and_(*inputs): - """binary and of basket of ts[ bool ]. Note that all inputs must be valid - before any value is returned""" - return andnode(list(inputs)) - - -@node -def ornode(x: [ts[bool]]) -> ts[bool]: - if csp.valid(x): - return any(x.validvalues()) - - -def or_(*inputs): - """binary or of ts[ bool ] inputs. Note that all inputs must be valid - before any value is returned""" - return ornode(list(inputs)) - - -# Math/comparison binary operators are supported in C++ only for (int,int) and -# (float, float) arguments. For all other types, the Python implementation is used. - -MATH_OPS = [ - # binary - "add", - "sub", - "multiply", - "divide", - "pow", - "max", - "min", - "floordiv", - "mod", - # unary - "abs", - "ln", - "log2", - "log10", - "exp", - "exp2", - "sqrt", - "erf", - "sin", - "cos", - "tan", - "arcsin", - "arccos", - "arctan", - "sinh", - "cosh", - "tanh", - "arcsinh", - "arccosh", - "arctanh", -] - -COMP_OPS = ["eq", "ne", "lt", "gt", "le", "ge"] - -MATH_COMP_OPS_CPP = { - # binary math - ("add", "float"): _cspbaselibimpl.add_f, - ("add", "int"): _cspbaselibimpl.add_i, - ("sub", "float"): _cspbaselibimpl.sub_f, - ("sub", "int"): _cspbaselibimpl.sub_i, - ("multiply", "float"): _cspbaselibimpl.mul_f, - ("multiply", "int"): _cspbaselibimpl.mul_i, - ("divide", "float"): _cspbaselibimpl.div_f, - ("divide", "int"): _cspbaselibimpl.div_i, - ("pow", "float"): _cspbaselibimpl.pow_f, - ("pow", "int"): _cspbaselibimpl.pow_i, - ("max", "float"): _cspbaselibimpl.max_f, - ("max", "int"): _cspbaselibimpl.max_i, - ("max", "np"): np.maximum, - ("min", "float"): _cspbaselibimpl.min_f, - ("min", "int"): _cspbaselibimpl.min_i, - ("min", "np"): np.minimum, - # unary math - ("abs", "float"): _cspbaselibimpl.abs_f, - ("abs", "int"): _cspbaselibimpl.abs_i, - ("abs", "np"): np.abs, - ("ln", "float"): _cspbaselibimpl.ln_f, - ("ln", "int"): _cspbaselibimpl.ln_i, - ("ln", "np"): np.log, - ("log2", "float"): _cspbaselibimpl.log2_f, - ("log2", "int"): _cspbaselibimpl.log2_i, - ("log2", "np"): np.log2, - ("log10", "float"): _cspbaselibimpl.log10_f, - ("log10", "int"): _cspbaselibimpl.log10_i, - ("log10", "np"): np.log10, - ("exp", "float"): _cspbaselibimpl.exp_f, - ("exp", "int"): _cspbaselibimpl.exp_i, - ("exp", "np"): np.exp, - ("exp2", "float"): _cspbaselibimpl.exp2_f, - ("exp2", "int"): _cspbaselibimpl.exp2_i, - ("exp2", "np"): np.exp2, - ("sqrt", "float"): _cspbaselibimpl.sqrt_f, - ("sqrt", "int"): _cspbaselibimpl.sqrt_i, - ("sqrt", "np"): np.sqrt, - ("erf", "float"): _cspbaselibimpl.erf_f, - ("erf", "int"): _cspbaselibimpl.erf_i, - # ("erf", "np"): np.erf, # erf is in scipy, worth it to import? - ("sin", "float"): _cspbaselibimpl.sin_f, - ("sin", "int"): _cspbaselibimpl.sin_i, - ("sin", "np"): np.sin, - ("cos", "float"): _cspbaselibimpl.cos_f, - ("cos", "int"): _cspbaselibimpl.cos_i, - ("cos", "np"): np.cos, - ("tan", "float"): _cspbaselibimpl.tan_f, - ("tan", "int"): _cspbaselibimpl.tan_i, - ("tan", "np"): np.tan, - ("arcsin", "float"): _cspbaselibimpl.asin_f, - ("arcsin", "int"): _cspbaselibimpl.asin_i, - ("arcsin", "np"): np.arcsin, - ("arccos", "float"): _cspbaselibimpl.acos_f, - ("arccos", "int"): _cspbaselibimpl.acos_i, - ("arccos", "np"): np.arccos, - ("arctan", "float"): _cspbaselibimpl.atan_f, - ("arctan", "int"): _cspbaselibimpl.atan_i, - ("arctan", "np"): np.arctan, - ("sinh", "float"): _cspbaselibimpl.sinh_f, - ("sinh", "int"): _cspbaselibimpl.sinh_i, - ("sinh", "np"): np.sinh, - ("cosh", "float"): _cspbaselibimpl.cosh_f, - ("cosh", "int"): _cspbaselibimpl.cosh_i, - ("cosh", "np"): np.cosh, - ("tanh", "float"): _cspbaselibimpl.tanh_f, - ("tanh", "int"): _cspbaselibimpl.tanh_i, - ("tanh", "np"): np.tanh, - ("arcsinh", "float"): _cspbaselibimpl.asinh_f, - ("arcsinh", "int"): _cspbaselibimpl.asinh_i, - ("arcsinh", "np"): np.arcsinh, - ("arccosh", "float"): _cspbaselibimpl.acosh_f, - ("arccosh", "int"): _cspbaselibimpl.acosh_i, - ("arccosh", "np"): np.arccosh, - ("arctanh", "float"): _cspbaselibimpl.atanh_f, - ("arctanh", "int"): _cspbaselibimpl.atanh_i, - ("arctanh", "np"): np.arctanh, - # binary comparator - ("eq", "float"): _cspbaselibimpl.eq_f, - ("eq", "int"): _cspbaselibimpl.eq_i, - ("ne", "float"): _cspbaselibimpl.ne_f, - ("ne", "int"): _cspbaselibimpl.ne_i, - ("lt", "float"): _cspbaselibimpl.lt_f, - ("lt", "int"): _cspbaselibimpl.lt_i, - ("gt", "float"): _cspbaselibimpl.gt_f, - ("gt", "int"): _cspbaselibimpl.gt_i, - ("le", "float"): _cspbaselibimpl.le_f, - ("le", "int"): _cspbaselibimpl.le_i, - ("ge", "float"): _cspbaselibimpl.ge_f, - ("ge", "int"): _cspbaselibimpl.ge_i, -} - - -@lru_cache(maxsize=512) -def define_binary_op(name, op_lambda): - float_out_type, int_out_type, generic_out_type = [None] * 3 - if name in COMP_OPS: - float_out_type = bool - int_out_type = bool - generic_out_type = bool - elif name in MATH_OPS: - float_out_type = float - if name != "divide": - int_out_type = int - generic_out_type = "T" - else: - int_out_type = float - generic_out_type = float - - from csp.impl.wiring.node import _node_internal_use - - @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP.get((name, "float"), None), name=name) - def float_type(x: ts[float], y: ts[float]) -> ts[float_out_type]: - if csp.valid(x, y): - return op_lambda(x, y) - - @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP.get((name, "int"), None), name=name) - def int_type(x: ts[int], y: ts[int]) -> ts[int_out_type]: - if csp.valid(x, y): - return op_lambda(x, y) - - numpy_func = MATH_COMP_OPS_CPP.get((name, "np"), op_lambda) - - @_node_internal_use(name=name) - def numpy_type(x: ts["T"], y: ts["U"]) -> ts[np.ndarray]: - if csp.valid(x, y): - return numpy_func(x, y) - - @_node_internal_use(name=name) - def generic_type(x: ts["T"], y: ts["T"]) -> ts[generic_out_type]: - if csp.valid(x, y): - return op_lambda(x, y) - - def comp(x: ts["T"], y: ts["U"]): - if typing.get_origin(x.tstype.typ) in [Numpy1DArray, NumpyNDArray] or typing.get_origin(y.tstype.typ) in [ - Numpy1DArray, - NumpyNDArray, - ]: - return numpy_type(x, y) - elif x.tstype.typ is float and y.tstype.typ is float: - return float_type(x, y) - elif x.tstype.typ is int and y.tstype.typ is int: - return int_type(x, y) - - return generic_type(x, y) - - comp.__name__ = name - return comp - - -@lru_cache(maxsize=512) -def define_unary_op(name, op_lambda): - float_out_type, int_out_type, generic_out_type = [None] * 3 - if name in COMP_OPS: - float_out_type = bool - int_out_type = bool - generic_out_type = bool - elif name in MATH_OPS: - float_out_type = float - if name in ("abs",): - int_out_type = int - generic_out_type = "T" - else: - int_out_type = float - generic_out_type = float - - from csp.impl.wiring.node import _node_internal_use - - @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP.get((name, "float"), None), name=name) - def float_type(x: ts[float]) -> ts[float_out_type]: - if csp.valid(x): - return op_lambda(x) - - @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP.get((name, "int"), None), name=name) - def int_type(x: ts[int]) -> ts[int_out_type]: - if csp.valid(x): - return op_lambda(x) - - numpy_func = MATH_COMP_OPS_CPP.get((name, "np"), op_lambda) - - @_node_internal_use(name=name) - def numpy_type(x: ts["T"]) -> ts[np.ndarray]: - if csp.valid(x): - return numpy_func(x) - - @_node_internal_use(name=name) - def generic_type(x: ts["T"]) -> ts[generic_out_type]: - if csp.valid(x): - return op_lambda(x) - - def comp(x: ts["T"]): - if typing.get_origin(x.tstype.typ) in [Numpy1DArray, NumpyNDArray]: - return numpy_type(x) - elif x.tstype.typ is float: - return float_type(x) - elif x.tstype.typ is int: - return int_type(x) - return generic_type(x) - - comp.__name__ = name - return comp - - -# Math operators -add = define_binary_op("add", lambda x, y: x + y) -sub = define_binary_op("sub", lambda x, y: x - y) -multiply = define_binary_op("multiply", lambda x, y: x * y) -divide = define_binary_op("divide", lambda x, y: x / y) -pow = define_binary_op("pow", lambda x, y: x**y) -min = define_binary_op("min", lambda x, y: x if x < y else y) -max = define_binary_op("max", lambda x, y: x if x > y else y) -floordiv = define_binary_op("floordiv", lambda x, y: x // y) -mod = define_binary_op("mod", lambda x, y: x % y) -neg = define_unary_op("mod", lambda x: -x) - -# Other math ops -_python_abs = abs -abs = define_unary_op("abs", lambda x: _python_abs(x)) -ln = define_unary_op("ln", lambda x: math.log(x)) -log2 = define_unary_op("log2", lambda x: math.log2(x)) -log10 = define_unary_op("log10", lambda x: math.log10(x)) -exp = define_unary_op("exp", lambda x: math.exp(x)) -exp2 = define_unary_op("exp2", lambda x: math.exp2(x)) -sqrt = define_unary_op("sqrt", lambda x: math.sqrt(x)) -erf = define_unary_op("erf", lambda x: math.erf(x)) -sin = define_unary_op("sin", lambda x: math.sin(x)) -cos = define_unary_op("cos", lambda x: math.cos(x)) -tan = define_unary_op("tan", lambda x: math.tan(x)) -arcsin = define_unary_op("arcsin", lambda x: math.asin(x)) -arccos = define_unary_op("arccos", lambda x: math.acos(x)) -arctan = define_unary_op("arctan", lambda x: math.atan(x)) -sinh = define_unary_op("sinh", lambda x: math.sinh(x)) -cosh = define_unary_op("cosh", lambda x: math.cosh(x)) -tanh = define_unary_op("tanh", lambda x: math.tanh(x)) -arcsinh = define_unary_op("arcsinh", lambda x: math.asinh(x)) -arccosh = define_unary_op("arccosh", lambda x: math.acosh(x)) -arctanh = define_unary_op("arctanh", lambda x: math.atanh(x)) - -# Comparison operators -eq = define_binary_op("eq", lambda x, y: x == y) -ne = define_binary_op("ne", lambda x, y: x != y) -gt = define_binary_op("gt", lambda x, y: x > y) -lt = define_binary_op("lt", lambda x, y: x < y) -ge = define_binary_op("ge", lambda x, y: x >= y) -le = define_binary_op("le", lambda x, y: x <= y) - - @node def accum(x: ts["T"], start: "~T" = 0) -> ts["T"]: with csp.state(): diff --git a/csp/impl/wiring/edge.py b/csp/impl/wiring/edge.py index 7188e1b1..fbffba0c 100644 --- a/csp/impl/wiring/edge.py +++ b/csp/impl/wiring/edge.py @@ -1,4 +1,29 @@ -import numpy as np +from types import MappingProxyType + +_UFUNC_MAP = MappingProxyType( + { + "add": lambda x, y: x.__add__(y) if isinstance(x, Edge) else y.__add__(x), + "subtract": lambda x, y: x.__sub__(y) if isinstance(x, Edge) else y.__sub__(x), + "multiply": lambda x, y: x.__mul__(y) if isinstance(x, Edge) else y.__mul__(x), + "divide": lambda x, y: x.__truediv__(y) if isinstance(x, Edge) else y.__truediv__(x), + "floor_divide": lambda x, y: x.__floordiv__(y) if isinstance(x, Edge) else y.__floordiv__(x), + "power": lambda x, y: x.pow(y), + "abs": lambda x: x.abs(), + "log": lambda x: x.ln(), + "log2": lambda x: x.log2(), + "log10": lambda x: x.log10(), + "exp": lambda x: x.exp(), + "exp2": lambda x: x.exp2(), + "sin": lambda x: x.sin(), + "cos": lambda x: x.cos(), + "tan": lambda x: x.tan(), + "arcsin": lambda x: x.asin(), + "arccos": lambda x: x.acos(), + "arctan": lambda x: x.atan(), + "sqrt": lambda x: x.sqrt(), + "erf": lambda x: x.erf(), + } +) class Edge: @@ -213,62 +238,18 @@ def erf(self): return csp.erf(self) def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): - if ufunc == np.add: - if isinstance(inputs[0], Edge): - return inputs[0].__add__(inputs[1]) - else: - return inputs[1].__add__(inputs[0]) - elif ufunc == np.subtract: - if isinstance(inputs[0], Edge): - return inputs[0].__sub__(inputs[1]) - else: - return inputs[1].__sub__(inputs[0]) - elif ufunc == np.multiply: - if isinstance(inputs[0], Edge): - return inputs[0].__mul__(inputs[1]) - else: - return inputs[1].__mul__(inputs[0]) - elif ufunc == np.divide: - if isinstance(inputs[0], Edge): - return inputs[0].__truediv__(inputs[1]) - else: - return inputs[1].__truediv__(inputs[0]) - elif ufunc == np.floor_divide: - if isinstance(inputs[0], Edge): - return inputs[0].__floordiv__(inputs[1]) - else: - return inputs[1].__floordiv__(inputs[0]) - elif ufunc == np.power: - return inputs[0].pow(inputs[1]) - elif ufunc == np.abs: - return inputs[0].abs() - elif ufunc == np.log: - return inputs[0].ln() - elif ufunc == np.log2: - return inputs[0].log2() - elif ufunc == np.log10: - return inputs[0].log10() - elif ufunc == np.exp: - return inputs[0].exp() - elif ufunc == np.exp2: - return inputs[0].exp2() - elif ufunc == np.sin: - return inputs[0].sin() - elif ufunc == np.cos: - return inputs[0].cos() - elif ufunc == np.tan: - return inputs[0].tan() - elif ufunc == np.arcsin: - return inputs[0].asin() - elif ufunc == np.arccos: - return inputs[0].acos() - elif ufunc == np.arctan: - return inputs[0].atan() - elif ufunc == np.sqrt: - return inputs[0].sqrt() - elif ufunc.__name__ == "erf": - # TODO can we use name for all? - return inputs[0].erf() + ufunc_func = _UFUNC_MAP.get(ufunc.__name__, None) + if ufunc_func: + if ufunc.__name__ in ( + "add", + "subtract", + "multiply", + "divide", + "floor_divide", + "power", + ): + return ufunc_func(inputs[0], inputs[1]) + return ufunc_func(self) raise NotImplementedError("Not Implemented for type csp.Edge: {}".format(ufunc)) def __getattr__(self, key): diff --git a/csp/math.py b/csp/math.py new file mode 100644 index 00000000..fc7513ae --- /dev/null +++ b/csp/math.py @@ -0,0 +1,380 @@ +import math +import numpy as np +import typing +from functools import lru_cache + +import csp +from csp.impl.types.tstype import ts +from csp.impl.wiring import node +from csp.lib import _cspbaselibimpl +from csp.typing import Numpy1DArray, NumpyNDArray + +__all__ = [ + "abs", + "add", + "and_", + "arccos", + "arccosh", + "arcsin", + "arcsinh", + "arctan", + "arctanh", + "bitwise_not", + "cos", + "cosh", + "divide", + "eq", + "erf", + "exp", + "exp2", + "floordiv", + "ge", + "gt", + "le", + "ln", + "log10", + "log2", + "lt", + "max", + "min", + "mod", + "multiply", + "ne", + "neg", + "not_", + "or_", + "pow", + "sin", + "sinh", + "sqrt", + "sub", + "tan", + "tanh", +] + +T = typing.TypeVar("T") +U = typing.TypeVar("U") + + +@node(cppimpl=_cspbaselibimpl.bitwise_not) +def bitwise_not(x: ts[int]) -> ts[int]: + return ~x + + +@node(cppimpl=_cspbaselibimpl.not_, name="not_") +def not_(x: ts[bool]) -> ts[bool]: + """boolean not""" + if csp.ticked(x): + return not x + + +@node +def andnode(x: [ts[bool]]) -> ts[bool]: + if csp.valid(x): + return all(x.validvalues()) + + +def and_(*inputs): + """binary and of basket of ts[ bool ]. Note that all inputs must be valid + before any value is returned""" + return andnode(list(inputs)) + + +@node +def ornode(x: [ts[bool]]) -> ts[bool]: + if csp.valid(x): + return any(x.validvalues()) + + +def or_(*inputs): + """binary or of ts[ bool ] inputs. Note that all inputs must be valid + before any value is returned""" + return ornode(list(inputs)) + + +# Math/comparison binary operators are supported in C++ only for (int,int) and +# (float, float) arguments. For all other types, the Python implementation is used. + +MATH_OPS = [ + # binary + "add", + "sub", + "multiply", + "divide", + "pow", + "max", + "min", + "floordiv", + "mod", + # unary + "abs", + "ln", + "log2", + "log10", + "exp", + "exp2", + "sqrt", + "erf", + "sin", + "cos", + "tan", + "arcsin", + "arccos", + "arctan", + "sinh", + "cosh", + "tanh", + "arcsinh", + "arccosh", + "arctanh", +] + +COMP_OPS = ["eq", "ne", "lt", "gt", "le", "ge"] + +MATH_COMP_OPS_CPP = { + # binary math + ("add", "float"): _cspbaselibimpl.add_f, + ("add", "int"): _cspbaselibimpl.add_i, + ("sub", "float"): _cspbaselibimpl.sub_f, + ("sub", "int"): _cspbaselibimpl.sub_i, + ("multiply", "float"): _cspbaselibimpl.mul_f, + ("multiply", "int"): _cspbaselibimpl.mul_i, + ("divide", "float"): _cspbaselibimpl.div_f, + ("divide", "int"): _cspbaselibimpl.div_i, + ("pow", "float"): _cspbaselibimpl.pow_f, + ("pow", "int"): _cspbaselibimpl.pow_i, + ("max", "float"): _cspbaselibimpl.max_f, + ("max", "int"): _cspbaselibimpl.max_i, + ("max", "np"): np.maximum, + ("min", "float"): _cspbaselibimpl.min_f, + ("min", "int"): _cspbaselibimpl.min_i, + ("min", "np"): np.minimum, + # unary math + ("abs", "float"): _cspbaselibimpl.abs_f, + ("abs", "int"): _cspbaselibimpl.abs_i, + ("abs", "np"): np.abs, + ("ln", "float"): _cspbaselibimpl.ln_f, + ("ln", "int"): _cspbaselibimpl.ln_i, + ("ln", "np"): np.log, + ("log2", "float"): _cspbaselibimpl.log2_f, + ("log2", "int"): _cspbaselibimpl.log2_i, + ("log2", "np"): np.log2, + ("log10", "float"): _cspbaselibimpl.log10_f, + ("log10", "int"): _cspbaselibimpl.log10_i, + ("log10", "np"): np.log10, + ("exp", "float"): _cspbaselibimpl.exp_f, + ("exp", "int"): _cspbaselibimpl.exp_i, + ("exp", "np"): np.exp, + ("exp2", "float"): _cspbaselibimpl.exp2_f, + ("exp2", "int"): _cspbaselibimpl.exp2_i, + ("exp2", "np"): np.exp2, + ("sqrt", "float"): _cspbaselibimpl.sqrt_f, + ("sqrt", "int"): _cspbaselibimpl.sqrt_i, + ("sqrt", "np"): np.sqrt, + ("erf", "float"): _cspbaselibimpl.erf_f, + ("erf", "int"): _cspbaselibimpl.erf_i, + # ("erf", "np"): np.erf, # erf is in scipy, worth it to import? + ("sin", "float"): _cspbaselibimpl.sin_f, + ("sin", "int"): _cspbaselibimpl.sin_i, + ("sin", "np"): np.sin, + ("cos", "float"): _cspbaselibimpl.cos_f, + ("cos", "int"): _cspbaselibimpl.cos_i, + ("cos", "np"): np.cos, + ("tan", "float"): _cspbaselibimpl.tan_f, + ("tan", "int"): _cspbaselibimpl.tan_i, + ("tan", "np"): np.tan, + ("arcsin", "float"): _cspbaselibimpl.asin_f, + ("arcsin", "int"): _cspbaselibimpl.asin_i, + ("arcsin", "np"): np.arcsin, + ("arccos", "float"): _cspbaselibimpl.acos_f, + ("arccos", "int"): _cspbaselibimpl.acos_i, + ("arccos", "np"): np.arccos, + ("arctan", "float"): _cspbaselibimpl.atan_f, + ("arctan", "int"): _cspbaselibimpl.atan_i, + ("arctan", "np"): np.arctan, + ("sinh", "float"): _cspbaselibimpl.sinh_f, + ("sinh", "int"): _cspbaselibimpl.sinh_i, + ("sinh", "np"): np.sinh, + ("cosh", "float"): _cspbaselibimpl.cosh_f, + ("cosh", "int"): _cspbaselibimpl.cosh_i, + ("cosh", "np"): np.cosh, + ("tanh", "float"): _cspbaselibimpl.tanh_f, + ("tanh", "int"): _cspbaselibimpl.tanh_i, + ("tanh", "np"): np.tanh, + ("arcsinh", "float"): _cspbaselibimpl.asinh_f, + ("arcsinh", "int"): _cspbaselibimpl.asinh_i, + ("arcsinh", "np"): np.arcsinh, + ("arccosh", "float"): _cspbaselibimpl.acosh_f, + ("arccosh", "int"): _cspbaselibimpl.acosh_i, + ("arccosh", "np"): np.arccosh, + ("arctanh", "float"): _cspbaselibimpl.atanh_f, + ("arctanh", "int"): _cspbaselibimpl.atanh_i, + ("arctanh", "np"): np.arctanh, + # binary comparator + ("eq", "float"): _cspbaselibimpl.eq_f, + ("eq", "int"): _cspbaselibimpl.eq_i, + ("ne", "float"): _cspbaselibimpl.ne_f, + ("ne", "int"): _cspbaselibimpl.ne_i, + ("lt", "float"): _cspbaselibimpl.lt_f, + ("lt", "int"): _cspbaselibimpl.lt_i, + ("gt", "float"): _cspbaselibimpl.gt_f, + ("gt", "int"): _cspbaselibimpl.gt_i, + ("le", "float"): _cspbaselibimpl.le_f, + ("le", "int"): _cspbaselibimpl.le_i, + ("ge", "float"): _cspbaselibimpl.ge_f, + ("ge", "int"): _cspbaselibimpl.ge_i, +} + + +@lru_cache(maxsize=512) +def define_binary_op(name, op_lambda): + float_out_type, int_out_type, generic_out_type = [None] * 3 + if name in COMP_OPS: + float_out_type = bool + int_out_type = bool + generic_out_type = bool + elif name in MATH_OPS: + float_out_type = float + if name != "divide": + int_out_type = int + generic_out_type = "T" + else: + int_out_type = float + generic_out_type = float + + from csp.impl.wiring.node import _node_internal_use + + @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP.get((name, "float"), None), name=name) + def float_type(x: ts[float], y: ts[float]) -> ts[float_out_type]: + if csp.valid(x, y): + return op_lambda(x, y) + + @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP.get((name, "int"), None), name=name) + def int_type(x: ts[int], y: ts[int]) -> ts[int_out_type]: + if csp.valid(x, y): + return op_lambda(x, y) + + numpy_func = MATH_COMP_OPS_CPP.get((name, "np"), op_lambda) + + @_node_internal_use(name=name) + def numpy_type(x: ts["T"], y: ts["U"]) -> ts[np.ndarray]: + if csp.valid(x, y): + return numpy_func(x, y) + + @_node_internal_use(name=name) + def generic_type(x: ts["T"], y: ts["T"]) -> ts[generic_out_type]: + if csp.valid(x, y): + return op_lambda(x, y) + + def comp(x: ts["T"], y: ts["U"]): + if typing.get_origin(x.tstype.typ) in [Numpy1DArray, NumpyNDArray] or typing.get_origin(y.tstype.typ) in [ + Numpy1DArray, + NumpyNDArray, + ]: + return numpy_type(x, y) + elif x.tstype.typ is float and y.tstype.typ is float: + return float_type(x, y) + elif x.tstype.typ is int and y.tstype.typ is int: + return int_type(x, y) + + return generic_type(x, y) + + comp.__name__ = name + return comp + + +@lru_cache(maxsize=512) +def define_unary_op(name, op_lambda): + float_out_type, int_out_type, generic_out_type = [None] * 3 + if name in COMP_OPS: + float_out_type = bool + int_out_type = bool + generic_out_type = bool + elif name in MATH_OPS: + float_out_type = float + if name in ("abs",): + int_out_type = int + generic_out_type = "T" + else: + int_out_type = float + generic_out_type = float + + from csp.impl.wiring.node import _node_internal_use + + @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP.get((name, "float"), None), name=name) + def float_type(x: ts[float]) -> ts[float_out_type]: + if csp.valid(x): + return op_lambda(x) + + @_node_internal_use(cppimpl=MATH_COMP_OPS_CPP.get((name, "int"), None), name=name) + def int_type(x: ts[int]) -> ts[int_out_type]: + if csp.valid(x): + return op_lambda(x) + + numpy_func = MATH_COMP_OPS_CPP.get((name, "np"), op_lambda) + + @_node_internal_use(name=name) + def numpy_type(x: ts["T"]) -> ts[np.ndarray]: + if csp.valid(x): + return numpy_func(x) + + @_node_internal_use(name=name) + def generic_type(x: ts["T"]) -> ts[generic_out_type]: + if csp.valid(x): + return op_lambda(x) + + def comp(x: ts["T"]): + if typing.get_origin(x.tstype.typ) in [Numpy1DArray, NumpyNDArray]: + return numpy_type(x) + elif x.tstype.typ is float: + return float_type(x) + elif x.tstype.typ is int: + return int_type(x) + return generic_type(x) + + comp.__name__ = name + return comp + + +# Math operators +add = define_binary_op("add", lambda x, y: x + y) +sub = define_binary_op("sub", lambda x, y: x - y) +multiply = define_binary_op("multiply", lambda x, y: x * y) +divide = define_binary_op("divide", lambda x, y: x / y) +pow = define_binary_op("pow", lambda x, y: x**y) +min = define_binary_op("min", lambda x, y: x if x < y else y) +max = define_binary_op("max", lambda x, y: x if x > y else y) +floordiv = define_binary_op("floordiv", lambda x, y: x // y) +mod = define_binary_op("mod", lambda x, y: x % y) +neg = define_unary_op("mod", lambda x: -x) + +# Other math ops +_python_abs = abs +abs = define_unary_op("abs", lambda x: _python_abs(x)) +ln = define_unary_op("ln", lambda x: math.log(x)) +log2 = define_unary_op("log2", lambda x: math.log2(x)) +log10 = define_unary_op("log10", lambda x: math.log10(x)) +exp = define_unary_op("exp", lambda x: math.exp(x)) +exp2 = define_unary_op("exp2", lambda x: math.exp2(x)) +sqrt = define_unary_op("sqrt", lambda x: math.sqrt(x)) +erf = define_unary_op("erf", lambda x: math.erf(x)) +sin = define_unary_op("sin", lambda x: math.sin(x)) +cos = define_unary_op("cos", lambda x: math.cos(x)) +tan = define_unary_op("tan", lambda x: math.tan(x)) +arcsin = define_unary_op("arcsin", lambda x: math.asin(x)) +arccos = define_unary_op("arccos", lambda x: math.acos(x)) +arctan = define_unary_op("arctan", lambda x: math.atan(x)) +sinh = define_unary_op("sinh", lambda x: math.sinh(x)) +cosh = define_unary_op("cosh", lambda x: math.cosh(x)) +tanh = define_unary_op("tanh", lambda x: math.tanh(x)) +arcsinh = define_unary_op("arcsinh", lambda x: math.asinh(x)) +arccosh = define_unary_op("arccosh", lambda x: math.acosh(x)) +arctanh = define_unary_op("arctanh", lambda x: math.atanh(x)) + +# Comparison operators +eq = define_binary_op("eq", lambda x, y: x == y) +ne = define_binary_op("ne", lambda x, y: x != y) +gt = define_binary_op("gt", lambda x, y: x > y) +lt = define_binary_op("lt", lambda x, y: x < y) +ge = define_binary_op("ge", lambda x, y: x >= y) +le = define_binary_op("le", lambda x, y: x <= y) diff --git a/csp/tests/test_baselib.py b/csp/tests/test_baselib.py index 8929d386..827669dd 100644 --- a/csp/tests/test_baselib.py +++ b/csp/tests/test_baselib.py @@ -6,7 +6,6 @@ import unittest from datetime import date, datetime, timedelta, timezone from enum import Enum, auto -from io import StringIO import csp from csp import ts @@ -417,281 +416,6 @@ def test_exprtk(self): results[0], list(zip([start_time + timedelta(seconds=i) for i in range(5)], [0, 77, 154, 231, 308])) ) - def test_math_binary_ops(self): - OPS = { - csp.add: lambda x, y: x + y, - csp.sub: lambda x, y: x - y, - csp.multiply: lambda x, y: x * y, - csp.divide: lambda x, y: x / y, - csp.pow: lambda x, y: x**y, - csp.min: lambda x, y: min(x, y), - csp.max: lambda x, y: max(x, y), - csp.floordiv: lambda x, y: x // y, - } - - @csp.graph - def graph(use_promotion: bool): - x = csp.count(csp.timer(timedelta(seconds=0.25))) - if use_promotion: - y = 10 - y_edge = csp.const(y) - else: - y = csp.default(csp.count(csp.timer(timedelta(seconds=1))), 1, delay=timedelta(seconds=0.25)) - y_edge = y - - csp.add_graph_output("x", csp.merge(x, csp.sample(y_edge, x))) - csp.add_graph_output("y", csp.merge(y_edge, csp.sample(x, y_edge))) - - for op in OPS.keys(): - if use_promotion: - if op in [csp.min, csp.max]: - continue # can't type promote, it's not being called ON an edge - p_op = OPS[op] - csp.add_graph_output(op.__name__, p_op(x, y)) - csp.add_graph_output(op.__name__ + "-rev", p_op(y, x)) - else: - csp.add_graph_output(op.__name__, op(x, y)) - csp.add_graph_output(op.__name__ + "-rev", op(y, x)) - - for use_promotion in [False, True]: - st = datetime(2020, 1, 1) - results = csp.run(graph, use_promotion, starttime=st, endtime=st + timedelta(seconds=3)) - xv = [v[1] for v in results["x"]] - yv = [v[1] for v in results["y"]] - - for op, comp in OPS.items(): - if op in [csp.min, csp.max] and use_promotion: - continue - self.assertEqual( - [v[1] for v in results[op.__name__]], [comp(x, y) for x, y in zip(xv, yv)], op.__name__ - ) - self.assertEqual( - [v[1] for v in results[op.__name__ + "-rev"]], [comp(y, x) for x, y in zip(xv, yv)], op.__name__ - ) - - def test_math_binary_ops_numpy(self): - OPS = { - csp.add: lambda x, y: x + y, - csp.sub: lambda x, y: x - y, - csp.multiply: lambda x, y: x * y, - csp.divide: lambda x, y: x / y, - csp.pow: lambda x, y: x**y, - csp.min: lambda x, y: np.minimum(x, y), - csp.max: lambda x, y: np.maximum(x, y), - csp.floordiv: lambda x, y: x // y, - } - - @csp.graph - def graph(use_promotion: bool): - x = csp.count(csp.timer(timedelta(seconds=0.25))) + csp.const(np.random.rand(10)) - if use_promotion: - y = 10 - y_edge = csp.const(y) - else: - y = csp.default( - csp.count(csp.timer(timedelta(seconds=1))), 1, delay=timedelta(seconds=0.25) - ) * csp.const(np.random.randint(1, 2, (10,))) - y_edge = y - - csp.add_graph_output("x", csp.merge(x, csp.sample(y_edge, x))) - csp.add_graph_output("y", csp.merge(y_edge, csp.sample(x, y_edge))) - - for op in OPS.keys(): - if use_promotion: - if op in [csp.min, csp.max]: - continue # can't type promote, it's not being called ON an edge - p_op = OPS[op] - csp.add_graph_output(op.__name__, p_op(x, y)) - csp.add_graph_output(op.__name__ + "-rev", p_op(y, x)) - else: - csp.add_graph_output(op.__name__, op(x, y)) - csp.add_graph_output(op.__name__ + "-rev", op(y, x)) - - for use_promotion in [False, True]: - st = datetime(2020, 1, 1) - results = csp.run(graph, use_promotion, starttime=st, endtime=st + timedelta(seconds=3)) - xv = [v[1] for v in results["x"]] - yv = [v[1] for v in results["y"]] - - for op, comp in OPS.items(): - if op in [csp.min, csp.max] and use_promotion: - continue - for i, (_, result) in enumerate(results[op.__name__]): - reference = comp(xv[i], yv[i]) - self.assertTrue((result == reference).all(), op.__name__) - for i, (_, result) in enumerate(results[op.__name__ + "-rev"]): - reference = comp(yv[i], xv[i]) - self.assertTrue((result == reference).all(), op.__name__) - - def test_math_unary_ops(self): - OPS = { - csp.neg: lambda x: -x, - csp.abs: lambda x: abs(x), - csp.ln: lambda x: math.log(x), - csp.log2: lambda x: math.log2(x), - csp.log10: lambda x: math.log10(x), - csp.exp: lambda x: math.exp(x), - csp.exp2: lambda x: math.exp2(x), - csp.sin: lambda x: math.sin(x), - csp.cos: lambda x: math.cos(x), - csp.tan: lambda x: math.tan(x), - csp.arctan: lambda x: math.atan(x), - csp.sinh: lambda x: math.sinh(x), - csp.cosh: lambda x: math.cosh(x), - csp.tanh: lambda x: math.tanh(x), - csp.arcsinh: lambda x: math.asinh(x), - csp.arccosh: lambda x: math.acosh(x), - csp.erf: lambda x: math.erf(x), - } - - @csp.graph - def graph(): - x = csp.count(csp.timer(timedelta(seconds=0.25))) - csp.add_graph_output("x", x) - - for op in OPS.keys(): - csp.add_graph_output(op.__name__, op(x)) - - st = datetime(2020, 1, 1) - results = csp.run(graph, starttime=st, endtime=st + timedelta(seconds=3)) - xv = [v[1] for v in results["x"]] - - for op, comp in OPS.items(): - self.assertEqual([v[1] for v in results[op.__name__]], [comp(x) for x in xv], op.__name__) - - def test_math_unary_ops_numpy(self): - OPS = { - csp.abs: lambda x: np.abs(x), - csp.ln: lambda x: np.log(x), - csp.log2: lambda x: np.log2(x), - csp.log10: lambda x: np.log10(x), - csp.exp: lambda x: np.exp(x), - csp.exp2: lambda x: np.exp2(x), - csp.sin: lambda x: np.sin(x), - csp.cos: lambda x: np.cos(x), - csp.tan: lambda x: np.tan(x), - csp.arctan: lambda x: np.arctan(x), - csp.sinh: lambda x: np.sinh(x), - csp.cosh: lambda x: np.cosh(x), - csp.tanh: lambda x: np.tanh(x), - csp.arcsinh: lambda x: np.arcsinh(x), - csp.arccosh: lambda x: np.arccosh(x), - # csp.erf: lambda x: math.erf(x), - } - - @csp.graph - def graph(): - x = csp.count(csp.timer(timedelta(seconds=0.25))) + csp.const(np.random.rand(10)) - csp.add_graph_output("x", x) - - for op in OPS.keys(): - csp.add_graph_output(op.__name__, op(x)) - - st = datetime(2020, 1, 1) - results = csp.run(graph, starttime=st, endtime=st + timedelta(seconds=3)) - xv = [v[1] for v in results["x"]] - - for op, comp in OPS.items(): - for i, (_, result) in enumerate(results[op.__name__]): - reference = comp(xv[i]) - # drop nans - result = result[~np.isnan(result)] - reference = reference[~np.isnan(reference)] - self.assertTrue((result == reference).all(), op.__name__) - - def test_math_unary_ops_other_domain(self): - OPS = { - csp.arcsin: lambda x: math.asin(x), - csp.arccos: lambda x: math.acos(x), - csp.arctanh: lambda x: math.atanh(x), - } - - @csp.graph - def graph(): - x = 1 / (csp.count(csp.timer(timedelta(seconds=0.25))) * math.pi) - csp.add_graph_output("x", x) - - for op in OPS.keys(): - csp.add_graph_output(op.__name__, op(x)) - - st = datetime(2020, 1, 1) - results = csp.run(graph, starttime=st, endtime=st + timedelta(seconds=3)) - xv = [v[1] for v in results["x"]] - - for op, comp in OPS.items(): - self.assertEqual([v[1] for v in results[op.__name__]], [comp(x) for x in xv], op.__name__) - - def test_comparisons(self): - OPS = { - csp.gt: lambda x, y: x > y, - csp.ge: lambda x, y: x >= y, - csp.lt: lambda x, y: x < y, - csp.le: lambda x, y: x <= y, - csp.eq: lambda x, y: x == y, - csp.ne: lambda x, y: x != y, - } - - @csp.graph - def graph(use_promotion: bool): - x = csp.count(csp.timer(timedelta(seconds=0.25))) - if use_promotion: - y = 10 - y_edge = csp.const(y) - else: - y = csp.default(csp.count(csp.timer(timedelta(seconds=1))), 1, delay=timedelta(seconds=0.25)) - y_edge = y - - csp.add_graph_output("x", csp.merge(x, csp.sample(y_edge, x))) - csp.add_graph_output("y", csp.merge(y_edge, csp.sample(x, y_edge))) - - for op in OPS.keys(): - if use_promotion: - p_op = OPS[op] - csp.add_graph_output(op.__name__, p_op(x, y)) - csp.add_graph_output(op.__name__ + "-rev", p_op(y, x)) - else: - csp.add_graph_output(op.__name__, op(x, y)) - csp.add_graph_output(op.__name__ + "-rev", op(y, x)) - - for use_promotion in [False, True]: - st = datetime(2020, 1, 1) - results = csp.run(graph, use_promotion, starttime=st, endtime=st + timedelta(seconds=10)) - xv = [v[1] for v in results["x"]] - yv = [v[1] for v in results["y"]] - - for op, comp in OPS.items(): - self.assertEqual( - [v[1] for v in results[op.__name__]], [comp(x, y) for x, y in zip(xv, yv)], op.__name__ - ) - self.assertEqual( - [v[1] for v in results[op.__name__ + "-rev"]], [comp(y, x) for x, y in zip(xv, yv)], op.__name__ - ) - - def test_boolean_ops(self): - def graph(): - x = csp.default(csp.curve(bool, [(timedelta(seconds=s), s % 2 == 0) for s in range(1, 20)]), False) - y = csp.default(csp.curve(bool, [(timedelta(seconds=s * 0.5), s % 2 == 0) for s in range(1, 40)]), False) - z = csp.default(csp.curve(bool, [(timedelta(seconds=s * 2), s % 2 == 0) for s in range(1, 10)]), False) - - csp.add_graph_output("rawx", x) - csp.add_graph_output("x", csp.merge(x, csp.merge(csp.sample(y, x), csp.sample(z, x)))) - csp.add_graph_output("y", csp.merge(y, csp.merge(csp.sample(x, y), csp.sample(z, y)))) - csp.add_graph_output("z", csp.merge(z, csp.merge(csp.sample(x, z), csp.sample(y, z)))) - - csp.add_graph_output("and_", csp.baselib.and_(x, y, z)) - csp.add_graph_output("or_", csp.baselib.or_(x, y, z)) - csp.add_graph_output("not_", csp.baselib.not_(x)) - - results = csp.run(graph, starttime=datetime(2020, 5, 18)) - x = [v[1] for v in results["x"]] - y = [v[1] for v in results["y"]] - z = [v[1] for v in results["z"]] - - self.assertEqual([v[1] for v in results["and_"]], [all([a, b, c]) for a, b, c in zip(x, y, z)]) - self.assertEqual([v[1] for v in results["or_"]], [any([a, b, c]) for a, b, c in zip(x, y, z)]) - self.assertEqual([v[1] for v in results["not_"]], [not v[1] for v in results["rawx"]]) - pass - def test_multiplex_decode(self): class Trade(csp.Struct): price: float @@ -1280,7 +1004,6 @@ def handle_collect(node, typ): assertEqual(node.__name__, str(typ), python, cpp) for node, apply in other_nodes.items(): - print(node.__name__) python = apply(node.python) cpp = apply(node) assertEqual(node.__name__, "", python, cpp) diff --git a/csp/tests/test_math.py b/csp/tests/test_math.py new file mode 100644 index 00000000..8cf2c483 --- /dev/null +++ b/csp/tests/test_math.py @@ -0,0 +1,287 @@ +import math +import numpy as np +import unittest +from datetime import datetime, timedelta + +import csp + + +class TestMath(unittest.TestCase): + def test_math_binary_ops(self): + OPS = { + csp.add: lambda x, y: x + y, + csp.sub: lambda x, y: x - y, + csp.multiply: lambda x, y: x * y, + csp.divide: lambda x, y: x / y, + csp.pow: lambda x, y: x**y, + csp.min: lambda x, y: min(x, y), + csp.max: lambda x, y: max(x, y), + csp.floordiv: lambda x, y: x // y, + } + + @csp.graph + def graph(use_promotion: bool): + x = csp.count(csp.timer(timedelta(seconds=0.25))) + if use_promotion: + y = 10 + y_edge = csp.const(y) + else: + y = csp.default(csp.count(csp.timer(timedelta(seconds=1))), 1, delay=timedelta(seconds=0.25)) + y_edge = y + + csp.add_graph_output("x", csp.merge(x, csp.sample(y_edge, x))) + csp.add_graph_output("y", csp.merge(y_edge, csp.sample(x, y_edge))) + + for op in OPS.keys(): + if use_promotion: + if op in [csp.min, csp.max]: + continue # can't type promote, it's not being called ON an edge + p_op = OPS[op] + csp.add_graph_output(op.__name__, p_op(x, y)) + csp.add_graph_output(op.__name__ + "-rev", p_op(y, x)) + else: + csp.add_graph_output(op.__name__, op(x, y)) + csp.add_graph_output(op.__name__ + "-rev", op(y, x)) + + for use_promotion in [False, True]: + st = datetime(2020, 1, 1) + results = csp.run(graph, use_promotion, starttime=st, endtime=st + timedelta(seconds=3)) + xv = [v[1] for v in results["x"]] + yv = [v[1] for v in results["y"]] + + for op, comp in OPS.items(): + if op in [csp.min, csp.max] and use_promotion: + continue + self.assertEqual( + [v[1] for v in results[op.__name__]], [comp(x, y) for x, y in zip(xv, yv)], op.__name__ + ) + self.assertEqual( + [v[1] for v in results[op.__name__ + "-rev"]], [comp(y, x) for x, y in zip(xv, yv)], op.__name__ + ) + + def test_math_binary_ops_numpy(self): + OPS = { + csp.add: lambda x, y: x + y, + csp.sub: lambda x, y: x - y, + csp.multiply: lambda x, y: x * y, + csp.divide: lambda x, y: x / y, + csp.pow: lambda x, y: x**y, + csp.min: lambda x, y: np.minimum(x, y), + csp.max: lambda x, y: np.maximum(x, y), + csp.floordiv: lambda x, y: x // y, + } + + @csp.graph + def graph(use_promotion: bool): + x = csp.count(csp.timer(timedelta(seconds=0.25))) + csp.const(np.random.rand(10)) + if use_promotion: + y = 10 + y_edge = csp.const(y) + else: + y = csp.default( + csp.count(csp.timer(timedelta(seconds=1))), 1, delay=timedelta(seconds=0.25) + ) * csp.const(np.random.randint(1, 2, (10,))) + y_edge = y + + csp.add_graph_output("x", csp.merge(x, csp.sample(y_edge, x))) + csp.add_graph_output("y", csp.merge(y_edge, csp.sample(x, y_edge))) + + for op in OPS.keys(): + if use_promotion: + if op in [csp.min, csp.max]: + continue # can't type promote, it's not being called ON an edge + p_op = OPS[op] + csp.add_graph_output(op.__name__, p_op(x, y)) + csp.add_graph_output(op.__name__ + "-rev", p_op(y, x)) + else: + csp.add_graph_output(op.__name__, op(x, y)) + csp.add_graph_output(op.__name__ + "-rev", op(y, x)) + + for use_promotion in [False, True]: + st = datetime(2020, 1, 1) + results = csp.run(graph, use_promotion, starttime=st, endtime=st + timedelta(seconds=3)) + xv = [v[1] for v in results["x"]] + yv = [v[1] for v in results["y"]] + + for op, comp in OPS.items(): + if op in [csp.min, csp.max] and use_promotion: + continue + for i, (_, result) in enumerate(results[op.__name__]): + reference = comp(xv[i], yv[i]) + self.assertTrue((result == reference).all(), op.__name__) + for i, (_, result) in enumerate(results[op.__name__ + "-rev"]): + reference = comp(yv[i], xv[i]) + self.assertTrue((result == reference).all(), op.__name__) + + def test_math_unary_ops(self): + OPS = { + csp.neg: lambda x: -x, + csp.abs: lambda x: abs(x), + csp.ln: lambda x: math.log(x), + csp.log2: lambda x: math.log2(x), + csp.log10: lambda x: math.log10(x), + csp.exp: lambda x: math.exp(x), + csp.exp2: lambda x: math.exp2(x), + csp.sin: lambda x: math.sin(x), + csp.cos: lambda x: math.cos(x), + csp.tan: lambda x: math.tan(x), + csp.arctan: lambda x: math.atan(x), + csp.sinh: lambda x: math.sinh(x), + csp.cosh: lambda x: math.cosh(x), + csp.tanh: lambda x: math.tanh(x), + csp.arcsinh: lambda x: math.asinh(x), + csp.arccosh: lambda x: math.acosh(x), + csp.erf: lambda x: math.erf(x), + } + + @csp.graph + def graph(): + x = csp.count(csp.timer(timedelta(seconds=0.25))) + csp.add_graph_output("x", x) + + for op in OPS.keys(): + csp.add_graph_output(op.__name__, op(x)) + + st = datetime(2020, 1, 1) + results = csp.run(graph, starttime=st, endtime=st + timedelta(seconds=3)) + xv = [v[1] for v in results["x"]] + + for op, comp in OPS.items(): + self.assertEqual([v[1] for v in results[op.__name__]], [comp(x) for x in xv], op.__name__) + + def test_math_unary_ops_numpy(self): + OPS = { + csp.abs: lambda x: np.abs(x), + csp.ln: lambda x: np.log(x), + csp.log2: lambda x: np.log2(x), + csp.log10: lambda x: np.log10(x), + csp.exp: lambda x: np.exp(x), + csp.exp2: lambda x: np.exp2(x), + csp.sin: lambda x: np.sin(x), + csp.cos: lambda x: np.cos(x), + csp.tan: lambda x: np.tan(x), + csp.arctan: lambda x: np.arctan(x), + csp.sinh: lambda x: np.sinh(x), + csp.cosh: lambda x: np.cosh(x), + csp.tanh: lambda x: np.tanh(x), + csp.arcsinh: lambda x: np.arcsinh(x), + csp.arccosh: lambda x: np.arccosh(x), + # csp.erf: lambda x: math.erf(x), + } + + @csp.graph + def graph(): + x = csp.count(csp.timer(timedelta(seconds=0.25))) + csp.const(np.random.rand(10)) + csp.add_graph_output("x", x) + + for op in OPS.keys(): + csp.add_graph_output(op.__name__, op(x)) + + st = datetime(2020, 1, 1) + results = csp.run(graph, starttime=st, endtime=st + timedelta(seconds=3)) + xv = [v[1] for v in results["x"]] + + for op, comp in OPS.items(): + for i, (_, result) in enumerate(results[op.__name__]): + reference = comp(xv[i]) + # drop nans + result = result[~np.isnan(result)] + reference = reference[~np.isnan(reference)] + self.assertTrue((result == reference).all(), op.__name__) + + def test_math_unary_ops_other_domain(self): + OPS = { + csp.arcsin: lambda x: math.asin(x), + csp.arccos: lambda x: math.acos(x), + csp.arctanh: lambda x: math.atanh(x), + } + + @csp.graph + def graph(): + x = 1 / (csp.count(csp.timer(timedelta(seconds=0.25))) * math.pi) + csp.add_graph_output("x", x) + + for op in OPS.keys(): + csp.add_graph_output(op.__name__, op(x)) + + st = datetime(2020, 1, 1) + results = csp.run(graph, starttime=st, endtime=st + timedelta(seconds=3)) + xv = [v[1] for v in results["x"]] + + for op, comp in OPS.items(): + self.assertEqual([v[1] for v in results[op.__name__]], [comp(x) for x in xv], op.__name__) + + def test_comparisons(self): + OPS = { + csp.gt: lambda x, y: x > y, + csp.ge: lambda x, y: x >= y, + csp.lt: lambda x, y: x < y, + csp.le: lambda x, y: x <= y, + csp.eq: lambda x, y: x == y, + csp.ne: lambda x, y: x != y, + } + + @csp.graph + def graph(use_promotion: bool): + x = csp.count(csp.timer(timedelta(seconds=0.25))) + if use_promotion: + y = 10 + y_edge = csp.const(y) + else: + y = csp.default(csp.count(csp.timer(timedelta(seconds=1))), 1, delay=timedelta(seconds=0.25)) + y_edge = y + + csp.add_graph_output("x", csp.merge(x, csp.sample(y_edge, x))) + csp.add_graph_output("y", csp.merge(y_edge, csp.sample(x, y_edge))) + + for op in OPS.keys(): + if use_promotion: + p_op = OPS[op] + csp.add_graph_output(op.__name__, p_op(x, y)) + csp.add_graph_output(op.__name__ + "-rev", p_op(y, x)) + else: + csp.add_graph_output(op.__name__, op(x, y)) + csp.add_graph_output(op.__name__ + "-rev", op(y, x)) + + for use_promotion in [False, True]: + st = datetime(2020, 1, 1) + results = csp.run(graph, use_promotion, starttime=st, endtime=st + timedelta(seconds=10)) + xv = [v[1] for v in results["x"]] + yv = [v[1] for v in results["y"]] + + for op, comp in OPS.items(): + self.assertEqual( + [v[1] for v in results[op.__name__]], [comp(x, y) for x, y in zip(xv, yv)], op.__name__ + ) + self.assertEqual( + [v[1] for v in results[op.__name__ + "-rev"]], [comp(y, x) for x, y in zip(xv, yv)], op.__name__ + ) + + def test_boolean_ops(self): + def graph(): + x = csp.default(csp.curve(bool, [(timedelta(seconds=s), s % 2 == 0) for s in range(1, 20)]), False) + y = csp.default(csp.curve(bool, [(timedelta(seconds=s * 0.5), s % 2 == 0) for s in range(1, 40)]), False) + z = csp.default(csp.curve(bool, [(timedelta(seconds=s * 2), s % 2 == 0) for s in range(1, 10)]), False) + + csp.add_graph_output("rawx", x) + csp.add_graph_output("x", csp.merge(x, csp.merge(csp.sample(y, x), csp.sample(z, x)))) + csp.add_graph_output("y", csp.merge(y, csp.merge(csp.sample(x, y), csp.sample(z, y)))) + csp.add_graph_output("z", csp.merge(z, csp.merge(csp.sample(x, z), csp.sample(y, z)))) + + csp.add_graph_output("and_", csp.and_(x, y, z)) + csp.add_graph_output("or_", csp.or_(x, y, z)) + csp.add_graph_output("not_", csp.not_(x)) + + results = csp.run(graph, starttime=datetime(2020, 5, 18)) + x = [v[1] for v in results["x"]] + y = [v[1] for v in results["y"]] + z = [v[1] for v in results["z"]] + + self.assertEqual([v[1] for v in results["and_"]], [all([a, b, c]) for a, b, c in zip(x, y, z)]) + self.assertEqual([v[1] for v in results["or_"]], [any([a, b, c]) for a, b, c in zip(x, y, z)]) + self.assertEqual([v[1] for v in results["not_"]], [not v[1] for v in results["rawx"]]) + pass + + +if __name__ == "__main__": + unittest.main() From 4bc1cf441603b98f8a4a1fc7543aa380c9fa774d Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Tue, 20 Feb 2024 14:03:17 -0500 Subject: [PATCH 5/8] Incorporate feedback, add missing __pos__ method, add __pos__, __neg__, and __abs__ to pandas tsarray Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- csp/impl/pandas_ext_type.py | 1 + csp/impl/wiring/edge.py | 25 ++++++++++++++++--------- csp/math.py | 8 +++++--- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/csp/impl/pandas_ext_type.py b/csp/impl/pandas_ext_type.py index a7a8b881..427d0dde 100644 --- a/csp/impl/pandas_ext_type.py +++ b/csp/impl/pandas_ext_type.py @@ -603,3 +603,4 @@ def is_csp_type(arr_or_dtype) -> bool: TsArray._add_arithmetic_ops() TsArray._add_comparison_ops() TsArray._add_logical_ops() +TsArray._add_math_ops() diff --git a/csp/impl/wiring/edge.py b/csp/impl/wiring/edge.py index fbffba0c..bcbfa752 100644 --- a/csp/impl/wiring/edge.py +++ b/csp/impl/wiring/edge.py @@ -8,6 +8,8 @@ "divide": lambda x, y: x.__truediv__(y) if isinstance(x, Edge) else y.__truediv__(x), "floor_divide": lambda x, y: x.__floordiv__(y) if isinstance(x, Edge) else y.__floordiv__(x), "power": lambda x, y: x.pow(y), + "pos": lambda x: x.pos(), + "neg": lambda x: x.neg(), "abs": lambda x: x.abs(), "log": lambda x: x.ln(), "log2": lambda x: x.log2(), @@ -153,20 +155,16 @@ def __invert__(self): return csp.bitwise_not(self) raise TypeError(f"Cannot call invert with a ts[{self.tstype.typ.__name__}], not an integer type") + def __pos__(self): + import csp + + return csp.pos(self) + def __neg__(self): import csp return csp.neg(self) - # def __ceil__(self): - # def __floor__(self): - # def __round__(self): - # def __trunc__(self): - # def __lshift__(self): - # def __rshift__(self): - # def __pos__(self): - # def __xor__(self): - def __abs__(self): import csp @@ -177,6 +175,15 @@ def abs(self): return csp.abs(self) + # def __ceil__(self): + # def __floor__(self): + # def __round__(self): + # def __trunc__(self): + # def __lshift__(self): + # def __rshift__(self): + # def __pos__(self): + # def __xor__(self): + def ln(self): import csp diff --git a/csp/math.py b/csp/math.py index fc7513ae..72941922 100644 --- a/csp/math.py +++ b/csp/math.py @@ -43,6 +43,7 @@ "neg", "not_", "or_", + "pos", "pow", "sin", "sinh", @@ -267,7 +268,7 @@ def generic_type(x: ts["T"], y: ts["T"]) -> ts[generic_out_type]: return op_lambda(x, y) def comp(x: ts["T"], y: ts["U"]): - if typing.get_origin(x.tstype.typ) in [Numpy1DArray, NumpyNDArray] or typing.get_origin(y.tstype.typ) in [ + if typing.get_origin(x.tstype.typ) is [Numpy1DArray, NumpyNDArray] or typing.get_origin(y.tstype.typ) in [ Numpy1DArray, NumpyNDArray, ]: @@ -314,7 +315,7 @@ def int_type(x: ts[int]) -> ts[int_out_type]: numpy_func = MATH_COMP_OPS_CPP.get((name, "np"), op_lambda) @_node_internal_use(name=name) - def numpy_type(x: ts["T"]) -> ts[np.ndarray]: + def numpy_type(x: ts[np.ndarray]) -> ts[np.ndarray]: if csp.valid(x): return numpy_func(x) @@ -346,7 +347,8 @@ def comp(x: ts["T"]): max = define_binary_op("max", lambda x, y: x if x > y else y) floordiv = define_binary_op("floordiv", lambda x, y: x // y) mod = define_binary_op("mod", lambda x, y: x % y) -neg = define_unary_op("mod", lambda x: -x) +pos = define_unary_op("pos", lambda x: +x) +neg = define_unary_op("neg", lambda x: -x) # Other math ops _python_abs = abs From 1a7de83c7481f4e9fb5475e09e9dff587f95e35f Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:05:27 -0500 Subject: [PATCH 6/8] Separate math into own C++ files Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- cpp/csp/cppnodes/CMakeLists.txt | 5 +- cpp/csp/cppnodes/baselibimpl.cpp | 167 ---------------------------- cpp/csp/cppnodes/mathimpl.cpp | 175 ++++++++++++++++++++++++++++++ cpp/csp/python/CMakeLists.txt | 17 +-- cpp/csp/python/cspbaselibimpl.cpp | 73 ------------- cpp/csp/python/cspmathimpl.cpp | 98 +++++++++++++++++ csp/baselib.py | 4 + csp/math.py | 150 +++++++++++++------------ csp/tests/test_math.py | 1 + 9 files changed, 370 insertions(+), 320 deletions(-) create mode 100644 cpp/csp/cppnodes/mathimpl.cpp create mode 100644 cpp/csp/python/cspmathimpl.cpp diff --git a/cpp/csp/cppnodes/CMakeLists.txt b/cpp/csp/cppnodes/CMakeLists.txt index 4c1e0116..32fb9c7e 100644 --- a/cpp/csp/cppnodes/CMakeLists.txt +++ b/cpp/csp/cppnodes/CMakeLists.txt @@ -4,11 +4,14 @@ target_link_libraries(baselibimpl csp_core csp_engine) add_library(basketlibimpl STATIC basketlibimpl.cpp) target_link_libraries(basketlibimpl baselibimpl csp_core csp_engine) +add_library(mathimpl STATIC mathimpl.cpp) +target_link_libraries(mathimpl csp_core csp_engine) + add_library(statsimpl STATIC statsimpl.cpp) set_target_properties(statsimpl PROPERTIES PUBLIC_HEADER statsimpl.h) target_link_libraries(statsimpl baselibimpl csp_core csp_engine) -install(TARGETS baselibimpl basketlibimpl statsimpl +install(TARGETS baselibimpl basketlibimpl mathimpl statsimpl PUBLIC_HEADER DESTINATION include/csp/cppnodes RUNTIME DESTINATION bin/ LIBRARY DESTINATION lib/ diff --git a/cpp/csp/cppnodes/baselibimpl.cpp b/cpp/csp/cppnodes/baselibimpl.cpp index aae979af..0be73c11 100644 --- a/cpp/csp/cppnodes/baselibimpl.cpp +++ b/cpp/csp/cppnodes/baselibimpl.cpp @@ -574,173 +574,6 @@ DECLARE_CPPNODE( times_ns ) EXPORT_CPPNODE( times_ns ); -/* -Math operations -*/ - -// Unary operation - -template -DECLARE_CPPNODE( _unary_op ) -{ - TS_INPUT( ArgT, x ); - TS_OUTPUT( OutT ); - - //Expanded out INIT_CPPNODE without create call... - CSP csp; - const char * name() const override { return "_unary_op"; } - -public: - _STATIC_CREATE_METHOD( SINGLE_ARG( _unary_op ) ); - _unary_op( csp::Engine * engine, const csp::CppNode::NodeDef & nodedef ) : csp::CppNode( engine, nodedef ) - {} - - INVOKE() - { - RETURN( Func( x ) ); - } -}; - -template inline T _abs( T x ){ return std::abs( x ); } -template inline double _ln( T x ){ return std::log( x ); } -template inline double _log2( T x ){ return std::log2( x ); } -template inline double _log10( T x ){ return std::log10( x ); } -template inline double _exp( T x ){ return std::exp( x ); } -template inline double _exp2( T x ){ return std::exp2( x ); } -template inline double _sqrt( T x ){ return std::sqrt( x ); } -template inline double _erf( T x ){ return std::erf( x ); } -template inline double _sin( T x ){ return std::sin( x ); } -template inline double _cos( T x ){ return std::cos( x ); } -template inline double _tan( T x ){ return std::tan( x ); } -template inline double _asin( T x ){ return std::asin( x ); } -template inline double _acos( T x ){ return std::acos( x ); } -template inline double _atan( T x ){ return std::atan( x ); } -template inline double _sinh( T x ){ return std::sinh( x ); } -template inline double _cosh( T x ){ return std::cosh( x ); } -template inline double _tanh( T x ){ return std::tanh( x ); } -template inline double _asinh( T x ){ return std::asinh( x ); } -template inline double _acosh( T x ){ return std::acosh( x ); } -template inline double _atanh( T x ){ return std::atanh( x ); } - -inline bool _not_( bool x ){ return !x; } -inline int64_t _bitwise_not(int64_t x) { return ~x; } - -#define EXPORT_UNARY_OP( Name, ArgType, OutType, Func ) EXPORT_TEMPLATE_CPPNODE( Name, SINGLE_ARG( _unary_op ) ) - EXPORT_UNARY_OP( abs_f, double, double, abs ); - EXPORT_UNARY_OP( abs_i, int64_t, int64_t, abs ); - EXPORT_UNARY_OP( ln_f, double, double, ln ); - EXPORT_UNARY_OP( ln_i, int64_t, double, ln ); - EXPORT_UNARY_OP( log2_f, double, double, log2 ); - EXPORT_UNARY_OP( log2_i, int64_t, double, log2 ); - EXPORT_UNARY_OP( log10_f, double, double, log10 ); - EXPORT_UNARY_OP( log10_i, int64_t, double, log10 ); - EXPORT_UNARY_OP( exp_f, double, double, exp ); - EXPORT_UNARY_OP( exp_i, int64_t, double, exp ); - EXPORT_UNARY_OP( exp2_f, double, double, exp2 ); - EXPORT_UNARY_OP( exp2_i, int64_t, double, exp2 ); - EXPORT_UNARY_OP( sqrt_f, double, double, sqrt ); - EXPORT_UNARY_OP( sqrt_i, int64_t, double, sqrt ); - EXPORT_UNARY_OP( erf_f, double, double, erf ); - EXPORT_UNARY_OP( erf_i, int64_t, double, erf ); - EXPORT_UNARY_OP( sin_f, double, double, sin ); - EXPORT_UNARY_OP( sin_i, int64_t, double, sin ); - EXPORT_UNARY_OP( cos_f, double, double, cos ); - EXPORT_UNARY_OP( cos_i, int64_t, double, cos ); - EXPORT_UNARY_OP( tan_f, double, double, tan ); - EXPORT_UNARY_OP( tan_i, int64_t, double, tan ); - EXPORT_UNARY_OP( asin_f, double, double, asin ); - EXPORT_UNARY_OP( asin_i, int64_t, double, asin ); - EXPORT_UNARY_OP( acos_f, double, double, acos ); - EXPORT_UNARY_OP( acos_i, int64_t, double, acos ); - EXPORT_UNARY_OP( atan_f, double, double, atan ); - EXPORT_UNARY_OP( atan_i, int64_t, double, atan ); - EXPORT_UNARY_OP( sinh_f, double, double, sinh ); - EXPORT_UNARY_OP( sinh_i, int64_t, double, sinh ); - EXPORT_UNARY_OP( cosh_f, double, double, cosh ); - EXPORT_UNARY_OP( cosh_i, int64_t, double, cosh ); - EXPORT_UNARY_OP( tanh_f, double, double, tanh ); - EXPORT_UNARY_OP( tanh_i, int64_t, double, tanh ); - EXPORT_UNARY_OP( asinh_f, double, double, asinh ); - EXPORT_UNARY_OP( asinh_i, int64_t, double, asinh ); - EXPORT_UNARY_OP( acosh_f, double, double, acosh ); - EXPORT_UNARY_OP( acosh_i, int64_t, double, acosh ); - EXPORT_UNARY_OP( atanh_f, double, double, atanh ); - EXPORT_UNARY_OP( atanh_i, int64_t, double, atanh ); - EXPORT_UNARY_OP( not_, bool, bool, not_ ); - EXPORT_UNARY_OP( bitwise_not, int64_t, int64_t, bitwise_not ); -#undef EXPORT_UNARY_OP - -// Binary operation -template -DECLARE_CPPNODE( _binary_op ) -{ - TS_INPUT( ArgT, x ); - TS_INPUT( ArgT, y ); - TS_OUTPUT( OutT ); - - //Expanded out INIT_CPPNODE without create call... - CSP csp; - const char * name() const override { return "_binary_op"; } - -public: - _STATIC_CREATE_METHOD( SINGLE_ARG( _binary_op ) ); - _binary_op( csp::Engine * engine, const csp::CppNode::NodeDef & nodedef ) : csp::CppNode( engine, nodedef ) - {} - - INVOKE() - { - if( csp.valid( x, y ) ) - RETURN( Func( x, y ) ); - } -}; - -// Math ops -template inline T _add( T x, T y ){ return x + y; } -template inline T _sub( T x, T y ){ return x - y; } -template inline T _mul( T x, T y ){ return x * y; } -template inline T _max( T x, T y ){ return std::max( x, y ); } -template inline T _min( T x, T y ){ return std::min( x, y ); } -template inline double _div( T x, T y ){ return x / ( double )y; } -inline int64_t _pow_i( int64_t x, int64_t y ){ return ( int64_t )pow( ( double )x, ( double )y ); } -inline double _pow_f( double x, double y ){ return pow( x, y ); } - -// Comparison ops -template inline bool _eq( T x, T y ){ return x == y; } -template inline bool _ne( T x, T y ){ return x != y; } -template inline bool _gt( T x, T y ){ return x > y; } -template inline bool _ge( T x, T y ){ return x >= y; } -template inline bool _lt( T x, T y ){ return x < y; } -template inline bool _le( T x, T y ){ return x <= y; } - -#define EXPORT_BINARY_OP( Name, ArgType, OutType, Func ) EXPORT_TEMPLATE_CPPNODE( Name, SINGLE_ARG( _binary_op ) ) - EXPORT_BINARY_OP( add_i, int64_t, int64_t, _add ); - EXPORT_BINARY_OP( sub_i, int64_t, int64_t, _sub ); - EXPORT_BINARY_OP( mul_i, int64_t, int64_t, _mul ); - EXPORT_BINARY_OP( div_i, int64_t, double, _div ); - EXPORT_BINARY_OP( pow_i, int64_t, int64_t, _pow_i ); - EXPORT_BINARY_OP( max_i, int64_t, int64_t, _max ); - EXPORT_BINARY_OP( min_i, int64_t, int64_t, _min ); - EXPORT_BINARY_OP( add_f, double, double, _add ); - EXPORT_BINARY_OP( sub_f, double, double, _sub ); - EXPORT_BINARY_OP( mul_f, double, double, _mul ); - EXPORT_BINARY_OP( div_f, double, double, _div ); - EXPORT_BINARY_OP( pow_f, double, double, _pow_f ); - EXPORT_BINARY_OP( max_f, double, double, _max ); - EXPORT_BINARY_OP( min_f, double, double, _min ); - EXPORT_BINARY_OP( eq_i, int64_t, bool, _eq ); - EXPORT_BINARY_OP( ne_i, int64_t, bool, _ne ); - EXPORT_BINARY_OP( gt_i, int64_t, bool, _gt ); - EXPORT_BINARY_OP( ge_i, int64_t, bool, _ge ); - EXPORT_BINARY_OP( lt_i, int64_t, bool, _lt ); - EXPORT_BINARY_OP( le_i, int64_t, bool, _le ); - EXPORT_BINARY_OP( eq_f, double, bool, _eq ); - EXPORT_BINARY_OP( ne_f, double, bool, _ne ); - EXPORT_BINARY_OP( gt_f, double, bool, _gt ); - EXPORT_BINARY_OP( ge_f, double, bool, _ge ); - EXPORT_BINARY_OP( lt_f, double, bool, _lt ); - EXPORT_BINARY_OP( le_f, double, bool, _le ); -#undef EXPORT_BINARY_OP - /* @csp.node def struct_field(x: ts['T'], field: str, fieldType: 'Y'): diff --git a/cpp/csp/cppnodes/mathimpl.cpp b/cpp/csp/cppnodes/mathimpl.cpp new file mode 100644 index 00000000..eeb865f8 --- /dev/null +++ b/cpp/csp/cppnodes/mathimpl.cpp @@ -0,0 +1,175 @@ +#include +#include + + +namespace csp::cppnodes +{ + +/* +Math operations +*/ + +// Unary operation + +template +DECLARE_CPPNODE( _unary_op ) +{ + TS_INPUT( ArgT, x ); + TS_OUTPUT( OutT ); + + //Expanded out INIT_CPPNODE without create call... + CSP csp; + const char * name() const override { return "_unary_op"; } + +public: + _STATIC_CREATE_METHOD( SINGLE_ARG( _unary_op ) ); + _unary_op( csp::Engine * engine, const csp::CppNode::NodeDef & nodedef ) : csp::CppNode( engine, nodedef ) + {} + + INVOKE() + { + RETURN( Func( x ) ); + } +}; + +template inline T _abs( T x ){ return std::abs( x ); } +template inline double _ln( T x ){ return std::log( x ); } +template inline double _log2( T x ){ return std::log2( x ); } +template inline double _log10( T x ){ return std::log10( x ); } +template inline double _exp( T x ){ return std::exp( x ); } +template inline double _exp2( T x ){ return std::exp2( x ); } +template inline double _sqrt( T x ){ return std::sqrt( x ); } +template inline double _erf( T x ){ return std::erf( x ); } +template inline double _sin( T x ){ return std::sin( x ); } +template inline double _cos( T x ){ return std::cos( x ); } +template inline double _tan( T x ){ return std::tan( x ); } +template inline double _asin( T x ){ return std::asin( x ); } +template inline double _acos( T x ){ return std::acos( x ); } +template inline double _atan( T x ){ return std::atan( x ); } +template inline double _sinh( T x ){ return std::sinh( x ); } +template inline double _cosh( T x ){ return std::cosh( x ); } +template inline double _tanh( T x ){ return std::tanh( x ); } +template inline double _asinh( T x ){ return std::asinh( x ); } +template inline double _acosh( T x ){ return std::acosh( x ); } +template inline double _atanh( T x ){ return std::atanh( x ); } + +inline bool _not_( bool x ){ return !x; } +inline int64_t _bitwise_not(int64_t x) { return ~x; } + +#define EXPORT_UNARY_OP( Name, ArgType, OutType, Func ) EXPORT_TEMPLATE_CPPNODE( Name, SINGLE_ARG( _unary_op ) ) + EXPORT_UNARY_OP( abs_f, double, double, abs ); + EXPORT_UNARY_OP( abs_i, int64_t, int64_t, abs ); + EXPORT_UNARY_OP( ln_f, double, double, ln ); + EXPORT_UNARY_OP( ln_i, int64_t, double, ln ); + EXPORT_UNARY_OP( log2_f, double, double, log2 ); + EXPORT_UNARY_OP( log2_i, int64_t, double, log2 ); + EXPORT_UNARY_OP( log10_f, double, double, log10 ); + EXPORT_UNARY_OP( log10_i, int64_t, double, log10 ); + EXPORT_UNARY_OP( exp_f, double, double, exp ); + EXPORT_UNARY_OP( exp_i, int64_t, double, exp ); + EXPORT_UNARY_OP( exp2_f, double, double, exp2 ); + EXPORT_UNARY_OP( exp2_i, int64_t, double, exp2 ); + EXPORT_UNARY_OP( sqrt_f, double, double, sqrt ); + EXPORT_UNARY_OP( sqrt_i, int64_t, double, sqrt ); + EXPORT_UNARY_OP( erf_f, double, double, erf ); + EXPORT_UNARY_OP( erf_i, int64_t, double, erf ); + EXPORT_UNARY_OP( sin_f, double, double, sin ); + EXPORT_UNARY_OP( sin_i, int64_t, double, sin ); + EXPORT_UNARY_OP( cos_f, double, double, cos ); + EXPORT_UNARY_OP( cos_i, int64_t, double, cos ); + EXPORT_UNARY_OP( tan_f, double, double, tan ); + EXPORT_UNARY_OP( tan_i, int64_t, double, tan ); + EXPORT_UNARY_OP( asin_f, double, double, asin ); + EXPORT_UNARY_OP( asin_i, int64_t, double, asin ); + EXPORT_UNARY_OP( acos_f, double, double, acos ); + EXPORT_UNARY_OP( acos_i, int64_t, double, acos ); + EXPORT_UNARY_OP( atan_f, double, double, atan ); + EXPORT_UNARY_OP( atan_i, int64_t, double, atan ); + EXPORT_UNARY_OP( sinh_f, double, double, sinh ); + EXPORT_UNARY_OP( sinh_i, int64_t, double, sinh ); + EXPORT_UNARY_OP( cosh_f, double, double, cosh ); + EXPORT_UNARY_OP( cosh_i, int64_t, double, cosh ); + EXPORT_UNARY_OP( tanh_f, double, double, tanh ); + EXPORT_UNARY_OP( tanh_i, int64_t, double, tanh ); + EXPORT_UNARY_OP( asinh_f, double, double, asinh ); + EXPORT_UNARY_OP( asinh_i, int64_t, double, asinh ); + EXPORT_UNARY_OP( acosh_f, double, double, acosh ); + EXPORT_UNARY_OP( acosh_i, int64_t, double, acosh ); + EXPORT_UNARY_OP( atanh_f, double, double, atanh ); + EXPORT_UNARY_OP( atanh_i, int64_t, double, atanh ); + EXPORT_UNARY_OP( not_, bool, bool, not_ ); + EXPORT_UNARY_OP( bitwise_not, int64_t, int64_t, bitwise_not ); +#undef EXPORT_UNARY_OP + +// Binary operation +template +DECLARE_CPPNODE( _binary_op ) +{ + TS_INPUT( ArgT, x ); + TS_INPUT( ArgT, y ); + TS_OUTPUT( OutT ); + + //Expanded out INIT_CPPNODE without create call... + CSP csp; + const char * name() const override { return "_binary_op"; } + +public: + _STATIC_CREATE_METHOD( SINGLE_ARG( _binary_op ) ); + _binary_op( csp::Engine * engine, const csp::CppNode::NodeDef & nodedef ) : csp::CppNode( engine, nodedef ) + {} + + INVOKE() + { + if( csp.valid( x, y ) ) + RETURN( Func( x, y ) ); + } +}; + +// Math ops +template inline T _add( T x, T y ){ return x + y; } +template inline T _sub( T x, T y ){ return x - y; } +template inline T _mul( T x, T y ){ return x * y; } +template inline T _max( T x, T y ){ return std::max( x, y ); } +template inline T _min( T x, T y ){ return std::min( x, y ); } +template inline double _div( T x, T y ){ return x / ( double )y; } +inline int64_t _pow_i( int64_t x, int64_t y ){ return ( int64_t )pow( ( double )x, ( double )y ); } +inline double _pow_f( double x, double y ){ return pow( x, y ); } + +// Comparison ops +template inline bool _eq( T x, T y ){ return x == y; } +template inline bool _ne( T x, T y ){ return x != y; } +template inline bool _gt( T x, T y ){ return x > y; } +template inline bool _ge( T x, T y ){ return x >= y; } +template inline bool _lt( T x, T y ){ return x < y; } +template inline bool _le( T x, T y ){ return x <= y; } + +#define EXPORT_BINARY_OP( Name, ArgType, OutType, Func ) EXPORT_TEMPLATE_CPPNODE( Name, SINGLE_ARG( _binary_op ) ) + EXPORT_BINARY_OP( add_i, int64_t, int64_t, _add ); + EXPORT_BINARY_OP( sub_i, int64_t, int64_t, _sub ); + EXPORT_BINARY_OP( mul_i, int64_t, int64_t, _mul ); + EXPORT_BINARY_OP( div_i, int64_t, double, _div ); + EXPORT_BINARY_OP( pow_i, int64_t, int64_t, _pow_i ); + EXPORT_BINARY_OP( max_i, int64_t, int64_t, _max ); + EXPORT_BINARY_OP( min_i, int64_t, int64_t, _min ); + EXPORT_BINARY_OP( add_f, double, double, _add ); + EXPORT_BINARY_OP( sub_f, double, double, _sub ); + EXPORT_BINARY_OP( mul_f, double, double, _mul ); + EXPORT_BINARY_OP( div_f, double, double, _div ); + EXPORT_BINARY_OP( pow_f, double, double, _pow_f ); + EXPORT_BINARY_OP( max_f, double, double, _max ); + EXPORT_BINARY_OP( min_f, double, double, _min ); + EXPORT_BINARY_OP( eq_i, int64_t, bool, _eq ); + EXPORT_BINARY_OP( ne_i, int64_t, bool, _ne ); + EXPORT_BINARY_OP( gt_i, int64_t, bool, _gt ); + EXPORT_BINARY_OP( ge_i, int64_t, bool, _ge ); + EXPORT_BINARY_OP( lt_i, int64_t, bool, _lt ); + EXPORT_BINARY_OP( le_i, int64_t, bool, _le ); + EXPORT_BINARY_OP( eq_f, double, bool, _eq ); + EXPORT_BINARY_OP( ne_f, double, bool, _ne ); + EXPORT_BINARY_OP( gt_f, double, bool, _gt ); + EXPORT_BINARY_OP( ge_f, double, bool, _ge ); + EXPORT_BINARY_OP( lt_f, double, bool, _lt ); + EXPORT_BINARY_OP( le_f, double, bool, _le ); +#undef EXPORT_BINARY_OP + +} diff --git a/cpp/csp/python/CMakeLists.txt b/cpp/csp/python/CMakeLists.txt index 711057a4..d39e9e13 100644 --- a/cpp/csp/python/CMakeLists.txt +++ b/cpp/csp/python/CMakeLists.txt @@ -71,10 +71,9 @@ add_library(cspimpl SHARED set_target_properties(cspimpl PROPERTIES PUBLIC_HEADER "${CSPIMPL_PUBLIC_HEADERS}") - target_link_libraries(cspimpl csptypesimpl csp_core csp_engine) target_compile_definitions(cspimpl PUBLIC NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION) - + ## Baselib c++ module add_library(cspbaselibimpl SHARED cspbaselibimpl.cpp) target_link_libraries(cspbaselibimpl cspimpl baselibimpl) @@ -82,6 +81,14 @@ target_link_libraries(cspbaselibimpl cspimpl baselibimpl) # Include exprtk include directory for exprtk node target_include_directories(cspbaselibimpl PRIVATE ${EXPRTK_INCLUDE_DIRS}) +## Basketlib c++ module +add_library(cspbasketlibimpl SHARED cspbasketlibimpl.cpp) +target_link_libraries(cspbasketlibimpl cspimpl basketlibimpl) + +## Math c++ module +add_library(cspmathimpl SHARED cspmathimpl.cpp) +target_link_libraries(cspmathimpl cspimpl mathimpl) + ## Stats c++ module add_library(cspstatsimpl SHARED cspstatsimpl.cpp) target_link_libraries(cspstatsimpl cspimpl statsimpl) @@ -93,11 +100,7 @@ target_link_libraries(cspnpstatsimpl cspimpl npstatsimpl) target_include_directories(npstatsimpl PRIVATE ${NUMPY_INCLUDE_DIRS}) target_include_directories(cspnpstatsimpl PRIVATE ${NUMPY_INCLUDE_DIRS}) -## Basketlib c++ module -add_library(cspbasketlibimpl SHARED cspbasketlibimpl.cpp) -target_link_libraries(cspbasketlibimpl cspimpl basketlibimpl) - -install(TARGETS cspimpl cspstatsimpl cspnpstatsimpl cspbaselibimpl csptypesimpl cspbasketlibimpl +install(TARGETS csptypesimpl cspimpl cspbaselibimpl cspbasketlibimpl cspmathimpl cspstatsimpl cspnpstatsimpl PUBLIC_HEADER DESTINATION include/csp/python RUNTIME DESTINATION bin/ LIBRARY DESTINATION lib/ diff --git a/cpp/csp/python/cspbaselibimpl.cpp b/cpp/csp/python/cspbaselibimpl.cpp index c392adbf..754d12fa 100644 --- a/cpp/csp/python/cspbaselibimpl.cpp +++ b/cpp/csp/python/cspbaselibimpl.cpp @@ -346,79 +346,6 @@ REGISTER_CPPNODE( csp::cppnodes, struct_field ); REGISTER_CPPNODE( csp::cppnodes, struct_fromts ); REGISTER_CPPNODE( csp::cppnodes, struct_collectts ); -// Math ops -REGISTER_CPPNODE( csp::cppnodes, add_f ); -REGISTER_CPPNODE( csp::cppnodes, add_i ); -REGISTER_CPPNODE( csp::cppnodes, sub_f ); -REGISTER_CPPNODE( csp::cppnodes, sub_i ); -REGISTER_CPPNODE( csp::cppnodes, mul_f ); -REGISTER_CPPNODE( csp::cppnodes, mul_i ); -REGISTER_CPPNODE( csp::cppnodes, div_f ); -REGISTER_CPPNODE( csp::cppnodes, div_i ); -REGISTER_CPPNODE( csp::cppnodes, pow_f ); -REGISTER_CPPNODE( csp::cppnodes, pow_i ); -REGISTER_CPPNODE( csp::cppnodes, max_f ); -REGISTER_CPPNODE( csp::cppnodes, max_i ); -REGISTER_CPPNODE( csp::cppnodes, min_f ); -REGISTER_CPPNODE( csp::cppnodes, min_i ); -REGISTER_CPPNODE( csp::cppnodes, abs_f ); -REGISTER_CPPNODE( csp::cppnodes, abs_i ); -REGISTER_CPPNODE( csp::cppnodes, ln_f ); -REGISTER_CPPNODE( csp::cppnodes, ln_i ); -REGISTER_CPPNODE( csp::cppnodes, log2_f ); -REGISTER_CPPNODE( csp::cppnodes, log2_i ); -REGISTER_CPPNODE( csp::cppnodes, log10_f ); -REGISTER_CPPNODE( csp::cppnodes, log10_i ); -REGISTER_CPPNODE( csp::cppnodes, exp_f ); -REGISTER_CPPNODE( csp::cppnodes, exp_i ); -REGISTER_CPPNODE( csp::cppnodes, exp2_f ); -REGISTER_CPPNODE( csp::cppnodes, exp2_i ); -REGISTER_CPPNODE( csp::cppnodes, sqrt_f ); -REGISTER_CPPNODE( csp::cppnodes, sqrt_i ); -REGISTER_CPPNODE( csp::cppnodes, erf_f ); -REGISTER_CPPNODE( csp::cppnodes, erf_i ); -REGISTER_CPPNODE( csp::cppnodes, sin_f ); -REGISTER_CPPNODE( csp::cppnodes, sin_i ); -REGISTER_CPPNODE( csp::cppnodes, cos_f ); -REGISTER_CPPNODE( csp::cppnodes, cos_i ); -REGISTER_CPPNODE( csp::cppnodes, tan_f ); -REGISTER_CPPNODE( csp::cppnodes, tan_i ); -REGISTER_CPPNODE( csp::cppnodes, asin_f ); -REGISTER_CPPNODE( csp::cppnodes, asin_i ); -REGISTER_CPPNODE( csp::cppnodes, acos_f ); -REGISTER_CPPNODE( csp::cppnodes, acos_i ); -REGISTER_CPPNODE( csp::cppnodes, atan_f ); -REGISTER_CPPNODE( csp::cppnodes, atan_i ); -REGISTER_CPPNODE( csp::cppnodes, sinh_f ); -REGISTER_CPPNODE( csp::cppnodes, sinh_i ); -REGISTER_CPPNODE( csp::cppnodes, cosh_f ); -REGISTER_CPPNODE( csp::cppnodes, cosh_i ); -REGISTER_CPPNODE( csp::cppnodes, tanh_f ); -REGISTER_CPPNODE( csp::cppnodes, tanh_i ); -REGISTER_CPPNODE( csp::cppnodes, asinh_f ); -REGISTER_CPPNODE( csp::cppnodes, asinh_i ); -REGISTER_CPPNODE( csp::cppnodes, acosh_f ); -REGISTER_CPPNODE( csp::cppnodes, acosh_i ); -REGISTER_CPPNODE( csp::cppnodes, atanh_f ); -REGISTER_CPPNODE( csp::cppnodes, atanh_i ); - - -REGISTER_CPPNODE( csp::cppnodes, bitwise_not ); - -// Comparisons -REGISTER_CPPNODE( csp::cppnodes, not_ ); -REGISTER_CPPNODE( csp::cppnodes, eq_f ); -REGISTER_CPPNODE( csp::cppnodes, eq_i ); -REGISTER_CPPNODE( csp::cppnodes, ne_f ); -REGISTER_CPPNODE( csp::cppnodes, ne_i ); -REGISTER_CPPNODE( csp::cppnodes, gt_f ); -REGISTER_CPPNODE( csp::cppnodes, gt_i ); -REGISTER_CPPNODE( csp::cppnodes, lt_f ); -REGISTER_CPPNODE( csp::cppnodes, lt_i ); -REGISTER_CPPNODE( csp::cppnodes, ge_f ); -REGISTER_CPPNODE( csp::cppnodes, ge_i ); -REGISTER_CPPNODE( csp::cppnodes, le_f ); -REGISTER_CPPNODE( csp::cppnodes, le_i ); REGISTER_CPPNODE( csp::cppnodes, exprtk_impl ); static PyModuleDef _cspbaselibimpl_module = { diff --git a/cpp/csp/python/cspmathimpl.cpp b/cpp/csp/python/cspmathimpl.cpp new file mode 100644 index 00000000..27559047 --- /dev/null +++ b/cpp/csp/python/cspmathimpl.cpp @@ -0,0 +1,98 @@ +#include +#include +#include + +// Math ops +REGISTER_CPPNODE( csp::cppnodes, add_f ); +REGISTER_CPPNODE( csp::cppnodes, add_i ); +REGISTER_CPPNODE( csp::cppnodes, sub_f ); +REGISTER_CPPNODE( csp::cppnodes, sub_i ); +REGISTER_CPPNODE( csp::cppnodes, mul_f ); +REGISTER_CPPNODE( csp::cppnodes, mul_i ); +REGISTER_CPPNODE( csp::cppnodes, div_f ); +REGISTER_CPPNODE( csp::cppnodes, div_i ); +REGISTER_CPPNODE( csp::cppnodes, pow_f ); +REGISTER_CPPNODE( csp::cppnodes, pow_i ); +REGISTER_CPPNODE( csp::cppnodes, max_f ); +REGISTER_CPPNODE( csp::cppnodes, max_i ); +REGISTER_CPPNODE( csp::cppnodes, min_f ); +REGISTER_CPPNODE( csp::cppnodes, min_i ); +REGISTER_CPPNODE( csp::cppnodes, abs_f ); +REGISTER_CPPNODE( csp::cppnodes, abs_i ); +REGISTER_CPPNODE( csp::cppnodes, ln_f ); +REGISTER_CPPNODE( csp::cppnodes, ln_i ); +REGISTER_CPPNODE( csp::cppnodes, log2_f ); +REGISTER_CPPNODE( csp::cppnodes, log2_i ); +REGISTER_CPPNODE( csp::cppnodes, log10_f ); +REGISTER_CPPNODE( csp::cppnodes, log10_i ); +REGISTER_CPPNODE( csp::cppnodes, exp_f ); +REGISTER_CPPNODE( csp::cppnodes, exp_i ); +REGISTER_CPPNODE( csp::cppnodes, exp2_f ); +REGISTER_CPPNODE( csp::cppnodes, exp2_i ); +REGISTER_CPPNODE( csp::cppnodes, sqrt_f ); +REGISTER_CPPNODE( csp::cppnodes, sqrt_i ); +REGISTER_CPPNODE( csp::cppnodes, erf_f ); +REGISTER_CPPNODE( csp::cppnodes, erf_i ); +REGISTER_CPPNODE( csp::cppnodes, sin_f ); +REGISTER_CPPNODE( csp::cppnodes, sin_i ); +REGISTER_CPPNODE( csp::cppnodes, cos_f ); +REGISTER_CPPNODE( csp::cppnodes, cos_i ); +REGISTER_CPPNODE( csp::cppnodes, tan_f ); +REGISTER_CPPNODE( csp::cppnodes, tan_i ); +REGISTER_CPPNODE( csp::cppnodes, asin_f ); +REGISTER_CPPNODE( csp::cppnodes, asin_i ); +REGISTER_CPPNODE( csp::cppnodes, acos_f ); +REGISTER_CPPNODE( csp::cppnodes, acos_i ); +REGISTER_CPPNODE( csp::cppnodes, atan_f ); +REGISTER_CPPNODE( csp::cppnodes, atan_i ); +REGISTER_CPPNODE( csp::cppnodes, sinh_f ); +REGISTER_CPPNODE( csp::cppnodes, sinh_i ); +REGISTER_CPPNODE( csp::cppnodes, cosh_f ); +REGISTER_CPPNODE( csp::cppnodes, cosh_i ); +REGISTER_CPPNODE( csp::cppnodes, tanh_f ); +REGISTER_CPPNODE( csp::cppnodes, tanh_i ); +REGISTER_CPPNODE( csp::cppnodes, asinh_f ); +REGISTER_CPPNODE( csp::cppnodes, asinh_i ); +REGISTER_CPPNODE( csp::cppnodes, acosh_f ); +REGISTER_CPPNODE( csp::cppnodes, acosh_i ); +REGISTER_CPPNODE( csp::cppnodes, atanh_f ); +REGISTER_CPPNODE( csp::cppnodes, atanh_i ); + +REGISTER_CPPNODE( csp::cppnodes, bitwise_not ); + +// Comparisons +REGISTER_CPPNODE( csp::cppnodes, not_ ); +REGISTER_CPPNODE( csp::cppnodes, eq_f ); +REGISTER_CPPNODE( csp::cppnodes, eq_i ); +REGISTER_CPPNODE( csp::cppnodes, ne_f ); +REGISTER_CPPNODE( csp::cppnodes, ne_i ); +REGISTER_CPPNODE( csp::cppnodes, gt_f ); +REGISTER_CPPNODE( csp::cppnodes, gt_i ); +REGISTER_CPPNODE( csp::cppnodes, lt_f ); +REGISTER_CPPNODE( csp::cppnodes, lt_i ); +REGISTER_CPPNODE( csp::cppnodes, ge_f ); +REGISTER_CPPNODE( csp::cppnodes, ge_i ); +REGISTER_CPPNODE( csp::cppnodes, le_f ); +REGISTER_CPPNODE( csp::cppnodes, le_i ); + +static PyModuleDef _cspmathimpl_module = { + PyModuleDef_HEAD_INIT, + "_cspmathimpl", + "_cspmathimpl c++ module", + -1, + NULL, NULL, NULL, NULL, NULL +}; + +PyMODINIT_FUNC PyInit__cspmathimpl(void) +{ + PyObject* m; + + m = PyModule_Create( &_cspmathimpl_module); + if( m == NULL ) + return NULL; + + if( !csp::python::InitHelper::instance().execute( m ) ) + return NULL; + + return m; +} diff --git a/csp/baselib.py b/csp/baselib.py index 9b37c940..ec2e7ad4 100644 --- a/csp/baselib.py +++ b/csp/baselib.py @@ -189,6 +189,10 @@ def _print_ts(tag: str, x: ts["T"]): builtins.print("%s %s:%s" % (t, tag, x)) +# Because python's builtin print is masked +# in the next function definition, add a local +# variable in case it is needed during debugging. +# NOTE: this should not be exported in __all__ _python_print = print diff --git a/csp/math.py b/csp/math.py index 72941922..2e9beac2 100644 --- a/csp/math.py +++ b/csp/math.py @@ -6,7 +6,7 @@ import csp from csp.impl.types.tstype import ts from csp.impl.wiring import node -from csp.lib import _cspbaselibimpl +from csp.lib import _cspmathimpl from csp.typing import Numpy1DArray, NumpyNDArray __all__ = [ @@ -57,12 +57,12 @@ U = typing.TypeVar("U") -@node(cppimpl=_cspbaselibimpl.bitwise_not) +@node(cppimpl=_cspmathimpl.bitwise_not) def bitwise_not(x: ts[int]) -> ts[int]: return ~x -@node(cppimpl=_cspbaselibimpl.not_, name="not_") +@node(cppimpl=_cspmathimpl.not_, name="not_") def not_(x: ts[bool]) -> ts[bool]: """boolean not""" if csp.ticked(x): @@ -108,6 +108,8 @@ def or_(*inputs): "floordiv", "mod", # unary + "pos", + "neg", "abs", "ln", "log2", @@ -134,96 +136,96 @@ def or_(*inputs): MATH_COMP_OPS_CPP = { # binary math - ("add", "float"): _cspbaselibimpl.add_f, - ("add", "int"): _cspbaselibimpl.add_i, - ("sub", "float"): _cspbaselibimpl.sub_f, - ("sub", "int"): _cspbaselibimpl.sub_i, - ("multiply", "float"): _cspbaselibimpl.mul_f, - ("multiply", "int"): _cspbaselibimpl.mul_i, - ("divide", "float"): _cspbaselibimpl.div_f, - ("divide", "int"): _cspbaselibimpl.div_i, - ("pow", "float"): _cspbaselibimpl.pow_f, - ("pow", "int"): _cspbaselibimpl.pow_i, - ("max", "float"): _cspbaselibimpl.max_f, - ("max", "int"): _cspbaselibimpl.max_i, + ("add", "float"): _cspmathimpl.add_f, + ("add", "int"): _cspmathimpl.add_i, + ("sub", "float"): _cspmathimpl.sub_f, + ("sub", "int"): _cspmathimpl.sub_i, + ("multiply", "float"): _cspmathimpl.mul_f, + ("multiply", "int"): _cspmathimpl.mul_i, + ("divide", "float"): _cspmathimpl.div_f, + ("divide", "int"): _cspmathimpl.div_i, + ("pow", "float"): _cspmathimpl.pow_f, + ("pow", "int"): _cspmathimpl.pow_i, + ("max", "float"): _cspmathimpl.max_f, + ("max", "int"): _cspmathimpl.max_i, ("max", "np"): np.maximum, - ("min", "float"): _cspbaselibimpl.min_f, - ("min", "int"): _cspbaselibimpl.min_i, + ("min", "float"): _cspmathimpl.min_f, + ("min", "int"): _cspmathimpl.min_i, ("min", "np"): np.minimum, # unary math - ("abs", "float"): _cspbaselibimpl.abs_f, - ("abs", "int"): _cspbaselibimpl.abs_i, + ("abs", "float"): _cspmathimpl.abs_f, + ("abs", "int"): _cspmathimpl.abs_i, ("abs", "np"): np.abs, - ("ln", "float"): _cspbaselibimpl.ln_f, - ("ln", "int"): _cspbaselibimpl.ln_i, + ("ln", "float"): _cspmathimpl.ln_f, + ("ln", "int"): _cspmathimpl.ln_i, ("ln", "np"): np.log, - ("log2", "float"): _cspbaselibimpl.log2_f, - ("log2", "int"): _cspbaselibimpl.log2_i, + ("log2", "float"): _cspmathimpl.log2_f, + ("log2", "int"): _cspmathimpl.log2_i, ("log2", "np"): np.log2, - ("log10", "float"): _cspbaselibimpl.log10_f, - ("log10", "int"): _cspbaselibimpl.log10_i, + ("log10", "float"): _cspmathimpl.log10_f, + ("log10", "int"): _cspmathimpl.log10_i, ("log10", "np"): np.log10, - ("exp", "float"): _cspbaselibimpl.exp_f, - ("exp", "int"): _cspbaselibimpl.exp_i, + ("exp", "float"): _cspmathimpl.exp_f, + ("exp", "int"): _cspmathimpl.exp_i, ("exp", "np"): np.exp, - ("exp2", "float"): _cspbaselibimpl.exp2_f, - ("exp2", "int"): _cspbaselibimpl.exp2_i, + ("exp2", "float"): _cspmathimpl.exp2_f, + ("exp2", "int"): _cspmathimpl.exp2_i, ("exp2", "np"): np.exp2, - ("sqrt", "float"): _cspbaselibimpl.sqrt_f, - ("sqrt", "int"): _cspbaselibimpl.sqrt_i, + ("sqrt", "float"): _cspmathimpl.sqrt_f, + ("sqrt", "int"): _cspmathimpl.sqrt_i, ("sqrt", "np"): np.sqrt, - ("erf", "float"): _cspbaselibimpl.erf_f, - ("erf", "int"): _cspbaselibimpl.erf_i, + ("erf", "float"): _cspmathimpl.erf_f, + ("erf", "int"): _cspmathimpl.erf_i, # ("erf", "np"): np.erf, # erf is in scipy, worth it to import? - ("sin", "float"): _cspbaselibimpl.sin_f, - ("sin", "int"): _cspbaselibimpl.sin_i, + ("sin", "float"): _cspmathimpl.sin_f, + ("sin", "int"): _cspmathimpl.sin_i, ("sin", "np"): np.sin, - ("cos", "float"): _cspbaselibimpl.cos_f, - ("cos", "int"): _cspbaselibimpl.cos_i, + ("cos", "float"): _cspmathimpl.cos_f, + ("cos", "int"): _cspmathimpl.cos_i, ("cos", "np"): np.cos, - ("tan", "float"): _cspbaselibimpl.tan_f, - ("tan", "int"): _cspbaselibimpl.tan_i, + ("tan", "float"): _cspmathimpl.tan_f, + ("tan", "int"): _cspmathimpl.tan_i, ("tan", "np"): np.tan, - ("arcsin", "float"): _cspbaselibimpl.asin_f, - ("arcsin", "int"): _cspbaselibimpl.asin_i, + ("arcsin", "float"): _cspmathimpl.asin_f, + ("arcsin", "int"): _cspmathimpl.asin_i, ("arcsin", "np"): np.arcsin, - ("arccos", "float"): _cspbaselibimpl.acos_f, - ("arccos", "int"): _cspbaselibimpl.acos_i, + ("arccos", "float"): _cspmathimpl.acos_f, + ("arccos", "int"): _cspmathimpl.acos_i, ("arccos", "np"): np.arccos, - ("arctan", "float"): _cspbaselibimpl.atan_f, - ("arctan", "int"): _cspbaselibimpl.atan_i, + ("arctan", "float"): _cspmathimpl.atan_f, + ("arctan", "int"): _cspmathimpl.atan_i, ("arctan", "np"): np.arctan, - ("sinh", "float"): _cspbaselibimpl.sinh_f, - ("sinh", "int"): _cspbaselibimpl.sinh_i, + ("sinh", "float"): _cspmathimpl.sinh_f, + ("sinh", "int"): _cspmathimpl.sinh_i, ("sinh", "np"): np.sinh, - ("cosh", "float"): _cspbaselibimpl.cosh_f, - ("cosh", "int"): _cspbaselibimpl.cosh_i, + ("cosh", "float"): _cspmathimpl.cosh_f, + ("cosh", "int"): _cspmathimpl.cosh_i, ("cosh", "np"): np.cosh, - ("tanh", "float"): _cspbaselibimpl.tanh_f, - ("tanh", "int"): _cspbaselibimpl.tanh_i, + ("tanh", "float"): _cspmathimpl.tanh_f, + ("tanh", "int"): _cspmathimpl.tanh_i, ("tanh", "np"): np.tanh, - ("arcsinh", "float"): _cspbaselibimpl.asinh_f, - ("arcsinh", "int"): _cspbaselibimpl.asinh_i, + ("arcsinh", "float"): _cspmathimpl.asinh_f, + ("arcsinh", "int"): _cspmathimpl.asinh_i, ("arcsinh", "np"): np.arcsinh, - ("arccosh", "float"): _cspbaselibimpl.acosh_f, - ("arccosh", "int"): _cspbaselibimpl.acosh_i, + ("arccosh", "float"): _cspmathimpl.acosh_f, + ("arccosh", "int"): _cspmathimpl.acosh_i, ("arccosh", "np"): np.arccosh, - ("arctanh", "float"): _cspbaselibimpl.atanh_f, - ("arctanh", "int"): _cspbaselibimpl.atanh_i, + ("arctanh", "float"): _cspmathimpl.atanh_f, + ("arctanh", "int"): _cspmathimpl.atanh_i, ("arctanh", "np"): np.arctanh, # binary comparator - ("eq", "float"): _cspbaselibimpl.eq_f, - ("eq", "int"): _cspbaselibimpl.eq_i, - ("ne", "float"): _cspbaselibimpl.ne_f, - ("ne", "int"): _cspbaselibimpl.ne_i, - ("lt", "float"): _cspbaselibimpl.lt_f, - ("lt", "int"): _cspbaselibimpl.lt_i, - ("gt", "float"): _cspbaselibimpl.gt_f, - ("gt", "int"): _cspbaselibimpl.gt_i, - ("le", "float"): _cspbaselibimpl.le_f, - ("le", "int"): _cspbaselibimpl.le_i, - ("ge", "float"): _cspbaselibimpl.ge_f, - ("ge", "int"): _cspbaselibimpl.ge_i, + ("eq", "float"): _cspmathimpl.eq_f, + ("eq", "int"): _cspmathimpl.eq_i, + ("ne", "float"): _cspmathimpl.ne_f, + ("ne", "int"): _cspmathimpl.ne_i, + ("lt", "float"): _cspmathimpl.lt_f, + ("lt", "int"): _cspmathimpl.lt_i, + ("gt", "float"): _cspmathimpl.gt_f, + ("gt", "int"): _cspmathimpl.gt_i, + ("le", "float"): _cspmathimpl.le_f, + ("le", "int"): _cspmathimpl.le_i, + ("ge", "float"): _cspmathimpl.ge_f, + ("ge", "int"): _cspmathimpl.ge_i, } @@ -268,7 +270,7 @@ def generic_type(x: ts["T"], y: ts["T"]) -> ts[generic_out_type]: return op_lambda(x, y) def comp(x: ts["T"], y: ts["U"]): - if typing.get_origin(x.tstype.typ) is [Numpy1DArray, NumpyNDArray] or typing.get_origin(y.tstype.typ) in [ + if typing.get_origin(x.tstype.typ) in [Numpy1DArray, NumpyNDArray] or typing.get_origin(y.tstype.typ) in [ Numpy1DArray, NumpyNDArray, ]: @@ -277,7 +279,6 @@ def comp(x: ts["T"], y: ts["U"]): return float_type(x, y) elif x.tstype.typ is int and y.tstype.typ is int: return int_type(x, y) - return generic_type(x, y) comp.__name__ = name @@ -350,8 +351,13 @@ def comp(x: ts["T"]): pos = define_unary_op("pos", lambda x: +x) neg = define_unary_op("neg", lambda x: -x) -# Other math ops +# Because python's builtin abs is masked +# in the next definition, add a local +# variable so it can still be used in lambda. +# NOTE: this should not be exported in __all__ _python_abs = abs + +# Other math ops abs = define_unary_op("abs", lambda x: _python_abs(x)) ln = define_unary_op("ln", lambda x: math.log(x)) log2 = define_unary_op("log2", lambda x: math.log2(x)) diff --git a/csp/tests/test_math.py b/csp/tests/test_math.py index 8cf2c483..6177ceb1 100644 --- a/csp/tests/test_math.py +++ b/csp/tests/test_math.py @@ -115,6 +115,7 @@ def graph(use_promotion: bool): def test_math_unary_ops(self): OPS = { + csp.pos: lambda x: +x, csp.neg: lambda x: -x, csp.abs: lambda x: abs(x), csp.ln: lambda x: math.log(x), From 2f75ee91a1bb7203b4a448011553bf738044ffca Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:59:40 -0500 Subject: [PATCH 7/8] Fix bug in pandas extension integration for few other operators Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- csp/impl/pandas_ext_type.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/csp/impl/pandas_ext_type.py b/csp/impl/pandas_ext_type.py index 427d0dde..5e346eaa 100644 --- a/csp/impl/pandas_ext_type.py +++ b/csp/impl/pandas_ext_type.py @@ -603,4 +603,6 @@ def is_csp_type(arr_or_dtype) -> bool: TsArray._add_arithmetic_ops() TsArray._add_comparison_ops() TsArray._add_logical_ops() -TsArray._add_math_ops() +setattr(TsArray, "__pos__", TsArray._create_arithmetic_method(operator.pos)) +setattr(TsArray, "__neg__", TsArray._create_arithmetic_method(operator.neg)) +setattr(TsArray, "__abs__", TsArray._create_arithmetic_method(operator.abs)) From fce9b5a1cc9b3c962b7dc8a87363b4118b691f01 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Tue, 20 Feb 2024 20:05:08 -0500 Subject: [PATCH 8/8] Incorporate feedback to make modulus align with usage of other binary methods Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- csp/impl/wiring/edge.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/csp/impl/wiring/edge.py b/csp/impl/wiring/edge.py index bcbfa752..1ba4df30 100644 --- a/csp/impl/wiring/edge.py +++ b/csp/impl/wiring/edge.py @@ -116,6 +116,11 @@ def __rpow__(self, other): def __mod__(self, other): import csp + return self.__wrap_binary_method(other, csp.mod) + + def __rmod__(self, other): + import csp + return csp.mod(csp.const(other), self) def __gt__(self, other):