using System;
public class Test
{
public static void Main()
{
// your code goes here
}
public interface LinearAlgebra
{
private const double EPS = 1e-12;
private const double TINY = 1e-20;
private const double DEFAULT_EPS = 1e-6;
private const double SECOND_DIFF_EPS = 1e-4;
private static bool IsEqual(double a, double b) => Math.Abs(a - b) < EPS;
// Глубокое копирование jagged-массива
private static double[][] DeepCopy(double[][] original)
{
if (original == null) return null;
double[][] copy = new double[original.Length][];
for (int i = 0; i < original.Length; i++)
{
if (original[i] != null)
{
copy[i] = new double[original[i].Length];
Array.Copy(original[i], copy[i], original[i].Length);
}
}
return copy;
}
// Копирование вектора
private static double[] DeepCopy(double[] original)
{
if (original == null) return null;
double[] copy = new double[original.Length];
Array.Copy(original, copy, original.Length);
return copy;
}
/// <summary>
/// Решает систему A * x = b методом LU-разложения.
/// Исходные матрица A и вектор b НЕ изменяются.
/// </summary>
/// <param name="a">Квадратная матрица коэффициентов (jagged array)</param>
/// <param name="b">Вектор правой части</param>
/// <returns>Вектор решения x</returns>
public static double[] SolveLinearSystem(double[][] a, double[] b)
{
if (a == null || b == null)
throw new ArgumentNullException();
int n = a.Length;
if (n == 0 || b.Length != n)
throw new ArgumentException("Matrix must be square and vector size must match n.");
for (int i = 0; i < n; i++)
{
if (a[i] == null || a[i].Length != n)
throw new ArgumentException($"Row {i} is null or has incorrect length.");
}
// Работаем с копиями!
double[][] aCopy = DeepCopy(a);
double[] bCopy = DeepCopy(b);
int[] indx = new int[n];
double d = 1.0;
LU_Decomposition(ref aCopy, indx, ref d, n);
LU_Solve(aCopy, ref bCopy, indx, n);
return bCopy; // теперь это решение x
}
private static void LU_Decomposition(ref double[][] a, int[] indx, ref double d, int n)
{
double[] vv = new double[n];
d = 1.0;
for (int i = 0; i < n; i++)
{
double big = 0.0;
for (int j = 0; j < n; j++)
{
double temp = Math.Abs(a[i][j]);
if (temp > big) big = temp;
}
if (IsEqual(big, 0.0))
throw new InvalidOperationException("Singular matrix in LU_Decomposition.");
vv[i] = 1.0 / big;
}
for (int j = 0; j < n; j++)
{
for (int i = 0; i < j; i++)
{
double sum = a[i][j];
for (int k = 0; k < i; k++)
sum -= a[i][k] * a[k][j];
a[i][j] = sum;
}
double big = 0.0;
int imax = j;
for (int i = j; i < n; i++)
{
double sum = a[i][j];
for (int k = 0; k < j; k++)
sum -= a[i][k] * a[k][j];
a[i][j] = sum;
double dum = vv[i] * Math.Abs(sum);
if (dum > big || IsEqual(dum, big))
{
big = dum;
imax = i;
}
}
if (j != imax)
{
(a[imax], a[j]) = (a[j], a[imax]);
d = -d;
vv[imax] = vv[j];
}
indx[j] = imax;
if (IsEqual(a[j][j], 0.0))
a[j][j] = TINY;
if (j != n - 1)
{
double dum = 1.0 / a[j][j];
for (int i = j + 1; i < n; i++)
a[i][j] *= dum;
}
}
}
private static void LU_Solve(double[][] a, ref double[] b, int[] indx, int n)
{
int ii = -1;
for (int i = 0; i < n; i++)
{
int ip = indx[i];
double sum = b[ip];
b[ip] = b[i];
if (ii != -1)
{
for (int j = ii; j < i; j++)
sum -= a[i][j] * b[j];
}
else if (!IsEqual(sum, 0.0))
{
ii = i;
}
b[i] = sum;
}
for (int i = n - 1; i >= 0; i--)
{
double sum = b[i];
for (int j = i + 1; j < n; j++)
sum -= a[i][j] * b[j];
double ld = a[i][i];
b[i] = IsEqual(ld, 0.0) ? 0.0 : sum / ld;
}
}
/// <summary>
/// Вычисляет обратную матрицу для заданной квадратной матрицы.
/// Исходная матрица НЕ изменяется.
/// </summary>
/// <param name="a">Исходная квадратная матрица (jagged array)</param>
/// <returns>Обратная матрица A⁻¹</returns>
public static double[][] InvertMatrix(double[][] a)
{
if (a == null)
throw new ArgumentNullException(nameof(a));
int n = a.Length;
if (n == 0)
throw new ArgumentException("Matrix cannot be empty.");
for (int i = 0; i < n; i++)
{
if (a[i] == null || a[i].Length != n)
throw new ArgumentException($"Row {i} is null or has incorrect length.");
}
// Создаём единичную матрицу
double[][] identity = CreateIdentityMatrix(n);
double[][] inverse = new double[n][];
// Для каждого столбца единичной матрицы решаем A * x = e_j
for (int j = 0; j < n; j++)
{
double[] e_j = new double[n];
for (int i = 0; i < n; i++)
e_j[i] = identity[i][j]; // j-й столбец
// Решаем систему A * x = e_j
double[] x_j = SolveLinearSystem(a, e_j);
// x_j — это j-й столбец обратной матрицы
// Сохраняем его как j-й столбец в inverse
if (inverse[0] == null) // инициализируем строки, если ещё не созданы
{
for (int i = 0; i < n; i++)
inverse[i] = new double[n];
}
for (int i = 0; i < n; i++)
inverse[i][j] = x_j[i];
}
return inverse;
}
// Вспомогательный метод: создаёт единичную матрицу n×n
private static double[][] CreateIdentityMatrix(int n)
{
double[][] I = new double[n][];
for (int i = 0; i < n; i++)
{
I[i] = new double[n];
I[i][i] = 1.0;
}
return I;
}
/// <summary>
/// Умножает матрицу A (m×n) на матрицу B (n×p): C = A * B
/// </summary>
public static double[][] MultiplyMatrices(double[][] a, double[][] b)
{
if (a == null || b == null)
throw new ArgumentNullException();
if (a.Length == 0 || b.Length == 0)
throw new ArgumentException("Matrices cannot be empty.");
int m = a.Length; // строки A
int n = a[0].Length; // столбцы A (и строки B)
int p = b[0].Length; // столбцы B
// Проверка совместимости размеров
if (b.Length != n)
throw new ArgumentException("Number of columns in A must equal number of rows in B.");
// Проверка, что все строки A и B имеют правильную длину
for (int i = 0; i < m; i++)
if (a[i] == null || a[i].Length != n)
throw new ArgumentException($"Row {i} of matrix A is invalid.");
for (int i = 0; i < n; i++)
if (b[i] == null || b[i].Length != p)
throw new ArgumentException($"Row {i} of matrix B is invalid.");
// Результат: m×p
double[][] result = new double[m][];
for (int i = 0; i < m; i++)
{
result[i] = new double[p];
for (int j = 0; j < p; j++)
{
double sum = 0.0;
for (int k = 0; k < n; k++)
{
sum += a[i][k] * b[k][j];
}
result[i][j] = sum;
}
}
return result;
}
/// <summary>
/// Умножает матрицу A (m×n) на вектор v (n): y = A * v
/// </summary>
public static double[] MultiplyMatrixByVector(double[][] a, double[] v)
{
if (a == null || v == null)
throw new ArgumentNullException();
if (a.Length == 0 || v.Length == 0)
throw new ArgumentException("Matrix or vector cannot be empty.");
int m = a.Length;
int n = v.Length;
// Проверка: все строки A должны иметь длину n
for (int i = 0; i < m; i++)
{
if (a[i] == null || a[i].Length != n)
throw new ArgumentException($"Row {i} of matrix A does not match vector length.");
}
double[] result = new double[m];
for (int i = 0; i < m; i++)
{
double sum = 0.0;
for (int k = 0; k < n; k++)
{
sum += a[i][k] * v[k];
}
result[i] = sum;
}
return result;
}
/// <summary>
/// Вычисляет определитель квадратной матрицы с использованием LU-разложения.
/// </summary>
/// <param name="a">Квадратная матрица (jagged array)</param>
/// <returns>Определитель матрицы det(A)</returns>
public static double Determinant(double[][] a)
{
if (a == null)
throw new ArgumentNullException(nameof(a));
int n = a.Length;
if (n == 0)
throw new ArgumentException("Matrix cannot be empty.");
for (int i = 0; i < n; i++)
{
if (a[i] == null || a[i].Length != n)
throw new ArgumentException($"Row {i} is null or has incorrect length.");
}
// Работаем с копией, чтобы не менять оригинал
double[][] aCopy = DeepCopy(a);
int[] indx = new int[n];
double d = 1.0;
// Выполняем LU-разложение (с pivoting)
LU_Decomposition(ref aCopy, indx, ref d, n);
// Определитель = d * произведение диагональных элементов U
double det = d;
for (int i = 0; i < n; i++)
{
det *= aCopy[i][i];
}
return det;
}
/// <summary>
/// Вычисляет C = A + alpha * B (поэлементно)
/// </summary>
/// <param name="a">Вектор A</param>
/// <param name="b">Вектор B</param>
/// <param name="alpha">Коэффициент</param>
/// <returns>Новый вектор C</returns>
public static double[] AddVectorsWithScale(double[] a, double[] b, double alpha)
{
if (a == null || b == null)
throw new ArgumentNullException();
if (a.Length != b.Length)
throw new ArgumentException("Vectors must have the same length.");
double[] c = new double[a.Length];
for (int i = 0; i < a.Length; i++)
{
c[i] = a[i] + alpha * b[i];
}
return c;
}
/// <summary>
/// Численно вычисляет градиент скалярной функции R: ℝⁿ → ℝ в точке x.
/// </summary>
/// <param name="R">Функция R(x)</param>
/// <param name="x">Точка, в которой вычисляется градиент</param>
/// <param name="eps">Шаг конечных разностей (по умолчанию 1e-8)</param>
/// <returns>Вектор градиента ∇R(x) размера n</returns>
public static double[] Gradient(Func<double[], double> R, double[] x, double eps = DEFAULT_EPS)
{
if (R == null) throw new ArgumentNullException(nameof(R));
if (x == null) throw new ArgumentNullException(nameof(x));
int n = x.Length;
double[] grad = new double[n];
double[] xPerturbed = new double[n];
Array.Copy(x, xPerturbed, n);
double f0 = R(x); // f(x)
for (int i = 0; i < n; i++)
{
double original = x[i];
xPerturbed[i] = original + eps;
double f1 = R(xPerturbed);
grad[i] = (f1 - f0) / eps;
xPerturbed[i] = original; // восстановить
}
return grad;
}
/// <summary>
/// Численно вычисляет якобиан вектор-функции R: ℝⁿ → ℝᵐ в точке x.
/// Якобиан J — матрица m×n, где J[i,j] = ∂R_i/∂x_j
/// </summary>
/// <param name="R">Вектор-функция R(x) → double[m]</param>
/// <param name="x">Точка в ℝⁿ</param>
/// <param name="eps">Шаг конечных разностей</param>
/// <returns>Якобиан: J[i][j]</returns>
public static double[][] Jacobian(Func<double[], double[]> R, double[] x, double eps = DEFAULT_EPS)
{
if (R == null) throw new ArgumentNullException(nameof(R));
if (x == null) throw new ArgumentNullException(nameof(x));
double[] y0 = R(x);
if (y0 == null) throw new InvalidOperationException("R(x) returned null.");
int m = y0.Length;
int n = x.Length;
double[][] J = new double[m][];
for (int i = 0; i < m; i++)
J[i] = new double[n];
double[] xPerturbed = new double[n];
Array.Copy(x, xPerturbed, n);
for (int j = 0; j < n; j++)
{
double original = x[j];
xPerturbed[j] = original + eps;
double[] y1 = R(xPerturbed);
for (int i = 0; i < m; i++)
{
J[i][j] = (y1[i] - y0[i]) / eps;
}
xPerturbed[j] = original;
}
return J;
}
/// <summary>
/// Численно вычисляет гессиан скалярной функции R: ℝⁿ → ℝ в точке x.
/// Гессиан H — симметричная матрица n×n, где H[i,j] = ∂²R/∂x_i∂x_j
/// </summary>
/// <param name="R">Скалярная функция R(x)</param>
/// <param name="x">Точка в ℝⁿ</param>
/// <param name="eps">Шаг конечных разностей</param>
/// <returns>Гессиан: H[i][j]</returns>
public static double[][] Hessian(Func<double[], double> R, double[] x, double eps = SECOND_DIFF_EPS)
{
if (R == null) throw new ArgumentNullException(nameof(R));
if (x == null) throw new ArgumentNullException(nameof(x));
int n = x.Length;
double[][] H = new double[n][];
for (int i = 0; i < n; i++)
H[i] = new double[n];
double[] xTemp = new double[n];
Array.Copy(x, xTemp, n);
double f0 = R(x); // f(x)
for (int i = 0; i < n; i++)
{
for (int j = i; j < n; j++)
{
if (i == j)
{
// Вторая производная по x_i
double orig = x[i];
xTemp[i] = orig + eps;
double f1 = R(xTemp); // f(x + h e_i)
xTemp[i] = orig + 2 * eps;
double f2 = R(xTemp); // f(x + 2h e_i)
xTemp[i] = orig; // восстановить
H[i][i] = (f2 - 2 * f1 + f0) / (eps * eps);
}
else
{
// Смешанная производная ∂²f/∂x_i∂x_j
double origI = x[i], origJ = x[j];
// f(x + h e_i + h e_j)
xTemp[i] = origI + eps;
xTemp[j] = origJ + eps;
double fPP = R(xTemp);
// f(x + h e_i)
xTemp[j] = origJ;
double fP0 = R(xTemp);
// f(x + h e_j)
xTemp[i] = origI;
xTemp[j] = origJ + eps;
double f0P = R(xTemp);
// f(x)
xTemp[i] = origI;
xTemp[j] = origJ;
// f0 уже известен
H[i][j] = (fPP - fP0 - f0P + f0) / (eps * eps);
H[j][i] = H[i][j]; // симметрия
}
}
}
return H;
}
/// <summary>
/// Возвращает полный гиперкуб, полученный путем перебора значений переменных
/// от заданного минимального значения до заданного максимального значения.
/// По всем переменным одинаковое количество точек от максимума до минимума.
/// </summary>
/// <param name="variablesMinMax">Двухмерный массив. который содержит значения минимального и максимального значения для каждой переменной</param>
/// <param name="pointsPerVariables">Количество перебираемых точек для каждой переменной</param>
/// <returns>Гиперкуб, заполненный всеми возможными комбинациями. Размер растет экспоненциально от количества точек</returns>
/// <exception cref="ArgumentNullException"></exception>
static double[][] bruteForceGrid(double[][] variablesMinMax, int pointsPerVariables)
{
if (variablesMinMax == null) throw new ArgumentNullException(nameof(variablesMinMax));
if (pointsPerVariables < 2) pointsPerVariables = 2;
int Variables = (int) variablesMinMax.Length;
long M = (long) Math.Pow(pointsPerVariables, Variables);
double [][] totalCombinationsArray = new double[M][];
int[] indexes = new int[Variables];
for (long i = 0; i < M; i++)
{
long c = i;
int varCounter = 0;
totalCombinationsArray[i] = new double[Variables];
for (varCounter = 0; varCounter < Variables; varCounter++)
{
indexes[varCounter] = (int) c % pointsPerVariables;
c /= pointsPerVariables;
double min = variablesMinMax[varCounter][0];
double max = variablesMinMax[varCounter][1];
double delta = (max - min) / (pointsPerVariables-1) * indexes[varCounter];
double value = min + delta;
totalCombinationsArray[i][varCounter] = value;
}
}
return totalCombinationsArray;
}
/// <summary>
/// Находит минимум функции R(x) полным перебором по сетке (полному гиперкубу).
/// Полный гиперкуб, получается путем перебора значений переменных
/// от заданного минимального значения до заданного максимального значения.
/// По всем переменным одинаковое количество точек от максимума до минимума.
/// </summary>
/// <param name="variablesMinMax">Двухмерный массив. который содержит значения минимального и максимального значения для каждой переменной</param>
/// <param name="pointsPerVariables">Количество перебираемых точек для каждой переменной</param>
/// <returns>Вектор x, при котором достигает минимум функции R(x)</returns>
public static double[] BruteForceMinimize(Func<double[], double> R, double[][] variablesMinMax, int pointsPerVariables = 5)
{
int N = variablesMinMax.Length;
double[][] totalCombinationsArray = bruteForceGrid(variablesMinMax, pointsPerVariables);
long Variants = totalCombinationsArray.LongLength;
double[] X = new double[N];
X = totalCombinationsArray[0];
double Ri = R(X), Rmin;
Rmin = Ri;
for (long i = 0; i < Variants; i++)
{
Ri = R(totalCombinationsArray[i]);
if (Ri < Rmin && Ri > 0)
{
Rmin = Ri;
X = totalCombinationsArray[i];
}
}
return X;
}
/// <summary>
/// Минимизирует скалярную функцию R(x) методом Ньютона.
/// </summary>
/// <param name="R">Функция для минимизации: R(x) → double</param>
/// /// <param name="Validator">Функция для проверки вектора x на физичность: Validator(x) → bool</param>
/// <param name="x0">Начальное приближение (вектор)</param>
/// <param name="tol">Точность по норме градиента (по умолчанию 1e-8)</param>
/// <param name="maxIter">Максимальное число итераций (по умолчанию 20)</param>
/// <param name="eps">Шаг для численного дифференцирования (по умолчанию 1e-3)</param>
/// <param name="alpha0">Начальное значение alpha в методе Ньютона Xnext = Xcurr - alpha * H⁻¹ * ∇R(x) </param>
/// <param name="alpha_mult">Множитель на alpha, если вычисленное значение Xnext не уменьшает невязку</param>
/// <returns>Точка минимума x* (вектор)</returns>
///
public static double[] NewtonMinimize(Func<double[], double> R,
double[] x0,
Func<double[], bool>? Validator = null,
double tol = 1e-8,
int maxIter = 40,
double eps = 1e-3,
double alpha0 = 1,
double alpha_mult = 0.5
)
{
if (R == null) throw new ArgumentNullException(nameof(R));
if (x0 == null) throw new ArgumentNullException(nameof(x0));
if (maxIter <= 0) throw new ArgumentException("maxIter must be positive.");
// Если функция валидации текущего приближения не задана, то по умолчанию все компоненты вектора проверяются на неотрицательность
Validator ??= a => a.All(x => x>=0);
double R0 = R(x0);
double Ri = R0;
double alpha = alpha0;
double[] x = new double[x0.Length];
Array.Copy(x0, x, x0.Length);
for (int iter = 0; iter < maxIter; iter++)
{
// Находим градиент невязки ∇R(x) (вектор частных производных невязки по компонентам вектора x)
double[] grad = Gradient(R, x, eps);
// Находим Гессиан невязки H (матрица частных вторых производных невязки по компонентам вектора x)
double[][] hess = Hessian(R, x, eps);
R0 = Ri;
Ri = R(x);
if (Math.Abs(Ri) < eps || (Math.Abs(Ri-R0) < eps * Math.Abs(R0) && iter > 0))
return x;
double gradNorm = Math.Sqrt(grad.Sum(g => g * g));
if (gradNorm < tol)
{
return x;
}
// Находим -∇R(x)
double[] negGrad = AddVectorsWithScale(new double[grad.Length], grad, -1);
alpha = alpha0;
try
{
// Решаем H * d = -∇R(x), откуда d = - H⁻¹ * ∇R(x)
double[] d = SolveLinearSystem(hess, negGrad);
for (int j = 0; j < maxIter; j++)
{
// Xnext = Xcurr - alpha * H⁻¹ * ∇R(x)
double[] xNew = AddVectorsWithScale(x, d, alpha);
if (!Validator(xNew))
{
alpha *= alpha_mult;
continue;
}
double RNew = R(xNew);
// На каждой итерации невязка должна уменьшаться. Если это не так, то надо уменьшать коэффициент alpha
if (Math.Abs(RNew) < Math.Abs(Ri))
{
Array.Copy(xNew, x, x.Length);
break;
}
else
{
alpha *= alpha_mult;
}
}
}
catch (InvalidOperationException ex)
{
throw new InvalidOperationException($"Newton step failed at iteration {iter}: {ex.Message}");
}
}
return x;
}
}
}