diff --git a/cpp/src/arrow/compute/api_scalar.cc b/cpp/src/arrow/compute/api_scalar.cc index 3bdff691778..a5d3bf2f37f 100644 --- a/cpp/src/arrow/compute/api_scalar.cc +++ b/cpp/src/arrow/compute/api_scalar.cc @@ -653,6 +653,7 @@ SCALAR_ARITHMETIC_UNARY(Negate, "negate", "negate_checked") SCALAR_ARITHMETIC_UNARY(Sin, "sin", "sin_checked") SCALAR_ARITHMETIC_UNARY(Tan, "tan", "tan_checked") SCALAR_EAGER_UNARY(Atan, "atan") +SCALAR_EAGER_UNARY(Exp, "exp") SCALAR_EAGER_UNARY(Sign, "sign") Result Round(const Datum& arg, RoundOptions options, ExecContext* ctx) { diff --git a/cpp/src/arrow/compute/api_scalar.h b/cpp/src/arrow/compute/api_scalar.h index 7d86a555ec8..d9758f0b33b 100644 --- a/cpp/src/arrow/compute/api_scalar.h +++ b/cpp/src/arrow/compute/api_scalar.h @@ -610,6 +610,15 @@ Result Power(const Datum& left, const Datum& right, ArithmeticOptions options = ArithmeticOptions(), ExecContext* ctx = NULLPTR); +/// \brief Raise Euler's number to the specified power, element-wise. +/// If the exponent value is null the result will be null. +/// +/// \param[in] arg the base +/// \param[in] ctx the function execution context, optional +/// \return the element-wise Euler's number raised to the power of exponent +ARROW_EXPORT +Result Exp(const Datum& arg, ExecContext* ctx = NULLPTR); + /// \brief Left shift the left array by the right array. Array values must be the /// same length. If either operand is null, the result will be null. /// diff --git a/cpp/src/arrow/compute/kernels/base_arithmetic_internal.h b/cpp/src/arrow/compute/kernels/base_arithmetic_internal.h index f416881ccb8..9c0a9472526 100644 --- a/cpp/src/arrow/compute/kernels/base_arithmetic_internal.h +++ b/cpp/src/arrow/compute/kernels/base_arithmetic_internal.h @@ -485,6 +485,22 @@ struct NegateChecked { } }; +struct Exp { + template + static constexpr enable_if_floating_value Call(KernelContext*, Arg exp, + Status*) { + static_assert(std::is_same::value, ""); + return std::exp(exp); + } + + template + static constexpr enable_if_decimal_value Call(KernelContext*, Arg exp, + Status*) { + static_assert(std::is_same::value, ""); + return exp.Exp(); + } +}; + struct Power { ARROW_NOINLINE static uint64_t IntegerPower(uint64_t base, uint64_t exp) { diff --git a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc index d18946599be..bbc2cda1c8d 100644 --- a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc +++ b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc @@ -1717,6 +1717,11 @@ const FunctionDoc pow_doc{ "wraps around. If either base or exponent is null the result will be null."), {"base", "exponent"}}; +const FunctionDoc exp_doc{ + "Compute Euler's number raised to the specified power, element-wise", + ("If exponent is null the result will be null."), + {"exponent"}}; + const FunctionDoc pow_checked_doc{ "Raise arguments to power element-wise", ("An error is returned when integer to negative integer power is encountered,\n" @@ -2224,6 +2229,10 @@ void RegisterScalarArithmetic(FunctionRegistry* registry) { "power_checked", pow_checked_doc); DCHECK_OK(registry->AddFunction(std::move(power_checked))); + // ---------------------------------------------------------------------- + auto exp = MakeUnaryArithmeticFunctionFloatingPoint("exp", exp_doc); + DCHECK_OK(registry->AddFunction(std::move(exp))); + // ---------------------------------------------------------------------- auto sqrt = MakeUnaryArithmeticFunctionFloatingPoint("sqrt", sqrt_doc); DCHECK_OK(registry->AddFunction(std::move(sqrt))); diff --git a/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc b/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc index 7b74e8e5d60..747f5a1bd3c 100644 --- a/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc +++ b/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc @@ -1239,7 +1239,7 @@ TEST(TestUnaryArithmetic, DispatchBest) { // Float types for (std::string name : - {"atan", "sign", "floor", "ceil", "trunc", "round", "round_to_multiple"}) { + {"atan", "sign", "floor", "ceil", "trunc", "round", "round_to_multiple", "exp"}) { for (const auto& ty : {float32(), float64()}) { CheckDispatchBest(name, {ty}, {ty}); CheckDispatchBest(name, {dictionary(int8(), ty)}, {ty}); @@ -1279,7 +1279,7 @@ TEST(TestUnaryArithmetic, Null) { } } - for (std::string name : {"atan", "bit_wise_not", "ceil", "floor", "round", + for (std::string name : {"atan", "bit_wise_not", "ceil", "exp", "floor", "round", "round_to_multiple", "sign", "trunc"}) { AssertNullToNull(name); } @@ -1519,6 +1519,65 @@ TEST_F(TestUnaryArithmeticDecimal, AbsoluteValue) { } } +TYPED_TEST(TestUnaryArithmeticFloating, Exp) { + using CType = typename TestFixture::CType; + + auto min = std::numeric_limits::lowest(); + auto max = std::numeric_limits::max(); + + auto exp = [](const Datum& arg, ArithmeticOptions, ExecContext* ctx) { + return Exp(arg, ctx); + }; + // Empty arrays + this->AssertUnaryOp(exp, "[]", "[]"); + // Array with nulls + this->AssertUnaryOp(exp, "[null]", "[null]"); + this->AssertUnaryOp(exp, this->MakeNullScalar(), this->MakeNullScalar()); + this->AssertUnaryOp(exp, "[-1.0, null, 10.0]", + "[0.36787944117144233, null, 22026.465794806718]"); + // Ordinary arrays (positive, negative, fractional, and zero inputs) + this->AssertUnaryOp( + exp, "[-10.0, 0, 0.5, 1.0]", + "[0.000045399929762484854,1.0,1.6487212707001282,2.718281828459045]"); + this->AssertUnaryOp(exp, 1.3F, 3.6692964926535487F); + this->AssertUnaryOp(exp, this->MakeScalar(1.3F), this->MakeScalar(3.6692964926535487F)); + // Arrays with infinites + this->AssertUnaryOp(exp, "[-Inf, Inf]", "[0, Inf]"); + // Arrays with NaNs + this->SetNansEqual(true); + this->AssertUnaryOp(exp, "[NaN]", "[NaN]"); + this->AssertUnaryOp(exp, "[NaN]", "[NaN]"); + this->AssertUnaryOp(exp, "[NaN]", "[NaN]"); + // Min/max + this->AssertUnaryOp(exp, min, 0.0); + this->AssertUnaryOp(exp, max, std::numeric_limits::infinity()); +} + +TEST_F(TestUnaryArithmeticDecimal, Exp) { + auto max128 = Decimal128::GetMaxValue(38); + auto max256 = Decimal256::GetMaxValue(76); + const auto func = "exp"; + for (const auto& ty : PositiveScaleTypes()) { + CheckScalar(func, {ArrayFromJSON(ty, R"([])")}, ArrayFromJSON(float64(), "[]")); + CheckScalar( + func, {ArrayFromJSON(ty, R"(["-1.00", "10.00", null])")}, + ArrayFromJSON(float64(), "[0.36787944117144233, 22026.465794806718, null]")); + } + CheckScalar(func, {std::make_shared(max128, decimal128(38, 0))}, + ScalarFromJSON(float64(), "Inf")); + CheckScalar(func, {std::make_shared(-max128, decimal128(38, 0))}, + ScalarFromJSON(float64(), "0")); + CheckScalar(func, {std::make_shared(max256, decimal256(76, 0))}, + ScalarFromJSON(float64(), "Inf")); + CheckScalar(func, {std::make_shared(-max256, decimal256(76, 0))}, + ScalarFromJSON(float64(), "0")); + for (const auto& ty : NegativeScaleTypes()) { + CheckScalar(func, {ArrayFromJSON(ty, R"([])")}, ArrayFromJSON(float64(), "[]")); + CheckScalar(func, {DecimalArrayFromJSON(ty, R"(["12E2", "0", "-42E2", null])")}, + ArrayFromJSON(float64(), "[Inf, 1.0, 0.0, null]")); + } +} + TEST_F(TestUnaryArithmeticDecimal, Log) { std::vector unchecked = {"ln", "log2", "log10", "log1p"}; std::vector checked = {"ln_checked", "log2_checked", "log10_checked", diff --git a/cpp/src/arrow/engine/substrait/extension_set.cc b/cpp/src/arrow/engine/substrait/extension_set.cc index 21d70e33330..1c0a9271561 100644 --- a/cpp/src/arrow/engine/substrait/extension_set.cc +++ b/cpp/src/arrow/engine/substrait/extension_set.cc @@ -776,6 +776,15 @@ ExtensionIdRegistry::SubstraitCallToArrow DecodeOptionlessOverflowableArithmetic }; } +ExtensionIdRegistry::SubstraitCallToArrow DecodeOptionlessUncheckedArithmetic( + const std::string& function_name) { + return [function_name](const SubstraitCall& call) -> Result { + ARROW_ASSIGN_OR_RAISE(std::vector value_args, + GetValueArgs(call, 0)); + return arrow::compute::call(function_name, std::move(value_args)); + }; +} + template ExtensionIdRegistry::ArrowToSubstraitCall EncodeOptionlessOverflowableArithmetic( Id substrait_fn_id) { @@ -917,11 +926,34 @@ struct DefaultExtensionIdRegistry : ExtensionIdRegistryImpl { // -------------- Substrait -> Arrow Functions ----------------- // Mappings with a _checked variant - for (const auto& function_name : {"add", "subtract", "multiply", "divide"}) { + for (const auto& function_name : + {"add", "subtract", "multiply", "divide", "power", "sqrt", "abs"}) { DCHECK_OK( AddSubstraitCallToArrow({kSubstraitArithmeticFunctionsUri, function_name}, DecodeOptionlessOverflowableArithmetic(function_name))); } + + // Mappings without a _checked variant + for (const auto& function_name : {"exp", "sign"}) { + DCHECK_OK( + AddSubstraitCallToArrow({kSubstraitArithmeticFunctionsUri, function_name}, + DecodeOptionlessUncheckedArithmetic(function_name))); + } + + // Mappings for log functions + for (const auto& function_name : {"ln", "log10", "log2", "logb", "log1p"}) { + DCHECK_OK( + AddSubstraitCallToArrow({kSubstraitLogarithmicFunctionsUri, function_name}, + DecodeOptionlessUncheckedArithmetic(function_name))); + } + + // Mappings for rounding functions + for (const auto& function_name : {"ceil", "floor"}) { + DCHECK_OK( + AddSubstraitCallToArrow({kSubstraitRoundingFunctionsUri, function_name}, + DecodeOptionlessUncheckedArithmetic(function_name))); + } + // Basic mappings that need _kleene appended to them for (const auto& function_name : {"or", "and"}) { DCHECK_OK(AddSubstraitCallToArrow( diff --git a/cpp/src/arrow/engine/substrait/extension_set.h b/cpp/src/arrow/engine/substrait/extension_set.h index 9c8a7013e59..12aa40115b1 100644 --- a/cpp/src/arrow/engine/substrait/extension_set.h +++ b/cpp/src/arrow/engine/substrait/extension_set.h @@ -53,6 +53,12 @@ constexpr const char* kSubstraitComparisonFunctionsUri = constexpr const char* kSubstraitDatetimeFunctionsUri = "https://github.com/substrait-io/substrait/blob/main/extensions/" "functions_datetime.yaml"; +constexpr const char* kSubstraitLogarithmicFunctionsUri = + "https://github.com/substrait-io/substrait/blob/main/extensions/" + "functions_logarithmic.yaml"; +constexpr const char* kSubstraitRoundingFunctionsUri = + "https://github.com/substrait-io/substrait/blob/main/extensions/" + "functions_rounding.yaml"; constexpr const char* kSubstraitStringFunctionsUri = "https://github.com/substrait-io/substrait/blob/main/extensions/" "functions_string.yaml"; diff --git a/cpp/src/arrow/engine/substrait/function_test.cc b/cpp/src/arrow/engine/substrait/function_test.cc index e7a26f7dc38..9f8fdbed628 100644 --- a/cpp/src/arrow/engine/substrait/function_test.cc +++ b/cpp/src/arrow/engine/substrait/function_test.cc @@ -128,6 +128,7 @@ Result> PlanFromTestCase( void CheckValidTestCases(const std::vector& valid_cases) { for (const FunctionTestCase& test_case : valid_cases) { + ARROW_SCOPED_TRACE("function_name=", test_case.function_id.ToString()); std::shared_ptr output_table; ASSERT_OK_AND_ASSIGN(std::shared_ptr plan, PlanFromTestCase(test_case, &output_table)); @@ -197,6 +198,36 @@ TEST(FunctionMapping, ValidCases) { {int8(), int8()}, "0", int8()}, + {{kSubstraitArithmeticFunctionsUri, "sign"}, + {"-1"}, + kNoOptions, + {int8()}, + "-1", + int8()}, + {{kSubstraitArithmeticFunctionsUri, "power"}, + {"2", "2"}, + {{"overflow", {"SILENT", "ERROR"}}}, + {int8(), int8()}, + "4", + int8()}, + {{kSubstraitArithmeticFunctionsUri, "sqrt"}, + {"4"}, + {{"overflow", {"SILENT", "ERROR"}}}, + {int8()}, + "2", + float64()}, + {{kSubstraitArithmeticFunctionsUri, "exp"}, + {"0"}, + kNoOptions, + {float64()}, + "1", + float64()}, + {{kSubstraitArithmeticFunctionsUri, "abs"}, + {"-1"}, + {{"overflow", {"SILENT", "ERROR"}}}, + {int8()}, + "1", + int8()}, {{kSubstraitBooleanFunctionsUri, "or"}, {"1", ""}, kNoOptions, @@ -370,7 +401,49 @@ TEST(FunctionMapping, ValidCases) { kNoOptions, {utf8(), utf8()}, "abcdef", - utf8()}}; + utf8()}, + {{kSubstraitLogarithmicFunctionsUri, "ln"}, + {"1"}, + kNoOptions, + {int8()}, + "0", + float64()}, + {{kSubstraitLogarithmicFunctionsUri, "log10"}, + {"1"}, + kNoOptions, + {int8()}, + "0", + float64()}, + {{kSubstraitLogarithmicFunctionsUri, "log2"}, + {"2"}, + kNoOptions, + {int8()}, + "1", + float64()}, + {{kSubstraitLogarithmicFunctionsUri, "log1p"}, + {"0"}, + kNoOptions, + {int8()}, + "0", + float64()}, + {{kSubstraitLogarithmicFunctionsUri, "logb"}, + {"10", "10"}, + kNoOptions, + {int8(), int8()}, + "1", + float64()}, + {{kSubstraitRoundingFunctionsUri, "floor"}, + {"3.1"}, + kNoOptions, + {float64()}, + "3", + float64()}, + {{kSubstraitRoundingFunctionsUri, "ceil"}, + {"3.1"}, + kNoOptions, + {float64()}, + "4", + float64()}}; CheckValidTestCases(valid_test_cases); } diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 39e6f278fb1..36ec45f96a2 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -235,12 +235,14 @@ include_directories(SYSTEM ${NUMPY_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS} src) if(ARROW_PROTOBUF_USE_SHARED) find_package(Protobuf) include_directories(${PROTOBUF_INCLUDE_DIRS}) - get_property(dirs DIRECTORY PROPERTY INCLUDE_DIRECTORIES) + get_property(dirs + DIRECTORY + PROPERTY INCLUDE_DIRECTORIES) foreach(dir ${dirs}) message("dir='${dir}'") endforeach() get_cmake_property(_variableNames VARIABLES) - foreach (_variableName ${_variableNames}) + foreach(_variableName ${_variableNames}) message("${_variableName}=${${_variableName}}") endforeach() endif()