Tensor — 任意階数テンソル

概要

Tensor<T> は sangi の任意階数 (rank) ・任意シェイプの動的テンソルクラスである。 Matrix / Vector / 3D グリッドを統一的に扱える設計で、機械学習や物理シミュレーションでの多次元配列の表現に用いる。 ヘッダオンリーで、追加のライブラリリンクは不要。

  • 動的ランク・動的シェイプTensor<T>({3, 4, 5}) のように任意次元を実行時に指定可能
  • ストライドベースのビューtranspose / reshape はデータコピーなしでビューを返す
  • shared_ptr によるデータ共有 — 複数のテンソルが安全に同じバッファを参照可能。書き込み時に必要なら独立コピーを作成 (copy-on-write 風)
  • Matrix / Vector 相互変換 — rank-2 ↔ Matrix<T>、rank-1 ↔ Vector<T>、rank-0 ↔ スカラー
  • C++23 conceptsTensorScalar コンセプトで要素型を制約

用語: ビュー・連続配置・独立コピー

本ページで頻出する 3 つの基本用語をまず説明する。 Tensor は内部で 1 本の連続したメモリバッファshape (各軸のサイズ) + strides (各軸を 1 進めるときのメモリ上の歩幅) を保持する。 この設計のため、 形状や軸順を変えるだけならメモリを実際にコピーする必要はなく、 「shape と strides の見方を変える」 だけで済む。

  • ビュー (view): 別のテンソルと 同じメモリバッファ を参照するテンソル。 例: auto r = t.reshape({6, 4}); としたとき、 rt は同じデータを共有する (r(0) = 99; と書くと t の対応要素も 99 になる)。 メモリ確保ゼロ・データコピーゼロで作れるが、 書き換えると他のビューにも影響する点に注意。
  • 連続配置 (contiguous): テンソルの要素がメモリ上に row-major (= 一番外側の軸が低速で、 内側に向かって高速に変化する) 順にぴったり詰まって並んでいる 状態。 普通にコンストラクタで作ったテンソルは連続配置。 transpose で軸を入れ替えるとストライドが row-major 順ではなくなり 非連続配置 になる (データは動かさず、 strides の見方だけ変える)。 isContiguous() で確認できる。 BLAS や SIMD カーネルの多くは「連続配置前提」 なので、 非連続配置なテンソルを高速関数に渡す前に contiguous() で連続配置化する必要がある。
  • 独立コピー (independent copy / deep copy): 他のテンソルと メモリバッファを共有しない 新しいテンソル。 書き換えても元データに影響しない。 contiguous() / fill() などはビュー状態だと 独立コピーを内部で確保してから処理する (元データを誤って書き換えないため)。

具体例:

Tensor<double> t({2, 3, 4}, 1.0);  // 連続配置、 単独所有
auto v = t.transpose({2, 1, 0});    // ビュー、 非連続配置 (strides を反転しただけ)
auto c = v.contiguous();            // 独立コピー、 v とは別のメモリ
                                    // (BLAS に渡す前にこれを呼ぶのが定石)

テンソルの理論的背景は テンソル (線形代数・中級) / 行列・テンソル微分 を参照。

TensorScalar コンセプト

Tensor<T> の要素型 T は次のコンセプトを満たす必要がある。

template<typename T>
concept TensorScalar = requires(T a, T b) {
    { a + b } -> std::convertible_to<T>;
    { a - b } -> std::convertible_to<T>;
    { a * b } -> std::convertible_to<T>;
    { -a }    -> std::convertible_to<T>;
    { T(0) };
};

すなわち加減乗算と単項マイナス、ゼロ初期化が可能な型。組み込みの float, double, std::complex<T> はもちろん、sangi の多倍長型 Int, Float, Rational, Complex も満たす。

Tensor<T> クラス

template<TensorScalar T>
class Tensor;

コンストラクタ

シグネチャ説明
Tensor()デフォルト構築。rank-0 スカラー (値 0)
Tensor(std::vector<size_t> shape)shape 指定のゼロ初期化テンソル
Tensor(std::initializer_list<size_t> shape)同上 (初期化子リスト版)
Tensor(std::vector<size_t> shape, const T& value)全要素を value で初期化
Tensor(std::vector<size_t> shape, std::vector<T> data)フラットデータから構築 (row-major 順)。サイズ不一致で DimensionError
Tensor(const T& scalar) (explicit)rank-0 スカラーテンソル
Tensor(const Matrix<T>& matrix) (explicit)Matrix から rank-2 テンソルへ変換
Tensor(const Vector<T>& vec) (explicit)Vector から rank-1 テンソルへ変換
Tensor(const Tensor&) / Tensor(Tensor&&) noexceptコピー / ムーブ (内部の shared_ptr を共有)
引数説明
shapestd::vector<size_t> または std::initializer_list<size_t>各軸のサイズを並べたリスト。 例: {3, 4, 5} なら 3×4×5 の rank-3 テンソル。 空 {} は rank-0 スカラーを意味する
valueconst T&全要素を初期化するための値
datastd::vector<T>テンソルの全要素を row-major 順 (一番内側の軸が高速に変化) で並べたフラット配列。 長さは shape の総積と一致する必要がある
scalarconst T&rank-0 スカラーテンソルの中身となる値
matrixconst Matrix<T>&rank-2 テンソルとして取り込む元の Matrix。 形状は (rows, cols) になる
vecconst Vector<T>&rank-1 テンソルとして取り込む元の Vector。 形状は (size,) になる
Tensor<double> t0;                       // rank-0 スカラー (値 0)
Tensor<double> t1({3, 4, 5});            // rank-3 ゼロ初期化 (60 要素)
Tensor<double> t2({2, 3}, 1.0);          // rank-2、全要素 1.0
Tensor<double> t3({2, 3}, {1,2,3,4,5,6}); // フラットデータから (row-major)
Tensor<double> s(3.14);                  // rank-0 スカラー

要素アクセス

メンバ関数説明
operator()(Indices... indices)variadic template 版。 各軸の index を引数に並べて渡す。 例: rank-3 なら t(i, j, k)。 rank と引数個数の不一致で DimensionError
at(std::span<const size_t> indices)境界チェック付きアクセス。 各軸の index を span で渡す
at(std::initializer_list<size_t> indices)同上 (初期化子リスト版)。 例: t.at({i, j, k})
flat(size_t index)論理フラットインデックスアクセス。 内部 strides に従って多次元 index から自動的にメモリ位置を計算する。 ビュー (非連続配置) でも一貫したアクセスが可能
data()内部バッファへの生ポインタ。 非連続配置なビューに対して使うと strides の影響で row-major 順とは異なる並びになる 点に注意 (BLAS に渡す場合はまず contiguous() を呼ぶ)
引数説明
indices...integral 型のパラメータパック
indicesstd::span<const size_t> / std::initializer_list<size_t>各軸の index を並べたコンテナ。 要素数は rank() と一致する必要がある
index (flat)size_t0 以上 size() 未満の論理フラット index
Tensor<double> t({2, 3}, {1,2,3,4,5,6});
double x = t(1, 2);                       // 6 (row 1, col 2)
double y = t.at({0, 1});                  // 2 (境界チェック)
double z = t.flat(3);                     // 4 (フラット index)
double* p = t.data();                     // raw pointer

プロパティ

メンバ関数返り値型説明
rank()size_t階数 (次元数)。rank-0 スカラーは 0
shape()const std::vector<size_t>&各軸のサイズ
shape(size_t axis)size_t特定軸のサイズ
strides()const std::vector<size_t>&ストライド (要素単位)
size()size_t全要素数
empty()bool要素数 0 (いずれかの軸が 0)
isContiguous()boolメモリが row-major に連続か (transpose 後は false)
isScalar()boolrank-0 スカラーか
isView()boolデータを他テンソルと共有中か (use_count > 1)
useCount()long内部 shared_ptr の参照カウント

変更操作

メンバ関数説明
fill(const T& value)全要素を value で埋める。 自身がビュー (他テンソルとメモリを共有している) の場合は、 共有元を書き換えないよう内部で独立コピーを確保してから埋める
zero()全要素をゼロに設定 (fill(T(0)) 相当)
引数説明
valueconst T&全要素に書き込む値

形状変更

メンバ関数説明
reshape(std::vector<size_t> newShape)shape を newShape に組み替える。 元の size()newShape の総積が一致する場合のみ可能 (不一致で DimensionError)。 自身が連続配置ならメモリは動かさずに ビュー として返す (元のテンソルとデータを共有)、 非連続配置なら強制的に 独立コピー を作成して新しいテンソルを返す
transpose(std::vector<size_t> perm)軸の順序を perm で並べ替えた ビュー を返す。 strides の見方を変えるだけでメモリコピーは発生しない (= 通常は非連続配置になる)。 perm0..rank-1 の順列
transpose()全軸を逆順に反転したビューを返す (rank-2 なら通常の行列転置、 rank-3 なら {2,1,0} 相当)
contiguous()メモリ上で row-major に連続配置された 独立コピー を返す (= 元のテンソルとデータを共有しない新しいバッファを確保し、 そこに要素を並べ直す)。 既に連続配置で単独所有 (useCount() == 1) のときは余計なコピーを避けて *this をそのまま返す
引数説明
newShapestd::vector<size_t>変更後の shape。 総積は元の size() と一致する必要がある
permstd::vector<size_t>軸の順列。 長さは rank()、 値は 0..rank-1 の置換。 例: rank-3 で {2, 0, 1} なら、 元の軸 0 が新しい軸 1 へ、 元の軸 1 が新しい軸 2 へ、 元の軸 2 が新しい軸 0 へ移動する
Tensor<double> t({2, 3, 4});
auto r  = t.reshape({6, 4});             // ビュー (t とデータを共有)
auto tT = t.transpose({2, 1, 0});        // 軸 (0,1,2) → (2,1,0) のビュー (非連続配置)
auto m  = t.transpose();                 // 全軸反転 (rank-3 なら {2,1,0})
auto c  = tT.contiguous();               // 独立コピー (BLAS に渡せる row-major 連続バッファ)

算術演算子 (複合代入)

演算説明
t += rhsshape 一致のテンソル加算。不一致で DimensionError
t -= rhsshape 一致のテンソル減算
t *= scalarスカラー乗算
t /= scalarスカラー除算 (ゼロ除算時 invalid_argument)

Matrix / Vector / スカラー変換

メンバ関数説明
toMatrix()rank-2 テンソルを Matrix<T> へ変換。rank ≠ 2 で DimensionError
toVector()rank-1 テンソルを Vector<T> へ変換。rank ≠ 1 で DimensionError
toScalar()rank-0 テンソルをスカラー T へ変換。rank ≠ 0 で DimensionError

出力

メンバ関数説明
to_string()shape と値を含む文字列表現。要素数が多い場合は先頭 5 + 末尾 3 を表示

非メンバの operator<< も提供 (to_string() を呼ぶ)。

フリー関数

算術演算子

シグネチャ説明
Tensor<T> operator+(const Tensor<T>& lhs, const Tensor<T>& rhs)テンソル加算
Tensor<T> operator-(const Tensor<T>& lhs, const Tensor<T>& rhs)テンソル減算
Tensor<T> operator*(const Tensor<T>&, const T&) / 左版スカラー乗算 (両側オーバーロード)
Tensor<T> operator/(const Tensor<T>&, const T&)スカラー除算
Tensor<T> operator-(const Tensor<T>&)単項マイナス (全要素の符号反転)

テンソル演算

シグネチャ説明
Tensor<T> hadamard(const Tensor<T>& a, const Tensor<T>& b)要素ごとの積 (Hadamard 積)。 shape 一致が必要 (不一致で DimensionError)
Tensor<T> contract(const Tensor<T>& a, const Tensor<T>& b, const std::vector<std::pair<size_t,size_t>>& axes)指定した軸ペア群で縮約 (= einsum 風のテンソル積)。 縮約軸の各サイズが一致している必要がある。 結果の rank は rank(a) + rank(b) − 2 · len(axes)
Tensor<T> outerProduct(const Tensor<T>& a, const Tensor<T>& b)外積 (テンソル積)。 結果の rank は rank(a) + rank(b)、 shape は a.shapeb.shape を連結したもの
引数説明
a, bconst Tensor<T>&演算対象の 2 つのテンソル
axesconst std::vector<std::pair<size_t,size_t>>&縮約する軸ペアのリスト。 各 axes[i] = {axis_of_a, axis_of_b} は 「a の軸 axis_of_a」 と 「b の軸 axis_of_b」 を内積で潰すことを意味する。 例: rank-2 同士の行列積は {{1, 0}} (a の列軸 1 と b の行軸 0 で縮約)
// Hadamard 積 (要素積)
Tensor<double> a({2,3}, {1,2,3,4,5,6});
Tensor<double> b({2,3}, {6,5,4,3,2,1});
auto h = hadamard(a, b);   // shape = {2,3}, data = {6, 10, 12, 12, 10, 6}

// 行列積 (rank-2 × rank-2 を axis 1 と axis 0 で縮約)
Tensor<double> M({3,4}, 1.0);
Tensor<double> N({4,5}, 2.0);
auto C = contract(M, N, {{1, 0}});   // shape = {3, 5}, 全要素 = 8 (= 4×1×2)

// 外積 (rank-1 × rank-1 で rank-2)
Tensor<double> u({3}, {1,2,3});
Tensor<double> v({2}, {10,20});
auto K = outerProduct(u, v);   // shape = {3,2}, data = {10,20,20,40,30,60}
                               // = {{10,20},{20,40},{30,60}}
// 実行結果:
//   h shape=[2,3] data=[6,10,12,12,10,6]
//   C shape=[3,5] data=[8,8,8,8,8, 8,8,8,8,8, 8,8,8,8,8]
//   K shape=[3,2] data=[10,20,20,40,30,60]

ファクトリ関数

sangi::tensor 名前空間に提供。

シグネチャ説明
tensor::zeros<T>(std::vector<size_t> shape)全要素 0 のテンソル
tensor::ones<T>(std::vector<size_t> shape)全要素 1 のテンソル

型特性

template<typename T> struct is_tensor;
template<typename T> inline constexpr bool is_tensor_v = is_tensor<T>::value;

is_tensor_v<X>XTensor<T> のインスタンスのときに true。SFINAE / concepts で分岐に使う。

使用例

#include <math/core/Tensor.hpp>
using namespace sangi;

int main() {
    // 3 階テンソルを作成し reshape ビューを取得
    Tensor<double> t({2, 3, 4}, 1.0);
    auto t_flat = t.reshape({24});           // ビュー
    t_flat(0) = 42.0;                        // t(0,0,0) も 42.0 (データ共有)

    // 軸の置換 (transpose) はビュー、contiguous() でコピー
    auto t_perm = t.transpose({2, 0, 1});    // shape {4,2,3}, データコピーなし
    auto t_copy = t_perm.contiguous();       // 物理的に row-major な独立コピー

    // Hadamard 積
    Tensor<double> a({2,2}, {1,2,3,4});
    Tensor<double> b({2,2}, {5,6,7,8});
    auto c = hadamard(a, b);                 // {5, 12, 21, 32}

    // テンソル縮約 (行列積として)
    Tensor<double> X({3, 4}, 1.0);
    Tensor<double> Y({4, 5}, 2.0);
    auto Z = contract(X, Y, {{1, 0}});       // shape {3,5}, 各要素 = 8.0

    // Matrix / Vector との相互変換
    Matrix<double> M(3, 3, 1.0);
    Tensor<double> tM(M);                    // rank-2 テンソルへ
    Matrix<double> M2 = tM.toMatrix();       // 戻す
    // 実行結果:
    //   t_perm  rank=3, shape={4,2,3}, isContiguous=false
    //   c       shape=[2,2], data=[5,12,21,32]
    //   Z       shape=[3,5], 全要素 8.0
    //   tM.rank=2, size=9; M2(0,0)=1, M2(2,2)=1
}

関連: Matrix / Vector / FFT (Tensor3D) / Matrix 算術 — Hadamard 積