diff --git a/docs/api/math/arcosh.rst b/docs/api/math/arcosh.rst index 99dcd786fe..856060232d 100644 --- a/docs/api/math/arcosh.rst +++ b/docs/api/math/arcosh.rst @@ -1,6 +1,6 @@ .. xfunction:: datatable.math.arcosh - :src: src/core/expr/funary/hyperbolic.cc resolve_op_arcosh + :src: src/core/expr/funary/hyperbolic.cc pyfn_arcosh :cvar: doc_math_arcosh :signature: arcosh(x) diff --git a/docs/api/math/arsinh.rst b/docs/api/math/arsinh.rst index 5200b3f963..4ff61d3f36 100644 --- a/docs/api/math/arsinh.rst +++ b/docs/api/math/arsinh.rst @@ -1,6 +1,6 @@ .. xfunction:: datatable.math.arsinh - :src: src/core/expr/funary/hyperbolic.cc resolve_op_arsinh + :src: src/core/expr/funary/hyperbolic.cc pyfn_arsinh :cvar: doc_math_arsinh :signature: arsinh(x) diff --git a/docs/api/math/artanh.rst b/docs/api/math/artanh.rst index a5ae09d214..dd68b7765f 100644 --- a/docs/api/math/artanh.rst +++ b/docs/api/math/artanh.rst @@ -1,6 +1,6 @@ .. xfunction:: datatable.math.artanh - :src: src/core/expr/funary/hyperbolic.cc resolve_op_artanh + :src: src/core/expr/funary/hyperbolic.cc pyfn_artanh :cvar: doc_math_artanh :signature: artanh(x) diff --git a/docs/api/math/cosh.rst b/docs/api/math/cosh.rst index 1db6cc49b5..1c490ca64a 100644 --- a/docs/api/math/cosh.rst +++ b/docs/api/math/cosh.rst @@ -1,6 +1,6 @@ .. xfunction:: datatable.math.cosh - :src: src/core/expr/funary/hyperbolic.cc resolve_op_cosh + :src: src/core/expr/funary/hyperbolic.cc pyfn_cosh :cvar: doc_math_cosh :signature: cosh(x) diff --git a/docs/api/math/sinh.rst b/docs/api/math/sinh.rst index 76380baf82..e3f2e39534 100644 --- a/docs/api/math/sinh.rst +++ b/docs/api/math/sinh.rst @@ -1,6 +1,6 @@ .. xfunction:: datatable.math.sinh - :src: src/core/expr/funary/hyperbolic.cc resolve_op_sinh + :src: src/core/expr/funary/hyperbolic.cc pyfn_sinh :cvar: doc_math_sinh :signature: sinh(x) diff --git a/docs/api/math/tanh.rst b/docs/api/math/tanh.rst index b0e9a6c86a..57c0d6e46e 100644 --- a/docs/api/math/tanh.rst +++ b/docs/api/math/tanh.rst @@ -1,6 +1,6 @@ .. xfunction:: datatable.math.tanh - :src: src/core/expr/funary/hyperbolic.cc resolve_op_tanh + :src: src/core/expr/funary/hyperbolic.cc pyfn_tanh :cvar: doc_math_tanh :signature: tanh(x) diff --git a/src/core/expr/funary/hyperbolic.cc b/src/core/expr/funary/hyperbolic.cc index 3d6f671c12..489ec4a94d 100644 --- a/src/core/expr/funary/hyperbolic.cc +++ b/src/core/expr/funary/hyperbolic.cc @@ -1,5 +1,5 @@ //------------------------------------------------------------------------------ -// Copyright 2019-2020 H2O.ai +// Copyright 2023 H2O.ai // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), @@ -20,130 +20,177 @@ // IN THE SOFTWARE. //------------------------------------------------------------------------------ #include +#include "column/const.h" +#include "column/func_unary.h" #include "documentation.h" -#include "expr/funary/pyfn.h" -#include "expr/funary/umaker.h" -#include "expr/funary/umaker_impl.h" -#include "ltype.h" +#include "expr/fexpr_func_unary.h" +#include "python/xargs.h" +#include "stype.h" namespace dt { namespace expr { - -using func32_t = float(*)(float); -using func64_t = double(*)(double); - -/** - * All standard hyperbolic functions have the same signature: - * - * VOID -> VOID - * {BOOL, INT*, FLOAT64} -> FLOAT64 - * FLOAT32 -> FLOAT32 - * - */ -static umaker_ptr _resolve_hyp(SType stype, const char* name, - func32_t fn32, func64_t fn64) -{ - if (stype == SType::VOID) { - return umaker_ptr(new umaker_copy()); - } - if (stype == SType::FLOAT64) { - return umaker1::make(fn64, SType::AUTO, SType::FLOAT64); - } - if (stype == SType::FLOAT32) { - return umaker1::make(fn32, SType::AUTO, SType::FLOAT32); - } - if (stype == SType::BOOL || stype_to_ltype(stype) == LType::INT) { - return umaker1::make(fn64, SType::FLOAT64, SType::FLOAT64); - } - throw TypeError() << "Function `" << name << "` cannot be applied to a " - "column of type `" << stype << "`"; +template +class FExpr_Hyperbolic : public FExpr_FuncUnary { + public: + using FExpr_FuncUnary::FExpr_FuncUnary; + + static std::string function_name() { + switch (POS) { + case 1: + return "sinh"; + case 2: + return "cosh"; + case 3: + return "tanh"; + case 4: + return "arsinh"; + case 5: + return "arcosh"; + case 6: + return "artanh"; + } + + } + + + std::string name() const override { + return function_name(); + } + + /** + * All standard hyperbolic functions have the same signature: + * + * VOID -> VOID + * {BOOL, INT*, FLOAT64} -> FLOAT64 + * FLOAT32 -> FLOAT32 + * + */ + template + static inline T op_hyperbolic(T x) { + switch (POSN) { + case 1: + return sinh(x); + case 2: + return cosh(x); + case 3: + return tanh(x); + case 4: + return asinh(x); + case 5: + return acosh(x); + case 6: + return atanh(x); + } + } + + + Column evaluate1(Column&& col) const override{ + SType stype = col.stype(); + + switch (stype) { + case SType::VOID: + return Column(new ConstNa_ColumnImpl(col.nrows(), SType::VOID)); + case SType::BOOL: + case SType::INT8: + case SType::INT16: + case SType::INT32: + case SType::INT64: + col.cast_inplace(SType::FLOAT64); + return make(std::move(col)); + case SType::FLOAT32: + return make(std::move(col)); + case SType::FLOAT64: + return make(std::move(col)); + default: + std::string func_name = function_name(); + throw TypeError() << "Function `" << func_name << "` cannot be applied to a " + "column of type `" << stype << "`"; + } + } + + private: + template + static Column make(Column&& col) { + xassert(compatible_type(col.stype())); + return Column(new FuncUnary1_ColumnImpl( + std::move(col), op_hyperbolic, + col.nrows(), col.stype() + )); + } + +}; + +static py::oobj pyfn_sinh(const py::XArgs &args) { + auto arg = args[0].to_oobj(); + return PyFExpr::make(new FExpr_Hyperbolic<1>(as_fexpr(arg))); } - - - -//------------------------------------------------------------------------------ -// Op::SINH -//------------------------------------------------------------------------------ - -py::PKArgs args_sinh(1, 0, 0, false, false, {"x"}, "sinh", dt::doc_math_sinh); - - -umaker_ptr resolve_op_sinh(SType stype) { - return _resolve_hyp(stype, "sinh", &std::sinh, &std::sinh); +static py::oobj pyfn_cosh(const py::XArgs &args) { + auto arg = args[0].to_oobj(); + return PyFExpr::make(new FExpr_Hyperbolic<2>(as_fexpr(arg))); } - - - -//------------------------------------------------------------------------------ -// Op::COSH -//------------------------------------------------------------------------------ - -py::PKArgs args_cosh(1, 0, 0, false, false, {"x"}, "cosh", dt::doc_math_cosh); - - -umaker_ptr resolve_op_cosh(SType stype) { - return _resolve_hyp(stype, "cosh", &std::cosh, &std::cosh); +static py::oobj pyfn_tanh(const py::XArgs &args) { + auto arg = args[0].to_oobj(); + return PyFExpr::make(new FExpr_Hyperbolic<3>(as_fexpr(arg))); } - - - -//------------------------------------------------------------------------------ -// Op::TANH -//------------------------------------------------------------------------------ - -py::PKArgs args_tanh(1, 0, 0, false, false, {"x"}, "tanh", dt::doc_math_tanh); - - -umaker_ptr resolve_op_tanh(SType stype) { - return _resolve_hyp(stype, "tanh", &std::tanh, &std::tanh); +static py::oobj pyfn_arsinh(const py::XArgs &args) { + auto arg = args[0].to_oobj(); + return PyFExpr::make(new FExpr_Hyperbolic<4>(as_fexpr(arg))); } - - - -//------------------------------------------------------------------------------ -// Op::ARSINH -//------------------------------------------------------------------------------ - -py::PKArgs args_arsinh(1, 0, 0, false, false, {"x"}, "arsinh", dt::doc_math_arsinh); - - -umaker_ptr resolve_op_arsinh(SType stype) { - return _resolve_hyp(stype, "arsinh", &std::asinh, &std::asinh); -} - - - - -//------------------------------------------------------------------------------ -// Op::ARCOSH -//------------------------------------------------------------------------------ - -py::PKArgs args_arcosh(1, 0, 0, false, false, {"x"}, "arcosh", dt::doc_math_arcosh); - - -umaker_ptr resolve_op_arcosh(SType stype) { - return _resolve_hyp(stype, "arcosh", &std::acosh, &std::acosh); +static py::oobj pyfn_arcosh(const py::XArgs &args) { + auto arg = args[0].to_oobj(); + return PyFExpr::make(new FExpr_Hyperbolic<5>(as_fexpr(arg))); } - - - -//------------------------------------------------------------------------------ -// Op::ARTANH -//------------------------------------------------------------------------------ - -py::PKArgs args_artanh(1, 0, 0, false, false, {"x"}, "artanh", dt::doc_math_artanh); - - -umaker_ptr resolve_op_artanh(SType stype) { - return _resolve_hyp(stype, "artanh", &std::atanh, &std::atanh); +static py::oobj pyfn_artanh(const py::XArgs &args) { + auto arg = args[0].to_oobj(); + return PyFExpr::make(new FExpr_Hyperbolic<6>(as_fexpr(arg))); } - - -}} // namespace dt::expr +DECLARE_PYFN(&pyfn_sinh) + ->name("sinh") + ->docs(doc_math_sinh) + ->arg_names({"cols"}) + ->n_positional_args(1) + ->n_required_args(1); + +DECLARE_PYFN(&pyfn_cosh) + ->name("cosh") + ->docs(doc_math_cosh) + ->arg_names({"cols"}) + ->n_positional_args(1) + ->n_required_args(1); + +DECLARE_PYFN(&pyfn_tanh) + ->name("tanh") + ->docs(doc_math_tanh) + ->arg_names({"cols"}) + ->n_positional_args(1) + ->n_required_args(1); + +DECLARE_PYFN(&pyfn_arsinh) + ->name("arsinh") + ->docs(doc_math_arsinh) + ->arg_names({"cols"}) + ->n_positional_args(1) + ->n_required_args(1); + +DECLARE_PYFN(&pyfn_arcosh) + ->name("arcosh") + ->docs(doc_math_arcosh) + ->arg_names({"cols"}) + ->n_positional_args(1) + ->n_required_args(1); + +DECLARE_PYFN(&pyfn_artanh) + ->name("artanh") + ->docs(doc_math_artanh) + ->arg_names({"cols"}) + ->n_positional_args(1) + ->n_required_args(1); + +}} // dt::expr \ No newline at end of file diff --git a/src/core/expr/funary/pyfn.cc b/src/core/expr/funary/pyfn.cc index 844cd03bea..ac12460029 100644 --- a/src/core/expr/funary/pyfn.cc +++ b/src/core/expr/funary/pyfn.cc @@ -121,14 +121,6 @@ void py::DatatableModule::init_funary() FUNARY(args_deg2rad, Op::DEG2RAD); FUNARY(args_rad2deg, Op::RAD2DEG); - // Hyperbolic - FUNARY(args_sinh, Op::SINH); - FUNARY(args_cosh, Op::COSH); - FUNARY(args_tanh, Op::TANH); - FUNARY(args_arsinh, Op::ARSINH); - FUNARY(args_arcosh, Op::ARCOSH); - FUNARY(args_artanh, Op::ARTANH); - // Exponential/power FUNARY(args_cbrt, Op::CBRT); FUNARY(args_exp, Op::EXP); diff --git a/src/core/expr/funary/pyfn.h b/src/core/expr/funary/pyfn.h index 274be5690a..64b43a85a8 100644 --- a/src/core/expr/funary/pyfn.h +++ b/src/core/expr/funary/pyfn.h @@ -36,14 +36,6 @@ extern py::PKArgs args_arctan; extern py::PKArgs args_deg2rad; extern py::PKArgs args_rad2deg; -// Hyperbolic -extern py::PKArgs args_sinh; -extern py::PKArgs args_cosh; -extern py::PKArgs args_tanh; -extern py::PKArgs args_arsinh; -extern py::PKArgs args_arcosh; -extern py::PKArgs args_artanh; - // Exponential/power extern py::PKArgs args_cbrt; extern py::PKArgs args_exp; diff --git a/src/core/expr/funary/umaker.cc b/src/core/expr/funary/umaker.cc index 0bd9867f8e..f19309cfba 100644 --- a/src/core/expr/funary/umaker.cc +++ b/src/core/expr/funary/umaker.cc @@ -75,14 +75,6 @@ umaker_ptr resolve_op(Op opcode, SType stype) case Op::DEG2RAD: return resolve_op_deg2rad(stype); case Op::RAD2DEG: return resolve_op_rad2deg(stype); - // Math: hyperbolic - case Op::SINH: return resolve_op_sinh(stype); - case Op::COSH: return resolve_op_cosh(stype); - case Op::TANH: return resolve_op_tanh(stype); - case Op::ARSINH: return resolve_op_arsinh(stype); - case Op::ARCOSH: return resolve_op_arcosh(stype); - case Op::ARTANH: return resolve_op_artanh(stype); - // Math: exponential/power case Op::CBRT: return resolve_op_cbrt(stype); case Op::EXP: return resolve_op_exp(stype); diff --git a/src/core/expr/funary/umaker.h b/src/core/expr/funary/umaker.h index 30031729a9..c17ce3b9fd 100644 --- a/src/core/expr/funary/umaker.h +++ b/src/core/expr/funary/umaker.h @@ -96,13 +96,6 @@ umaker_ptr resolve_op_arctan(SType); umaker_ptr resolve_op_deg2rad(SType); umaker_ptr resolve_op_rad2deg(SType); -// Hyperbolic -umaker_ptr resolve_op_sinh(SType); -umaker_ptr resolve_op_cosh(SType); -umaker_ptr resolve_op_tanh(SType); -umaker_ptr resolve_op_arsinh(SType); -umaker_ptr resolve_op_arcosh(SType); -umaker_ptr resolve_op_artanh(SType); // Exponential/power umaker_ptr resolve_op_cbrt(SType); diff --git a/src/core/expr/op.h b/src/core/expr/op.h index 6ca51b8f29..9b8fa8fcc0 100644 --- a/src/core/expr/op.h +++ b/src/core/expr/op.h @@ -91,14 +91,6 @@ enum class Op : size_t { DEG2RAD, // funary/trigonometric.cc RAD2DEG, // funary/trigonometric.cc - // Math: hyperbolic - SINH, // funary/hyperbolic.cc - COSH, // funary/hyperbolic.cc - TANH, // funary/hyperbolic.cc - ARSINH, // funary/hyperbolic.cc - ARCOSH, // funary/hyperbolic.cc - ARTANH, // funary/hyperbolic.cc - // Math: exponential/power CBRT, // funary/exponential.cc EXP, // funary/exponential.cc diff --git a/tests/ijby/test-func-unary.py b/tests/ijby/test-func-unary.py index 5ca952e80a..a57ac91601 100644 --- a/tests/ijby/test-func-unary.py +++ b/tests/ijby/test-func-unary.py @@ -25,9 +25,10 @@ import pytest import random import datatable as dt -from datatable import f, g, stype, join +from datatable import dt, f, g, stype, join from datatable.internal import frame_integrity_check from tests import assert_equals +from itertools import product, islice # Sets of tuples containing test columns of each type @@ -220,3 +221,56 @@ def test_log_srcs(src, fn): mathlog(x) for x in src] assert DT1.to_list()[0] == pyans + + +#------------------------------------------------------------------------------- +# Unary hyperbolic +#------------------------------------------------------------------------------- + +def hyperbolic_func(func, t): + if t is None: return None + return func(t) + + +funcs = [math.sinh, math.cosh, math.tanh] +dt_funcs = [dt.math.sinh, dt.math.cosh, dt.math.tanh] + +@pytest.mark.parametrize("math_func, dt_func", zip(funcs, dt_funcs)) +def test_dt_hyperbolic1(math_func, dt_func): + srcs = srcs_bool + srcs_int[:2] + DT = dt.Frame(srcs) + RES = DT[:, dt_func(f[:])] + frame_integrity_check(RES) + assert RES.to_list() == [[hyperbolic_func(math_func, x) for x in src] for src in srcs] + +def test_dt_hyperbolic_acosh(): + srcs = [[7, 3, 1]] + DT = dt.Frame(srcs) + RES = DT[:, dt.math.arcosh(f[:])] + frame_integrity_check(RES) + assert RES.to_list() == [[hyperbolic_func(math.acosh, x) for x in src] for src in srcs] + +def test_dt_hyperbolic_atanh(): + srcs = [[0.59, -0.12, 0.00008]] + DT = dt.Frame(srcs) + RES = DT[:, dt.math.artanh(f[:])] + frame_integrity_check(RES) + assert RES.to_list() == [[hyperbolic_func(math.atanh, x) for x in src] for src in srcs] + +def test_dt_hyperbolic_asinh(): + srcs = [[0.59, -0.12, 0.00008]] + DT = dt.Frame(srcs) + RES = DT[:, dt.math.arsinh(f[:])] + frame_integrity_check(RES) + assert RES.to_list() == [[hyperbolic_func(math.asinh, x) for x in src] for src in srcs] + +funcs = [math.sinh, math.cosh, math.tanh, math.acosh, math.asinh, math.atanh] +funcs = product(dt_funcs, srcs_str) +@pytest.mark.parametrize("dt_func, src", funcs) +def test_dt_hyperbolic_invalid(dt_func, src): + DT = dt.Frame(src) + col_stype = DT.stype.name + fn_name = dt_func.__name__ + with pytest.raises(TypeError, match=f"Function {fn_name} cannot be applied to a " + f"column of type {col_stype}"): + assert DT[:, dt_func(f[0])] \ No newline at end of file