Шаблонная ковариационная функция в собственных - PullRequest
0 голосов
/ 24 марта 2020

В одном из моих проектов требуется шаблонная ковариационная функция, т.е. использование MatrixXd, ArrayXXf и / или их .block () в качестве входных данных и возвращение выражения, которое будет использоваться в дальнейших вычислениях.

q1. В качестве подтверждения моя попытка ниже, кажется, работает, но действительно ли она возвращает выражение Eigen? (edit: @Darhuuk подтвердил, что это не так; к счастью, C ++ 14 или выше не проблема!)

q2. Очевидно, что мне понадобится нечто вроде «внутренней матрицы», а не внутреннего RowVector, с которым я столкнулся в документации Eigen о шаблонных функциях. Отсюда мой вопрос: как создать внутренний MatrixType z = x.rowwise () - x.colwise (). Mean (), чтобы функция могла возвращать (z.transpose () * z) / (x.rows () -1)? Внутренний вектор строки используется в примерах ковариации в руководстве Eigen (https://eigen.tuxfamily.org/dox/TopicFunctionTakingEigenTypes.html)

q3. Наконец, при добавлении шаблонной функции к классу с заголовочным файлом, как мне найти, какие Eigen-типы необходимо использовать для явного создания экземпляра шаблона, например, для MatrixXd, .block () ArrayXXd или Map? Все, что я мог найти, - это примеры с использованием простых типов данных (например, Хранение определений функций шаблона C ++ в. CPP файле )

template <typename Derived>
Matrix<typename Derived::Scalar, Derived::ColsAtCompileTime, Derived::ColsAtCompileTime>  
SampleCov( const DenseBase<Derived>& x )
{

  typedef typename internal::plain_row_type<Derived>::type RowVectorType;
  const RowVectorType x_mean = x.colwise().mean();

  return ((x.rowwise() - x_mean).matrix().transpose() * (x.rowwise() - x_mean).matrix()) / (x.rows()-1);
}

1 Ответ

1 голос
/ 24 марта 2020

Ваша функция явно возвращает объект Matrix, поэтому нет, она не возвращает объект выражения. Один из способов исправить это - позволить компилятору определить для вас тип возвращаемого значения (поскольку объектом выражения будет некоторый гигантский c шаблонный беспорядок):

template <typename Derived>
auto SampleCov (DenseBase<Derived> const & x) {
  auto const x_mean = x.colwise().mean();
  return ((x.rowwise() - x_mean).matrix().transpose()
    * (x.rowwise() - x_mean).matrix()) / (x.rows() - 1);
}

Это предполагает, что вы используете C + +14 или выше. Для C ++ 11, использующего только auto, поскольку тип возвращаемого значения не обрезает его, вам нужен конечный тип возвращаемого значения:

template <typename Derived>
auto SampleCov (DenseBase<Derived> const & x)
    -> decltype(((x.rowwise() - x.colwise().mean()).matrix().transpose()
      * (x.rowwise() - x.colwise().mean()).matrix()) / (x.rows() - 1)) {
  auto const x_mean = x.colwise().mean();
  return ((x.rowwise() - x_mean).matrix().transpose()
    * (x.rowwise() - x_mean).matrix()) / (x.rows() - 1);
}

Обратите внимание, что я удалил RowVectorType typedef, так как я не видел точка.

Что касается вопроса 2, я думаю, что приведенные выше примеры решают эту проблему, так как нет более явно названных типов. Об этом позаботится auto как для возвращаемого типа, так и внутри функции.

Наконец, вопрос 3. Это зависит от того, что именно вы хотите сделать. Исходя из того, что вы говорите, кажется, что вышеупомянутые функции не работают для MatrixXd объектов или объектов, возвращаемых путем вызова MatrixXd::block().

Для меня это на самом деле не означает, что вам нужно явное создание шаблона , о чем вы и просите.

Вместо этого вы можете просто сделать тип аргумента SampleCov более обобщенным c:

template <typename T>
auto SampleCovGeneric (T const & x) {
  auto const x_mean = x.colwise().mean();
  return ((x.rowwise() - x_mean).matrix().transpose()
    * (x.rowwise() - x_mean).matrix()) / (x.rows() - 1);
}

Пока вы можете вызывать colwise(), matrix(), ... для объекта типа T, вы хороши для go. В любом случае, это, вероятно, лучший способ для go, поскольку теперь можно передавать выражения Eigen в SampleCovGeneric. Например:

Eigen::MatrixXd a(2,2);

// aTranspose is NOT a matrix object!
// Its type is Eigen::Transpose<Eigen::Matrix<double, Dynamic, Dynamic>>.
auto aTranspose = a.transpose();

/* Note use of auto and eval() calls!
 * See https://eigen.tuxfamily.org/dox/TopicPitfalls.html.
 */
auto b = SampleCov(aTranspose.eval()).eval();
auto c = SampleCovGeneric(aTranspose).eval();

В приведенном выше примере SampleCov ожидает объект типа DenseBase<Derived>. Но aTranspose не такой тип. Таким образом, мы должны явно оценить (т.е. фактически рассчитать) транспонирование в первую очередь. В зависимости от того, что происходит внутри SampleCov, это бесполезный расчет. Например, представьте, что в первую очередь вычисляется аргумент (x) его транспонирования. В этом случае это будет просто оригинал. Но если транспонирование уже вычислено, то Эйген не может "увидеть" это и должен рассчитать транспонирование. С другой стороны,

SampleCovGeneric принимает любой тип, поэтому нет необходимости оценивать aTranspose первый. Это (возможно) позволяет Eigen "видеть" кучу вычислений и, таким образом, вычислять результат более оптимизированным способом. См., Например, https://eigen.tuxfamily.org/dox/TopicLazyEvaluation.html.

Если вам действительно нужно, вы можете явно создавать шаблоны , чтобы их можно было разделить на файлы заголовков и исходные файлы. Это будет go примерно так:

/* Header */
#pragma once

#include <utility>

// Reduce trailing return type mess a little
template <class T>
using SampleCovResultType = decltype(((std::declval<T const &>().rowwise()
      - std::declval<T const &>().colwise().mean()).matrix().transpose()
    * (std::declval<T const &>().rowwise()
      - std::declval<T const &>().colwise().mean()).matrix())
  / (std::declval<T const &>().rows() - 1));

template <typename T>
auto SampleCov (T const & x) -> SampleCovResultType<T>;

// Explicit template declaration
extern template
auto SampleCov<Eigen::MatrixXd> (Eigen::MatrixXd const & x)
    -> SampleCovResultType<Eigen::MatrixXd>;

/* Source */
#include "SampleCov.h"

template <class T>
auto SampleCov (T const & x) -> SampleCovResultType<T> {
  auto const x_mean = x.colwise().mean();
  return ((x.rowwise() - x_mean).matrix().transpose()
    * (x.rowwise() - x_mean).matrix()) / (x.rows() - 1);
}

// Explicit template definition
template
auto SampleCov<Eigen::MatrixXd> (Eigen::MatrixXd const & x)
    -> SampleCovResultType<Eigen::MatrixXd>;

Я не думаю, что вы можете избежать использования только auto в качестве возвращаемого типа. Поскольку определение функции помещается в отдельный исходный файл, компилятор не может видеть тело функции, поэтому он не может определить тип возвращаемого значения при вызове функции. Поэтому вы должны использовать конечный тип возврата.

Обратите внимание на следующее:

Явное объявление экземпляра (внешний шаблон) предотвращает неявное создание экземпляров: код, который в противном случае вызвал бы неявное создание должно использовать явное определение создания, предоставленное где-то еще в программе.

Т.е., если вы пытаетесь вызвать SampleCov с типом T, для которого вы явно не создали экземпляр SampleCov , компиляция не удалась.

Примечание: Весь приведенный выше код не проверен. Вероятность опечаток высока:).

Правки:

  • Исправление отсутствующего аргумента шаблона.
  • Удалено замечание о вызове matrix(). Я задавался вопросом, оценивал ли он выражение, которое вы не хотите делать. AFAIK, это не так, на основании, например, документации ArrayBase .
  • Добавлена ​​информация о явной реализации.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...