From 8eef94de4e887aeb412202a9ab8ba06c5a95d03b Mon Sep 17 00:00:00 2001 From: Johannes Beck Date: Sat, 13 Oct 2018 22:34:53 +0200 Subject: [PATCH] Sized cost function using variadic templates This PR changes the interface of sized_cost_fucntion, autodiff_cost_function and numeric_diff_costfunction from using ten hardcoded parameter blocks to a variable number of parameter blocks using variadic templates. Trailing parameter blocks of size zero are now considered as error. Change-Id: I37b9a0a420ef0eda6476a46672bbf6bd57e19760 --- include/ceres/autodiff_cost_function.h | 48 ++-- .../ceres/autodiff_local_parameterization.h | 9 +- .../dynamic_numeric_diff_cost_function.h | 55 ++-- include/ceres/internal/autodiff.h | 240 +++++++++--------- include/ceres/internal/numeric_diff.h | 159 +++++++----- include/ceres/internal/variadic_evaluate.h | 201 ++++----------- include/ceres/numeric_diff_cost_function.h | 128 +++------- include/ceres/sized_cost_function.h | 47 +--- include/ceres/tiny_solver_autodiff_function.h | 2 +- internal/ceres/autodiff_test.cc | 62 ++--- internal/ceres/local_parameterization_test.cc | 14 +- internal/ceres/parameter_dims_test.cc | 6 +- 12 files changed, 380 insertions(+), 591 deletions(-) diff --git a/include/ceres/autodiff_cost_function.h b/include/ceres/autodiff_cost_function.h index 23ed45698..4a52cbccc 100644 --- a/include/ceres/autodiff_cost_function.h +++ b/include/ceres/autodiff_cost_function.h @@ -110,10 +110,6 @@ // Dimension of x ------------------------------------+ | // Dimension of y ---------------------------------------+ // -// The framework can currently accommodate cost functions of up to 10 -// independent variables, and there is no limit on the dimensionality -// of each of them. -// // WARNING #1: Since the functor will get instantiated with different types for // T, you must convert from other numeric types to T before mixing // computations with other variables of type T. In the example above, this is @@ -153,19 +149,8 @@ namespace ceres { // of residuals for a single autodiff cost function at runtime. template // Number of parameters in block 9. -class AutoDiffCostFunction : public SizedCostFunction { + int... Ns> // Number of parameters in each parameter block. +class AutoDiffCostFunction : public SizedCostFunction { public: // Takes ownership of functor. Uses the template-provided value for the // number of residuals ("kNumResiduals"). @@ -186,10 +171,7 @@ class AutoDiffCostFunction : public SizedCostFunction - ::set_num_residuals(num_residuals); + SizedCostFunction::set_num_residuals(num_residuals); } virtual ~AutoDiffCostFunction() {} @@ -202,20 +184,20 @@ class AutoDiffCostFunction : public SizedCostFunction::ParameterDims; + if (!jacobians) { - return internal::VariadicEvaluate< - CostFunctor, double, N0, N1, N2, N3, N4, N5, N6, N7, N8, N9> - ::Call(*functor_, parameters, residuals); + return internal::VariadicEvaluate(*functor_, + parameters, + residuals); } - return internal::AutoDiff::Differentiate( - *functor_, - parameters, - SizedCostFunction::num_residuals(), - residuals, - jacobians); + return internal::AutoDifferentiate( + *functor_, + parameters, + SizedCostFunction::num_residuals(), + residuals, + jacobians); } private: diff --git a/include/ceres/autodiff_local_parameterization.h b/include/ceres/autodiff_local_parameterization.h index 257e9372d..649e05dc9 100644 --- a/include/ceres/autodiff_local_parameterization.h +++ b/include/ceres/autodiff_local_parameterization.h @@ -134,12 +134,9 @@ class AutoDiffLocalParameterization : public LocalParameterization { const double* parameter_ptrs[2] = {x, zero_delta}; double* jacobian_ptrs[2] = { NULL, jacobian }; - return internal::AutoDiff - ::Differentiate(*functor_, - parameter_ptrs, - kGlobalSize, - x_plus_delta, - jacobian_ptrs); + return internal::AutoDifferentiate< + internal::StaticParameterDims>( + *functor_, parameter_ptrs, kGlobalSize, x_plus_delta, jacobian_ptrs); } virtual int GlobalSize() const { return kGlobalSize; } diff --git a/include/ceres/dynamic_numeric_diff_cost_function.h b/include/ceres/dynamic_numeric_diff_cost_function.h index d5806eed0..33ac5e142 100644 --- a/include/ceres/dynamic_numeric_diff_cost_function.h +++ b/include/ceres/dynamic_numeric_diff_cost_function.h @@ -42,6 +42,7 @@ #include "ceres/dynamic_cost_function.h" #include "ceres/internal/eigen.h" #include "ceres/internal/numeric_diff.h" +#include "ceres/internal/parameter_dims.h" #include "ceres/numeric_diff_options.h" #include "glog/logging.h" @@ -103,7 +104,11 @@ class DynamicNumericDiffCostFunction : public DynamicCostFunction { << "You must call DynamicNumericDiffCostFunction::AddParameterBlock() " << "before DynamicNumericDiffCostFunction::Evaluate()."; - const bool status = EvaluateCostFunctor(parameters, residuals); + const bool status = + internal::VariadicEvaluate( + *functor_.get(), + parameters, + residuals); if (jacobians == NULL || !status) { return status; } @@ -127,18 +132,18 @@ class DynamicNumericDiffCostFunction : public DynamicCostFunction { for (size_t block = 0; block < block_sizes.size(); ++block) { if (jacobians[block] != NULL && - !NumericDiff::EvaluateJacobianForParameterBlock( - functor_.get(), - residuals, - options_, - this->num_residuals(), - block, - block_sizes[block], - ¶meters_references_copy[0], - jacobians[block])) { + !NumericDiff:: + EvaluateJacobianForParameterBlock( + functor_.get(), + residuals, + options_, + this->num_residuals(), + block, + block_sizes[block], + ¶meters_references_copy[0], + jacobians[block])) { return false; } } @@ -146,30 +151,6 @@ class DynamicNumericDiffCostFunction : public DynamicCostFunction { } private: - bool EvaluateCostFunctor(double const* const* parameters, - double* residuals) const { - return EvaluateCostFunctorImpl(functor_.get(), - parameters, - residuals, - functor_.get()); - } - - // Helper templates to allow evaluation of a functor or a - // CostFunction. - bool EvaluateCostFunctorImpl(const CostFunctor* functor, - double const* const* parameters, - double* residuals, - const void* /* NOT USED */) const { - return (*functor)(parameters, residuals); - } - - bool EvaluateCostFunctorImpl(const CostFunctor* functor, - double const* const* parameters, - double* residuals, - const CostFunction* /* NOT USED */) const { - return functor->Evaluate(parameters, residuals, NULL); - } - std::unique_ptr functor_; Ownership ownership_; NumericDiffOptions options_; diff --git a/include/ceres/internal/autodiff.h b/include/ceres/internal/autodiff.h index 903c89bc9..ff47fbf89 100644 --- a/include/ceres/internal/autodiff.h +++ b/include/ceres/internal/autodiff.h @@ -33,7 +33,7 @@ // dual numbers in jet.h. Before reading the rest of this file, it is advisable // to read jet.h's header comment in detail. // -// The helper wrapper AutoDiff::Differentiate() computes the jacobian of +// The helper wrapper AutoDifferentiate() computes the jacobian of // functors with templated operator() taking this form: // // struct F { @@ -142,10 +142,14 @@ #include -#include "ceres/jet.h" +#include + #include "ceres/internal/eigen.h" #include "ceres/internal/fixed_array.h" +#include "ceres/internal/parameter_dims.h" #include "ceres/internal/variadic_evaluate.h" +#include "ceres/jet.h" +#include "ceres/types.h" #include "glog/logging.h" namespace ceres { @@ -165,21 +169,51 @@ namespace internal { // // is what would get put in dst if N was 3, offset was 3, and the jet type JetT // was 8-dimensional. -template -inline void Make1stOrderPerturbation(int offset, const T* src, JetT* dst) { +template +inline void Make1stOrderPerturbation(const T* src, JetT* dst) { DCHECK(src); DCHECK(dst); for (int j = 0; j < N; ++j) { dst[j].a = src[j]; dst[j].v.setZero(); - dst[j].v[offset + j] = T(1.0); + dst[j].v[Offset + j] = T(1.0); } } +// Calls Make1stOrderPerturbation for every parameter block. +// +// Example: +// If one having three parameter blocks with dimensions (3, 2, 4), the call +// Make1stOrderPerturbations::Apply(params, x); +// will result in the following calls to Make1stOrderPerturbation: +// Make1stOrderPerturbation<0, 3>(params[0], x + 0); +// Make1stOrderPerturbation<3, 2>(params[1], x + 3); +// Make1stOrderPerturbation<5, 4>(params[2], x + 5); +template +struct Make1stOrderPerturbations; + +template +struct Make1stOrderPerturbations, ParameterIdx, + Offset> { + template + static void Apply(T const* const* parameters, JetT* x) { + Make1stOrderPerturbation(parameters[ParameterIdx], x + Offset); + Make1stOrderPerturbations, ParameterIdx + 1, + Offset + N>::Apply(parameters, x); + } +}; + +// End of 'recursion'. Nothing more to do. +template +struct Make1stOrderPerturbations, ParameterIdx, Total> { + template + static void Apply(T const* const* /* NOT USED */, JetT* /* NOT USED */) {} +}; + // Takes the 0th order part of src, assumed to be a Jet type, and puts it in // dst. This is used to pick out the "vector" part of the extended y. template -inline void Take0thOrderPart(int M, const JetT *src, T dst) { +inline void Take0thOrderPart(int M, const JetT* src, T dst) { DCHECK(src); for (int i = 0; i < M; ++i) { dst[i] = src[i].a; @@ -188,8 +222,8 @@ inline void Take0thOrderPart(int M, const JetT *src, T dst) { // Takes N 1st order parts, starting at index N0, and puts them in the M x N // matrix 'dst'. This is used to pick out the "matrix" parts of the extended y. -template -inline void Take1stOrderPart(const int M, const JetT *src, T *dst) { +template +inline void Take1stOrderPart(const int M, const JetT* src, T* dst) { DCHECK(src); DCHECK(dst); for (int i = 0; i < M; ++i) { @@ -198,126 +232,86 @@ inline void Take1stOrderPart(const int M, const JetT *src, T *dst) { } } -// This is in a struct because default template parameters on a -// function are not supported in C++03 (though it is available in -// C++0x). N0 through N9 are the dimension of the input arguments to -// the user supplied functor. -template -struct AutoDiff { - static bool Differentiate(const Functor& functor, - T const *const *parameters, - int num_outputs, - T *function_value, - T **jacobians) { - // This block breaks the 80 column rule to keep it somewhat readable. - DCHECK_GT(num_outputs, 0); - DCHECK((!N1 && !N2 && !N3 && !N4 && !N5 && !N6 && !N7 && !N8 && !N9) || - ((N1 > 0) && !N2 && !N3 && !N4 && !N5 && !N6 && !N7 && !N8 && !N9) || - ((N1 > 0) && (N2 > 0) && !N3 && !N4 && !N5 && !N6 && !N7 && !N8 && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && !N4 && !N5 && !N6 && !N7 && !N8 && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && (N4 > 0) && !N5 && !N6 && !N7 && !N8 && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && (N4 > 0) && (N5 > 0) && !N6 && !N7 && !N8 && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && (N4 > 0) && (N5 > 0) && (N6 > 0) && !N7 && !N8 && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && (N4 > 0) && (N5 > 0) && (N6 > 0) && (N7 > 0) && !N8 && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && (N4 > 0) && (N5 > 0) && (N6 > 0) && (N7 > 0) && (N8 > 0) && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && (N4 > 0) && (N5 > 0) && (N6 > 0) && (N7 > 0) && (N8 > 0) && (N9 > 0))) // NOLINT - << "Zero block cannot precede a non-zero block. Block sizes are " - << "(ignore trailing 0s): " << N0 << ", " << N1 << ", " << N2 << ", " - << N3 << ", " << N4 << ", " << N5 << ", " << N6 << ", " << N7 << ", " - << N8 << ", " << N9; +// Calls Take1stOrderPart for every parameter block. +// +// Example: +// If one having three parameter blocks with dimensions (3, 2, 4), the call +// Take1stOrderParts::Apply(num_outputs, +// output, +// jacobians); +// will result in the following calls to Take1stOrderPart: +// if (jacobians[0]) { +// Take1stOrderPart<0, 3>(num_outputs, output, jacobians[0]); +// } +// if (jacobians[1]) { +// Take1stOrderPart<3, 2>(num_outputs, output, jacobians[1]); +// } +// if (jacobians[2]) { +// Take1stOrderPart<5, 4>(num_outputs, output, jacobians[2]); +// } +template +struct Take1stOrderParts; - typedef Jet JetT; - FixedArray x( - N0 + N1 + N2 + N3 + N4 + N5 + N6 + N7 + N8 + N9 + num_outputs); - - // These are the positions of the respective jets in the fixed array x. - const int jet0 = 0; - const int jet1 = N0; - const int jet2 = N0 + N1; - const int jet3 = N0 + N1 + N2; - const int jet4 = N0 + N1 + N2 + N3; - const int jet5 = N0 + N1 + N2 + N3 + N4; - const int jet6 = N0 + N1 + N2 + N3 + N4 + N5; - const int jet7 = N0 + N1 + N2 + N3 + N4 + N5 + N6; - const int jet8 = N0 + N1 + N2 + N3 + N4 + N5 + N6 + N7; - const int jet9 = N0 + N1 + N2 + N3 + N4 + N5 + N6 + N7 + N8; - - const JetT *unpacked_parameters[10] = { - x.get() + jet0, - x.get() + jet1, - x.get() + jet2, - x.get() + jet3, - x.get() + jet4, - x.get() + jet5, - x.get() + jet6, - x.get() + jet7, - x.get() + jet8, - x.get() + jet9, - }; - - JetT* output = x.get() + N0 + N1 + N2 + N3 + N4 + N5 + N6 + N7 + N8 + N9; - - // Invalidate the output Jets, so that we can detect if the user - // did not assign values to all of them. - for (int i = 0; i < num_outputs; ++i) { - output[i].a = kImpossibleValue; - output[i].v.setConstant(kImpossibleValue); +template +struct Take1stOrderParts, ParameterIdx, + Offset> { + template + static void Apply(int num_outputs, JetT* output, T** jacobians) { + if (jacobians[ParameterIdx]) { + Take1stOrderPart(num_outputs, output, jacobians[ParameterIdx]); } - -#define CERES_MAKE_1ST_ORDER_PERTURBATION(i) \ - if (N ## i) { \ - internal::Make1stOrderPerturbation( \ - jet ## i, \ - parameters[i], \ - x.get() + jet ## i); \ - } - CERES_MAKE_1ST_ORDER_PERTURBATION(0); - CERES_MAKE_1ST_ORDER_PERTURBATION(1); - CERES_MAKE_1ST_ORDER_PERTURBATION(2); - CERES_MAKE_1ST_ORDER_PERTURBATION(3); - CERES_MAKE_1ST_ORDER_PERTURBATION(4); - CERES_MAKE_1ST_ORDER_PERTURBATION(5); - CERES_MAKE_1ST_ORDER_PERTURBATION(6); - CERES_MAKE_1ST_ORDER_PERTURBATION(7); - CERES_MAKE_1ST_ORDER_PERTURBATION(8); - CERES_MAKE_1ST_ORDER_PERTURBATION(9); -#undef CERES_MAKE_1ST_ORDER_PERTURBATION - - if (!VariadicEvaluate::Call( - functor, unpacked_parameters, output)) { - return false; - } - - internal::Take0thOrderPart(num_outputs, output, function_value); - -#define CERES_TAKE_1ST_ORDER_PERTURBATION(i) \ - if (N ## i) { \ - if (jacobians[i]) { \ - internal::Take1stOrderPart(num_outputs, \ - output, \ - jacobians[i]); \ - } \ - } - CERES_TAKE_1ST_ORDER_PERTURBATION(0); - CERES_TAKE_1ST_ORDER_PERTURBATION(1); - CERES_TAKE_1ST_ORDER_PERTURBATION(2); - CERES_TAKE_1ST_ORDER_PERTURBATION(3); - CERES_TAKE_1ST_ORDER_PERTURBATION(4); - CERES_TAKE_1ST_ORDER_PERTURBATION(5); - CERES_TAKE_1ST_ORDER_PERTURBATION(6); - CERES_TAKE_1ST_ORDER_PERTURBATION(7); - CERES_TAKE_1ST_ORDER_PERTURBATION(8); - CERES_TAKE_1ST_ORDER_PERTURBATION(9); -#undef CERES_TAKE_1ST_ORDER_PERTURBATION - return true; + Take1stOrderParts, ParameterIdx + 1, + Offset + N>::Apply(num_outputs, output, jacobians); } }; +// End of 'recursion'. Nothing more to do. +template +struct Take1stOrderParts, ParameterIdx, Offset> { + template + static void Apply(int /* NOT USED*/, JetT* /* NOT USED*/, + T** /* NOT USED */) {} +}; + +template +inline bool AutoDifferentiate(const Functor& functor, + T const *const *parameters, + int num_outputs, + T* function_value, + T** jacobians) { + DCHECK_GT(num_outputs, 0); + + typedef Jet JetT; + FixedArray x(ParameterDims::kNumParameters + + num_outputs); + + using Parameters = typename ParameterDims::Parameters; + + // These are the positions of the respective jets in the fixed array x. + std::array unpacked_parameters = + ParameterDims::GetUnpackedParameters(x.get()); + JetT* output = x.get() + ParameterDims::kNumParameters; + + // Invalidate the output Jets, so that we can detect if the user + // did not assign values to all of them. + for (int i = 0; i < num_outputs; ++i) { + output[i].a = kImpossibleValue; + output[i].v.setConstant(kImpossibleValue); + } + + Make1stOrderPerturbations::Apply(parameters, x.get()); + + if (!VariadicEvaluate(functor, unpacked_parameters.data(), + output)) { + return false; + } + + Take0thOrderPart(num_outputs, output, function_value); + Take1stOrderParts::Apply(num_outputs, output, jacobians); + + return true; +} + } // namespace internal } // namespace ceres diff --git a/include/ceres/internal/numeric_diff.h b/include/ceres/internal/numeric_diff.h index ab1abc0c6..f24fdb313 100644 --- a/include/ceres/internal/numeric_diff.h +++ b/include/ceres/internal/numeric_diff.h @@ -50,42 +50,11 @@ namespace ceres { namespace internal { -// Helper templates that allow evaluation of a variadic functor or a -// CostFunction object. -template -bool EvaluateImpl(const CostFunctor* functor, - double const* const* parameters, - double* residuals, - const void* /* NOT USED */) { - return VariadicEvaluate::Call( - *functor, - parameters, - residuals); -} - -template -bool EvaluateImpl(const CostFunctor* functor, - double const* const* parameters, - double* residuals, - const CostFunction* /* NOT USED */) { - return functor->Evaluate(parameters, residuals, NULL); -} - // This is split from the main class because C++ doesn't allow partial template // specializations for member functions. The alternative is to repeat the main // class for differing numbers of parameters, which is also unfortunate. -template struct NumericDiff { // Mutates parameters but must restore them before return. @@ -219,8 +188,9 @@ struct NumericDiff { // Mutate 1 element at a time and then restore. x_plus_delta(parameter_index) = x(parameter_index) + delta; - if (!EvaluateImpl( - functor, parameters, residuals.data(), functor)) { + if (!VariadicEvaluate(*functor, + parameters, + residuals.data())) { return false; } @@ -233,8 +203,9 @@ struct NumericDiff { // Compute the function on the other side of x(parameter_index). x_plus_delta(parameter_index) = x(parameter_index) - delta; - if (!EvaluateImpl( - functor, parameters, temp_residuals.data(), functor)) { + if (!VariadicEvaluate(*functor, + parameters, + temp_residuals.data())) { return false; } @@ -406,35 +377,91 @@ struct NumericDiff { } }; -template -struct NumericDiff { - // Mutates parameters but must restore them before return. - static bool EvaluateJacobianForParameterBlock( - const CostFunctor* functor, - const double* residuals_at_eval_point, - const NumericDiffOptions& options, - const int num_residuals, - const int parameter_block_index, - const int parameter_block_size, - double **parameters, - double *jacobian) { - // Silence unused parameter compiler warnings. - (void)functor; - (void)residuals_at_eval_point; - (void)options; - (void)num_residuals; - (void)parameter_block_index; - (void)parameter_block_size; - (void)parameters; - (void)jacobian; - LOG(FATAL) << "Control should never reach here."; +// This function calls NumericDiff<...>::EvaluateJacobianForParameterBlock for +// each parameter block. +// +// Example: +// A call to +// EvaluateJacobianForParameterBlocks>( +// functor, +// residuals_at_eval_point, +// options, +// num_residuals, +// parameters, +// jacobians); +// will result in the following calls to +// NumericDiff<...>::EvaluateJacobianForParameterBlock: +// +// if (!NumericDiff< +// CostFunctor, method, kNumResiduals, ParameterDims, 0, +// 2>::EvaluateJacobianForParameterBlock(functor, +// residuals_at_eval_point, +// options, +// num_residuals, +// 0, +// 2, +// parameters, +// jacobians[0])) { +// return false; +// } +// if (!NumericDiff< +// CostFunctor, method, kNumResiduals, ParameterDims, 1, +// 3>::EvaluateJacobianForParameterBlock(functor, +// residuals_at_eval_point, +// options, +// num_residuals, +// 1, +// 3, +// parameters, +// jacobians[1])) { +// return false; +// } +template +struct EvaluateJacobianForParameterBlocks; + +template +struct EvaluateJacobianForParameterBlocks< + ParameterDims, integer_sequence, ParameterIdx> { + template + static bool Apply(const CostFunctor* functor, + const double* residuals_at_eval_point, + const NumericDiffOptions& options, int num_residuals, + double** parameters, double** jacobians) { + if (!NumericDiff< + CostFunctor, method, kNumResiduals, ParameterDims, ParameterIdx, + N>::EvaluateJacobianForParameterBlock(functor, + residuals_at_eval_point, + options, + num_residuals, + ParameterIdx, + N, + parameters, + jacobians[ParameterIdx])) { + return false; + } + return EvaluateJacobianForParameterBlocks< + ParameterDims, integer_sequence, ParameterIdx + 1>:: + template Apply(functor, + residuals_at_eval_point, + options, + num_residuals, + parameters, jacobians); + } +}; + +// End of 'recursion'. Nothing more to do. +template +struct EvaluateJacobianForParameterBlocks, + ParameterIdx> { + template + static bool Apply(const CostFunctor* /* NOT USED*/, + const double* /* NOT USED*/, + const NumericDiffOptions& /* NOT USED*/, int /* NOT USED*/, + double** /* NOT USED*/, double** /* NOT USED*/) { return true; } }; diff --git a/include/ceres/internal/variadic_evaluate.h b/include/ceres/internal/variadic_evaluate.h index b3515b96d..26428d07a 100644 --- a/include/ceres/internal/variadic_evaluate.h +++ b/include/ceres/internal/variadic_evaluate.h @@ -28,165 +28,76 @@ // // Author: sameeragarwal@google.com (Sameer Agarwal) // mierle@gmail.com (Keir Mierle) +// jodebo_beck@gmx.de (Johannes Beck) #ifndef CERES_PUBLIC_INTERNAL_VARIADIC_EVALUATE_H_ #define CERES_PUBLIC_INTERNAL_VARIADIC_EVALUATE_H_ #include -#include "ceres/jet.h" -#include "ceres/types.h" -#include "ceres/internal/eigen.h" -#include "ceres/internal/fixed_array.h" -#include "glog/logging.h" +#include + +#include "ceres/cost_function.h" +#include "ceres/internal/parameter_dims.h" namespace ceres { namespace internal { -// This block of quasi-repeated code calls the user-supplied functor, which may -// take a variable number of arguments. This is accomplished by specializing the -// struct based on the size of the trailing parameters; parameters with 0 size -// are assumed missing. -template -struct VariadicEvaluate { - static bool Call(const Functor& functor, T const *const *input, T* output) { - return functor(input[0], - input[1], - input[2], - input[3], - input[4], - input[5], - input[6], - input[7], - input[8], - input[9], - output); - } -}; +// For fixed size cost functors +template +inline bool VariadicEvaluateImpl(const Functor& functor, T const* const* input, + T* output, std::false_type /*is_dynamic*/, + integer_sequence) { + static_assert(sizeof...(Indices), + "Invalid number of parameter blocks. At least one parameter " + "block must be specified."); + return functor(input[Indices]..., output); +} -template -struct VariadicEvaluate { - static bool Call(const Functor& functor, T const *const *input, T* output) { - return functor(input[0], - input[1], - input[2], - input[3], - input[4], - input[5], - input[6], - input[7], - input[8], - output); - } -}; +// For dynamic sized cost functors +template +inline bool VariadicEvaluateImpl(const Functor& functor, T const* const* input, + T* output, std::true_type /*is_dynamic*/, + integer_sequence) { + return functor(input, output); +} -template -struct VariadicEvaluate { - static bool Call(const Functor& functor, T const *const *input, T* output) { - return functor(input[0], - input[1], - input[2], - input[3], - input[4], - input[5], - input[6], - input[7], - output); - } -}; +// For ceres cost functors (not ceres::CostFunction) +template +inline bool VariadicEvaluateImpl(const Functor& functor, T const* const* input, + T* output, const void* /* NOT USED */) { + using ParameterBlockIndices = + make_integer_sequence; + using IsDynamic = std::integral_constant; + return VariadicEvaluateImpl(functor, input, output, IsDynamic(), + ParameterBlockIndices()); +} -template -struct VariadicEvaluate { - static bool Call(const Functor& functor, T const *const *input, T* output) { - return functor(input[0], - input[1], - input[2], - input[3], - input[4], - input[5], - input[6], - output); - } -}; +// For ceres::CostFunction +template +inline bool VariadicEvaluateImpl(const Functor& functor, T const* const* input, + T* output, + const CostFunction* /* NOT USED */) { + return functor.Evaluate(input, output, nullptr); +} -template -struct VariadicEvaluate { - static bool Call(const Functor& functor, T const *const *input, T* output) { - return functor(input[0], - input[1], - input[2], - input[3], - input[4], - input[5], - output); - } -}; - -template -struct VariadicEvaluate { - static bool Call(const Functor& functor, T const *const *input, T* output) { - return functor(input[0], - input[1], - input[2], - input[3], - input[4], - output); - } -}; - -template -struct VariadicEvaluate { - static bool Call(const Functor& functor, T const *const *input, T* output) { - return functor(input[0], - input[1], - input[2], - input[3], - output); - } -}; - -template -struct VariadicEvaluate { - static bool Call(const Functor& functor, T const *const *input, T* output) { - return functor(input[0], - input[1], - input[2], - output); - } -}; - -template -struct VariadicEvaluate { - static bool Call(const Functor& functor, T const *const *input, T* output) { - return functor(input[0], - input[1], - output); - } -}; - -template -struct VariadicEvaluate { - static bool Call(const Functor& functor, T const *const *input, T* output) { - return functor(input[0], - output); - } -}; - -// Template instantiation for dynamically-sized functors. -template -struct VariadicEvaluate { - static bool Call(const Functor& functor, T const *const *input, T* output) { - return functor(input, output); - } -}; +// Variadic evaluate is a helper function to evaluate ceres cost function or +// functors using an input, output and the parameter dimensions. There are +// several ways different possibilities: +// 1) If the passed functor is a 'ceres::CostFunction' its evaluate method is +// called. +// 2) If the functor is not a 'ceres::CostFunction' and the specified parameter +// dims is dynamic, the functor must have the following signature +// 'bool(T const* const* input, T* output)'. +// 3) If the functor is not a 'ceres::CostFunction' and the specified parameter +// dims is not dynamic, the input is expanded by using the number of parameter +// blocks. The signature of the functor must have the following signature +// 'bool()(const T* i_1, const T* i_2, ... const T* i_n, T* output)'. +template +inline bool VariadicEvaluate(const Functor& functor, T const* const* input, + T* output) { + return VariadicEvaluateImpl(functor, input, output, &functor); +} } // namespace internal } // namespace ceres diff --git a/include/ceres/numeric_diff_cost_function.h b/include/ceres/numeric_diff_cost_function.h index 6ec86fa36..f6cd32a64 100644 --- a/include/ceres/numeric_diff_cost_function.h +++ b/include/ceres/numeric_diff_cost_function.h @@ -52,8 +52,8 @@ // The actual cost added to the total problem is e^2, or (k - x'k)^2; however, // the squaring is implicitly done by the optimization framework. // -// To write an numerically-differentiable cost function for the above model, first -// define the object +// To write an numerically-differentiable cost function for the above model, +// first define the object // // class MyScalarCostFunctor { // explicit MyScalarCostFunctor(double k): k_(k) {} @@ -110,10 +110,6 @@ // Dimension of x ------------------------------------------------+ | // Dimension of y ---------------------------------------------------+ // -// The framework can currently accommodate cost functions of up to 10 -// independent variables, and there is no limit on the dimensionality -// of each of them. -// // The central difference method is considerably more accurate at the cost of // twice as many function evaluations than forward difference. Consider using // central differences begin with, and only after that works, trying forward @@ -161,10 +157,13 @@ #ifndef CERES_PUBLIC_NUMERIC_DIFF_COST_FUNCTION_H_ #define CERES_PUBLIC_NUMERIC_DIFF_COST_FUNCTION_H_ +#include #include + #include "Eigen/Dense" #include "ceres/cost_function.h" #include "ceres/internal/numeric_diff.h" +#include "ceres/internal/parameter_dims.h" #include "ceres/numeric_diff_options.h" #include "ceres/sized_cost_function.h" #include "ceres/types.h" @@ -175,20 +174,8 @@ namespace ceres { template // Number of parameters in block 9. -class NumericDiffCostFunction - : public SizedCostFunction { + int... Ns> // Parameters dimensions for each block. +class NumericDiffCostFunction : public SizedCostFunction { public: NumericDiffCostFunction( CostFunctor* functor, @@ -199,10 +186,7 @@ class NumericDiffCostFunction ownership_(ownership), options_(options) { if (kNumResiduals == DYNAMIC) { - SizedCostFunction - ::set_num_residuals(num_residuals); + SizedCostFunction::set_num_residuals(num_residuals); } } @@ -218,18 +202,17 @@ class NumericDiffCostFunction using internal::FixedArray; using internal::NumericDiff; - const int kNumParameters = N0 + N1 + N2 + N3 + N4 + N5 + N6 + N7 + N8 + N9; - const int kNumParameterBlocks = - (N0 > 0) + (N1 > 0) + (N2 > 0) + (N3 > 0) + (N4 > 0) + - (N5 > 0) + (N6 > 0) + (N7 > 0) + (N8 > 0) + (N9 > 0); + using ParameterDims = + typename SizedCostFunction::ParameterDims; + using Parameters = typename ParameterDims::Parameters; - // Get the function value (residuals) at the point to evaluate. - if (!internal::EvaluateImpl( - functor_.get(), - parameters, - residuals, - functor_.get())) { + constexpr int kNumParameters = ParameterDims::kNumParameters; + constexpr int kNumParameterBlocks = ParameterDims::kNumParameterBlocks; + + // Get the function value (residuals) at the the point to evaluate. + if (!internal::VariadicEvaluate(*functor_, + parameters, + residuals)) { return false; } @@ -239,71 +222,22 @@ class NumericDiffCostFunction // Create a copy of the parameters which will get mutated. FixedArray parameters_copy(kNumParameters); - FixedArray parameters_reference_copy(kNumParameterBlocks); + std::array parameters_reference_copy = + ParameterDims::GetUnpackedParameters(parameters_copy.get()); - parameters_reference_copy[0] = parameters_copy.get(); - if (N1) parameters_reference_copy[1] = parameters_reference_copy[0] + N0; - if (N2) parameters_reference_copy[2] = parameters_reference_copy[1] + N1; - if (N3) parameters_reference_copy[3] = parameters_reference_copy[2] + N2; - if (N4) parameters_reference_copy[4] = parameters_reference_copy[3] + N3; - if (N5) parameters_reference_copy[5] = parameters_reference_copy[4] + N4; - if (N6) parameters_reference_copy[6] = parameters_reference_copy[5] + N5; - if (N7) parameters_reference_copy[7] = parameters_reference_copy[6] + N6; - if (N8) parameters_reference_copy[8] = parameters_reference_copy[7] + N7; - if (N9) parameters_reference_copy[9] = parameters_reference_copy[8] + N8; - -#define CERES_COPY_PARAMETER_BLOCK(block) \ - if (N ## block) memcpy(parameters_reference_copy[block], \ - parameters[block], \ - sizeof(double) * N ## block); // NOLINT - - CERES_COPY_PARAMETER_BLOCK(0); - CERES_COPY_PARAMETER_BLOCK(1); - CERES_COPY_PARAMETER_BLOCK(2); - CERES_COPY_PARAMETER_BLOCK(3); - CERES_COPY_PARAMETER_BLOCK(4); - CERES_COPY_PARAMETER_BLOCK(5); - CERES_COPY_PARAMETER_BLOCK(6); - CERES_COPY_PARAMETER_BLOCK(7); - CERES_COPY_PARAMETER_BLOCK(8); - CERES_COPY_PARAMETER_BLOCK(9); - -#undef CERES_COPY_PARAMETER_BLOCK - -#define CERES_EVALUATE_JACOBIAN_FOR_BLOCK(block) \ - if (N ## block && jacobians[block] != NULL) { \ - if (!NumericDiff::EvaluateJacobianForParameterBlock( \ - functor_.get(), \ - residuals, \ - options_, \ - SizedCostFunction::num_residuals(), \ - block, \ - N ## block, \ - parameters_reference_copy.get(), \ - jacobians[block])) { \ - return false; \ - } \ + for (int block = 0; block < kNumParameterBlocks; ++block) { + memcpy(parameters_reference_copy[block], parameters[block], + sizeof(double) * ParameterDims::GetDim(block)); } - CERES_EVALUATE_JACOBIAN_FOR_BLOCK(0); - CERES_EVALUATE_JACOBIAN_FOR_BLOCK(1); - CERES_EVALUATE_JACOBIAN_FOR_BLOCK(2); - CERES_EVALUATE_JACOBIAN_FOR_BLOCK(3); - CERES_EVALUATE_JACOBIAN_FOR_BLOCK(4); - CERES_EVALUATE_JACOBIAN_FOR_BLOCK(5); - CERES_EVALUATE_JACOBIAN_FOR_BLOCK(6); - CERES_EVALUATE_JACOBIAN_FOR_BLOCK(7); - CERES_EVALUATE_JACOBIAN_FOR_BLOCK(8); - CERES_EVALUATE_JACOBIAN_FOR_BLOCK(9); - -#undef CERES_EVALUATE_JACOBIAN_FOR_BLOCK + internal::EvaluateJacobianForParameterBlocks::template Apply< + method, kNumResiduals>( + functor_.get(), + residuals, + options_, + SizedCostFunction::num_residuals(), + parameters_reference_copy.data(), + jacobians); return true; } diff --git a/include/ceres/sized_cost_function.h b/include/ceres/sized_cost_function.h index f9a984f14..50d03631d 100644 --- a/include/ceres/sized_cost_function.h +++ b/include/ceres/sized_cost_function.h @@ -41,49 +41,24 @@ #include "ceres/cost_function.h" #include "ceres/types.h" #include "glog/logging.h" +#include "internal/parameter_dims.h" namespace ceres { -template +template class SizedCostFunction : public CostFunction { public: + static_assert(kNumResiduals > 0 || kNumResiduals == DYNAMIC, + "Cost functions must have at least one residual block."); + static_assert(internal::StaticParameterDims::kIsValid, + "Invalid parameter block dimension detected. Each parameter " + "block dimension must be bigger than zero."); + + using ParameterDims = internal::StaticParameterDims; + SizedCostFunction() { - CHECK(kNumResiduals > 0 || kNumResiduals == DYNAMIC) - << "Cost functions must have at least one residual block."; - - // This block breaks the 80 column rule to keep it somewhat readable. - CHECK((!N1 && !N2 && !N3 && !N4 && !N5 && !N6 && !N7 && !N8 && !N9) || - ((N1 > 0) && !N2 && !N3 && !N4 && !N5 && !N6 && !N7 && !N8 && !N9) || - ((N1 > 0) && (N2 > 0) && !N3 && !N4 && !N5 && !N6 && !N7 && !N8 && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && !N4 && !N5 && !N6 && !N7 && !N8 && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && (N4 > 0) && !N5 && !N6 && !N7 && !N8 && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && (N4 > 0) && (N5 > 0) && !N6 && !N7 && !N8 && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && (N4 > 0) && (N5 > 0) && (N6 > 0) && !N7 && !N8 && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && (N4 > 0) && (N5 > 0) && (N6 > 0) && (N7 > 0) && !N8 && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && (N4 > 0) && (N5 > 0) && (N6 > 0) && (N7 > 0) && (N8 > 0) && !N9) || // NOLINT - ((N1 > 0) && (N2 > 0) && (N3 > 0) && (N4 > 0) && (N5 > 0) && (N6 > 0) && (N7 > 0) && (N8 > 0) && (N9 > 0))) // NOLINT - << "Zero block cannot precede a non-zero block. Block sizes are " - << "(ignore trailing 0s): " << N0 << ", " << N1 << ", " << N2 << ", " - << N3 << ", " << N4 << ", " << N5 << ", " << N6 << ", " << N7 << ", " - << N8 << ", " << N9; - set_num_residuals(kNumResiduals); - -#define CERES_ADD_PARAMETER_BLOCK(N) \ - if (N) mutable_parameter_block_sizes()->push_back(N); - CERES_ADD_PARAMETER_BLOCK(N0); - CERES_ADD_PARAMETER_BLOCK(N1); - CERES_ADD_PARAMETER_BLOCK(N2); - CERES_ADD_PARAMETER_BLOCK(N3); - CERES_ADD_PARAMETER_BLOCK(N4); - CERES_ADD_PARAMETER_BLOCK(N5); - CERES_ADD_PARAMETER_BLOCK(N6); - CERES_ADD_PARAMETER_BLOCK(N7); - CERES_ADD_PARAMETER_BLOCK(N8); - CERES_ADD_PARAMETER_BLOCK(N9); -#undef CERES_ADD_PARAMETER_BLOCK + *mutable_parameter_block_sizes() = std::vector{Ns...}; } virtual ~SizedCostFunction() { } diff --git a/include/ceres/tiny_solver_autodiff_function.h b/include/ceres/tiny_solver_autodiff_function.h index 76e4a24bc..cc93d73c6 100644 --- a/include/ceres/tiny_solver_autodiff_function.h +++ b/include/ceres/tiny_solver_autodiff_function.h @@ -120,7 +120,7 @@ class TinySolverAutoDiffFunction { NUM_RESIDUALS = kNumResiduals, }; - // This is similar to AutoDiff::Differentiate(), but since there is only one + // This is similar to AutoDifferentiate(), but since there is only one // parameter block it is easier to inline to avoid overhead. bool operator()(const T* parameters, T* residuals, diff --git a/internal/ceres/autodiff_test.cc b/internal/ceres/autodiff_test.cc index 20f4437d4..04a77eaa3 100644 --- a/internal/ceres/autodiff_test.cc +++ b/internal/ceres/autodiff_test.cc @@ -194,7 +194,7 @@ TEST(AutoDiff, ProjectiveCameraModel) { { double *parameters[] = { PX }; double *jacobians[] = { J_PX }; - ASSERT_TRUE((AutoDiff::Differentiate( + ASSERT_TRUE((AutoDifferentiate>( b, parameters, 2, ad_x1, jacobians))); for (int i = 0; i < 2; ++i) { @@ -209,7 +209,7 @@ TEST(AutoDiff, ProjectiveCameraModel) { double J_X[2 * 4]; double *parameters[] = { P, X }; double *jacobians[] = { J_P, J_X }; - ASSERT_TRUE((AutoDiff::Differentiate( + ASSERT_TRUE((AutoDifferentiate>( b, parameters, 2, ad_x2, jacobians))); for (int i = 0; i < 2; ++i) { @@ -316,7 +316,7 @@ TEST(AutoDiff, Metric) { double J_X[2 * 3]; double *parameters[] = { q, c, X }; double *jacobians[] = { J_q, J_c, J_X }; - ASSERT_TRUE((AutoDiff::Differentiate( + ASSERT_TRUE((AutoDifferentiate>( b, parameters, 2, ad_x, jacobians))); for (int i = 0; i < 2; ++i) { @@ -366,7 +366,7 @@ TEST(AutoDiff, VaryingNumberOfResidualsForOneCostFunctorType) { functor.num_residuals = num_residuals; // Run autodiff with the new number of residuals. - ASSERT_TRUE((AutoDiff::Differentiate( + ASSERT_TRUE((AutoDifferentiate>( functor, parameters, num_residuals, residuals, jacobians))); const double kTolerance = 1e-14; @@ -528,8 +528,8 @@ TEST(AutoDiff, VariadicAutoDiff) { { Residual1Param functor; int num_variables = 1; - EXPECT_TRUE((AutoDiff::Differentiate( - functor, parameters, 1, &residual, jacobians))); + EXPECT_TRUE((AutoDifferentiate>( + functor, parameters, 1, &residual, jacobians))); EXPECT_EQ(residual, pow(2, num_variables + 1) - 2); for (int i = 0; i < num_variables; ++i) { EXPECT_EQ(jacobian_values[i], (i + 1) * pow(2, i)); @@ -539,8 +539,8 @@ TEST(AutoDiff, VariadicAutoDiff) { { Residual2Param functor; int num_variables = 2; - EXPECT_TRUE((AutoDiff::Differentiate( - functor, parameters, 1, &residual, jacobians))); + EXPECT_TRUE((AutoDifferentiate>( + functor, parameters, 1, &residual, jacobians))); EXPECT_EQ(residual, pow(2, num_variables + 1) - 2); for (int i = 0; i < num_variables; ++i) { EXPECT_EQ(jacobian_values[i], (i + 1) * pow(2, i)); @@ -550,8 +550,8 @@ TEST(AutoDiff, VariadicAutoDiff) { { Residual3Param functor; int num_variables = 3; - EXPECT_TRUE((AutoDiff::Differentiate( - functor, parameters, 1, &residual, jacobians))); + EXPECT_TRUE((AutoDifferentiate>( + functor, parameters, 1, &residual, jacobians))); EXPECT_EQ(residual, pow(2, num_variables + 1) - 2); for (int i = 0; i < num_variables; ++i) { EXPECT_EQ(jacobian_values[i], (i + 1) * pow(2, i)); @@ -561,8 +561,8 @@ TEST(AutoDiff, VariadicAutoDiff) { { Residual4Param functor; int num_variables = 4; - EXPECT_TRUE((AutoDiff::Differentiate( - functor, parameters, 1, &residual, jacobians))); + EXPECT_TRUE((AutoDifferentiate>( + functor, parameters, 1, &residual, jacobians))); EXPECT_EQ(residual, pow(2, num_variables + 1) - 2); for (int i = 0; i < num_variables; ++i) { EXPECT_EQ(jacobian_values[i], (i + 1) * pow(2, i)); @@ -572,8 +572,8 @@ TEST(AutoDiff, VariadicAutoDiff) { { Residual5Param functor; int num_variables = 5; - EXPECT_TRUE((AutoDiff::Differentiate( - functor, parameters, 1, &residual, jacobians))); + EXPECT_TRUE((AutoDifferentiate>( + functor, parameters, 1, &residual, jacobians))); EXPECT_EQ(residual, pow(2, num_variables + 1) - 2); for (int i = 0; i < num_variables; ++i) { EXPECT_EQ(jacobian_values[i], (i + 1) * pow(2, i)); @@ -583,10 +583,8 @@ TEST(AutoDiff, VariadicAutoDiff) { { Residual6Param functor; int num_variables = 6; - EXPECT_TRUE((AutoDiff::Differentiate( - functor, parameters, 1, &residual, jacobians))); + EXPECT_TRUE((AutoDifferentiate>( + functor, parameters, 1, &residual, jacobians))); EXPECT_EQ(residual, pow(2, num_variables + 1) - 2); for (int i = 0; i < num_variables; ++i) { EXPECT_EQ(jacobian_values[i], (i + 1) * pow(2, i)); @@ -596,10 +594,8 @@ TEST(AutoDiff, VariadicAutoDiff) { { Residual7Param functor; int num_variables = 7; - EXPECT_TRUE((AutoDiff::Differentiate( - functor, parameters, 1, &residual, jacobians))); + EXPECT_TRUE((AutoDifferentiate>( + functor, parameters, 1, &residual, jacobians))); EXPECT_EQ(residual, pow(2, num_variables + 1) - 2); for (int i = 0; i < num_variables; ++i) { EXPECT_EQ(jacobian_values[i], (i + 1) * pow(2, i)); @@ -609,10 +605,8 @@ TEST(AutoDiff, VariadicAutoDiff) { { Residual8Param functor; int num_variables = 8; - EXPECT_TRUE((AutoDiff< - Residual8Param, - double, 1, 1, 1, 1, 1, 1, 1, 1>::Differentiate( - functor, parameters, 1, &residual, jacobians))); + EXPECT_TRUE((AutoDifferentiate>( + functor, parameters, 1, &residual, jacobians))); EXPECT_EQ(residual, pow(2, num_variables + 1) - 2); for (int i = 0; i < num_variables; ++i) { EXPECT_EQ(jacobian_values[i], (i + 1) * pow(2, i)); @@ -622,11 +616,9 @@ TEST(AutoDiff, VariadicAutoDiff) { { Residual9Param functor; int num_variables = 9; - EXPECT_TRUE((AutoDiff< - Residual9Param, - double, - 1, 1, 1, 1, 1, 1, 1, 1, 1>::Differentiate( - functor, parameters, 1, &residual, jacobians))); + EXPECT_TRUE( + (AutoDifferentiate>( + functor, parameters, 1, &residual, jacobians))); EXPECT_EQ(residual, pow(2, num_variables + 1) - 2); for (int i = 0; i < num_variables; ++i) { EXPECT_EQ(jacobian_values[i], (i + 1) * pow(2, i)); @@ -636,11 +628,9 @@ TEST(AutoDiff, VariadicAutoDiff) { { Residual10Param functor; int num_variables = 10; - EXPECT_TRUE((AutoDiff< - Residual10Param, - double, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1>::Differentiate( - functor, parameters, 1, &residual, jacobians))); + EXPECT_TRUE( + (AutoDifferentiate>( + functor, parameters, 1, &residual, jacobians))); EXPECT_EQ(residual, pow(2, num_variables + 1) - 2); for (int i = 0; i < num_variables; ++i) { EXPECT_EQ(jacobian_values[i], (i + 1) * pow(2, i)); diff --git a/internal/ceres/local_parameterization_test.cc b/internal/ceres/local_parameterization_test.cc index a7833d8ba..18b7e8c0f 100644 --- a/internal/ceres/local_parameterization_test.cc +++ b/internal/ceres/local_parameterization_test.cc @@ -265,14 +265,12 @@ void QuaternionParameterizationTestHelper( double* jacobian_array[2] = { NULL, jacobian_ref }; // Autodiff jacobian at delta_x = 0. - internal::AutoDiff::Differentiate(Plus(), - parameters, - kGlobalSize, - x_plus_delta, - jacobian_array); + internal::AutoDifferentiate>( + Plus(), + parameters, + kGlobalSize, + x_plus_delta, + jacobian_array); double jacobian[12]; parameterization.ComputeJacobian(x, jacobian); diff --git a/internal/ceres/parameter_dims_test.cc b/internal/ceres/parameter_dims_test.cc index 9a95cf509..f33536fb1 100644 --- a/internal/ceres/parameter_dims_test.cc +++ b/internal/ceres/parameter_dims_test.cc @@ -89,11 +89,11 @@ TEST(ParameterDims, GetUnpackedParameters) { constexpr int N1 = 4; constexpr int N2 = 2; - using Params = StaticParameterDims; + using ParameterDims = StaticParameterDims; - std::array packed_parameters{}; + std::array packed_parameters{}; std::array unpacked_parameters = - Params::GetUnpackedParameters(packed_parameters.data()); + ParameterDims::GetUnpackedParameters(packed_parameters.data()); EXPECT_EQ(packed_parameters.data(), unpacked_parameters[0]); EXPECT_EQ(packed_parameters.data() + N0, unpacked_parameters[1]);