From 9517969690bd5fbc7e6189f3f795be9bad6232d7 Mon Sep 17 00:00:00 2001 From: Alexey Bakharew Date: Fri, 11 Jun 2021 19:14:29 +0300 Subject: [PATCH 1/7] gauss decay --- .gitignore | 1 + arangod/Aql/AqlFunctionFeature.cpp | 3 + arangod/Aql/Functions.cpp | 99 ++++++++++++++++++ arangod/Aql/Functions.h | 9 ++ tests/Aql/DecaysFunctionTest.cpp | 162 +++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + 6 files changed, 275 insertions(+) create mode 100644 tests/Aql/DecaysFunctionTest.cpp diff --git a/.gitignore b/.gitignore index 0a6074d9a83c..3231deced673 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ build *.deb *.rpm +*.user .DS_Store *.swp diff --git a/arangod/Aql/AqlFunctionFeature.cpp b/arangod/Aql/AqlFunctionFeature.cpp index bf68831bf25e..d583843efa22 100644 --- a/arangod/Aql/AqlFunctionFeature.cpp +++ b/arangod/Aql/AqlFunctionFeature.cpp @@ -314,6 +314,9 @@ void AqlFunctionFeature::addListFunctions() { add({"REPLACE_NTH", ".,.,.|.", flags, &Functions::ReplaceNth}); add({"INTERLEAVE", ".,.|+", flags, &Functions::Interleave}); + add({"GAUSS_DECAY", ".,.,.,.,.,", flags, &Functions::GaussDecay}); + add({"EXP_DECAY", ".,.,.,.,.,", flags, &Functions::ExpDecay}); + add({"LINEAR_DECAY", ".,.,.,.,.,", flags, &Functions::LinearDecay}); // special flags: // CALL and APPLY will always run on the coordinator and are not deterministic // and not cacheable, as we don't know what function is actually gonna be diff --git a/arangod/Aql/Functions.cpp b/arangod/Aql/Functions.cpp index 88a25c62d98d..bedbdeffd500 100644 --- a/arangod/Aql/Functions.cpp +++ b/arangod/Aql/Functions.cpp @@ -8848,6 +8848,105 @@ AqlValue Functions::MakeDistributeGraphInput(arangodb::aql::ExpressionContext* e return AqlValue{input}; } +double gauss_decay_f(const double arg, const double origin, const double scale, const double offset = 0, const double decay = 0.5) { + + double sigma_sqr = - (scale * scale) / (2 * std::log(decay)); + double val = std::exp(- (std::pow(std::max(0.0, std::fabs(arg - origin) - offset), 2)) / (2 * sigma_sqr)); + return val > decay ? val : decay; +} + +AqlValue Functions::GaussDecay(arangodb::aql::ExpressionContext* expressionContext, + AstNode const& node, + VPackFunctionParameters const& parameters) { + + AqlValue const& arg_value = extractFunctionParameterValue(parameters, 0); + AqlValue const& origin_value = extractFunctionParameterValue(parameters, 1); + AqlValue const& scale_value = extractFunctionParameterValue(parameters, 2); + AqlValue const& offset_value = extractFunctionParameterValue(parameters, 3); + AqlValue const& decay_value = extractFunctionParameterValue(parameters, 4); + + // extract AQL function name + auto const* impl = static_cast(node.getData()); + TRI_ASSERT(impl != nullptr); + + // check type of arguments + if ((!arg_value.isRange() && !arg_value.isArray() && !arg_value.isNumber()) || + !origin_value.isNumber() || !scale_value.isNumber() || + !offset_value.isNumber() || !decay_value.isNumber()) { + + registerInvalidArgumentWarning(expressionContext, impl->name.c_str()); + return AqlValue(AqlValueHintNull()); + } + + // extracting values + bool failed; + bool error = false; + double origin = origin_value.toDouble(failed); + error |= failed; + double scale = scale_value.toDouble(failed); + error |= failed; + double offset = offset_value.toDouble(failed); + error |= failed; + double decay = decay_value.toDouble(failed); + error |= failed; + + if (error) { + registerWarning(expressionContext, impl->name.c_str(), TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + return AqlValue(AqlValueHintNull()); + } + + // check that parameters are correct + if (origin < 0 || scale < 0 || offset < 0 || decay < 0) { + registerWarning(expressionContext, impl->name.c_str(), TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE); + return AqlValue(AqlValueHintNull()); + } + + // argument is number + if (arg_value.isNumber()) { + double arg = arg_value.slice().getNumber(); + double func_res = gauss_decay_f(arg, origin, scale, offset, decay); + return ::numberValue(func_res, true); + } else { + // argument is array or range + auto* trx = &expressionContext->trx(); + AqlValueMaterializer materializer(&trx->vpackOptions()); + VPackSlice slice = materializer.slice(arg_value, true); + TRI_ASSERT(slice.isArray()); + + VPackBuilder builder; + { + VPackArrayBuilder array_builder(&builder); + for (VPackSlice curr_arg : VPackArrayIterator(slice)) { + if (!curr_arg.isNumber()) { + registerWarning(expressionContext, impl->name.c_str(), TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + return AqlValue(AqlValueHintNull()); + } + double arg = curr_arg.getNumber(); + double func_res = gauss_decay_f(arg, origin, scale, offset, decay); + builder.add(VPackValue(func_res)); + } + } + + return AqlValue(builder.slice()); + } +} + +AqlValue Functions::ExpDecay(arangodb::aql::ExpressionContext* expressionContext, + AstNode const& node, + VPackFunctionParameters const& parameters) { + // temp plug + AqlValue tmp; + return tmp; +} + +AqlValue Functions::LinearDecay(arangodb::aql::ExpressionContext* expressionContext, + AstNode const& node, + VPackFunctionParameters const& parameters) { + // temp plug + AqlValue tmp; + return tmp; +} + AqlValue Functions::NotImplemented(ExpressionContext* expressionContext, AstNode const&, VPackFunctionParameters const& params) { registerError(expressionContext, "UNKNOWN", TRI_ERROR_NOT_IMPLEMENTED); diff --git a/arangod/Aql/Functions.h b/arangod/Aql/Functions.h index d2c7d16ce751..5bb99d76b714 100644 --- a/arangod/Aql/Functions.h +++ b/arangod/Aql/Functions.h @@ -532,6 +532,15 @@ struct Functions { static AqlValue MakeDistributeGraphInput(arangodb::aql::ExpressionContext*, AstNode const&, VPackFunctionParameters const&); + static AqlValue GaussDecay(arangodb::aql::ExpressionContext*, + AstNode const&, VPackFunctionParameters const&); + + static AqlValue ExpDecay(arangodb::aql::ExpressionContext*, + AstNode const&, VPackFunctionParameters const&); + + static AqlValue LinearDecay(arangodb::aql::ExpressionContext*, + AstNode const&, VPackFunctionParameters const&); + /// @brief dummy function that will only throw an error when called static AqlValue NotImplemented(arangodb::aql::ExpressionContext*, AstNode const&, VPackFunctionParameters const&); diff --git a/tests/Aql/DecaysFunctionTest.cpp b/tests/Aql/DecaysFunctionTest.cpp new file mode 100644 index 000000000000..aac768cb8f30 --- /dev/null +++ b/tests/Aql/DecaysFunctionTest.cpp @@ -0,0 +1,162 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2014-2020 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Alexey Bakharew +//////////////////////////////////////////////////////////////////////////////// + +#include "gtest/gtest.h" + +#include "fakeit.hpp" + +#include + +#include "Aql/AstNode.h" +#include "Aql/ExpressionContext.h" +#include "Aql/Function.h" +#include "Aql/Functions.h" +#include "Containers/SmallVector.h" +#include "Transaction/Context.h" +#include "Transaction/Methods.h" +#include "IResearch/IResearchQueryCommon.h" +#include +#include +#include +#include +#include + +using namespace arangodb; +using namespace arangodb::aql; +using namespace arangodb::containers; + +namespace { + + +// helper function +SmallVector create_arg_vec(const VPackSlice slice) { + + SmallVector::allocator_type::arena_type arena; + SmallVector params{arena}; + + for (const auto arg : VPackArrayIterator(slice)) { + params.emplace_back(AqlValue(arg)); + } + + return params; +} + +void expect_eq_slices(const VPackSlice actual_slice, const VPackSlice expected_slice) { + if (actual_slice.isArray() && expected_slice.isArray()) { + VPackValueLength actual_size = actual_slice.length(); + VPackValueLength expected_size = actual_slice.length(); + ASSERT_EQ(actual_size, expected_size); + + double lhs, rhs; + for(VPackValueLength i = 0; i < actual_size; ++i) { + lhs = actual_slice.at(i).getNumber(); + rhs = actual_slice.at(i).getNumber(); + ASSERT_DOUBLE_EQ(lhs, rhs); + } + } else if (actual_slice.isNumber() && expected_slice.isNumber()) { + double lhs = actual_slice.getNumber(); + double rhs = expected_slice.getNumber(); + ASSERT_DOUBLE_EQ(lhs, rhs); + } else { + ASSERT_TRUE(false); + } + + return; +} + +AqlValue evaluate_gauss(const SmallVector params) { + fakeit::Mock expressionContextMock; + ExpressionContext& expressionContext = expressionContextMock.get(); + fakeit::When(Method(expressionContextMock, registerWarning)).AlwaysDo([](ErrorCode, char const*){ }); + + VPackOptions options; + fakeit::Mock trxCtxMock; + fakeit::When(Method(trxCtxMock, getVPackOptions)).AlwaysReturn(&options); + transaction::Context& trxCtx = trxCtxMock.get(); + + fakeit::Mock trxMock; + fakeit::When(Method(trxMock, transactionContextPtr)).AlwaysReturn(&trxCtx); + fakeit::When(Method(trxMock, vpackOptions)).AlwaysReturn(options); + transaction::Methods& trx = trxMock.get(); + + fakeit::When(Method(expressionContextMock, trx)).AlwaysDo([&trx]() -> transaction::Methods& { + return trx; + }); + + arangodb::aql::Function f("GAUSS_DECAY", &Functions::GaussDecay); + arangodb::aql::AstNode node(NODE_TYPE_FCALL); + node.setData(static_cast(&f)); + + return Functions::GaussDecay(&expressionContext, node, params); +} + +void assertGauss(char const* expected, char const* args) { + + // get slice for expected value + auto const expected_json = VPackParser::fromJson(expected); + auto const expected_slice = expected_json->slice(); + ASSERT_TRUE(expected_slice.isArray() || expected_slice.isNumber()); + + // get slice for args value + auto const args_json = VPackParser::fromJson(args); + auto const args_slice = args_json->slice(); + ASSERT_TRUE(args_slice.isArray()); + + // create params vector from args slice + SmallVector params = create_arg_vec(args_slice); + + // evaluate + auto const actual_value = evaluate_gauss(params); + ASSERT_TRUE(actual_value.isNumber() || actual_value.isArray()); + + // check equality + expect_eq_slices(actual_value.slice(), expected_slice); + return; +} + +void assertGaussFail(char const* args) { + // get slice for args value + auto const args_json = VPackParser::fromJson(args); + auto const args_slice = args_json->slice(); + ASSERT_TRUE(args_slice.isArray()); + + // create params vector from args slice + SmallVector params = create_arg_vec(args_slice); + + ASSERT_TRUE(evaluate_gauss(params).isNull(false)); +} + +TEST(GaussDecayFunctionTest, test) { + assertGauss("0.5", "[20, 40, 5, 5, 0.5]"); + assertGauss("1", "[41, 40, 5, 5, 0.5]"); + assertGauss("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]"); + assertGauss("1.0", "[40, 40, 5, 5, 0.5]"); + assertGauss("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]"); + assertGauss("0.2715403018822964", "[49.9889, 49.987, 0.001, 0.001, 0.2]"); + assertGauss("0.1", "[-10, 40, 5, 0, 0.1]"); + assertGaussFail("[30, 40, 5]"); + assertGaussFail("[30, 40, 5, 100]"); + assertGaussFail("[30, 40, 5, 100, -100]"); +} + +} // namespase diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7e93b9b5f366..0ec13848ceda 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -157,6 +157,7 @@ set(ARANGODB_TESTS_SOURCES Aql/VelocyPackHelper.cpp Aql/WaitingExecutionBlockMock.cpp Aql/WindowExecutorTest.cpp + Aql/DecaysFunctionTest.cpp AsyncAgencyComm/AsyncAgencyCommTest.cpp Auth/UserManagerTest.cpp Basics/conversions-test.cpp From 15a4a16eac92fa533d1a8c240533e08d3202a86d Mon Sep 17 00:00:00 2001 From: Alexey Bakharew Date: Tue, 15 Jun 2021 20:24:52 +0300 Subject: [PATCH 2/7] 3 functions are ready --- arangod/Aql/Functions.cpp | 88 ++++++++++++------ tests/Aql/DecaysFunctionTest.cpp | 149 +++++++++++++++++++++++++------ 2 files changed, 185 insertions(+), 52 deletions(-) diff --git a/arangod/Aql/Functions.cpp b/arangod/Aql/Functions.cpp index bedbdeffd500..f447e5228554 100644 --- a/arangod/Aql/Functions.cpp +++ b/arangod/Aql/Functions.cpp @@ -1313,6 +1313,13 @@ Result parseShape(ExpressionContext* exprCtx, } } +std::string getFunctionName(const AstNode& node) { + + auto const* impl = static_cast(node.getData()); + TRI_ASSERT(impl != nullptr); + return impl->name; +} + } // namespace namespace arangodb { @@ -8848,16 +8855,11 @@ AqlValue Functions::MakeDistributeGraphInput(arangodb::aql::ExpressionContext* e return AqlValue{input}; } -double gauss_decay_f(const double arg, const double origin, const double scale, const double offset = 0, const double decay = 0.5) { - - double sigma_sqr = - (scale * scale) / (2 * std::log(decay)); - double val = std::exp(- (std::pow(std::max(0.0, std::fabs(arg - origin) - offset), 2)) / (2 * sigma_sqr)); - return val > decay ? val : decay; -} - -AqlValue Functions::GaussDecay(arangodb::aql::ExpressionContext* expressionContext, - AstNode const& node, - VPackFunctionParameters const& parameters) { +template +AqlValue calc_function(arangodb::aql::ExpressionContext* expressionContext, + AstNode const& node, + VPackFunctionParameters const& parameters, + F lambda_func) { AqlValue const& arg_value = extractFunctionParameterValue(parameters, 0); AqlValue const& origin_value = extractFunctionParameterValue(parameters, 1); @@ -8865,16 +8867,12 @@ AqlValue Functions::GaussDecay(arangodb::aql::ExpressionContext* expressionConte AqlValue const& offset_value = extractFunctionParameterValue(parameters, 3); AqlValue const& decay_value = extractFunctionParameterValue(parameters, 4); - // extract AQL function name - auto const* impl = static_cast(node.getData()); - TRI_ASSERT(impl != nullptr); - // check type of arguments if ((!arg_value.isRange() && !arg_value.isArray() && !arg_value.isNumber()) || !origin_value.isNumber() || !scale_value.isNumber() || !offset_value.isNumber() || !decay_value.isNumber()) { - registerInvalidArgumentWarning(expressionContext, impl->name.c_str()); + registerInvalidArgumentWarning(expressionContext, getFunctionName(node).c_str()); return AqlValue(AqlValueHintNull()); } @@ -8891,20 +8889,23 @@ AqlValue Functions::GaussDecay(arangodb::aql::ExpressionContext* expressionConte error |= failed; if (error) { - registerWarning(expressionContext, impl->name.c_str(), TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + registerWarning(expressionContext, getFunctionName(node).c_str(), TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); return AqlValue(AqlValueHintNull()); } // check that parameters are correct - if (origin < 0 || scale < 0 || offset < 0 || decay < 0) { - registerWarning(expressionContext, impl->name.c_str(), TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE); + if (scale <= 0 || offset < 0 || decay <= 0 || decay >= 1) { + registerWarning(expressionContext, getFunctionName(node).c_str(), TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE); return AqlValue(AqlValueHintNull()); } + // get lambda for further calculation + auto f = lambda_func(origin, scale, offset, decay); + // argument is number if (arg_value.isNumber()) { double arg = arg_value.slice().getNumber(); - double func_res = gauss_decay_f(arg, origin, scale, offset, decay); + double func_res = f(arg); return ::numberValue(func_res, true); } else { // argument is array or range @@ -8918,11 +8919,11 @@ AqlValue Functions::GaussDecay(arangodb::aql::ExpressionContext* expressionConte VPackArrayBuilder array_builder(&builder); for (VPackSlice curr_arg : VPackArrayIterator(slice)) { if (!curr_arg.isNumber()) { - registerWarning(expressionContext, impl->name.c_str(), TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + registerWarning(expressionContext, getFunctionName(node).c_str(), TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); return AqlValue(AqlValueHintNull()); } double arg = curr_arg.getNumber(); - double func_res = gauss_decay_f(arg, origin, scale, offset, decay); + double func_res = f(arg); builder.add(VPackValue(func_res)); } } @@ -8931,20 +8932,53 @@ AqlValue Functions::GaussDecay(arangodb::aql::ExpressionContext* expressionConte } } +AqlValue Functions::GaussDecay(arangodb::aql::ExpressionContext* expressionContext, + AstNode const& node, + VPackFunctionParameters const& parameters) { + + auto gauss_decay_f = [](const double origin, const double scale, const double offset, const double decay) { + double sigma_sqr = - (scale * scale) / (2 * std::log(decay)); + return [=](double arg) { + double max = std::max(0.0, std::fabs(arg - origin) - offset); + double numerator = max * max; + double val = std::exp(- numerator / (2 * sigma_sqr)); + return val > decay ? val : decay; + }; + }; + + return calc_function(expressionContext, node, parameters, gauss_decay_f); +} + AqlValue Functions::ExpDecay(arangodb::aql::ExpressionContext* expressionContext, AstNode const& node, VPackFunctionParameters const& parameters) { - // temp plug - AqlValue tmp; - return tmp; + + auto exp_decay_f = [](const double origin, const double scale, const double offset, const double decay) { + double lambda = std::log(decay) / scale; + return [=](double arg) { + double numerator = lambda * std::max(0.0, std::abs(arg - origin) - offset); + double val = std::exp(numerator); + return val > decay ? val : decay; + }; + }; + + return calc_function(expressionContext, node, parameters, exp_decay_f); } AqlValue Functions::LinearDecay(arangodb::aql::ExpressionContext* expressionContext, AstNode const& node, VPackFunctionParameters const& parameters) { - // temp plug - AqlValue tmp; - return tmp; + + auto linear_decay_f = [](const double origin, const double scale, const double offset, const double decay) { + double s = scale / (1.0 - decay); + return [=](double arg) { + double max = std::max(0.0, std::fabs(arg - origin) - offset); + double val = std::max((s - max) / s, 0.0); + return val > decay ? val : decay; + }; + }; + + return calc_function(expressionContext, node, parameters, linear_decay_f); } AqlValue Functions::NotImplemented(ExpressionContext* expressionContext, AstNode const&, diff --git a/tests/Aql/DecaysFunctionTest.cpp b/tests/Aql/DecaysFunctionTest.cpp index aac768cb8f30..e43be2d5296d 100644 --- a/tests/Aql/DecaysFunctionTest.cpp +++ b/tests/Aql/DecaysFunctionTest.cpp @@ -47,8 +47,7 @@ using namespace arangodb::containers; namespace { - -// helper function +// helper functions SmallVector create_arg_vec(const VPackSlice slice) { SmallVector::allocator_type::arena_type arena; @@ -62,6 +61,9 @@ SmallVector create_arg_vec(const VPackSlice slice) { } void expect_eq_slices(const VPackSlice actual_slice, const VPackSlice expected_slice) { + ASSERT_TRUE((actual_slice.isNumber() && expected_slice.isNumber()) || + (actual_slice.isArray() && expected_slice.isArray())); + if (actual_slice.isArray() && expected_slice.isArray()) { VPackValueLength actual_size = actual_slice.length(); VPackValueLength expected_size = actual_slice.length(); @@ -73,18 +75,22 @@ void expect_eq_slices(const VPackSlice actual_slice, const VPackSlice expected_s rhs = actual_slice.at(i).getNumber(); ASSERT_DOUBLE_EQ(lhs, rhs); } - } else if (actual_slice.isNumber() && expected_slice.isNumber()) { + } else { double lhs = actual_slice.getNumber(); double rhs = expected_slice.getNumber(); ASSERT_DOUBLE_EQ(lhs, rhs); - } else { - ASSERT_TRUE(false); } return; } -AqlValue evaluate_gauss(const SmallVector params) { +enum class FunctionType { + Gauss = 0, + Exp, + Linear +}; + +AqlValue evaluateDecayFunction(const SmallVector& params, const FunctionType& type ) { fakeit::Mock expressionContextMock; ExpressionContext& expressionContext = expressionContextMock.get(); fakeit::When(Method(expressionContextMock, registerWarning)).AlwaysDo([](ErrorCode, char const*){ }); @@ -103,14 +109,27 @@ AqlValue evaluate_gauss(const SmallVector params) { return trx; }); - arangodb::aql::Function f("GAUSS_DECAY", &Functions::GaussDecay); arangodb::aql::AstNode node(NODE_TYPE_FCALL); - node.setData(static_cast(&f)); - - return Functions::GaussDecay(&expressionContext, node, params); + switch(type) { + case FunctionType::Gauss: { + arangodb::aql::Function f("GAUSS_DECAY", &Functions::GaussDecay); + node.setData(static_cast(&f)); + return Functions::GaussDecay(&expressionContext, node, params); + } + case FunctionType::Exp: { + arangodb::aql::Function f("EXP_DECAY", &Functions::ExpDecay); + node.setData(static_cast(&f)); + return Functions::ExpDecay(&expressionContext, node, params); + } + case FunctionType::Linear: { + arangodb::aql::Function f("LINEAR_DECAY", &Functions::LinearDecay); + node.setData(static_cast(&f)); + return Functions::LinearDecay(&expressionContext, node, params); + } + } } -void assertGauss(char const* expected, char const* args) { +void assertDecayFunction(char const* expected, char const* args, const FunctionType& type) { // get slice for expected value auto const expected_json = VPackParser::fromJson(expected); @@ -126,15 +145,15 @@ void assertGauss(char const* expected, char const* args) { SmallVector params = create_arg_vec(args_slice); // evaluate - auto const actual_value = evaluate_gauss(params); - ASSERT_TRUE(actual_value.isNumber() || actual_value.isArray()); + auto const actual_value = evaluateDecayFunction(params, type); // check equality expect_eq_slices(actual_value.slice(), expected_slice); + return; } -void assertGaussFail(char const* args) { +void assertDecayFunctionFail(char const* args, const FunctionType& type) { // get slice for args value auto const args_json = VPackParser::fromJson(args); auto const args_slice = args_json->slice(); @@ -143,20 +162,100 @@ void assertGaussFail(char const* args) { // create params vector from args slice SmallVector params = create_arg_vec(args_slice); - ASSERT_TRUE(evaluate_gauss(params).isNull(false)); + ASSERT_TRUE(evaluateDecayFunction(params, type).isNull(false)); } TEST(GaussDecayFunctionTest, test) { - assertGauss("0.5", "[20, 40, 5, 5, 0.5]"); - assertGauss("1", "[41, 40, 5, 5, 0.5]"); - assertGauss("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]"); - assertGauss("1.0", "[40, 40, 5, 5, 0.5]"); - assertGauss("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]"); - assertGauss("0.2715403018822964", "[49.9889, 49.987, 0.001, 0.001, 0.2]"); - assertGauss("0.1", "[-10, 40, 5, 0, 0.1]"); - assertGaussFail("[30, 40, 5]"); - assertGaussFail("[30, 40, 5, 100]"); - assertGaussFail("[30, 40, 5, 100, -100]"); + // expecting 1 + assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", FunctionType::Gauss); + assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", FunctionType::Gauss); + assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", FunctionType::Gauss); + + assertDecayFunction("1.0", "[49.987, 49.987, 0.000000000000000001, 0.001, 0.2]", FunctionType::Gauss); + + // with offset=0 + assertDecayFunction("0.9840344433634576", "[1, 0, 10, 0, 0.2]", FunctionType::Gauss); + assertDecayFunction("0.9376509540020155", "[2, 0, 10, 0, 0.2]", FunctionType::Gauss); + assertDecayFunction("0.668740304976422", "[5, 0, 10, 0, 0.2]", FunctionType::Gauss); + assertDecayFunction("0.21316171604122283", "[9.8, 0, 10, 0, 0.2]", FunctionType::Gauss); + + // with scale=0.001 (almost zero) + // also test array input and array output + assertDecayFunction("[1.0, 1.0, 1e0, 1, 2e-1]", "[[0,1,9.8,10,11], 0, 0.001, 10, 0.2]", FunctionType::Gauss); + + // test array input and array output + assertDecayFunction("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", FunctionType::Gauss); + + // expecting decay value + assertDecayFunction("0.5", "[20, 40, 5, 5, 0.5]", FunctionType::Gauss); + assertDecayFunction("0.2715403018822964", "[49.9889, 49.987, 0.001, 0.001, 0.2]", FunctionType::Gauss); + assertDecayFunction("0.1", "[-10, 40, 5, 0, 0.1]", FunctionType::Gauss); + + // incorrect input + assertDecayFunctionFail("[10, 10, 0.0, 2, 0.2]", FunctionType::Gauss); + assertDecayFunctionFail("[30, 40, 5]", FunctionType::Gauss); + assertDecayFunctionFail("[30, 40, 5, 100]", FunctionType::Gauss); + assertDecayFunctionFail("[30, 40, 5, 100, -100]", FunctionType::Gauss); + assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", FunctionType::Gauss); +} + +TEST(ExpDecayFunctionTest, test) { + // expecting 1 + assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", FunctionType::Exp); + assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", FunctionType::Exp); + assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", FunctionType::Exp); + + // with offset=0 + assertDecayFunction("0.8513399225207846", "[1, 0, 10, 0, 0.2]", FunctionType::Exp); + assertDecayFunction("0.7247796636776955", "[2, 0, 10, 0, 0.2]", FunctionType::Exp); + assertDecayFunction("0.447213595499958", "[5, 0, 10, 0, 0.2]", FunctionType::Exp); + assertDecayFunction("0.20654248397928862", "[9.8, 0, 10, 0, 0.2]", FunctionType::Exp); + + // with scale=0.001 (almost zero) + assertDecayFunction("1", "[0, 0, 0.001, 10, 0.2]", FunctionType::Exp); + assertDecayFunction("1", "[1, 0, 0.001, 10, 0.2]", FunctionType::Exp); + assertDecayFunction("1", "[9.8, 0, 0.001, 10, 0.2]", FunctionType::Exp); + assertDecayFunction("1", "[10, 0, 0.001, 10, 0.2]", FunctionType::Exp); + assertDecayFunction("0.2", "[11, 0, 0.001, 10, 0.2]", FunctionType::Exp); + + // expecting decay value + assertDecayFunction("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", FunctionType::Exp); + assertDecayFunction("0.2", "[49.9889, 50, 0.001, 0.001, 0.2]", FunctionType::Exp); + assertDecayFunction("0.1", "[-10, 40, 5, 0, 0.1]", FunctionType::Exp); + + // incorrect input + assertDecayFunctionFail("[10, 10, 3, 2, 1]", FunctionType::Exp); + assertDecayFunctionFail("[30, 40, 5]", FunctionType::Exp); + assertDecayFunctionFail("[30, 40, 5, 100]", FunctionType::Exp); + assertDecayFunctionFail("[30, 40, 5, 100, -100]", FunctionType::Exp); + assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", FunctionType::Exp); +} + +TEST(LinDecayFunctionTest, test) { + // expecting 1 + assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", FunctionType::Exp); + assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", FunctionType::Exp); + assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", FunctionType::Exp); + + // with offset=0 + assertDecayFunction("0.92", "[1, 0, 10, 0, 0.2]", FunctionType::Linear); + assertDecayFunction("0.84", "[2, 0, 10, 0, 0.2]", FunctionType::Linear); + assertDecayFunction("0.6", "[5, 0, 10, 0, 0.2]", FunctionType::Linear); + assertDecayFunction("0.21599999999999994", "[9.8, 0, 10, 0, 0.2]", FunctionType::Linear); + + // with scale=0.001 (almost zero) + assertDecayFunction("[1,1,1,1,0.2]", "[[0,1,5,9.8,10,11], 0, 10, 0, 0.2]", FunctionType::Linear); + + // expecting decay value + assertDecayFunction("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", FunctionType::Exp); + assertDecayFunction("0.2", "[49.9889, 50, 0.001, 0.001, 0.2]", FunctionType::Exp); + assertDecayFunction("0.1", "[-10, 40, 5, 0, 0.1]", FunctionType::Exp); + + // incorrect input + assertDecayFunctionFail("[30, 40, 5]", FunctionType::Exp); + assertDecayFunctionFail("[30, 40, 5, 100]", FunctionType::Exp); + assertDecayFunctionFail("[30, 40, 5, 100, -100]", FunctionType::Exp); + assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", FunctionType::Exp); } } // namespase From 00a52c12a117dfd13a8f020a17f40f7b30ea8c0e Mon Sep 17 00:00:00 2001 From: Alexey Bakharew Date: Tue, 15 Jun 2021 22:42:30 +0300 Subject: [PATCH 3/7] fix for win build --- tests/Aql/DecaysFunctionTest.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Aql/DecaysFunctionTest.cpp b/tests/Aql/DecaysFunctionTest.cpp index e43be2d5296d..f2b2f508f9f5 100644 --- a/tests/Aql/DecaysFunctionTest.cpp +++ b/tests/Aql/DecaysFunctionTest.cpp @@ -126,6 +126,9 @@ AqlValue evaluateDecayFunction(const SmallVector& params, const Functi node.setData(static_cast(&f)); return Functions::LinearDecay(&expressionContext, node, params); } + default: { + return AqlValue(AqlValueHintNull()); + } } } From 4161a8f74fd14acfde6a7eab6df8e495afea4386 Mon Sep 17 00:00:00 2001 From: Alexey Bakharew Date: Wed, 16 Jun 2021 14:15:02 +0300 Subject: [PATCH 4/7] changes after code review --- arangod/Aql/Functions.cpp | 115 +++++++++------- tests/Aql/DecaysFunctionTest.cpp | 218 ++++++++++++++++--------------- 2 files changed, 180 insertions(+), 153 deletions(-) diff --git a/arangod/Aql/Functions.cpp b/arangod/Aql/Functions.cpp index f447e5228554..0de953b4d354 100644 --- a/arangod/Aql/Functions.cpp +++ b/arangod/Aql/Functions.cpp @@ -1313,8 +1313,9 @@ Result parseShape(ExpressionContext* exprCtx, } } -std::string getFunctionName(const AstNode& node) { +irs::string_ref getFunctionName(const AstNode& node) { + TRI_ASSERT(aql::NODE_TYPE_FCALL == node.type); auto const* impl = static_cast(node.getData()); TRI_ASSERT(impl != nullptr); return impl->name; @@ -8856,75 +8857,82 @@ AqlValue Functions::MakeDistributeGraphInput(arangodb::aql::ExpressionContext* e } template -AqlValue calc_function(arangodb::aql::ExpressionContext* expressionContext, - AstNode const& node, - VPackFunctionParameters const& parameters, - F lambda_func) { +AqlValue decayFuncImpl(arangodb::aql::ExpressionContext* expressionContext, + AstNode const& node, + VPackFunctionParameters const& parameters, + F&& decayFuncFactory) { - AqlValue const& arg_value = extractFunctionParameterValue(parameters, 0); - AqlValue const& origin_value = extractFunctionParameterValue(parameters, 1); - AqlValue const& scale_value = extractFunctionParameterValue(parameters, 2); - AqlValue const& offset_value = extractFunctionParameterValue(parameters, 3); - AqlValue const& decay_value = extractFunctionParameterValue(parameters, 4); + AqlValue const& argValue = extractFunctionParameterValue(parameters, 0); + AqlValue const& originValue = extractFunctionParameterValue(parameters, 1); + AqlValue const& scaleValue = extractFunctionParameterValue(parameters, 2); + AqlValue const& offsetValue = extractFunctionParameterValue(parameters, 3); + AqlValue const& decayValue = extractFunctionParameterValue(parameters, 4); // check type of arguments - if ((!arg_value.isRange() && !arg_value.isArray() && !arg_value.isNumber()) || - !origin_value.isNumber() || !scale_value.isNumber() || - !offset_value.isNumber() || !decay_value.isNumber()) { + if ((!argValue.isRange() && !argValue.isArray() && !argValue.isNumber()) || + !originValue.isNumber() || !scaleValue.isNumber() || + !offsetValue.isNumber() || !decayValue.isNumber()) { - registerInvalidArgumentWarning(expressionContext, getFunctionName(node).c_str()); + registerInvalidArgumentWarning(expressionContext, + getFunctionName(node).c_str()); return AqlValue(AqlValueHintNull()); } // extracting values bool failed; bool error = false; - double origin = origin_value.toDouble(failed); + double origin = originValue.toDouble(failed); error |= failed; - double scale = scale_value.toDouble(failed); + double scale = scaleValue.toDouble(failed); error |= failed; - double offset = offset_value.toDouble(failed); + double offset = offsetValue.toDouble(failed); error |= failed; - double decay = decay_value.toDouble(failed); + double decay = decayValue.toDouble(failed); error |= failed; if (error) { - registerWarning(expressionContext, getFunctionName(node).c_str(), TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + registerWarning(expressionContext, + getFunctionName(node).c_str(), + TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); return AqlValue(AqlValueHintNull()); } // check that parameters are correct if (scale <= 0 || offset < 0 || decay <= 0 || decay >= 1) { - registerWarning(expressionContext, getFunctionName(node).c_str(), TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE); + registerWarning(expressionContext, + getFunctionName(node).c_str(), + TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE); return AqlValue(AqlValueHintNull()); } // get lambda for further calculation - auto f = lambda_func(origin, scale, offset, decay); + auto decayFunc = decayFuncFactory(origin, scale, offset, decay); // argument is number - if (arg_value.isNumber()) { - double arg = arg_value.slice().getNumber(); - double func_res = f(arg); - return ::numberValue(func_res, true); + if (argValue.isNumber()) { + double arg = argValue.slice().getNumber(); + double funcRes = decayFunc(arg); + return ::numberValue(funcRes, true); } else { // argument is array or range auto* trx = &expressionContext->trx(); AqlValueMaterializer materializer(&trx->vpackOptions()); - VPackSlice slice = materializer.slice(arg_value, true); + VPackSlice slice = materializer.slice(argValue, true); TRI_ASSERT(slice.isArray()); VPackBuilder builder; { - VPackArrayBuilder array_builder(&builder); - for (VPackSlice curr_arg : VPackArrayIterator(slice)) { - if (!curr_arg.isNumber()) { - registerWarning(expressionContext, getFunctionName(node).c_str(), TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + VPackArrayBuilder arrayBuilder(&builder); + for (VPackSlice currArg : VPackArrayIterator(slice)) { + if (!currArg.isNumber()) { + registerWarning(expressionContext, + getFunctionName(node).c_str(), + TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); return AqlValue(AqlValueHintNull()); } - double arg = curr_arg.getNumber(); - double func_res = f(arg); - builder.add(VPackValue(func_res)); + double arg = currArg.getNumber(); + double funcRes = decayFunc(arg); + builder.add(VPackValue(funcRes)); } } @@ -8933,28 +8941,34 @@ AqlValue calc_function(arangodb::aql::ExpressionContext* expressionContext, } AqlValue Functions::GaussDecay(arangodb::aql::ExpressionContext* expressionContext, - AstNode const& node, - VPackFunctionParameters const& parameters) { + AstNode const& node, + VPackFunctionParameters const& parameters) { - auto gauss_decay_f = [](const double origin, const double scale, const double offset, const double decay) { - double sigma_sqr = - (scale * scale) / (2 * std::log(decay)); + auto gaussDecayFactory = [](const double origin, + const double scale, + const double offset, + const double decay) { + const double sigmaSqr = - (scale * scale) / (2 * std::log(decay)); return [=](double arg) { double max = std::max(0.0, std::fabs(arg - origin) - offset); double numerator = max * max; - double val = std::exp(- numerator / (2 * sigma_sqr)); + double val = std::exp(- numerator / (2 * sigmaSqr)); return val > decay ? val : decay; }; }; - return calc_function(expressionContext, node, parameters, gauss_decay_f); + return decayFuncImpl(expressionContext, node, parameters, gaussDecayFactory); } AqlValue Functions::ExpDecay(arangodb::aql::ExpressionContext* expressionContext, - AstNode const& node, - VPackFunctionParameters const& parameters) { + AstNode const& node, + VPackFunctionParameters const& parameters) { - auto exp_decay_f = [](const double origin, const double scale, const double offset, const double decay) { - double lambda = std::log(decay) / scale; + auto expDecayFactory = [](const double origin, + const double scale, + const double offset, + const double decay) { + const double lambda = std::log(decay) / scale; return [=](double arg) { double numerator = lambda * std::max(0.0, std::abs(arg - origin) - offset); double val = std::exp(numerator); @@ -8962,15 +8976,18 @@ AqlValue Functions::ExpDecay(arangodb::aql::ExpressionContext* expressionContext }; }; - return calc_function(expressionContext, node, parameters, exp_decay_f); + return decayFuncImpl(expressionContext, node, parameters, expDecayFactory); } AqlValue Functions::LinearDecay(arangodb::aql::ExpressionContext* expressionContext, - AstNode const& node, - VPackFunctionParameters const& parameters) { + AstNode const& node, + VPackFunctionParameters const& parameters) { - auto linear_decay_f = [](const double origin, const double scale, const double offset, const double decay) { - double s = scale / (1.0 - decay); + auto linearDecayFactory = [](const double origin, + const double scale, + const double offset, + const double decay) { + const double s = scale / (1.0 - decay); return [=](double arg) { double max = std::max(0.0, std::fabs(arg - origin) - offset); double val = std::max((s - max) / s, 0.0); @@ -8978,7 +8995,7 @@ AqlValue Functions::LinearDecay(arangodb::aql::ExpressionContext* expressionCont }; }; - return calc_function(expressionContext, node, parameters, linear_decay_f); + return decayFuncImpl(expressionContext, node, parameters, linearDecayFactory); } AqlValue Functions::NotImplemented(ExpressionContext* expressionContext, AstNode const&, diff --git a/tests/Aql/DecaysFunctionTest.cpp b/tests/Aql/DecaysFunctionTest.cpp index f2b2f508f9f5..f28c8d8003f6 100644 --- a/tests/Aql/DecaysFunctionTest.cpp +++ b/tests/Aql/DecaysFunctionTest.cpp @@ -48,7 +48,7 @@ using namespace arangodb::containers; namespace { // helper functions -SmallVector create_arg_vec(const VPackSlice slice) { +SmallVector createArgVec(const VPackSlice slice) { SmallVector::allocator_type::arena_type arena; SmallVector params{arena}; @@ -60,24 +60,26 @@ SmallVector create_arg_vec(const VPackSlice slice) { return params; } -void expect_eq_slices(const VPackSlice actual_slice, const VPackSlice expected_slice) { - ASSERT_TRUE((actual_slice.isNumber() && expected_slice.isNumber()) || - (actual_slice.isArray() && expected_slice.isArray())); +void expectEqSlices(const VPackSlice actualSlice, + const VPackSlice expectedSlice) { - if (actual_slice.isArray() && expected_slice.isArray()) { - VPackValueLength actual_size = actual_slice.length(); - VPackValueLength expected_size = actual_slice.length(); - ASSERT_EQ(actual_size, expected_size); + ASSERT_TRUE((actualSlice.isNumber() && expectedSlice.isNumber()) || + (actualSlice.isArray() && expectedSlice.isArray())); + + if (actualSlice.isArray() && expectedSlice.isArray()) { + VPackValueLength actualSize = actualSlice.length(); + VPackValueLength expectedSize = expectedSlice.length(); + ASSERT_EQ(actualSize, expectedSize); double lhs, rhs; - for(VPackValueLength i = 0; i < actual_size; ++i) { - lhs = actual_slice.at(i).getNumber(); - rhs = actual_slice.at(i).getNumber(); + for(VPackValueLength i = 0; i < actualSize; ++i) { + lhs = actualSlice.at(i).getNumber(); + rhs = expectedSlice.at(i).getNumber(); ASSERT_DOUBLE_EQ(lhs, rhs); } } else { - double lhs = actual_slice.getNumber(); - double rhs = expected_slice.getNumber(); + double lhs = actualSlice.getNumber(); + double rhs = expectedSlice.getNumber(); ASSERT_DOUBLE_EQ(lhs, rhs); } @@ -90,7 +92,11 @@ enum class FunctionType { Linear }; -AqlValue evaluateDecayFunction(const SmallVector& params, const FunctionType& type ) { +template +AqlValue evaluateDecayFunction(const SmallVector& params, + F&& decayFunction, + const arangodb::aql::AstNode& node) { + fakeit::Mock expressionContextMock; ExpressionContext& expressionContext = expressionContextMock.get(); fakeit::When(Method(expressionContextMock, registerWarning)).AlwaysDo([](ErrorCode, char const*){ }); @@ -109,156 +115,160 @@ AqlValue evaluateDecayFunction(const SmallVector& params, const Functi return trx; }); - arangodb::aql::AstNode node(NODE_TYPE_FCALL); - switch(type) { - case FunctionType::Gauss: { - arangodb::aql::Function f("GAUSS_DECAY", &Functions::GaussDecay); - node.setData(static_cast(&f)); - return Functions::GaussDecay(&expressionContext, node, params); - } - case FunctionType::Exp: { - arangodb::aql::Function f("EXP_DECAY", &Functions::ExpDecay); - node.setData(static_cast(&f)); - return Functions::ExpDecay(&expressionContext, node, params); - } - case FunctionType::Linear: { - arangodb::aql::Function f("LINEAR_DECAY", &Functions::LinearDecay); - node.setData(static_cast(&f)); - return Functions::LinearDecay(&expressionContext, node, params); - } - default: { - return AqlValue(AqlValueHintNull()); - } - } + return decayFunction(&expressionContext, node, params); } -void assertDecayFunction(char const* expected, char const* args, const FunctionType& type) { +template +void assertDecayFunction(char const* expected, char const* args, + F&& decayFunction, + const arangodb::aql::AstNode& node) { // get slice for expected value - auto const expected_json = VPackParser::fromJson(expected); - auto const expected_slice = expected_json->slice(); - ASSERT_TRUE(expected_slice.isArray() || expected_slice.isNumber()); + auto const expectedJson = VPackParser::fromJson(expected); + auto const expectedSlice = expectedJson->slice(); + ASSERT_TRUE(expectedSlice.isArray() || expectedSlice.isNumber()); // get slice for args value - auto const args_json = VPackParser::fromJson(args); - auto const args_slice = args_json->slice(); - ASSERT_TRUE(args_slice.isArray()); + auto const argsJson = VPackParser::fromJson(args); + auto const argsSlice = argsJson->slice(); + ASSERT_TRUE(argsSlice.isArray()); // create params vector from args slice - SmallVector params = create_arg_vec(args_slice); + SmallVector params = createArgVec(argsSlice); // evaluate - auto const actual_value = evaluateDecayFunction(params, type); + auto const actual_value = evaluateDecayFunction(params, decayFunction, node); // check equality - expect_eq_slices(actual_value.slice(), expected_slice); + expectEqSlices(actual_value.slice(), expectedSlice); return; } -void assertDecayFunctionFail(char const* args, const FunctionType& type) { +template +void assertDecayFunctionFail(char const* args, + F&& decayFunction, + const arangodb::aql::AstNode& node) { // get slice for args value - auto const args_json = VPackParser::fromJson(args); - auto const args_slice = args_json->slice(); - ASSERT_TRUE(args_slice.isArray()); + auto const argsJson = VPackParser::fromJson(args); + auto const argsSlice = argsJson->slice(); + ASSERT_TRUE(argsSlice.isArray()); // create params vector from args slice - SmallVector params = create_arg_vec(args_slice); + SmallVector params = createArgVec(argsSlice); - ASSERT_TRUE(evaluateDecayFunction(params, type).isNull(false)); + ASSERT_TRUE(evaluateDecayFunction(params, decayFunction, node).isNull(false)); } TEST(GaussDecayFunctionTest, test) { + // preparing + arangodb::aql::AstNode node(NODE_TYPE_FCALL); + arangodb::aql::Function f("GAUSS_DECAY", &Functions::GaussDecay); + node.setData(static_cast(&f)); + // expecting 1 - assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", FunctionType::Gauss); - assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", FunctionType::Gauss); - assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", FunctionType::Gauss); + assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", Functions::GaussDecay, node); + assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", Functions::GaussDecay, node); + assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", Functions::GaussDecay, node); - assertDecayFunction("1.0", "[49.987, 49.987, 0.000000000000000001, 0.001, 0.2]", FunctionType::Gauss); + assertDecayFunction("1.0", "[49.987, 49.987, 0.000000000000000001, 0.001, 0.2]", Functions::GaussDecay, node); // with offset=0 - assertDecayFunction("0.9840344433634576", "[1, 0, 10, 0, 0.2]", FunctionType::Gauss); - assertDecayFunction("0.9376509540020155", "[2, 0, 10, 0, 0.2]", FunctionType::Gauss); - assertDecayFunction("0.668740304976422", "[5, 0, 10, 0, 0.2]", FunctionType::Gauss); - assertDecayFunction("0.21316171604122283", "[9.8, 0, 10, 0, 0.2]", FunctionType::Gauss); + assertDecayFunction("0.9840344433634576", "[1, 0, 10, 0, 0.2]", Functions::GaussDecay, node); + assertDecayFunction("0.9376509540020155", "[2, 0, 10, 0, 0.2]", Functions::GaussDecay, node); + assertDecayFunction("0.668740304976422", "[5, 0, 10, 0, 0.2]", Functions::GaussDecay, node); + assertDecayFunction("0.21316171604122283", "[9.8, 0, 10, 0, 0.2]", Functions::GaussDecay, node); // with scale=0.001 (almost zero) // also test array input and array output - assertDecayFunction("[1.0, 1.0, 1e0, 1, 2e-1]", "[[0,1,9.8,10,11], 0, 0.001, 10, 0.2]", FunctionType::Gauss); + assertDecayFunction("[1.0, 1.0, 1e0, 1, 2e-1]", "[[0,1,9.8,10,11], 0, 0.001, 10, 0.2]", Functions::GaussDecay, node); // test array input and array output - assertDecayFunction("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", FunctionType::Gauss); + assertDecayFunction("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", Functions::GaussDecay, node); // expecting decay value - assertDecayFunction("0.5", "[20, 40, 5, 5, 0.5]", FunctionType::Gauss); - assertDecayFunction("0.2715403018822964", "[49.9889, 49.987, 0.001, 0.001, 0.2]", FunctionType::Gauss); - assertDecayFunction("0.1", "[-10, 40, 5, 0, 0.1]", FunctionType::Gauss); + assertDecayFunction("0.5", "[20, 40, 5, 5, 0.5]", Functions::GaussDecay, node); + assertDecayFunction("0.2715403018822964", "[49.9889, 49.987, 0.001, 0.001, 0.2]", Functions::GaussDecay, node); + assertDecayFunction("0.1", "[-10, 40, 5, 0, 0.1]", Functions::GaussDecay, node); // incorrect input - assertDecayFunctionFail("[10, 10, 0.0, 2, 0.2]", FunctionType::Gauss); - assertDecayFunctionFail("[30, 40, 5]", FunctionType::Gauss); - assertDecayFunctionFail("[30, 40, 5, 100]", FunctionType::Gauss); - assertDecayFunctionFail("[30, 40, 5, 100, -100]", FunctionType::Gauss); - assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", FunctionType::Gauss); + assertDecayFunctionFail("[10, 10, 0.0, 2, 0.2]", Functions::GaussDecay, node); + assertDecayFunctionFail("[30, 40, 5]", Functions::GaussDecay, node); + assertDecayFunctionFail("[30, 40, 5, 100]", Functions::GaussDecay, node); + assertDecayFunctionFail("[30, 40, 5, 100, -100]", Functions::GaussDecay, node); + assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", Functions::GaussDecay, node); } TEST(ExpDecayFunctionTest, test) { + + // preparing + arangodb::aql::AstNode node(NODE_TYPE_FCALL); + arangodb::aql::Function f("EXP_DECAY", &Functions::ExpDecay); + node.setData(static_cast(&f)); + // expecting 1 - assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", FunctionType::Exp); - assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", FunctionType::Exp); - assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", FunctionType::Exp); + assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", Functions::ExpDecay, node); + assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", Functions::ExpDecay, node); + assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", Functions::ExpDecay, node); // with offset=0 - assertDecayFunction("0.8513399225207846", "[1, 0, 10, 0, 0.2]", FunctionType::Exp); - assertDecayFunction("0.7247796636776955", "[2, 0, 10, 0, 0.2]", FunctionType::Exp); - assertDecayFunction("0.447213595499958", "[5, 0, 10, 0, 0.2]", FunctionType::Exp); - assertDecayFunction("0.20654248397928862", "[9.8, 0, 10, 0, 0.2]", FunctionType::Exp); + assertDecayFunction("0.8513399225207846", "[1, 0, 10, 0, 0.2]", Functions::ExpDecay, node); + assertDecayFunction("0.7247796636776955", "[2, 0, 10, 0, 0.2]", Functions::ExpDecay, node); + assertDecayFunction("0.447213595499958", "[5, 0, 10, 0, 0.2]", Functions::ExpDecay, node); + assertDecayFunction("0.20654248397928862", "[9.8, 0, 10, 0, 0.2]", Functions::ExpDecay, node); // with scale=0.001 (almost zero) - assertDecayFunction("1", "[0, 0, 0.001, 10, 0.2]", FunctionType::Exp); - assertDecayFunction("1", "[1, 0, 0.001, 10, 0.2]", FunctionType::Exp); - assertDecayFunction("1", "[9.8, 0, 0.001, 10, 0.2]", FunctionType::Exp); - assertDecayFunction("1", "[10, 0, 0.001, 10, 0.2]", FunctionType::Exp); - assertDecayFunction("0.2", "[11, 0, 0.001, 10, 0.2]", FunctionType::Exp); + assertDecayFunction("1", "[0, 0, 0.001, 10, 0.2]", Functions::ExpDecay, node); + assertDecayFunction("1", "[1, 0, 0.001, 10, 0.2]", Functions::ExpDecay, node); + assertDecayFunction("1", "[9.8, 0, 0.001, 10, 0.2]", Functions::ExpDecay, node); + assertDecayFunction("1", "[10, 0, 0.001, 10, 0.2]", Functions::ExpDecay, node); + assertDecayFunction("0.2", "[11, 0, 0.001, 10, 0.2]", Functions::ExpDecay, node); // expecting decay value - assertDecayFunction("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", FunctionType::Exp); - assertDecayFunction("0.2", "[49.9889, 50, 0.001, 0.001, 0.2]", FunctionType::Exp); - assertDecayFunction("0.1", "[-10, 40, 5, 0, 0.1]", FunctionType::Exp); + assertDecayFunction("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", Functions::ExpDecay, node); + assertDecayFunction("0.2", "[49.9889, 50, 0.001, 0.001, 0.2]", Functions::ExpDecay, node); + assertDecayFunction("0.1", "[-10, 40, 5, 0, 0.1]", Functions::ExpDecay, node); // incorrect input - assertDecayFunctionFail("[10, 10, 3, 2, 1]", FunctionType::Exp); - assertDecayFunctionFail("[30, 40, 5]", FunctionType::Exp); - assertDecayFunctionFail("[30, 40, 5, 100]", FunctionType::Exp); - assertDecayFunctionFail("[30, 40, 5, 100, -100]", FunctionType::Exp); - assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", FunctionType::Exp); + assertDecayFunctionFail("[10, 10, 3, 2, 1]", Functions::ExpDecay, node); + assertDecayFunctionFail("[30, 40, 5]", Functions::ExpDecay, node); + assertDecayFunctionFail("[30, 40, 5, 100]", Functions::ExpDecay, node); + assertDecayFunctionFail("[30, 40, 5, 100, -100]", Functions::ExpDecay, node); + assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", Functions::ExpDecay, node); } TEST(LinDecayFunctionTest, test) { + + // preparing + arangodb::aql::AstNode node(NODE_TYPE_FCALL); + arangodb::aql::Function f("LINEAR_DECAY", &Functions::LinearDecay); + node.setData(static_cast(&f)); + // expecting 1 - assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", FunctionType::Exp); - assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", FunctionType::Exp); - assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", FunctionType::Exp); + assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", Functions::LinearDecay, node); + assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", Functions::LinearDecay, node); + assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", Functions::LinearDecay, node); // with offset=0 - assertDecayFunction("0.92", "[1, 0, 10, 0, 0.2]", FunctionType::Linear); - assertDecayFunction("0.84", "[2, 0, 10, 0, 0.2]", FunctionType::Linear); - assertDecayFunction("0.6", "[5, 0, 10, 0, 0.2]", FunctionType::Linear); - assertDecayFunction("0.21599999999999994", "[9.8, 0, 10, 0, 0.2]", FunctionType::Linear); + assertDecayFunction("0.92", "[1, 0, 10, 0, 0.2]", Functions::LinearDecay, node); + assertDecayFunction("0.84", "[2, 0, 10, 0, 0.2]", Functions::LinearDecay, node); + assertDecayFunction("0.6", "[5, 0, 10, 0, 0.2]", Functions::LinearDecay, node); + assertDecayFunction("0.21599999999999994", "[9.8, 0, 10, 0, 0.2]", Functions::LinearDecay, node); // with scale=0.001 (almost zero) - assertDecayFunction("[1,1,1,1,0.2]", "[[0,1,5,9.8,10,11], 0, 10, 0, 0.2]", FunctionType::Linear); + // also test array input and array output + assertDecayFunction("[1,1,1,1,0.2]", "[[0,1,9.8,10,11], 0, 0.001, 10, 0.2]", Functions::LinearDecay, node); // expecting decay value - assertDecayFunction("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", FunctionType::Exp); - assertDecayFunction("0.2", "[49.9889, 50, 0.001, 0.001, 0.2]", FunctionType::Exp); - assertDecayFunction("0.1", "[-10, 40, 5, 0, 0.1]", FunctionType::Exp); + assertDecayFunction("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", Functions::LinearDecay, node); + assertDecayFunction("0.2", "[49.9889, 50, 0.001, 0.001, 0.2]", Functions::LinearDecay, node); + assertDecayFunction("0.1", "[-10, 40, 5, 0, 0.1]", Functions::LinearDecay, node); // incorrect input - assertDecayFunctionFail("[30, 40, 5]", FunctionType::Exp); - assertDecayFunctionFail("[30, 40, 5, 100]", FunctionType::Exp); - assertDecayFunctionFail("[30, 40, 5, 100, -100]", FunctionType::Exp); - assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", FunctionType::Exp); + assertDecayFunctionFail("[30, 40, 5]", Functions::LinearDecay, node); + assertDecayFunctionFail("[30, 40, 5, 100]", Functions::LinearDecay, node); + assertDecayFunctionFail("[30, 40, 5, 100, -100]", Functions::LinearDecay, node); + assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", Functions::LinearDecay, node); } } // namespase From 87cf166be39e79bc0cf78e314962e6047180e213 Mon Sep 17 00:00:00 2001 From: Alexey Bakharew Date: Wed, 16 Jun 2021 14:32:29 +0300 Subject: [PATCH 5/7] changelog --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 063a22b4bc8c..1cc1141298b9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ devel ----- +* Add 3 AQL functions: GAUSS_DECAY, EXP_DECAY and LINEAR_DECAY. + * Fix URL request parsing in case data is handed in in small chunks. Previously the URL could be cut off if the chunk size was smaller than the URL size. From 35b14a341a4e623d092ecae2758ad2daa87df3b8 Mon Sep 17 00:00:00 2001 From: Alexey Bakharew Date: Thu, 17 Jun 2021 09:59:33 +0300 Subject: [PATCH 6/7] changes in functions + js tests + range test --- arangod/Aql/Functions.cpp | 8 +- tests/Aql/DecaysFunctionTest.cpp | 154 ++++++++++--------- tests/js/server/aql/aql-functions-numeric.js | 86 +++++++++++ 3 files changed, 170 insertions(+), 78 deletions(-) diff --git a/arangod/Aql/Functions.cpp b/arangod/Aql/Functions.cpp index 0de953b4d354..3c9a455b8c68 100644 --- a/arangod/Aql/Functions.cpp +++ b/arangod/Aql/Functions.cpp @@ -8936,7 +8936,7 @@ AqlValue decayFuncImpl(arangodb::aql::ExpressionContext* expressionContext, } } - return AqlValue(builder.slice()); + return AqlValue(std::move(*builder.steal())); } } @@ -8953,7 +8953,7 @@ AqlValue Functions::GaussDecay(arangodb::aql::ExpressionContext* expressionConte double max = std::max(0.0, std::fabs(arg - origin) - offset); double numerator = max * max; double val = std::exp(- numerator / (2 * sigmaSqr)); - return val > decay ? val : decay; + return val; }; }; @@ -8972,7 +8972,7 @@ AqlValue Functions::ExpDecay(arangodb::aql::ExpressionContext* expressionContext return [=](double arg) { double numerator = lambda * std::max(0.0, std::abs(arg - origin) - offset); double val = std::exp(numerator); - return val > decay ? val : decay; + return val; }; }; @@ -8991,7 +8991,7 @@ AqlValue Functions::LinearDecay(arangodb::aql::ExpressionContext* expressionCont return [=](double arg) { double max = std::max(0.0, std::fabs(arg - origin) - offset); double val = std::max((s - max) / s, 0.0); - return val > decay ? val : decay; + return val; }; }; diff --git a/tests/Aql/DecaysFunctionTest.cpp b/tests/Aql/DecaysFunctionTest.cpp index f28c8d8003f6..ff12a4403d9a 100644 --- a/tests/Aql/DecaysFunctionTest.cpp +++ b/tests/Aql/DecaysFunctionTest.cpp @@ -54,7 +54,14 @@ SmallVector createArgVec(const VPackSlice slice) { SmallVector params{arena}; for (const auto arg : VPackArrayIterator(slice)) { - params.emplace_back(AqlValue(arg)); + if (arg.isObject()) { + // range + int64_t low = arg.get("low").getNumber(); + int64_t high = arg.get("high").getNumber(); + params.emplace_back(AqlValue(low, high)); + } else { + params.emplace_back(AqlValue(arg)); + } } return params; @@ -86,15 +93,7 @@ void expectEqSlices(const VPackSlice actualSlice, return; } -enum class FunctionType { - Gauss = 0, - Exp, - Linear -}; - -template AqlValue evaluateDecayFunction(const SmallVector& params, - F&& decayFunction, const arangodb::aql::AstNode& node) { fakeit::Mock expressionContextMock; @@ -115,12 +114,11 @@ AqlValue evaluateDecayFunction(const SmallVector& params, return trx; }); - return decayFunction(&expressionContext, node, params); + auto decayFunction = static_cast(node.getData()); + return decayFunction->implementation(&expressionContext, node, params); } -template void assertDecayFunction(char const* expected, char const* args, - F&& decayFunction, const arangodb::aql::AstNode& node) { // get slice for expected value @@ -137,17 +135,13 @@ void assertDecayFunction(char const* expected, char const* args, SmallVector params = createArgVec(argsSlice); // evaluate - auto const actual_value = evaluateDecayFunction(params, decayFunction, node); + auto const actual_value = evaluateDecayFunction(params, node); // check equality expectEqSlices(actual_value.slice(), expectedSlice); - - return; } -template void assertDecayFunctionFail(char const* args, - F&& decayFunction, const arangodb::aql::AstNode& node) { // get slice for args value auto const argsJson = VPackParser::fromJson(args); @@ -157,7 +151,7 @@ void assertDecayFunctionFail(char const* args, // create params vector from args slice SmallVector params = createArgVec(argsSlice); - ASSERT_TRUE(evaluateDecayFunction(params, decayFunction, node).isNull(false)); + ASSERT_TRUE(evaluateDecayFunction(params, node).isNull(false)); } TEST(GaussDecayFunctionTest, test) { @@ -167,36 +161,41 @@ TEST(GaussDecayFunctionTest, test) { node.setData(static_cast(&f)); // expecting 1 - assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", Functions::GaussDecay, node); - assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", Functions::GaussDecay, node); - assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", Functions::GaussDecay, node); + assertDecayFunction("1", "[41, 40, 5, 5, 0.7]", node); + assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", node); + assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", node); - assertDecayFunction("1.0", "[49.987, 49.987, 0.000000000000000001, 0.001, 0.2]", Functions::GaussDecay, node); + // test range input + assertDecayFunction("[0.5, 0.6417129487814521, 0.7791645796604999, 0.8950250709279725, 0.9726549474122855, 1.0, " + "0.9726549474122855, 0.8950250709279725, 0.7791645796604999, 0.6417129487814521, 0.5, 0.36856730432277535, 0.2570284566640167]", + "[{\"low\":-5, \"high\":7}, 0, 5, 0, 0.5]", node); + + + assertDecayFunction("1.0", "[49.987, 49.987, 0.000000000000000001, 0.001, 0.2]", node); // with offset=0 - assertDecayFunction("0.9840344433634576", "[1, 0, 10, 0, 0.2]", Functions::GaussDecay, node); - assertDecayFunction("0.9376509540020155", "[2, 0, 10, 0, 0.2]", Functions::GaussDecay, node); - assertDecayFunction("0.668740304976422", "[5, 0, 10, 0, 0.2]", Functions::GaussDecay, node); - assertDecayFunction("0.21316171604122283", "[9.8, 0, 10, 0, 0.2]", Functions::GaussDecay, node); + assertDecayFunction("0.9840344433634576", "[1, 0, 10, 0, 0.2]", node); + assertDecayFunction("0.9376509540020155", "[2, 0, 10, 0, 0.2]", node); + assertDecayFunction("0.668740304976422", "[5, 0, 10, 0, 0.2]", node); + assertDecayFunction("0.21316171604122283", "[9.8, 0, 10, 0, 0.2]", node); // with scale=0.001 (almost zero) // also test array input and array output - assertDecayFunction("[1.0, 1.0, 1e0, 1, 2e-1]", "[[0,1,9.8,10,11], 0, 0.001, 10, 0.2]", Functions::GaussDecay, node); + assertDecayFunction("[1.0, 1.0, 1e0, 1, 0.0]", "[[0,1,9.8,10,11], 0, 0.001, 10, 0.2]", node); // test array input and array output - assertDecayFunction("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", Functions::GaussDecay, node); + assertDecayFunction("[0.0019531250000000017, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", node); - // expecting decay value - assertDecayFunction("0.5", "[20, 40, 5, 5, 0.5]", Functions::GaussDecay, node); - assertDecayFunction("0.2715403018822964", "[49.9889, 49.987, 0.001, 0.001, 0.2]", Functions::GaussDecay, node); - assertDecayFunction("0.1", "[-10, 40, 5, 0, 0.1]", Functions::GaussDecay, node); + assertDecayFunction("0.0019531250000000017", "[20, 40, 5, 5, 0.5]", node); + assertDecayFunction("0.2715403018822964", "[49.9889, 49.987, 0.001, 0.001, 0.2]", node); + assertDecayFunction("1.0000000000000458e-100", "[-10, 40, 5, 0, 0.1]", node); // incorrect input - assertDecayFunctionFail("[10, 10, 0.0, 2, 0.2]", Functions::GaussDecay, node); - assertDecayFunctionFail("[30, 40, 5]", Functions::GaussDecay, node); - assertDecayFunctionFail("[30, 40, 5, 100]", Functions::GaussDecay, node); - assertDecayFunctionFail("[30, 40, 5, 100, -100]", Functions::GaussDecay, node); - assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", Functions::GaussDecay, node); + assertDecayFunctionFail("[10, 10, 0.0, 2, 0.2]", node); + assertDecayFunctionFail("[30, 40, 5]", node); + assertDecayFunctionFail("[30, 40, 5, 100]", node); + assertDecayFunctionFail("[30, 40, 5, 100, -100]", node); + assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", node); } TEST(ExpDecayFunctionTest, test) { @@ -207,34 +206,38 @@ TEST(ExpDecayFunctionTest, test) { node.setData(static_cast(&f)); // expecting 1 - assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", Functions::ExpDecay, node); - assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", Functions::ExpDecay, node); - assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", Functions::ExpDecay, node); + assertDecayFunction("1", "[41, 40, 5, 5, 0.7]", node); + assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", node); + assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", node); // with offset=0 - assertDecayFunction("0.8513399225207846", "[1, 0, 10, 0, 0.2]", Functions::ExpDecay, node); - assertDecayFunction("0.7247796636776955", "[2, 0, 10, 0, 0.2]", Functions::ExpDecay, node); - assertDecayFunction("0.447213595499958", "[5, 0, 10, 0, 0.2]", Functions::ExpDecay, node); - assertDecayFunction("0.20654248397928862", "[9.8, 0, 10, 0, 0.2]", Functions::ExpDecay, node); + assertDecayFunction("0.8513399225207846", "[1, 0, 10, 0, 0.2]", node); + assertDecayFunction("0.7247796636776955", "[2, 0, 10, 0, 0.2]", node); + assertDecayFunction("0.447213595499958", "[5, 0, 10, 0, 0.2]", node); + assertDecayFunction("0.20654248397928862", "[9.8, 0, 10, 0, 0.2]", node); // with scale=0.001 (almost zero) - assertDecayFunction("1", "[0, 0, 0.001, 10, 0.2]", Functions::ExpDecay, node); - assertDecayFunction("1", "[1, 0, 0.001, 10, 0.2]", Functions::ExpDecay, node); - assertDecayFunction("1", "[9.8, 0, 0.001, 10, 0.2]", Functions::ExpDecay, node); - assertDecayFunction("1", "[10, 0, 0.001, 10, 0.2]", Functions::ExpDecay, node); - assertDecayFunction("0.2", "[11, 0, 0.001, 10, 0.2]", Functions::ExpDecay, node); + assertDecayFunction("1", "[0, 0, 0.001, 10, 0.2]", node); + assertDecayFunction("1", "[1, 0, 0.001, 10, 0.2]", node); + assertDecayFunction("1", "[9.8, 0, 0.001, 10, 0.2]", node); + assertDecayFunction("1", "[10, 0, 0.001, 10, 0.2]", node); + assertDecayFunction("0.0", "[11, 0, 0.001, 10, 0.2]", node); - // expecting decay value - assertDecayFunction("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", Functions::ExpDecay, node); - assertDecayFunction("0.2", "[49.9889, 50, 0.001, 0.001, 0.2]", Functions::ExpDecay, node); - assertDecayFunction("0.1", "[-10, 40, 5, 0, 0.1]", Functions::ExpDecay, node); + // test range input + assertDecayFunction("[0.5, 0.5743491774985175, 0.6597539553864472, 0.7578582832551991, 0.8705505632961241, 1.0, 0.8705505632961241, " + "0.7578582832551991, 0.6597539553864472, 0.5743491774985175, 0.5, 0.4352752816480621, 0.37892914162759955]", + "[{\"low\":-5, \"high\":7}, 0, 5, 0, 0.5]", node); + + assertDecayFunction("[0.12500000000000003, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", node); + assertDecayFunction("8.717720806626885e-08", "[49.9889, 50, 0.001, 0.001, 0.2]", node); + assertDecayFunction("9.999999999999996e-11", "[-10, 40, 5, 0, 0.1]", node); // incorrect input - assertDecayFunctionFail("[10, 10, 3, 2, 1]", Functions::ExpDecay, node); - assertDecayFunctionFail("[30, 40, 5]", Functions::ExpDecay, node); - assertDecayFunctionFail("[30, 40, 5, 100]", Functions::ExpDecay, node); - assertDecayFunctionFail("[30, 40, 5, 100, -100]", Functions::ExpDecay, node); - assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", Functions::ExpDecay, node); + assertDecayFunctionFail("[10, 10, 3, 2, 1]", node); + assertDecayFunctionFail("[30, 40, 5]", node); + assertDecayFunctionFail("[30, 40, 5, 100]", node); + assertDecayFunctionFail("[30, 40, 5, 100, -100]", node); + assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", node); } TEST(LinDecayFunctionTest, test) { @@ -245,30 +248,33 @@ TEST(LinDecayFunctionTest, test) { node.setData(static_cast(&f)); // expecting 1 - assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", Functions::LinearDecay, node); - assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", Functions::LinearDecay, node); - assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", Functions::LinearDecay, node); + assertDecayFunction("1", "[41, 40, 5, 5, 0.5]", node); + assertDecayFunction("1.0", "[40, 40, 5, 5, 0.5]", node); + assertDecayFunction("1.0", "[49.987, 49.987, 0.001, 0.001, 0.2]", node); // with offset=0 - assertDecayFunction("0.92", "[1, 0, 10, 0, 0.2]", Functions::LinearDecay, node); - assertDecayFunction("0.84", "[2, 0, 10, 0, 0.2]", Functions::LinearDecay, node); - assertDecayFunction("0.6", "[5, 0, 10, 0, 0.2]", Functions::LinearDecay, node); - assertDecayFunction("0.21599999999999994", "[9.8, 0, 10, 0, 0.2]", Functions::LinearDecay, node); + assertDecayFunction("0.92", "[1, 0, 10, 0, 0.2]", node); + assertDecayFunction("0.84", "[2, 0, 10, 0, 0.2]", node); + assertDecayFunction("0.6", "[5, 0, 10, 0, 0.2]", node); + assertDecayFunction("0.21599999999999994", "[9.8, 0, 10, 0, 0.2]", node); // with scale=0.001 (almost zero) // also test array input and array output - assertDecayFunction("[1,1,1,1,0.2]", "[[0,1,9.8,10,11], 0, 0.001, 10, 0.2]", Functions::LinearDecay, node); + assertDecayFunction("[1,1,1,1,0]", "[[0,1,9.8,10,11], 0, 0.001, 10, 0.2]", node); + + // test range input + assertDecayFunction("[0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3]", + "[{\"low\":-5, \"high\":7}, 0, 5, 0, 0.5]", node); - // expecting decay value - assertDecayFunction("[0.5, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", Functions::LinearDecay, node); - assertDecayFunction("0.2", "[49.9889, 50, 0.001, 0.001, 0.2]", Functions::LinearDecay, node); - assertDecayFunction("0.1", "[-10, 40, 5, 0, 0.1]", Functions::LinearDecay, node); + assertDecayFunction("[0, 1.0]", "[[20.0, 41], 40, 5, 5, 0.5]", node); + assertDecayFunction("0", "[49.9889, 50, 0.001, 0.001, 0.2]", node); + assertDecayFunction("0", "[-10, 40, 5, 0, 0.1]", node); // incorrect input - assertDecayFunctionFail("[30, 40, 5]", Functions::LinearDecay, node); - assertDecayFunctionFail("[30, 40, 5, 100]", Functions::LinearDecay, node); - assertDecayFunctionFail("[30, 40, 5, 100, -100]", Functions::LinearDecay, node); - assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", Functions::LinearDecay, node); + assertDecayFunctionFail("[30, 40, 5]", node); + assertDecayFunctionFail("[30, 40, 5, 100]", node); + assertDecayFunctionFail("[30, 40, 5, 100, -100]", node); + assertDecayFunctionFail("[\"a\", 40, 5, 5, 0.5]", node); } } // namespase diff --git a/tests/js/server/aql/aql-functions-numeric.js b/tests/js/server/aql/aql-functions-numeric.js index 0ecdfd47e590..dfa091f0edb4 100644 --- a/tests/js/server/aql/aql-functions-numeric.js +++ b/tests/js/server/aql/aql-functions-numeric.js @@ -5760,6 +5760,92 @@ function ahuacatlNumericFunctionsTestSuite () { assertEqual([ 0 ], getQueryResults(buildQuery(i, "[ ], 1"))); assertEqual([ 0 ], getQueryResults(buildQuery(i, "{ }, 1"))); } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test GAUSS_DECAY function +//////////////////////////////////////////////////////////////////////////////// + + testGaussDecay : function () { + var data = [ + [1, 0, 10, 0, 0.2, 0.9840344433634576], + [-10, 40, 5, 0, 0.1, 1.0000000000000458e-100], + [41, 40, 5, 5, 0.7, 1] + ]; + + var valgrind = require("internal").valgrind; + + data.forEach(function (value) { + var arg = JSON.stringify(value[0]); + var origin = JSON.stringify(value[1]); + var scale = JSON.stringify(value[2]); + var offset = JSON.stringify(value[3]); + var decay = JSON.stringify(value[4]); + + var query = "RETURN GAUSS_DECAY(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay + ")"; + var actual = getQueryResults(query); + assertAlmostEqual(value[5], actual[0], query + " " + JSON.stringify(value)); + + actual = getQueryResults("RETURN NOOPT(GAUSS_DECAY(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay +"))"); + assertAlmostEqual(value[5], actual[0], value, query + " " + JSON.stringify(value)); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test EXP_DECAY function +//////////////////////////////////////////////////////////////////////////////// + + testExpDecay : function () { + var data = [ + [1, 0, 10, 0, 0.2, 0.8513399225207846], + [0, 0, 0.001, 10, 0.2, 1], + [41, 40, 5, 5, 0.7, 1] + ]; + + var valgrind = require("internal").valgrind; + + data.forEach(function (value) { + var arg = JSON.stringify(value[0]); + var origin = JSON.stringify(value[1]); + var scale = JSON.stringify(value[2]); + var offset = JSON.stringify(value[3]); + var decay = JSON.stringify(value[4]); + + var query = "RETURN EXP_DECAY(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay + ")"; + var actual = getQueryResults(query); + assertAlmostEqual(value[5], actual[0], query + " " + JSON.stringify(value)); + + actual = getQueryResults("RETURN NOOPT(EXP_DECAY(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay +"))"); + assertAlmostEqual(value[5], actual[0], value, query + " " + JSON.stringify(value)); + }); + }, +//////////////////////////////////////////////////////////////////////////////// +/// @brief test LINEAR_DECAY function +//////////////////////////////////////////////////////////////////////////////// + + testLinDecay : function () { + var data = [ + [41, 40, 5, 5, 0.5, 1], + [1, 0, 10, 0, 0.2, 0.92], + [-10, 40, 5, 0, 0.1, 0] + ]; + + var valgrind = require("internal").valgrind; + + data.forEach(function (value) { + var arg = JSON.stringify(value[0]); + var origin = JSON.stringify(value[1]); + var scale = JSON.stringify(value[2]); + var offset = JSON.stringify(value[3]); + var decay = JSON.stringify(value[4]); + + var query = "RETURN LINEAR_DECAY(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay + ")"; + var actual = getQueryResults(query); + assertAlmostEqual(value[5], actual[0], query + " " + JSON.stringify(value)); + + actual = getQueryResults("RETURN NOOPT(LINEAR_DECAY(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay +"))"); + assertAlmostEqual(value[5], actual[0], value, query + " " + JSON.stringify(value)); + }); } }; } From 542ed4441869b6f1fcc7dfac1fd87c757d6dd892 Mon Sep 17 00:00:00 2001 From: Alexey Bakharew Date: Thu, 17 Jun 2021 15:34:03 +0300 Subject: [PATCH 7/7] renamed functions --- CHANGELOG | 2 +- arangod/Aql/AqlFunctionFeature.cpp | 6 ++--- arangod/Aql/Functions.cpp | 6 ++--- arangod/Aql/Functions.h | 6 ++--- tests/Aql/DecaysFunctionTest.cpp | 6 ++--- tests/js/server/aql/aql-functions-numeric.js | 24 ++++++++++---------- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7986a2000b06..c6f570b9461b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,7 +5,7 @@ devel * Slightly improve specific warning messages for better readability. -* Add 3 AQL functions: GAUSS_DECAY, EXP_DECAY and LINEAR_DECAY. +* Add 3 AQL functions: DECAY_GAUSS, DECAY_EXP and DECAY_LINEAR. * Fix URL request parsing in case data is handed in in small chunks. Previously the URL could be cut off if the chunk size was smaller than diff --git a/arangod/Aql/AqlFunctionFeature.cpp b/arangod/Aql/AqlFunctionFeature.cpp index d583843efa22..62698b6b49aa 100644 --- a/arangod/Aql/AqlFunctionFeature.cpp +++ b/arangod/Aql/AqlFunctionFeature.cpp @@ -314,9 +314,9 @@ void AqlFunctionFeature::addListFunctions() { add({"REPLACE_NTH", ".,.,.|.", flags, &Functions::ReplaceNth}); add({"INTERLEAVE", ".,.|+", flags, &Functions::Interleave}); - add({"GAUSS_DECAY", ".,.,.,.,.,", flags, &Functions::GaussDecay}); - add({"EXP_DECAY", ".,.,.,.,.,", flags, &Functions::ExpDecay}); - add({"LINEAR_DECAY", ".,.,.,.,.,", flags, &Functions::LinearDecay}); + add({"DECAY_GAUSS", ".,.,.,.,.,", flags, &Functions::DecayGauss}); + add({"DECAY_EXP", ".,.,.,.,.,", flags, &Functions::DecayExp}); + add({"DECAY_LINEAR", ".,.,.,.,.,", flags, &Functions::DecayLinear}); // special flags: // CALL and APPLY will always run on the coordinator and are not deterministic // and not cacheable, as we don't know what function is actually gonna be diff --git a/arangod/Aql/Functions.cpp b/arangod/Aql/Functions.cpp index 3c9a455b8c68..e0b67dc2c4f0 100644 --- a/arangod/Aql/Functions.cpp +++ b/arangod/Aql/Functions.cpp @@ -8940,7 +8940,7 @@ AqlValue decayFuncImpl(arangodb::aql::ExpressionContext* expressionContext, } } -AqlValue Functions::GaussDecay(arangodb::aql::ExpressionContext* expressionContext, +AqlValue Functions::DecayGauss(arangodb::aql::ExpressionContext* expressionContext, AstNode const& node, VPackFunctionParameters const& parameters) { @@ -8960,7 +8960,7 @@ AqlValue Functions::GaussDecay(arangodb::aql::ExpressionContext* expressionConte return decayFuncImpl(expressionContext, node, parameters, gaussDecayFactory); } -AqlValue Functions::ExpDecay(arangodb::aql::ExpressionContext* expressionContext, +AqlValue Functions::DecayExp(arangodb::aql::ExpressionContext* expressionContext, AstNode const& node, VPackFunctionParameters const& parameters) { @@ -8979,7 +8979,7 @@ AqlValue Functions::ExpDecay(arangodb::aql::ExpressionContext* expressionContext return decayFuncImpl(expressionContext, node, parameters, expDecayFactory); } -AqlValue Functions::LinearDecay(arangodb::aql::ExpressionContext* expressionContext, +AqlValue Functions::DecayLinear(arangodb::aql::ExpressionContext* expressionContext, AstNode const& node, VPackFunctionParameters const& parameters) { diff --git a/arangod/Aql/Functions.h b/arangod/Aql/Functions.h index 5bb99d76b714..e01953daff89 100644 --- a/arangod/Aql/Functions.h +++ b/arangod/Aql/Functions.h @@ -532,13 +532,13 @@ struct Functions { static AqlValue MakeDistributeGraphInput(arangodb::aql::ExpressionContext*, AstNode const&, VPackFunctionParameters const&); - static AqlValue GaussDecay(arangodb::aql::ExpressionContext*, + static AqlValue DecayGauss(arangodb::aql::ExpressionContext*, AstNode const&, VPackFunctionParameters const&); - static AqlValue ExpDecay(arangodb::aql::ExpressionContext*, + static AqlValue DecayExp(arangodb::aql::ExpressionContext*, AstNode const&, VPackFunctionParameters const&); - static AqlValue LinearDecay(arangodb::aql::ExpressionContext*, + static AqlValue DecayLinear(arangodb::aql::ExpressionContext*, AstNode const&, VPackFunctionParameters const&); /// @brief dummy function that will only throw an error when called diff --git a/tests/Aql/DecaysFunctionTest.cpp b/tests/Aql/DecaysFunctionTest.cpp index ff12a4403d9a..400a2b97e8e9 100644 --- a/tests/Aql/DecaysFunctionTest.cpp +++ b/tests/Aql/DecaysFunctionTest.cpp @@ -157,7 +157,7 @@ void assertDecayFunctionFail(char const* args, TEST(GaussDecayFunctionTest, test) { // preparing arangodb::aql::AstNode node(NODE_TYPE_FCALL); - arangodb::aql::Function f("GAUSS_DECAY", &Functions::GaussDecay); + arangodb::aql::Function f("DECAY_GAUSS", &Functions::DecayGauss); node.setData(static_cast(&f)); // expecting 1 @@ -202,7 +202,7 @@ TEST(ExpDecayFunctionTest, test) { // preparing arangodb::aql::AstNode node(NODE_TYPE_FCALL); - arangodb::aql::Function f("EXP_DECAY", &Functions::ExpDecay); + arangodb::aql::Function f("DECAY_EXP", &Functions::DecayExp); node.setData(static_cast(&f)); // expecting 1 @@ -244,7 +244,7 @@ TEST(LinDecayFunctionTest, test) { // preparing arangodb::aql::AstNode node(NODE_TYPE_FCALL); - arangodb::aql::Function f("LINEAR_DECAY", &Functions::LinearDecay); + arangodb::aql::Function f("DECAY_LINEAR", &Functions::DecayLinear); node.setData(static_cast(&f)); // expecting 1 diff --git a/tests/js/server/aql/aql-functions-numeric.js b/tests/js/server/aql/aql-functions-numeric.js index dfa091f0edb4..f9d8e564b2d6 100644 --- a/tests/js/server/aql/aql-functions-numeric.js +++ b/tests/js/server/aql/aql-functions-numeric.js @@ -5763,10 +5763,10 @@ function ahuacatlNumericFunctionsTestSuite () { }, //////////////////////////////////////////////////////////////////////////////// -/// @brief test GAUSS_DECAY function +/// @brief test DECAY_GAUSS function //////////////////////////////////////////////////////////////////////////////// - testGaussDecay : function () { + testDecayGauss : function () { var data = [ [1, 0, 10, 0, 0.2, 0.9840344433634576], [-10, 40, 5, 0, 0.1, 1.0000000000000458e-100], @@ -5782,20 +5782,20 @@ function ahuacatlNumericFunctionsTestSuite () { var offset = JSON.stringify(value[3]); var decay = JSON.stringify(value[4]); - var query = "RETURN GAUSS_DECAY(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay + ")"; + var query = "RETURN DECAY_GAUSS(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay + ")"; var actual = getQueryResults(query); assertAlmostEqual(value[5], actual[0], query + " " + JSON.stringify(value)); - actual = getQueryResults("RETURN NOOPT(GAUSS_DECAY(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay +"))"); + actual = getQueryResults("RETURN NOOPT(DECAY_GAUSS(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay +"))"); assertAlmostEqual(value[5], actual[0], value, query + " " + JSON.stringify(value)); }); }, //////////////////////////////////////////////////////////////////////////////// -/// @brief test EXP_DECAY function +/// @brief test DECAY_EXP function //////////////////////////////////////////////////////////////////////////////// - testExpDecay : function () { + testDecayExp : function () { var data = [ [1, 0, 10, 0, 0.2, 0.8513399225207846], [0, 0, 0.001, 10, 0.2, 1], @@ -5811,19 +5811,19 @@ function ahuacatlNumericFunctionsTestSuite () { var offset = JSON.stringify(value[3]); var decay = JSON.stringify(value[4]); - var query = "RETURN EXP_DECAY(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay + ")"; + var query = "RETURN DECAY_EXP(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay + ")"; var actual = getQueryResults(query); assertAlmostEqual(value[5], actual[0], query + " " + JSON.stringify(value)); - actual = getQueryResults("RETURN NOOPT(EXP_DECAY(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay +"))"); + actual = getQueryResults("RETURN NOOPT(DECAY_EXP(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay +"))"); assertAlmostEqual(value[5], actual[0], value, query + " " + JSON.stringify(value)); }); }, //////////////////////////////////////////////////////////////////////////////// -/// @brief test LINEAR_DECAY function +/// @brief test DECAY_LINEAR function //////////////////////////////////////////////////////////////////////////////// - testLinDecay : function () { + testDecayLin : function () { var data = [ [41, 40, 5, 5, 0.5, 1], [1, 0, 10, 0, 0.2, 0.92], @@ -5839,11 +5839,11 @@ function ahuacatlNumericFunctionsTestSuite () { var offset = JSON.stringify(value[3]); var decay = JSON.stringify(value[4]); - var query = "RETURN LINEAR_DECAY(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay + ")"; + var query = "RETURN DECAY_LINEAR(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay + ")"; var actual = getQueryResults(query); assertAlmostEqual(value[5], actual[0], query + " " + JSON.stringify(value)); - actual = getQueryResults("RETURN NOOPT(LINEAR_DECAY(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay +"))"); + actual = getQueryResults("RETURN NOOPT(DECAY_LINEAR(" + arg + ", " + origin + ", " + scale + ", " + offset + ", " + decay +"))"); assertAlmostEqual(value[5], actual[0], value, query + " " + JSON.stringify(value)); }); }