/** * @file function_test.cpp * @author Ryan Curtin * @author Shikhar Bhardwaj * @author Marcus Edel * @author Conrad Sanderson * * ensmallen is free software; you may redistribute it and/or modify it under * the terms of the 3-clause BSD license. You should have received a copy of * the 3-clause BSD license along with ensmallen. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #include #include "catch.hpp" using namespace ens; using namespace ens::test; using namespace ens::traits; /** * Utility class with no functions. */ class EmptyTestFunction { }; /** * Utility class with Evaluate() but no Evaluate(). */ class EvaluateTestFunction { public: double Evaluate(const arma::mat& coordinates) { return arma::accu(coordinates); } double Evaluate(const arma::mat& coordinates, const size_t begin, const size_t batchSize) { return arma::accu(coordinates) + begin + batchSize; } }; /** * Utility class with Gradient() but no Evaluate(). */ class GradientTestFunction { public: void Gradient(const arma::mat& coordinates, arma::mat& gradient) { gradient.ones(coordinates.n_rows, coordinates.n_cols); } void Gradient(const arma::mat& coordinates, const size_t /* begin */, arma::mat& gradient, const size_t /* batchSize */) { gradient.ones(coordinates.n_rows, coordinates.n_cols); } }; /** * Utility class with Gradient() and Evaluate(). */ class EvaluateGradientTestFunction { public: double Evaluate(const arma::mat& coordinates) { return arma::accu(coordinates); } double Evaluate(const arma::mat& coordinates, const size_t /* begin */, const size_t /* batchSize */) { return arma::accu(coordinates); } void Gradient(const arma::mat& coordinates, arma::mat& gradient) { gradient.ones(coordinates.n_rows, coordinates.n_cols); } void Gradient(const arma::mat& coordinates, const size_t /* begin */, arma::mat& gradient, const size_t /* batchSize */) { gradient.ones(coordinates.n_rows, coordinates.n_cols); } }; /** * Utility class with EvaluateWithGradient(). */ class EvaluateWithGradientTestFunction { public: double EvaluateWithGradient(const arma::mat& coordinates, arma::mat& gradient) { gradient.ones(coordinates.n_rows, coordinates.n_cols); return arma::accu(coordinates); } double EvaluateWithGradient(const arma::mat& coordinates, const size_t /* begin */, arma::mat& gradient, const size_t /* batchSize */) { gradient.ones(coordinates.n_rows, coordinates.n_cols); return arma::accu(coordinates); } }; /** * Utility class with all three functions. */ class EvaluateAndWithGradientTestFunction { public: double Evaluate(const arma::mat& coordinates) { return arma::accu(coordinates); } double Evaluate(const arma::mat& coordinates, const size_t begin, const size_t batchSize) { return arma::accu(coordinates) + batchSize + begin; } void Gradient(const arma::mat& coordinates, arma::mat& gradient) { gradient.ones(coordinates.n_rows, coordinates.n_cols); } void Gradient(const arma::mat& coordinates, const size_t /* begin */, arma::mat& gradient, const size_t /* batchSize */) { gradient.ones(coordinates.n_rows, coordinates.n_cols); } double EvaluateWithGradient(const arma::mat& coordinates, arma::mat& gradient) { gradient.ones(coordinates.n_rows, coordinates.n_cols); return arma::accu(coordinates); } double EvaluateWithGradient(const arma::mat& coordinates, const size_t /* begin */, arma::mat& gradient, const size_t /* batchSize */) { gradient.ones(coordinates.n_rows, coordinates.n_cols); return arma::accu(coordinates); } }; /** * Utility class with const Evaluate() and non-const Gradient(). */ class EvaluateAndNonConstGradientTestFunction { public: double Evaluate(const arma::mat& coordinates) const { return arma::accu(coordinates); } void Gradient(const arma::mat& coordinates, arma::mat& gradient) { gradient.ones(coordinates.n_rows, coordinates.n_cols); } }; /** * Utility class with const Evaluate() and non-const Gradient(). */ class EvaluateAndStaticGradientTestFunction { public: double Evaluate(const arma::mat& coordinates) const { return arma::accu(coordinates); } static void Gradient(const arma::mat& coordinates, arma::mat& gradient) { gradient.ones(coordinates.n_rows, coordinates.n_cols); } }; /** * Make sure that an empty class doesn't have any methods added to it. */ TEST_CASE("AddEvaluateWithGradientEmptyTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template EvaluateForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template GradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template EvaluateWithGradientForm>::value; REQUIRE(hasEvaluate == false); REQUIRE(hasGradient == false); REQUIRE(hasEvaluateWithGradient == false); } /** * Make sure we don't add any functions if we only have Evaluate(). */ TEST_CASE("AddEvaluateWithGradientEvaluateOnlyTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template EvaluateForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template GradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template EvaluateWithGradientForm>::value; REQUIRE(hasEvaluate == true); REQUIRE(hasGradient == false); REQUIRE(hasEvaluateWithGradient == false); } /** * Make sure we don't add any functions if we only have Gradient(). */ TEST_CASE("AddEvaluateWithGradientGradientOnlyTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template EvaluateForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template GradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template EvaluateWithGradientForm>::value; REQUIRE(hasEvaluate == false); REQUIRE(hasGradient == true); REQUIRE(hasEvaluateWithGradient == false); } /** * Make sure we add EvaluateWithGradient() when we have both Evaluate() and * Gradient(). */ TEST_CASE("AddEvaluateWithGradientBothTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template EvaluateForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template GradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template EvaluateWithGradientForm>::value; REQUIRE(hasEvaluate == true); REQUIRE(hasGradient == true); REQUIRE(hasEvaluateWithGradient == true); } /** * Make sure we add Evaluate() and Gradient() when we have only * EvaluateWithGradient(). */ TEST_CASE("AddEvaluateWithGradientEvaluateWithGradientTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template EvaluateForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template GradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template EvaluateWithGradientForm>::value; REQUIRE(hasEvaluate == true); REQUIRE(hasGradient == true); REQUIRE(hasEvaluateWithGradient == true); } /** * Make sure we add no methods when we already have all three. */ TEST_CASE("AddEvaluateWithGradientAllThreeTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template EvaluateForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template GradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template EvaluateWithGradientForm>::value; REQUIRE(hasEvaluate == true); REQUIRE(hasGradient == true); REQUIRE(hasEvaluateWithGradient == true); } TEST_CASE("LogisticRegressionEvaluateWithGradientTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, arma::mat, arma::mat>, TypedForms::template EvaluateConstForm>::value; const bool hasGradient = HasGradient< Function, arma::mat, arma::mat>, TypedForms::template GradientConstForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, arma::mat, arma::mat>, TypedForms::template EvaluateWithGradientConstForm>::value; REQUIRE(hasEvaluate == true); REQUIRE(hasGradient == true); REQUIRE(hasEvaluateWithGradient == true); } TEST_CASE("SDPTest", "[FunctionTest]") { typedef AugLagrangianFunction>> FunctionType; const bool hasEvaluate = HasEvaluate< Function, TypedForms::template EvaluateConstForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template GradientConstForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template EvaluateWithGradientConstForm>::value; REQUIRE(hasEvaluate == true); REQUIRE(hasGradient == true); REQUIRE(hasEvaluateWithGradient == true); } /** * Make sure that an empty class doesn't have any methods added to it. */ TEST_CASE("AddSeparableEvaluateWithGradientEmptyTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template SeparableEvaluateForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template SeparableGradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template SeparableEvaluateWithGradientForm>::value; REQUIRE(hasEvaluate == false); REQUIRE(hasGradient == false); REQUIRE(hasEvaluateWithGradient == false); } /** * Make sure we don't add any functions if we only have Evaluate(). */ TEST_CASE("AddSeparableEvaluateWithGradientEvaluateOnlyTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template SeparableEvaluateForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template SeparableGradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template SeparableEvaluateWithGradientForm>::value; REQUIRE(hasEvaluate == true); REQUIRE(hasGradient == false); REQUIRE(hasEvaluateWithGradient == false); } /** * Make sure we don't add any functions if we only have Gradient(). */ TEST_CASE("AddSeparableEvaluateWithGradientGradientOnlyTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template SeparableEvaluateForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template SeparableGradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template SeparableEvaluateWithGradientForm>::value; REQUIRE(hasEvaluate == false); REQUIRE(hasGradient == true); REQUIRE(hasEvaluateWithGradient == false); } /** * Make sure we add EvaluateWithGradient() when we have both Evaluate() and * Gradient(). */ TEST_CASE("AddSeparableEvaluateWithGradientBothTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template SeparableEvaluateForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template SeparableGradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template SeparableEvaluateWithGradientForm>::value; REQUIRE(hasEvaluate == true); REQUIRE(hasGradient == true); REQUIRE(hasEvaluateWithGradient == true); } /** * Make sure we add Evaluate() and Gradient() when we have only * EvaluateWithGradient(). */ TEST_CASE("AddSeparableEvaluateWGradientEvaluateWithGradientTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template SeparableEvaluateForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template SeparableGradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template SeparableEvaluateWithGradientForm>::value; Function f; arma::mat coordinates(10, 10, arma::fill::ones); arma::mat gradient; f.Gradient(coordinates, 0, gradient, 5); REQUIRE(hasEvaluate == true); REQUIRE(hasGradient == true); REQUIRE(hasEvaluateWithGradient == true); } /** * Make sure we add no methods when we already have all three. */ TEST_CASE("AddSeparableEvaluateWithGradientAllThreeTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template SeparableEvaluateForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template SeparableGradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template SeparableEvaluateWithGradientForm>::value; REQUIRE(hasEvaluate == true); REQUIRE(hasGradient == true); REQUIRE(hasEvaluateWithGradient == true); } /** * Make sure we can properly create EvaluateWithGradient() even when one of the * functions is non-const. */ TEST_CASE("AddEvaluateWithGradientMixedTypesTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template EvaluateConstForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template GradientForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template EvaluateWithGradientForm>::value; REQUIRE(hasEvaluate == true); REQUIRE(hasGradient == true); REQUIRE(hasEvaluateWithGradient == true); } /** * Make sure we can properly create EvaluateWithGradient() even when one of the * functions is static. */ TEST_CASE("AddEvaluateWithGradientMixedTypesStaticTest", "[FunctionTest]") { const bool hasEvaluate = HasEvaluate< Function, TypedForms::template EvaluateConstForm>::value; const bool hasGradient = HasGradient< Function, TypedForms::template GradientStaticForm>::value; const bool hasEvaluateWithGradient = HasEvaluateWithGradient< Function, TypedForms::template EvaluateWithGradientConstForm>::value; REQUIRE(hasEvaluate == true); REQUIRE(hasGradient == true); REQUIRE(hasEvaluateWithGradient == true); } class A { public: size_t NumFunctions() const; size_t NumFeatures() const; double Evaluate(const arma::mat&, const size_t, const size_t) const; void Gradient(const arma::mat&, const size_t, arma::mat&, const size_t) const; void Gradient(const arma::mat&, const size_t, arma::sp_mat&, const size_t) const; void PartialGradient(const arma::mat&, const size_t, arma::sp_mat&) const; }; class B { public: size_t NumFunctions(); size_t NumFeatures(); double Evaluate(const arma::mat&, const size_t, const size_t); void Gradient(const arma::mat&, const size_t, arma::mat&, const size_t); void Gradient(const arma::mat&, const size_t, arma::sp_mat&, const size_t); void PartialGradient(const arma::mat&, const size_t, arma::sp_mat&); }; class C { public: size_t NumConstraints() const; double Evaluate(const arma::mat&) const; void Gradient(const arma::mat&, arma::mat&) const; double EvaluateConstraint(const size_t, const arma::mat&) const; void GradientConstraint(const size_t, const arma::mat&, arma::mat&) const; }; class D { public: size_t NumConstraints(); double Evaluate(const arma::mat&); void Gradient(const arma::mat&, arma::mat&); double EvaluateConstraint(const size_t, const arma::mat&); void GradientConstraint(const size_t, const arma::mat&, arma::mat&); }; /** * Test the correctness of the static check for SeparableFunctionType API. */ TEST_CASE("SeparableFunctionTypeCheckTest", "[FunctionTest]") { static_assert(CheckNumFunctions::value, "CheckNumFunctions static check failed."); static_assert(CheckNumFunctions::value, "CheckNumFunctions static check failed."); static_assert(!CheckNumFunctions::value, "CheckNumFunctions static check failed."); static_assert(!CheckNumFunctions::value, "CheckNumFunctions static check failed."); static_assert(CheckSeparableEvaluate::value, "CheckSeparableEvaluate static check failed."); static_assert(CheckSeparableEvaluate::value, "CheckSeparableEvaluate static check failed."); static_assert(!CheckSeparableEvaluate::value, "CheckSeparableEvaluate static check failed."); static_assert(!CheckSeparableEvaluate::value, "CheckSeparableEvaluate static check failed."); static_assert(CheckSeparableGradient::value, "CheckSeparableGradient static check failed."); static_assert(CheckSeparableGradient::value, "CheckSeparableGradient static check failed."); static_assert(!CheckSeparableGradient::value, "CheckSeparableGradient static check failed."); static_assert(!CheckSeparableGradient::value, "CheckSeparableGradient static check failed."); } /** * Test the correctness of the static check for LagrangianFunctionType API. */ TEST_CASE("LagrangianFunctionTypeCheckTest", "[FunctionTest]") { static_assert(!CheckEvaluate::value, "CheckEvaluate static check failed."); static_assert(!CheckEvaluate::value, "CheckEvaluate static check failed."); static_assert(CheckEvaluate::value, "CheckEvaluate static check failed."); static_assert(CheckEvaluate::value, "CheckEvaluate static check failed."); static_assert(!CheckGradient::value, "CheckGradient static check failed."); static_assert(!CheckGradient::value, "CheckGradient static check failed."); static_assert(CheckGradient::value, "CheckGradient static check failed."); static_assert(CheckGradient::value, "CheckGradient static check failed."); static_assert(!CheckNumConstraints::value, "CheckNumConstraints static check failed."); static_assert(!CheckNumConstraints::value, "CheckNumConstraints static check failed."); static_assert(CheckNumConstraints::value, "CheckNumConstraints static check failed."); static_assert(CheckNumConstraints::value, "CheckNumConstraints static check failed."); static_assert(!CheckEvaluateConstraint::value, "CheckEvaluateConstraint static check failed."); static_assert(!CheckEvaluateConstraint::value, "CheckEvaluateConstraint static check failed."); static_assert(CheckEvaluateConstraint::value, "CheckEvaluateConstraint static check failed."); static_assert(CheckEvaluateConstraint::value, "CheckEvaluateConstraint static check failed."); static_assert(!CheckGradientConstraint::value, "CheckGradientConstraint static check failed."); static_assert(!CheckGradientConstraint::value, "CheckGradientConstraint static check failed."); static_assert(CheckGradientConstraint::value, "CheckGradientConstraint static check failed."); static_assert(CheckGradientConstraint::value, "CheckGradientConstraint static check failed."); } /** * Test the correctness of the static check for SparseFunctionType API. */ TEST_CASE("SparseFunctionTypeCheckTest", "[FunctionTest]") { static_assert(CheckSparseGradient::value, "CheckSparseGradient static check failed."); static_assert(CheckSparseGradient::value, "CheckSparseGradient static check failed."); static_assert(!CheckSparseGradient::value, "CheckSparseGradient static check failed."); static_assert(!CheckSparseGradient::value, "CheckSparseGradient static check failed."); } /** * Test the correctness of the static check for SparseFunctionType API. */ TEST_CASE("ResolvableFunctionTypeCheckTest", "[FunctionTest]") { static_assert(CheckNumFeatures::value, "CheckNumFeatures static check failed."); static_assert(CheckNumFeatures::value, "CheckNumFeatures static check failed."); static_assert(!CheckNumFeatures::value, "CheckNumFeatures static check failed."); static_assert(!CheckNumFeatures::value, "CheckNumFeatures static check failed."); static_assert(CheckPartialGradient::value, "CheckPartialGradient static check failed."); static_assert(CheckPartialGradient::value, "CheckPartialGradient static check failed."); static_assert(!CheckPartialGradient::value, "CheckPartialGradient static check failed."); static_assert(!CheckPartialGradient::value, "CheckPartialGradient static check failed."); }