Решение СЛАУ методом ГАУССА - C#

Узнай цену своей работы

Формулировка задачи:

По роду деятельности зачастую необходимо решать СЛАУ вида A*x=b A[N,N] x[N] b[N]

До недавних пор использовал библиотеку ALGLIB для .NET Но(!!!) буквально на днях понадобилось решать несколько тысяч систем при N=20k. Половина ресурсов сервака проработала весть день и ночь исключительно на мою задачу )))) Стал разбираться, почему же с ростом размерность так деградирует скорость - оказалось все в высшей степени тривиально. NET не умеет оптимизировать 2х мерные массивы - т.е. при запросе A[i,j] вызывается функция GetElement(i,j) которая внутри себя берет адрес начала массива, добавляет к нему i*(число столбцов)+j, извлекает адрес и возвращает значение. (!) Кстати, при работе с одномерными массивами скорость также деградирует при запросе больших(длинных) индесков, хотя и не так сильно, как для многомерных.

Решение 1.

Сказал нафиг, скачал BLAS(LAPACK) и стал юзать ее посредством PInvoke. Довольно долго разбирался, почему решает неправильно - выяснил, что выравнивание матриц происходит в памяти по столбцам, т.е. A(0,0),A(1,0),A(2,0),... В С# же построчно - A(0,0),A(0,1),A(0,2),... В итоге транспонирование матрицы в C# перед передачей в PInvoke решило задачу. И о чудо - скорость возросла в 20-50 раз.

Решение 2.

Подумал, а почему бы не написать самому, по умному алгоритм на C#, который будет быстро перемножать матрицы. Весь день убил и в итоге написал алгоритм, который по скорости оказался быстрее(!) LAPACK и не требовал транспонирования матрицы. Вот финальный результат:
Листинг программы
  1. // result - указатель на буффер правой части СЛАУ
  2. // mtx - указатель на первый элемент матрицы
  3. // n - размер системы
  4. static bool _Solve_square_system(double* result, double* mtx, int n)
  5. {
  6. double* mtx_u_ii, mtx_ii_j;
  7. double a;
  8. double* mtx_end = mtx + n * n, result_i, mtx_u_ii_j = null;
  9. double mx;
  10. int d = 0;
  11. // mtx_ii - указатель на 'ведущий' диагональный элемент матрицы
  12. for (double* mtx_ii = mtx, mtx_ii_end = mtx + n; mtx_ii < mtx_end; result++, mtx_ii += n + 1, mtx_ii_end += n, d++)
  13. {
  14. // Смотрим под ведущим элеменом, значение максимальное по модулю
  15. {
  16. mx = System.Math.Abs(*(mtx_ii_j = mtx_ii));
  17. for (mtx_u_ii = mtx_ii + n, result_i = result + 1; mtx_u_ii < mtx_end; mtx_u_ii += n, result_i++)
  18. {
  19. if (mx < System.Math.Abs(*mtx_u_ii))
  20. {
  21. mx = System.Math.Abs(*(mtx_ii_j = mtx_u_ii));
  22. mtx_u_ii_j = result_i;
  23. }
  24. }
  25. // если максимальный по модулю элемент равен 0 - система вырождена и не имеет решения
  26. if (mx == 0) return false;
  27. // если максимальный элемент не является ведущим, делаем перестановку строк, чтобы он стал ведущим
  28. else if (mtx_ii_j != mtx_ii)
  29. {
  30. a = *result;
  31. *result = *mtx_u_ii_j;
  32. *mtx_u_ii_j = a;
  33. for (mtx_u_ii = mtx_ii; mtx_u_ii < mtx_ii_end; mtx_ii_j++, mtx_u_ii++)
  34. {
  35. a = *mtx_u_ii; *mtx_u_ii = *mtx_ii_j; *mtx_ii_j = a;
  36. }
  37. }
  38. }
  39. //'обнуляем' элементы над ведущим
  40. for (mtx_u_ii = mtx_ii - n, result_i = result - 1; mtx_u_ii > mtx; mtx_u_ii -= n)
  41. {
  42. a = *(mtx_u_ii) / *mtx_ii;
  43. for (mtx_ii_j = mtx_ii + 1, mtx_u_ii_j = mtx_u_ii + 1; mtx_ii_j < mtx_ii_end; *(mtx_u_ii_j++) -= *(mtx_ii_j++) * a) ;
  44. *(result_i--) -= *(result) * a;
  45. }
  46. //'обнуляем' элементы под ведущим
  47. for (mtx_u_ii = mtx_ii + n, result_i = result + 1; mtx_u_ii < mtx_end; mtx_u_ii += d)
  48. {
  49. a = *(mtx_u_ii++) / *mtx_ii;
  50. for (mtx_ii_j = mtx_ii + 1; mtx_ii_j < mtx_ii_end; *(mtx_u_ii++) -= *(mtx_ii_j++) * a) ;
  51. *(result_i++) -= *(result) * a;
  52. }
  53. }
  54. //матрица приведена к дигональному виду
  55. //приводим ее к единичному, получая права решение
  56. for (; mtx_end > mtx; *(--result) /= *(--mtx_end), mtx_end -= n) ;
  57. return true;
  58. }
  59. public static bool SolveSquare(double[] outResult, double[,] A, double[] x)
  60. {
  61. int n = outResult.Length;
  62. if (n == x.Length && n == A.GetLength(0) && n == A.GetLength(1))
  63. {
  64. if (n > 0)
  65. {
  66. Array.Copy(x, outResult, n);
  67. fixed(double* presult = &outResult[0])
  68. fixed (double* pmtx = &A[0,0])
  69. return _Solve_square_system(presult, pmtx, n);
  70. }
  71. else return true;
  72. }
  73. throw new IndexOutOfRangeException();
  74. }
Буду рад, если кому-то пригодиться. А также выслушаю ваши комменты и замечания.

Решение задачи: «Решение СЛАУ методом ГАУССА»

textual
Листинг программы
  1. static public void _SMul(double* rmDestination, double* rmX, int xn, int xm, double* rmY, int ym)
  2. {
  3.     for (double* _dest_end = rmDestination + xn * ym; rmDestination < _dest_end; rmDestination += ym)
  4.         for (double* _x_i_end = rmX + xm, _dest_i_end = rmDestination + ym, _y_tmp = rmY; rmX < _x_i_end; rmX++)
  5.         {
  6.             //сохраняем текущее значение для многократного использования
  7.             double cache_val = *rmX;
  8.             for (double* _dest_ij = rmDestination; _dest_ij < _dest_i_end; _dest_ij++, _y_tmp++)
  9.                 *_dest_ij += cache_val * *_y_tmp;
  10.         }
  11. }
  12.  
  13. static public double[,] SMul(double[,] X, double[,] Y)
  14. {
  15.     int xm = X.GetLength(1);
  16.     if (xm == Y.GetLength(1))
  17.     {
  18.         int xn = X.GetLength(0);
  19.         int ym = Y.GetLength(1);
  20.         double[,] result = new double[xn, ym];
  21.         if (result.Length > 0)
  22.             fixed (double* p_result = &result[0, 0])
  23.             fixed (double* p_x = &X[0, 0])
  24.             fixed (double* p_y = &Y[0, 0])
  25.                 _SMul(p_result, p_x, xn, xm, p_y, ym);
  26.         return result;
  27.     }
  28.     else throw new ArgumentException();
  29. }

ИИ поможет Вам:


  • решить любую задачу по программированию
  • объяснить код
  • расставить комментарии в коде
  • и т.д
Попробуйте бесплатно

Оцени полезность:

6   голосов , оценка 4 из 5

Нужна аналогичная работа?

Оформи быстрый заказ и узнай стоимость

Бесплатно
Оформите заказ и авторы начнут откликаться уже через 10 минут
Похожие ответы