diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt index 161187026..738b43104 100644 --- a/internal/ceres/CMakeLists.txt +++ b/internal/ceres/CMakeLists.txt @@ -370,7 +370,8 @@ if (BUILD_TESTING AND GFLAGS) add_library(test_util evaluator_test_utils.cc numeric_diff_test_utils.cc - test_util.cc) + test_util.cc + codegen/test_utils.cc) target_include_directories(test_util PUBLIC ${Ceres_SOURCE_DIR}/internal) if (MINIGLOG) @@ -499,10 +500,6 @@ if (BUILD_TESTING AND GFLAGS) add_subdirectory(generated_bundle_adjustment_tests) - if(CODE_GENERATION) - add_subdirectory(codegen) - endif() - endif (BUILD_TESTING AND GFLAGS) macro(add_dependencies_to_benchmark BENCHMARK_TARGET) @@ -528,3 +525,8 @@ if (BUILD_BENCHMARKS) add_executable(schur_eliminator_benchmark schur_eliminator_benchmark.cc) add_dependencies_to_benchmark(schur_eliminator_benchmark) endif (BUILD_BENCHMARKS) + +if(CODE_GENERATION) + add_subdirectory(codegen) +endif() + diff --git a/internal/ceres/codegen/CMakeLists.txt b/internal/ceres/codegen/CMakeLists.txt index db21f9c3b..4bea21ff4 100644 --- a/internal/ceres/codegen/CMakeLists.txt +++ b/internal/ceres/codegen/CMakeLists.txt @@ -38,7 +38,8 @@ # # The semantic tests, which check the correctness by executing the generated code # are still run on all platforms. -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") +if (BUILD_TESTING AND GFLAGS) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CXX_FLAGS_OLD ${CMAKE_CXX_FLAGS}) # From the man page: # The C++ standard allows an implementation to omit creating a @@ -49,10 +50,41 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # gcc and different optimization levels. Without this flag, testing would # be very painfull and might break when a new compiler version is released. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-elide-constructors") - ceres_test(expression) - ceres_test(expression_graph) ceres_test(expression_ref) ceres_test(code_generator) ceres_test(eliminate_nops) set(CMAKE_CXX_FLAGS ${CXX_FLAGS_OLD}) + endif() + + ceres_test(expression) + ceres_test(expression_graph) + + macro (generate_test_functor FUNCTOR_NAME FUNCTOR_FILE) + ceres_generate_cost_function_implementation_for_functor( + NAME ${FUNCTOR_NAME} + INPUT_FILE ${FUNCTOR_FILE} + OUTPUT_DIRECTORY tests + NAMESPACE test + ) + endmacro() + + # Semantic tests should work on every platform + include(CeresCodeGeneration) + + + generate_test_functor(InputOutputAssignment autodiff_codegen_test.h) + generate_test_functor(CompileTimeConstants autodiff_codegen_test.h) + generate_test_functor(Assignments autodiff_codegen_test.h) + generate_test_functor(BinaryArithmetic autodiff_codegen_test.h) + generate_test_functor(UnaryArithmetic autodiff_codegen_test.h) + generate_test_functor(BinaryComparison autodiff_codegen_test.h) + generate_test_functor(LogicalOperators autodiff_codegen_test.h) + generate_test_functor(ScalarFunctions autodiff_codegen_test.h) + generate_test_functor(LogicalFunctions autodiff_codegen_test.h) + generate_test_functor(Branches autodiff_codegen_test.h) + ceres_test(autodiff_codegen) + target_link_libraries(autodiff_codegen_test PUBLIC + InputOutputAssignment CompileTimeConstants Assignments BinaryArithmetic + UnaryArithmetic BinaryComparison LogicalOperators ScalarFunctions + LogicalFunctions Branches) endif() diff --git a/internal/ceres/codegen/autodiff_codegen_test.cc b/internal/ceres/codegen/autodiff_codegen_test.cc new file mode 100644 index 000000000..c6c5cbcc1 --- /dev/null +++ b/internal/ceres/codegen/autodiff_codegen_test.cc @@ -0,0 +1,117 @@ +// Ceres Solver - A fast non-linear least squares minimizer +// Copyright 2019 Google Inc. All rights reserved. +// http://code.google.com/p/ceres-solver/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Author: darius.rueckert@fau.de (Darius Rueckert) +// +// This file tests the Expression class. For each member function one test is +// included here. +// +#include "autodiff_codegen_test.h" + +#include "ceres/autodiff_cost_function.h" +#include "codegen/test_utils.h" +#include "gtest/gtest.h" + +namespace ceres { +namespace internal { + +class AutoDiffCodegenTest : public testing::TestWithParam { + public: + template + void TestCostFunction() { + using CostFunctorType = CostFunctionToFunctor; + CostFunctionType generated_cost_function; + CostFunctorType cost_functor; + auto* cost_function_ad = + new AutoDiffCostFunction( + &cost_functor); + auto value = GetParam(); + CompareCostFunctions(&generated_cost_function, + cost_function_ad, + value, + kRelativeErrorThreshold); + } + static constexpr double kRelativeErrorThreshold = 0; +}; + +TEST_P(AutoDiffCodegenTest, InputOutputAssignment) { + TestCostFunction(); +} + +TEST_P(AutoDiffCodegenTest, CompileTimeConstants) { + TestCostFunction(); +} + +TEST_P(AutoDiffCodegenTest, Assignments) { + TestCostFunction(); +} + +TEST_P(AutoDiffCodegenTest, BinaryArithmetic) { + TestCostFunction(); +} + +TEST_P(AutoDiffCodegenTest, UnaryArithmetic) { + TestCostFunction(); +} + +TEST_P(AutoDiffCodegenTest, BinaryComparison) { + TestCostFunction(); +} + +TEST_P(AutoDiffCodegenTest, LogicalOperators) { + TestCostFunction(); +} + +TEST_P(AutoDiffCodegenTest, ScalarFunctions) { + TestCostFunction(); +} + +TEST_P(AutoDiffCodegenTest, LogicalFunctions) { + TestCostFunction(); +} + +TEST_P(AutoDiffCodegenTest, Branches) { + TestCostFunction(); +} + +INSTANTIATE_TEST_SUITE_P( + AutoDiffCodegenTest, + AutoDiffCodegenTest, + testing::Values(0, + -1, + 1, + 0.5, + -0.5, + 10, + -10, + 1e20, + 1e-20, + std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN())); +} // namespace internal +} // namespace ceres diff --git a/internal/ceres/codegen/autodiff_codegen_test.h b/internal/ceres/codegen/autodiff_codegen_test.h new file mode 100644 index 000000000..f5a9f447c --- /dev/null +++ b/internal/ceres/codegen/autodiff_codegen_test.h @@ -0,0 +1,358 @@ +// Ceres Solver - A fast non-linear least squares minimizer +// Copyright 2020 Google Inc. All rights reserved. +// http://code.google.com/p/ceres-solver/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Author: darius.rueckert@fau.de (Darius Rueckert) +// +// This file includes unit test functors for every supported expression type. +// This is similar to expression_ref_test and codegeneration_test, but for the +// complete pipeline including automatic differentation. For each of the structs +// below, the Evaluate function is generated using GenerateCodeForFunctor. After +// that this function is executed with random parameters. The result of the +// residuals and jacobians is then compared to AutoDiff (without code +// generation). Of course, the correctness of this module depends on the +// correctness of autodiff. +// +#include +#include + +#include "ceres/codegen/codegen_cost_function.h" +namespace test { + +struct InputOutputAssignment : public ceres::CodegenCostFunction<7, 4, 2, 1> { + template + bool operator()(const T* x0, const T* x1, const T* x2, T* y) const { + y[0] = x0[0]; + y[1] = x0[1]; + y[2] = x0[2]; + y[3] = x0[3]; + + y[4] = x1[0]; + y[5] = x1[1]; + + y[6] = x2[0]; + return true; + } +#include "tests/inputoutputassignment.h" +}; + +struct CompileTimeConstants : public ceres::CodegenCostFunction<7, 1> { + template + bool operator()(const T* x0, T* y) const { + y[0] = T(0); + y[1] = T(1); + y[2] = T(-1); + y[3] = T(1e-10); + y[4] = T(1e10); + y[5] = T(std::numeric_limits::infinity()); + y[6] = T(std::numeric_limits::quiet_NaN()); + + return true; + } +#include "tests/compiletimeconstants.h" +}; + +struct Assignments : public ceres::CodegenCostFunction<8, 2> { + template + bool operator()(const T* x0, T* y) const { + T a = x0[0]; + T b = x0[1]; + y[0] = a; + y[1] = b; + y[2] = y[3] = a; + + T c = a; + y[4] = c; + + T d(b); + y[5] = d; + + y[6] = std::move(c); + + y[7] = std::move(T(T(std::move(T(a))))); + return true; + } +#include "tests/assignments.h" +}; + +struct BinaryArithmetic : public ceres::CodegenCostFunction<9, 2> { + template + bool operator()(const T* x0, T* y) const { + T a = x0[0]; + T b = x0[1]; + y[0] = a + b; + y[1] = a - b; + y[2] = a * b; + y[3] = a / b; + + y[4] = a; + y[4] += b; + y[5] = a; + y[5] -= b; + y[6] = a; + y[6] *= b; + y[7] = a; + y[7] /= b; + + y[8] = a + b * a / a - b + b / a; + return true; + } +#include "tests/binaryarithmetic.h" +}; + +struct UnaryArithmetic : public ceres::CodegenCostFunction<3, 1> { + template + bool operator()(const T* x0, T* y) const { + T a = x0[0]; + y[0] = -a; + y[1] = +a; + y[2] = a; + return true; + } +#include "tests/unaryarithmetic.h" +}; + +struct BinaryComparison : public ceres::CodegenCostFunction<12, 2> { + template + bool operator()(const T* x0, T* y) const { + T a = x0[0]; + T b = x0[1]; + + // For each operator we swap the inputs so both branches are evaluated once. + CERES_IF(a < b) { y[0] = T(0); } + CERES_ELSE { y[0] = T(1); } + CERES_ENDIF + CERES_IF(b < a) { y[1] = T(0); } + CERES_ELSE { y[1] = T(1); } + CERES_ENDIF + + CERES_IF(a > b) { y[2] = T(0); } + CERES_ELSE { y[2] = T(1); } + CERES_ENDIF + CERES_IF(b > a) { y[3] = T(0); } + CERES_ELSE { y[3] = T(1); } + CERES_ENDIF + + CERES_IF(a <= b) { y[4] = T(0); } + CERES_ELSE { y[4] = T(1); } + CERES_ENDIF + CERES_IF(b <= a) { y[5] = T(0); } + CERES_ELSE { y[5] = T(1); } + CERES_ENDIF + + CERES_IF(a >= b) { y[6] = T(0); } + CERES_ELSE { y[6] = T(1); } + CERES_ENDIF + CERES_IF(b >= a) { y[7] = T(0); } + CERES_ELSE { y[7] = T(1); } + CERES_ENDIF + + CERES_IF(a == b) { y[8] = T(0); } + CERES_ELSE { y[8] = T(1); } + CERES_ENDIF + CERES_IF(b == a) { y[9] = T(0); } + CERES_ELSE { y[9] = T(1); } + CERES_ENDIF + + CERES_IF(a != b) { y[10] = T(0); } + CERES_ELSE { y[10] = T(1); } + CERES_ENDIF + CERES_IF(b != a) { y[11] = T(0); } + CERES_ELSE { y[11] = T(1); } + CERES_ENDIF + + return true; + } +#include "tests/binarycomparison.h" +}; + +struct LogicalOperators : public ceres::CodegenCostFunction<8, 3> { + template + bool operator()(const T* x0, T* y) const { + T a = x0[0]; + T b = x0[1]; + T c = x0[2]; + auto r1 = a < b; + auto r2 = a < c; + + CERES_IF(r1) { y[0] = T(0); } + CERES_ELSE { y[0] = T(1); } + CERES_ENDIF + CERES_IF(r2) { y[1] = T(0); } + CERES_ELSE { y[1] = T(1); } + CERES_ENDIF + CERES_IF(!r1) { y[2] = T(0); } + CERES_ELSE { y[2] = T(1); } + CERES_ENDIF + CERES_IF(!r2) { y[3] = T(0); } + CERES_ELSE { y[3] = T(1); } + CERES_ENDIF + + CERES_IF(r1 && r2) { y[4] = T(0); } + CERES_ELSE { y[4] = T(1); } + CERES_ENDIF + CERES_IF(!r1 && !r2) { y[5] = T(0); } + CERES_ELSE { y[5] = T(1); } + CERES_ENDIF + + CERES_IF(r1 || r2) { y[6] = T(0); } + CERES_ELSE { y[6] = T(1); } + CERES_ENDIF + CERES_IF(!r1 || !r2) { y[7] = T(0); } + CERES_ELSE { y[7] = T(1); } + CERES_ENDIF + + return true; + } +#include "tests/logicaloperators.h" +}; + +struct ScalarFunctions : public ceres::CodegenCostFunction<20, 22> { + template + bool operator()(const T* x0, T* y) const { + y[0] = abs(x0[0]); + y[1] = acos(x0[1]); + y[2] = asin(x0[2]); + y[3] = atan(x0[3]); + y[4] = cbrt(x0[4]); + y[5] = ceil(x0[5]); + y[6] = cos(x0[6]); + y[7] = cosh(x0[7]); + y[8] = exp(x0[8]); + y[9] = exp2(x0[9]); + y[10] = floor(x0[10]); + y[11] = log(x0[11]); + y[12] = log2(x0[12]); + y[13] = sin(x0[13]); + y[14] = sinh(x0[14]); + y[15] = sqrt(x0[15]); + y[16] = tan(x0[16]); + y[17] = tanh(x0[17]); + y[18] = atan2(x0[18], x0[19]); + y[19] = pow(x0[20], x0[21]); + return true; + } +#include "tests/scalarfunctions.h" +}; + +struct LogicalFunctions : public ceres::CodegenCostFunction<4, 4> { + template + bool operator()(const T* x0, T* y) const { + using std::isfinite; + using std::isinf; + using std::isnan; + using std::isnormal; + T a = x0[0]; + auto r1 = isfinite(a); + auto r2 = isinf(a); + auto r3 = isnan(a); + auto r4 = isnormal(a); + + CERES_IF(r1) { y[0] = T(0); } + CERES_ELSE { y[0] = T(1); } + CERES_ENDIF + CERES_IF(r2) { y[1] = T(0); } + CERES_ELSE { y[1] = T(1); } + CERES_ENDIF + CERES_IF(r3) { y[2] = T(0); } + CERES_ELSE { y[2] = T(1); } + CERES_ENDIF + CERES_IF(r4) { y[3] = T(0); } + CERES_ELSE { y[3] = T(1); } + CERES_ENDIF + + return true; + } +#include "tests/logicalfunctions.h" +}; + +struct Branches : public ceres::CodegenCostFunction<4, 3> { + template + bool operator()(const T* x0, T* y) const { + T a = x0[0]; + T b = x0[1]; + T c = x0[2]; + auto r1 = a < b; + auto r2 = a < c; + auto r3 = b < c; + + // If without else + y[0] = T(0); + CERES_IF(r1) { y[0] += T(1); } + CERES_ENDIF + + // If else + y[1] = T(0); + CERES_IF(r1) { y[1] += T(-1); } + CERES_ELSE { y[1] += T(1); } + CERES_ENDIF + + // Nested if + y[2] = T(0); + CERES_IF(r1) { + y[2] += T(1); + CERES_IF(r2) { + y[2] += T(4); + CERES_IF(r2) { y[2] += T(8); } + CERES_ENDIF + } + CERES_ENDIF + } + CERES_ENDIF + + // Nested if-else + y[3] = T(0); + CERES_IF(r1) { + y[3] += T(1); + CERES_IF(r2) { + y[3] += T(2); + CERES_IF(r3) { y[3] += T(4); } + CERES_ELSE { y[3] += T(8); } + CERES_ENDIF + } + CERES_ELSE { + y[3] += T(16); + CERES_IF(r3) { y[3] += T(32); } + CERES_ELSE { y[3] += T(64); } + CERES_ENDIF + } + CERES_ENDIF + } + CERES_ELSE { + y[3] += T(128); + CERES_IF(r2) { y[3] += T(256); } + CERES_ELSE { y[3] += T(512); } + CERES_ENDIF + } + CERES_ENDIF + + return true; + } +#include "tests/branches.h" +}; + +} // namespace test diff --git a/internal/ceres/codegen/test_utils.cc b/internal/ceres/codegen/test_utils.cc new file mode 100644 index 000000000..c34e77355 --- /dev/null +++ b/internal/ceres/codegen/test_utils.cc @@ -0,0 +1,87 @@ +// Ceres Solver - A fast non-linear least squares minimizer +// Copyright 2020 Google Inc. All rights reserved. +// http://code.google.com/p/ceres-solver/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Author: darius.rueckert@fau.de (Darius Rueckert) + +#include "codegen/test_utils.h" + +namespace ceres { +namespace internal { + +std::pair, std::vector > EvaluateCostFunction( + CostFunction* cost_function, double value) { + auto num_residuals = cost_function->num_residuals(); + auto parameter_block_sizes = cost_function->parameter_block_sizes(); + auto num_parameter_blocks = parameter_block_sizes.size(); + + int total_num_parameters = 0; + for (auto block_size : parameter_block_sizes) { + total_num_parameters += block_size; + } + + std::vector params_array(total_num_parameters, value); + std::vector params(num_parameter_blocks); + std::vector residuals(num_residuals, 0); + std::vector jacobians_array(num_residuals * total_num_parameters, 0); + std::vector jacobians(num_parameter_blocks); + + for (int i = 0, k = 0; i < num_parameter_blocks; + k += parameter_block_sizes[i], ++i) { + params[i] = ¶ms_array[k]; + } + + for (int i = 0, k = 0; i < num_parameter_blocks; + k += parameter_block_sizes[i], ++i) { + jacobians[i] = &jacobians_array[k * num_residuals]; + } + + cost_function->Evaluate(params.data(), residuals.data(), jacobians.data()); + + return std::make_pair(residuals, jacobians_array); +} + +void CompareCostFunctions(CostFunction* cost_function1, + CostFunction* cost_function2, + + double value, + double tol) { + auto residuals_and_jacobians_1 = EvaluateCostFunction(cost_function1, value); + auto residuals_and_jacobians_2 = EvaluateCostFunction(cost_function2, value); + + ExpectArraysClose(residuals_and_jacobians_1.first.size(), + residuals_and_jacobians_1.first.data(), + residuals_and_jacobians_2.first.data(), + tol); + ExpectArraysClose(residuals_and_jacobians_1.second.size(), + residuals_and_jacobians_1.second.data(), + residuals_and_jacobians_2.second.data(), + tol); +} + +} // namespace internal +} // namespace ceres diff --git a/internal/ceres/codegen/test_utils.h b/internal/ceres/codegen/test_utils.h new file mode 100644 index 000000000..0a2792727 --- /dev/null +++ b/internal/ceres/codegen/test_utils.h @@ -0,0 +1,83 @@ +// Ceres Solver - A fast non-linear least squares minimizer +// Copyright 2020 Google Inc. All rights reserved. +// http://code.google.com/p/ceres-solver/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Author: darius.rueckert@fau.de (Darius Rueckert) + +#ifndef CERES_INTERNAL_CODEGEN_TEST_UTILS_H_ +#define CERES_INTERNAL_CODEGEN_TEST_UTILS_H_ + +#include "ceres/internal/autodiff.h" +#include "ceres/random.h" +#include "ceres/sized_cost_function.h" +#include "test_util.h" + +namespace ceres { +namespace internal { + +// CodegenCostFunctions have both, an templated operator() and the Evaluate() +// function. The operator() is used during code generation and Evaluate() is +// used during execution. +// +// If we want to use the operator() during execution (with autodiff) this +// wrapper class here has to be used. Autodiff doesn't support functors that +// have an Evaluate() function. +// +// CostFunctionToFunctor hides the Evaluate() function, because it doesn't +// derive from CostFunction. Autodiff sees it as a simple functor and will use +// the operator() as expected. +template +struct CostFunctionToFunctor { + template + CostFunctionToFunctor(_Args&&... __args) + : cost_function(std::forward<_Args>(__args)...) {} + + template + bool operator()(_Args&&... __args) const { + return cost_function(std::forward<_Args>(__args)...); + } + + CostFunction cost_function; +}; + +// Evaluate a cost function and return the residuals and jacobians. +// All parameters are set to 'value'. +std::pair, std::vector> EvaluateCostFunction( + CostFunction* cost_function, double value); + +// Evaluates the two cost functions using the method above and then compares the +// result. The comparison uses GTEST expect macros so this function should be +// called from a test enviroment. +void CompareCostFunctions(CostFunction* cost_function1, + CostFunction* cost_function2, + double value, + double tol); + +} // namespace internal +} // namespace ceres + +#endif diff --git a/internal/ceres/test_util.cc b/internal/ceres/test_util.cc index 515685679..a131b795e 100644 --- a/internal/ceres/test_util.cc +++ b/internal/ceres/test_util.cc @@ -34,6 +34,7 @@ #include #include + #include "ceres/file.h" #include "ceres/stringprintf.h" #include "ceres/types.h" @@ -54,6 +55,15 @@ namespace ceres { namespace internal { bool ExpectClose(double x, double y, double max_abs_relative_difference) { + if (std::isinf(x) && std::isinf(y)) { + EXPECT_EQ(std::signbit(x), std::signbit(y)); + return true; + } + + if (std::isnan(x) && std::isnan(y)) { + return true; + } + double absolute_difference = fabs(x - y); double relative_difference = absolute_difference / std::max(fabs(x), fabs(y)); if (x == 0 || y == 0) { @@ -63,7 +73,10 @@ bool ExpectClose(double x, double y, double max_abs_relative_difference) { } if (relative_difference > max_abs_relative_difference) { VLOG(1) << StringPrintf("x=%17g y=%17g abs=%17g rel=%17g", - x, y, absolute_difference, relative_difference); + x, + y, + absolute_difference, + relative_difference); } EXPECT_NEAR(relative_difference, 0.0, max_abs_relative_difference); @@ -108,36 +121,30 @@ void ExpectArraysCloseUptoScale(int n, } } -void ExpectArraysClose(int n, - const double* p, - const double* q, - double tol) { +void ExpectArraysClose(int n, const double* p, const double* q, double tol) { CHECK_GT(n, 0); CHECK(p); CHECK(q); for (int i = 0; i < n; ++i) { - EXPECT_TRUE(ExpectClose(p[i], q[i], tol)) - << "p[" << i << "]" << p[i] << " " - << "q[" << i << "]" << q[i] << " " - << "tol: " << tol; + EXPECT_TRUE(ExpectClose(p[i], q[i], tol)) << "p[" << i << "]" << p[i] << " " + << "q[" << i << "]" << q[i] << " " + << "tol: " << tol; } } std::string TestFileAbsolutePath(const std::string& filename) { - return JoinPath(FLAGS_test_srcdir + CERES_TEST_SRCDIR_SUFFIX, - filename); + return JoinPath(FLAGS_test_srcdir + CERES_TEST_SRCDIR_SUFFIX, filename); } std::string ToString(const Solver::Options& options) { - return StringPrintf( - "(%s, %s, %s, %s, %d)", - LinearSolverTypeToString(options.linear_solver_type), - SparseLinearAlgebraLibraryTypeToString( - options.sparse_linear_algebra_library_type), - options.linear_solver_ordering? "USER": "AUTOMATIC", - PreconditionerTypeToString(options.preconditioner_type), - options.num_threads); + return StringPrintf("(%s, %s, %s, %s, %d)", + LinearSolverTypeToString(options.linear_solver_type), + SparseLinearAlgebraLibraryTypeToString( + options.sparse_linear_algebra_library_type), + options.linear_solver_ordering ? "USER" : "AUTOMATIC", + PreconditionerTypeToString(options.preconditioner_type), + options.num_threads); } } // namespace internal diff --git a/internal/ceres/test_util.h b/internal/ceres/test_util.h index b5f13938f..ad9822833 100644 --- a/internal/ceres/test_util.h +++ b/internal/ceres/test_util.h @@ -32,6 +32,7 @@ #define CERES_INTERNAL_TEST_UTIL_H_ #include + #include "ceres/internal/port.h" #include "ceres/problem.h" #include "ceres/solver.h" @@ -44,6 +45,9 @@ namespace internal { // Expects that x and y have a relative difference of no more than // max_abs_relative_difference. If either x or y is zero, then the relative // difference is interpreted as an absolute difference. +// +// If x and y have the same non-finite value (inf or nan) we treat them as being +// close. In such a case no error is thrown and true is returned. bool ExpectClose(double x, double y, double max_abs_relative_difference); // Expects that for all i = 1,.., n - 1 @@ -113,11 +117,8 @@ class SystemTest : public ::testing::Test { Solver::Summary summary; Solve(options, problem, &summary); CHECK_NE(summary.termination_type, ceres::FAILURE); - problem->Evaluate(Problem::EvaluateOptions(), - nullptr, - final_residuals, - nullptr, - nullptr); + problem->Evaluate( + Problem::EvaluateOptions(), nullptr, final_residuals, nullptr, nullptr); } std::vector expected_final_residuals_;