using System;
using System.Collections.Generic;
namespace NDimensionalProjection
{
public class Matrix
{
private readonly double[,] _data;
public int Rows { get; }
public int Cols { get; }
public Matrix(int rows, int cols)
{
Rows = rows;
Cols = cols;
_data = new double[rows, cols];
}
public Matrix(double[,] data)
{
Rows = data.GetLength(0);
Cols = data.GetLength(1);
_data = (double[,])data.Clone();
}
public double this[int r, int c]
{
get { return _data[r, c]; }
set { _data[r, c] = value; }
}
// --- 演算子オーバーロード ---
public static Matrix operator +(Matrix a, Matrix b)
{
if (a.Rows != b.Rows || a.Cols != b.Cols)
throw new ArgumentException("行列のサイズが一致しません。");
var result = new Matrix(a.Rows, a.Cols);
for (int i = 0; i < a.Rows; i++)
for (int j = 0; j < a.Cols; j++)
result[i, j] = a[i, j] + b[i, j];
return result;
}
public static Matrix operator -(Matrix a, Matrix b)
{
if (a.Rows != b.Rows || a.Cols != b.Cols)
throw new ArgumentException("行列のサイズが一致しません。");
var result = new Matrix(a.Rows, a.Cols);
for (int i = 0; i < a.Rows; i++)
for (int j = 0; j < a.Cols; j++)
result[i, j] = a[i, j] - b[i, j];
return result;
}
public static Matrix operator *(Matrix a, Matrix b)
{
if (a.Cols != b.Rows)
throw new ArgumentException("左の行列の列数と右の行列の行数が一致しません。");
var result = new Matrix(a.Rows, b.Cols);
for (int i = 0; i < result.Rows; i++)
{
for (int j = 0; j < result.Cols; j++)
{
double sum = 0;
for (int k = 0; k < a.Cols; k++)
sum += a[i, k] * b[k, j];
result[i, j] = sum;
}
}
return result;
}
public static Matrix operator *(Matrix a, double scalar)
{
var result = new Matrix(a.Rows, a.Cols);
for (int i = 0; i < a.Rows; i++)
for (int j = 0; j < a.Cols; j++)
result[i, j] = a[i, j] * scalar;
return result;
}
public static Matrix operator *(double scalar, Matrix a)
{
return a * scalar;
}
// --- 転置行列 (Transpose) ---
public Matrix Transpose()
{
var result = new Matrix(Cols, Rows); // 行と列のサイズを入れ替え
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Cols; j++)
{
result[j, i] = _data[i, j]; // 成分を入れ替え
}
}
return result;
}
// --- 逆行列 (Inverse) ---
public Matrix Inverse()
{
if (Rows != Cols)
throw new InvalidOperationException("正方行列ではありません。");
int n = Rows;
double[,] augmented = new double[n, 2 * n];
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
augmented[i, j] = _data[i, j];
augmented[i, j + n] = (i == j) ? 1.0 : 0.0;
}
}
for (int i = 0; i < n; i++)
{
double pivot = augmented[i, i];
int pivotRow = i;
for (int k = i + 1; k < n; k++)
{
if (Math.Abs(augmented[k, i]) > Math.Abs(pivot))
{
pivot = augmented[k, i];
pivotRow = k;
}
}
if (Math.Abs(pivot) < 1e-10)
throw new InvalidOperationException("特異行列のため逆行列が存在しません。");
if (pivotRow != i)
{
for (int j = 0; j < 2 * n; j++)
{
double temp = augmented[i, j];
augmented[i, j] = augmented[pivotRow, j];
augmented[pivotRow, j] = temp;
}
}
for (int j = 0; j < 2 * n; j++) augmented[i, j] /= pivot;
for (int k = 0; k < n; k++)
{
if (k != i)
{
double factor = augmented[k, i];
for (int j = 0; j < 2 * n; j++) augmented[k, j] -= factor * augmented[i, j];
}
}
}
var inverse = new Matrix(n, n);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
inverse[i, j] = augmented[i, j + n];
return inverse;
}
public override string ToString()
{
string s = "";
for (int i = 0; i < Rows; i++)
{
s += "| ";
for (int j = 0; j < Cols; j++) s += $"{_data[i, j],6:F2} ";
s += "|\n";
}
return s;
}
// 1. 各要素に関数を適用する (Map)
// 例: 行列の全要素にシグモイド関数を掛ける時などに使用
public Matrix Map(Func<double, double> func)
{
var result = new Matrix(Rows, Cols);
for (int i = 0; i < Rows; i++)
for (int j = 0; j < Cols; j++)
result[i, j] = func(_data[i, j]);
return result;
}
// 2. 要素ごとの掛け算 (アダマール積)
// 通常の行列積(*)とは異なり、同じ位置の成分同士を掛け合わせる
public static Matrix ElementWiseMultiply(Matrix a, Matrix b)
{
if (a.Rows != b.Rows || a.Cols != b.Cols)
throw new ArgumentException("行列のサイズが一致しません。");
var result = new Matrix(a.Rows, a.Cols);
for (int i = 0; i < a.Rows; i++)
for (int j = 0; j < a.Cols; j++)
result[i, j] = a[i, j] * b[i, j];
return result;
}
}
/// <summary>
/// n次元空間からm次元空間への投影を行うクラス
/// </summary>
public class NDimensionalProjector
{
public int SourceDimension { get; } // 投影元の次元数 (n)
public int TargetDimension { get; } // 投影先の次元数 (m)
// 投影時に残す軸のインデックス
private int[] _projectionAxes;
// 回転させる平面(軸iと軸j)と、その回転角度(ラジアン)のリスト
// 回転は順番によって結果が変わるため、リストで保持します
private readonly List<(int axis1, int axis2, double angle)> _rotations;
public NDimensionalProjector(int sourceDimension, int targetDimension)
{
if (sourceDimension <= 0 || targetDimension <= 0)
throw new ArgumentException("次元数は1以上である必要があります。");
if (targetDimension > sourceDimension)
throw new ArgumentException("投影先の次元(m)は、投影元の次元(n)以下である必要があります。");
SourceDimension = sourceDimension;
TargetDimension = targetDimension;
_rotations = new List<(int, int, double)>();
// デフォルトでは先頭から m 個の軸を残す
_projectionAxes = new int[targetDimension];
for (int i = 0; i < targetDimension; i++) _projectionAxes[i] = i;
}
/// <summary>
/// 投影時に残す(スクリーンとする)軸を指定します。
/// </summary>
/// <param name="axes">残す軸のインデックス(例: XとZを残すなら 0, 2)</param>
public void SetProjectionAxes(params int[] axes)
{
if (axes.Length != TargetDimension)
throw new ArgumentException($"指定する軸の数は投影先の次元数({TargetDimension})と一致する必要があります。");
foreach (int axis in axes)
{
if (axis < 0 || axis >= SourceDimension)
throw new ArgumentOutOfRangeException("指定された軸のインデックスが投影元の次元数の範囲外です。");
}
_projectionAxes = (int[])axes.Clone();
}
/// <summary>
/// 指定した2つの軸で構成される平面での回転を追加します。
/// </summary>
/// <param name="axis1">軸のインデックス (0からn-1)</param>
/// <param name="axis2">軸のインデックス (0からn-1)</param>
/// <param name="angleRadians">回転角度(ラジアン)</param>
public void AddRotation(int axis1, int axis2, double angleRadians)
{
if (axis1 < 0 || axis1 >= SourceDimension || axis2 < 0 || axis2 >= SourceDimension)
throw new ArgumentOutOfRangeException("軸のインデックスが範囲外です。");
if (axis1 == axis2)
throw new ArgumentException("異なる2つの軸を指定してください。");
_rotations.Add((axis1, axis2, angleRadians));
}
/// <summary>
/// 登録されたすべての回転をクリアします。
/// </summary>
public void ClearRotations()
{
_rotations.Clear();
}
/// <summary>
/// n x n の合成回転行列を生成します。
/// </summary>
public Matrix GetCombinedRotationMatrix()
{
// 初期状態は単位行列
Matrix combined = CreateIdentityMatrix(SourceDimension);
foreach (var rot in _rotations)
{
Matrix r = CreateGivensRotationMatrix(SourceDimension, rot.axis1, rot.axis2, rot.angle);
// 回転行列を掛け合わせる (後から追加した回転が左から掛かるようにする)
combined = r * combined;
}
return combined;
}
/// <summary>
/// m x n の射影行列を生成します。(正投影)
/// 指定されたm個の成分を残し、残りを捨てる行列です。
/// </summary>
public Matrix GetProjectionMatrix()
{
var proj = new Matrix(TargetDimension, SourceDimension);
for (int i = 0; i < TargetDimension; i++)
{
proj[i, _projectionAxes[i]] = 1.0;
}
return proj;
}
/// <summary>
/// n次元の座標を回転させ、m次元に投影します。
/// </summary>
/// <param name="point">n行1列の行列(列ベクトル)</param>
/// <returns>m行1列の行列(列ベクトル)</returns>
public Matrix Project(Matrix point)
{
if (point.Rows != SourceDimension || point.Cols != 1)
throw new ArgumentException($"入力点は {SourceDimension}行 1列 の列ベクトルである必要があります。");
Matrix rotationMatrix = GetCombinedRotationMatrix();
Matrix projectionMatrix = GetProjectionMatrix();
// P * R * x の計算
Matrix rotatedPoint = rotationMatrix * point;
Matrix projectedPoint = projectionMatrix * rotatedPoint;
return projectedPoint;
}
// --- ヘルパーメソッド ---
private Matrix CreateIdentityMatrix(int size)
{
var m = new Matrix(size, size);
for (int i = 0; i < size; i++) m[i, i] = 1.0;
return m;
}
// 2つの軸で構成される平面での回転行列 (Givens回転行列)
private Matrix CreateGivensRotationMatrix(int size, int i, int j, double angle)
{
Matrix m = CreateIdentityMatrix(size);
m[i, i] = Math.Cos(angle);
m[j, j] = Math.Cos(angle);
m[i, j] = -Math.Sin(angle);
m[j, i] = Math.Sin(angle);
return m;
}
}
}