4

好的,我已经在谷歌上搜索了太久,我只是不确定如何称呼这种技术,所以我认为最好在 SO 上问一下。如果它有一个明显的名称和/或我忽略的解决方案,请指出我正确的方向。

对于外行来说:张量是矩阵的逻辑扩展,就像矩阵是向量的逻辑扩展一样。向量是 rank-1 张量(在编程术语中,一维数字数组),矩阵是 rank-2 张量(二维数字数组),然后 rank-N 张量只是 ND 数字数组.

现在,假设我有这样的张量类:

template<typename T = double> // possibly also with size parameters
class Tensor
{
  private: 
    T *M;  // Tensor data (C-array)
           // alternatively, std::vector<T> *M 
           // or std::array<T> *M 
           // etc., or possibly their constant-sized versions
           // using Tensor<>'s template parameters

  public: 

    ... // insert trivial fluffy stuff here 

    // read elements
    const T & operator() (size_t a, size_t b) const {
        ... // error checks etc.
        return M[a + rows*b];
    }

    // write elements
    T & operator() (size_t a, size_t b) {
        ... // error checks etc.
        return M[a + rows*b];
    }

    ... 

};

使用 的这些定义operator()(...),索引/分配单个元素然后具有相同的调用签名:

Tensor<> B(5,5);
double a = B(3,4);   // operator() (size_t,size_t) used to both GET elements
B(3,4) = 5.5;        // and SET elements

将其扩展到任意张量等级是相当简单的。但我希望能够实现的是一种更高级的索引/分配元素的方式:

Tensor<> B(5,5);
Tensor<> C = B( Slice(0,4,2), 2 );  // operator() (Slice(),size_t) used to GET elements
B( Slice(0,4,2), 2 ) = C;           // and SET elements 
         // (C is another tensor of the correct dimensions)

我知道std::valarray(以及许多其他人)已经做了非常相似的事情,但仅仅完成行为并不是我的目标;我的目标是学习如何优雅高效安全地将以下功能添加到我的Tensor<>课程中:

// Indexing/assigning with Tensor<bool>
B( B>0 ) += 1.0;   

// Indexing/assigning arbitrary amount of dimensions, each dimension indexed 
// with either Tensor<bool>, size_t, Tensor<size_t>, or Slice()
B( Slice(0,2,FINAL), 3, Slice(0,3,FINAL), 4 ) = C; 

// double indexing/assignment operation
B(3, Slice(0,4,FINAL))(mask) = C;  // [mask] == Tensor<bool>

.. etc.

请注意,我打算operator[]operator(). 或者,我将更多地坚持std::vector<>使用.at()检查版本的方法的方法operator[]。无论如何,这是一个设计选择,除了现在的问题。

我想出了以下不完整的“解决方案”。此方法仅对向量/矩阵(rank-1 或 rank-2 张量)真正可管理,并且具有许多不良副作用:

// define a simple slice class
Slice () 
{ 
  private:
    size_t 
        start, stride, end; 

  public: 
    Slice(size_t s, size_t e) : start(s), stride(1), end(e) {}
    Slice(size_t s, size_t S, size_t e) : start(s), stride(S), end(e) {}
    ...

};

template<typename T = double>
class Tensor
{
    ... // same as before

  public:       

    // define two operators() for use with slices:     

    // version for retrieving data
    const Tensor<T> & operator() (Slice r, size_t c) const {
        // use slicing logic to construct return tensor
        ...
        return M;
    {

    // version for assigning data
    Sass operator() (Slice r, size_t c) {
        // returns Sass object, defined below
        return Sass(*this, r,c);
    }

  protected:

    class Sass 
    {
        friend class Tensor<T>;

     private:        
        Tensor<T>& M;
        const Slice &R;
        const size_t c;

      public:

        Sass(Tensor<T> &M, const Slice &R, const size_t c)
            : M(M)
            , R(R)
            , c(c)
        {}

        operator Tensor<T>() const { return M; }

        Tensor<T> & operator= (const Tensor<T> &M2) {
            // use R/c to copy contents of M2 into M using the same 
            // Slice-logic as in "Tensor<T>::operator()(...) const" above
            ...

            return M;
        }

    };  

但这只是感觉不对...

对于上面列出的每个索引/分配方法,我必须为每个这样的操作定义一个单独的Tensor<T>::Sass::Sass(...)构造函数、一个 newTensor<T>::Sass::operator=(...)和一个 new 。Tensor<T>::operator()(...)此外,Tensor<T>::Sass::operators=(...)将需要包含许多已经在对应的相同内容,并且使所有内容都Tensor<T>::operator()(...)适合任意等级使这种方法变得非常丑陋,过于冗长,更重要的是,完全无法管理。Tensor<>

所以,我的印象是有一种更有效的方法来解决所有这些问题。

有什么建议么?

4

3 回答 3

3

首先我想指出一些设计问题:

T & operator() (size_t a, size_t b) const; 

建议您不能通过此方法更改矩阵,因为它是const. 但是您正在返回对矩阵元素的非常量引用,因此实际上您可以更改它。这仅因为您使用的原始指针而编译。我建议std::vector改用它,它会为您进行内存管理,并且会给您一个错误,因为 vector 的 const 版本operator[]提供了应有的 const 引用。

关于您的实际问题,我不确定 Slice 构造函数的参数应该做什么,也不确定 Sass 对象的含义(我不是母语人士,“Sass”在字典中只给了我一个翻译,意思是…… .如“无礼”、“无礼”)。但是,我想您希望使用切片创建一个对象,该对象可以访问由切片参数定义的矩阵子集。

我建议不要使用operator()各种方式访问​​矩阵。使用两个索引访问给定元素的 op() 似乎很自然。使用类似的运算符来获得整个矩阵对我来说似乎不太直观。

这是一个想法:创建一个 Slice 类,该类包含对 Matrix 的引用以及定义 Matrix 的哪个部分由 Slice 表示的必要参数。这样,Slice 就像是它定义的 Matrix 子集的代理,类似于一对迭代器,可以看作是它们指向的容器子范围的代理。给您的 Matrix 提供一对slice()返回 Slice/ConstSlice 的方法(const 和 nonconst),引用您调用该方法的 Matrix。这样,您甚至可以对方法进行检查,以查看 Slice 的参数对于它所引用的 Matrix 是否有意义。如果有意义且有必要,您还可以添加一个转换运算符,将 Slice 转换为它自己的 Matrix。

一次又一次地重载 operator() 并将参数用作掩码,因为线性索引和其他东西比帮助 imo 更令人困惑。operator()如果它做了每个人都期望的自然的事情,那么它就是光滑的。如果它在任何地方使用,它只会混淆代码。改用命名方法。

于 2012-12-03T09:48:19.960 回答
0

如果你允许你的张量隐含地是一个双精度,你可以只从你的 operator() 重载中返回张量。

operator double() { 
    return M.size() == 1 ? M[0] : std::numeric_limits<double>::quiet_NaN(); 
};

这应该允许

double a = B(3,4);
Tensor<> a = B(Slice(1,2,3),4); 

让 operator() 使用 Slice 和 integer 处理多个重载是另一个问题。我可能只是使用 Slice 并创建另一个隐式转换,以便整数可以是 Slice 的,然后可能使用变量参数省略。

const Tensor<T> & operator() (int numOfDimensions, ...)

尽管可变参数路由是一种混合,最好只为 Slice 的 1-8 个参数提供 8 个特化。

于 2012-12-13T13:48:40.453 回答
0

不是答案,只是跟进我的评论的注释:

Tensor<bool> T(false);
// T (whatever its rank) contains all false
auto lazy = T(Slice(0,4,2));
// if I use lazy here, it will be all false
T = true;
// now T contains all true
// if I use lazy here, it will be all true

可能是您想要的,也可能是意料之外的。

一般来说,这可以与不可变张量一起干净地工作,但允许变异会产生与 COW 字符串相同的问题。

于 2012-12-10T10:59:08.677 回答