diff --git a/cpp/csp/cppnodes/CMakeLists.txt b/cpp/csp/cppnodes/CMakeLists.txt index 0e3f5d47..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) +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 952b3cb1..0be73c11 100644 --- a/cpp/csp/cppnodes/baselibimpl.cpp +++ b/cpp/csp/cppnodes/baselibimpl.cpp @@ -574,118 +574,6 @@ DECLARE_CPPNODE( times_ns ) EXPORT_CPPNODE( times_ns ); -/* -Math operations -*/ - -// Unary operation - -template -DECLARE_CPPNODE( _unary_op ) -{ - TS_INPUT( T, x ); - TS_OUTPUT( T ); - - //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 ) ); - } -}; - -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 ); } -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 ); -#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 6f940c59..754d12fa 100644 --- a/cpp/csp/python/cspbaselibimpl.cpp +++ b/cpp/csp/python/cspbaselibimpl.cpp @@ -346,40 +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, ln ); -REGISTER_CPPNODE( csp::cppnodes, exp ); -REGISTER_CPPNODE( csp::cppnodes, abs ); -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/__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 0aac1f52..ec2e7ad4 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,72 +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", - "pow", - "ln", - "exp", - "abs", - "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") @@ -212,6 +189,13 @@ 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 + + def print(tag: str, x): return _print_ts(tag, _convert_ts_object_for_print(x)) @@ -332,9 +316,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""" @@ -417,11 +398,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"]: """ @@ -635,158 +611,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 = ["add", "sub", "multiply", "divide", "pow", "max", "min"] - -COMP_OPS = ["eq", "ne", "lt", "gt", "le", "ge"] - -MATH_COMP_OPS_CPP = { - ("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, - ("min", "float"): _cspbaselibimpl.min_f, - ("min", "int"): _cspbaselibimpl.min_i, - ("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_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[(name, "float")], 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) - def int_type(x: ts[int], y: ts[int]) -> ts[int_out_type]: - if csp.valid(x, y): - return op_lambda(x, y) - - @_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) - - @_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 x.tstype.typ in [Numpy1DArray[float], NumpyNDArray[float]] or y.tstype.typ in [ - Numpy1DArray[float], - NumpyNDArray[float], - ]: - 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 - - -# Math operators - -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) - -# Comparison operators - -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) - -# Other math ops - - -@node -def floordiv(x: ts["T"], y: ts["T"]) -> ts["T"]: - if csp.ticked(x, y) and csp.valid(x, y): - return x // y - - @node def accum(x: ts["T"], start: "~T" = 0) -> ts["T"]: with csp.state(): @@ -797,24 +621,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/pandas_ext_type.py b/csp/impl/pandas_ext_type.py index a7a8b881..5e346eaa 100644 --- a/csp/impl/pandas_ext_type.py +++ b/csp/impl/pandas_ext_type.py @@ -603,3 +603,6 @@ def is_csp_type(arr_or_dtype) -> bool: TsArray._add_arithmetic_ops() TsArray._add_comparison_ops() TsArray._add_logical_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)) diff --git a/csp/impl/wiring/edge.py b/csp/impl/wiring/edge.py index d285fe2e..1ba4df30 100644 --- a/csp/impl/wiring/edge.py +++ b/csp/impl/wiring/edge.py @@ -1,3 +1,33 @@ +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), + "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(), + "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: __slots__ = ["tstype", "nodedef", "output_idx", "basket_idx"] @@ -13,7 +43,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 +60,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 +68,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 +78,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 +86,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 +96,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 +106,52 @@ 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 self.__wrap_binary_method(other, csp.mod) + + def __rmod__(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 +160,110 @@ 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 __abs__(self): + import csp + + return csp.abs(self) + + def abs(self): + import csp + + 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 + + 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): + 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): from csp.impl.struct import Struct diff --git a/csp/math.py b/csp/math.py new file mode 100644 index 00000000..2e9beac2 --- /dev/null +++ b/csp/math.py @@ -0,0 +1,388 @@ +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 _cspmathimpl +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_", + "pos", + "pow", + "sin", + "sinh", + "sqrt", + "sub", + "tan", + "tanh", +] + +T = typing.TypeVar("T") +U = typing.TypeVar("U") + + +@node(cppimpl=_cspmathimpl.bitwise_not) +def bitwise_not(x: ts[int]) -> ts[int]: + return ~x + + +@node(cppimpl=_cspmathimpl.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 + "pos", + "neg", + "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"): _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"): _cspmathimpl.min_f, + ("min", "int"): _cspmathimpl.min_i, + ("min", "np"): np.minimum, + # unary math + ("abs", "float"): _cspmathimpl.abs_f, + ("abs", "int"): _cspmathimpl.abs_i, + ("abs", "np"): np.abs, + ("ln", "float"): _cspmathimpl.ln_f, + ("ln", "int"): _cspmathimpl.ln_i, + ("ln", "np"): np.log, + ("log2", "float"): _cspmathimpl.log2_f, + ("log2", "int"): _cspmathimpl.log2_i, + ("log2", "np"): np.log2, + ("log10", "float"): _cspmathimpl.log10_f, + ("log10", "int"): _cspmathimpl.log10_i, + ("log10", "np"): np.log10, + ("exp", "float"): _cspmathimpl.exp_f, + ("exp", "int"): _cspmathimpl.exp_i, + ("exp", "np"): np.exp, + ("exp2", "float"): _cspmathimpl.exp2_f, + ("exp2", "int"): _cspmathimpl.exp2_i, + ("exp2", "np"): np.exp2, + ("sqrt", "float"): _cspmathimpl.sqrt_f, + ("sqrt", "int"): _cspmathimpl.sqrt_i, + ("sqrt", "np"): np.sqrt, + ("erf", "float"): _cspmathimpl.erf_f, + ("erf", "int"): _cspmathimpl.erf_i, + # ("erf", "np"): np.erf, # erf is in scipy, worth it to import? + ("sin", "float"): _cspmathimpl.sin_f, + ("sin", "int"): _cspmathimpl.sin_i, + ("sin", "np"): np.sin, + ("cos", "float"): _cspmathimpl.cos_f, + ("cos", "int"): _cspmathimpl.cos_i, + ("cos", "np"): np.cos, + ("tan", "float"): _cspmathimpl.tan_f, + ("tan", "int"): _cspmathimpl.tan_i, + ("tan", "np"): np.tan, + ("arcsin", "float"): _cspmathimpl.asin_f, + ("arcsin", "int"): _cspmathimpl.asin_i, + ("arcsin", "np"): np.arcsin, + ("arccos", "float"): _cspmathimpl.acos_f, + ("arccos", "int"): _cspmathimpl.acos_i, + ("arccos", "np"): np.arccos, + ("arctan", "float"): _cspmathimpl.atan_f, + ("arctan", "int"): _cspmathimpl.atan_i, + ("arctan", "np"): np.arctan, + ("sinh", "float"): _cspmathimpl.sinh_f, + ("sinh", "int"): _cspmathimpl.sinh_i, + ("sinh", "np"): np.sinh, + ("cosh", "float"): _cspmathimpl.cosh_f, + ("cosh", "int"): _cspmathimpl.cosh_i, + ("cosh", "np"): np.cosh, + ("tanh", "float"): _cspmathimpl.tanh_f, + ("tanh", "int"): _cspmathimpl.tanh_i, + ("tanh", "np"): np.tanh, + ("arcsinh", "float"): _cspmathimpl.asinh_f, + ("arcsinh", "int"): _cspmathimpl.asinh_i, + ("arcsinh", "np"): np.arcsinh, + ("arccosh", "float"): _cspmathimpl.acosh_f, + ("arccosh", "int"): _cspmathimpl.acosh_i, + ("arccosh", "np"): np.arccosh, + ("arctanh", "float"): _cspmathimpl.atanh_f, + ("arctanh", "int"): _cspmathimpl.atanh_i, + ("arctanh", "np"): np.arctanh, + # binary comparator + ("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, +} + + +@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[np.ndarray]) -> 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) +pos = define_unary_op("pos", lambda x: +x) +neg = define_unary_op("neg", lambda x: -x) + +# 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)) +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 972b3f3f..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,129 +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_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.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_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 @@ -1096,9 +972,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)), } 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): diff --git a/csp/tests/test_math.py b/csp/tests/test_math.py new file mode 100644 index 00000000..6177ceb1 --- /dev/null +++ b/csp/tests/test_math.py @@ -0,0 +1,288 @@ +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.pos: lambda x: +x, + 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()