4

OK、私はあまりにも長い間グーグルで検索してきましたが、この手法を何と呼ぶべきかわからないので、SOでここで質問する方がよいと考えました. これに明らかな名前や見落としている解決策がある場合は、正しい方向に向けてください。

素人向け:テンソルは行列の論理拡張であり、行列がベクトルの論理拡張であるのと同じです。ベクトルはランク 1 テンソル (プログラミング用語では、数値の 1D 配列) であり、行列はランク 2 テンソル (数値の 2D 配列) であり、ランク N テンソルは単に数値の ND 配列です。 .

ここで、次の Tensor クラスのようなものがあるとします。

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[]

私は次の不完全な「解決策」を思いつきました。このメソッドは、ベクトル/行列 (ランク 1 またはランク 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(...)コンストラクター、 new Tensor<T>::Sass::operator=(...)、およびそのTensor<T>::operator()(...)ような操作ごとに new を定義する必要があります。さらに、 にはTensor<T>::Sass::operators=(...)、対応する にすでにあるものと同じものの多くを含める必要があり、すべてを任意のランクTensor<T>::operator()(...)の に適したものにすることで、このアプローチは非常に醜く、冗長になりすぎ、さらに重要なことに、完全に管理できなくなります。Tensor<>

ですから、私は、これらすべてに対してもっと効果的なアプローチがあるという印象を受けています。

助言がありますか?

4

3 に答える 3

3

まず最初に、いくつかの設計上の問題を指摘したいと思います。

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

は、このメソッドを使用してマトリックスを変更できないことを示していますconst。しかし、あなたは行列要素への非定数参照を返しているので、実際にはそれを変更することができます。これは、使用しているrawポインターのためにのみコンパイルされます。代わりに使用することをお勧めしますstd::vector。これはメモリ管理を行い、vectorのconstバージョンはoperator[]const参照を提供するため、エラーが発生します。

あなたの実際の質問に関しては、Sliceコンストラクターのパラメーターが何をすべきか、Sassオブジェクトが何を意味するのかわかりません(私はネイティブスピーカーではなく、「Sass」は辞書で1つの翻訳のみを提供します。つまりsth 。「impudence」、「impertinence」のように)。ただし、スライスを使用して、スライスのパラメーターで定義されたマトリックスのサブセットへのアクセスを提供するオブジェクトを作成するとします。

operator()マトリックスにアクセスするためにあらゆる方法で使用することはお勧めしません。特定の要素にアクセスするための2つのインデックスを持つop()は自然なようです。同様の演算子を使用して行列全体を取得するのは、直感的ではないようです。

アイデアは次のとおりです。マトリックスへの参照と、マトリックスのどの部分がスライスで表されるかを定義する必要なパラメーターを保持するスライスクラスを作成します。そうすれば、スライスは、それが定義するMatrixサブセットのプロキシのようなものになります。これは、ポイントしているコンテナのサブレンジのプロキシと見なすことができるイテレータのペアに似ています。slice()メソッドを呼び出すマトリックスを参照して、Slice / ConstSliceを返すメソッドのペア(constとnonconst)をMatrixに与えます。そうすれば、メソッドにチェックを入れて、スライスのパラメーターが参照するマトリックスにとって意味があるかどうかを確認することもできます。意味があり、必要な場合は、変換演算子を追加して、スライスを独自のマトリックスに変換することもできます。

operator()を何度もオーバーロードし、パラメーターをマスクとして使用します。線形インデックスやその他のものは、imoを支援するよりも混乱を招きます。operator()誰もが期待するような自然なことをするなら、それは滑らかです。どこでも使用されている場合にのみ、コードがわかりにくくなります。代わりに名前付きメソッドを使用してください。

于 2012-12-03T09:48:19.960 に答える
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 に答える
0

Tensor が暗黙的に double になることを許可する場合、operator() オーバーロードから Tensor のみを返すことができます。

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 を使用した複数のオーバーロードを処理することは、別の問題です。おそらく、スライスを使用して別の暗黙的な変換を作成し、整数をスライスにすることができます。その後、可変引数の省略記号を使用することもできます。

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

可変引数ルートはちょっと面倒ですが、Slice の 1 ~ 8 個のパラメーターに対して 8 個の特殊化を行うのが最善です。

于 2012-12-13T13:48:40.453 に答える