Министерство науки и высшего образования Российской Федерации Федеральное государственное бюджетное образовательное учреждение высшего образования «НОВОСИБИРСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ» Кафедра вычислительных технологий Лабораторная работа №3 по дисциплине «Численные методы» «ПРОЕКЦИОННЫЕ МЕТОДЫ НА ПОДПРОСТРАНСТВАХ КРЫЛОВА ДЛЯ РЕШЕНИЯ СЛАУ С КВАДРАТНОЙ НЕВЫРОЖДЕННОЙ МАТРИЦЕЙ» Факультет: ПМИ Группа: ПМИ-01 Студенты: Куликов Данил Иовенко Дмитрий Преподаватели: Иткина Наталья Борисовна Марков Сергей Игоревич Новосибирск 2022 Цель: Изучить, реализовать методы на подпространствах Крылова для решения СЛАУ с квадратной невырожденной матрицей. Содержание: 1. Разработать алгоритмы, реализующие классические и предобусловленные методы сопряжённых (CG) и бисопряжённых (BiCG) градиентов для решения СЛАУ с квадратной невырожденной матрицей. 2. Реализовать два вида предобусловливания: диагональное и неполное LU-разложение. 3. Разработать подпрограмму чтения текстовых файлов СЛАУ, выданных преподавателем, в следующем разреженном строчностолбцовом формате (CSlR): //Size.txt: размер системы (тип int) N //iptr.txt: индексный массив начала строк/столбцов (тип int) I1 I2 … IN+1 //jptr.txt: индексный массив столбцов/строк (тип int) I1 I2 … IN+1 //altr.txt: нижний треугольник (тип double) A1 A2 … AM //autr.txt: верхний треугольник (тип double) A1 A2 … AM //di.txt: диагональ (тип double) A1 A2 … AN //F.txt: вектор правой части (тип double) F1 F2 … FN //X.txt: вектор решения (тип double) X1 X2 … XN 4. Для каждого из реализованных методов выполнить замер времени решения СЛАУ, вычислить относительную погрешность численного решения по формуле: 𝛿𝑥∗ = ||𝑥−𝑥 ∗ ||2 ||𝑥 ∗ ||2 , где x – численное решение; x* – точное решение. 2 5. Для разработанных методов привести графики зависимости величины нормы невязки от номера итерации: ось абсцисс – номер итерации, ось ординат – норма невязки 𝑟 (𝑘) . Использовать логарифмический масштаб, если норма невязки сильно осциллирует. Ход работы Задание №1 Алгоритмы, реализующие классические и предобусловленные методы сопряжённых (CG) и бисопряжённых (BiCG) градиентов для решения СЛАУ с квадратной невырожденной матрицей: Сопряженные: using System; namespace Com_Methods { //метод сопряжённых градиентов class Conjugate_Gradient_Method: IIteration_Solver { //максимальное число итераций public int Max_Iter { set; get; } //точность решения public double Eps { set; get; } //текущая итерация public int Iter { set; get; } //предобусловливатель public Preconditioner Preconditioner { set; get; } //конструктор public Conjugate_Gradient_Method(int MAX_ITER, double EPS) { Max_Iter = MAX_ITER; Eps = EPS; Iter = 0; } //реализация метода public Vector Start_Solver (CSlR_Matrix A, Vector F, Preconditioner.Type_Preconditioner PREC) { //реализация предобусловливателя switch (PREC) { //диагональный case Com_Methods.Preconditioner.Type_Preconditioner.Diagonal_Preconditioner: { Preconditioner = new Diagonal_Preconditioner(A); break; } //ILU(0) case Com_Methods.Preconditioner.Type_Preconditioner.LU_Decomposition: { Preconditioner = new LU_Preconditioner(A); break; } } 3 //размер системы int n = A.N; //начальное приближение var RES = new Vector(n); for (int i = 0; i < n; i++) RES.Elem[i] = 0.0; //вспомогательные векторы var r = new Vector(n); var p = new Vector(n); var vec = new Vector(n); //параметры метода double alpha, beta, sc1, sc2; bool Flag = true; //норма невязки double Norma_r = 0; //невязка r = F - Ax A.Mult_MV(RES, vec); for (int i = 0; i < n; i++) r.Elem[i] = F.Elem[i] - vec.Elem[i]; //вектор поиска Mp = r Preconditioner.Start_Preconditioner(r, p); //итерационный процесс while (Flag && Iter < Max_Iter) { //(M^(-1) * r; r) sc1 = p * r; //vec = A * p A.Mult_MV(p, vec); //sc2 = (A * p; p) sc2 = vec * p; //коэффициент линейного поиска alpha = sc1 / sc2; //вектор результата и невязка for (int i = 0; i < n; i++) { RES.Elem[i] += alpha * p.Elem[i]; r.Elem[i] -= alpha * vec.Elem[i]; } //vec = M^(-1) * r Preconditioner.Start_Preconditioner(r, vec); //(M^(-1) * r; r) sc2 = vec * r; //коэффициент в выборе направления поиска на базе предыдущих beta = sc2 / sc1; //норма невязки Norma_r = r.Norm(); //условие завершения if (Norma_r < Eps) Flag = false; //новое направление поиска if (Flag) for (int i = 0; i < n; i++) p.Elem[i] = vec.Elem[i] + beta * p.Elem[i]; Iter++; Console.WriteLine("\n{0,-20} } 4 {1,-20}", Iter, Norma_r.ToString("E")); return RES; } } } Бисопряженные: using System; namespace Com_Methods { //метод бисопряжённых градиентов class BiConjugate_Gradient_Method : IIteration_Solver { //максимальное число итераций public int Max_Iter { set; get; } //точность решения public double Eps { set; get; } //текущая итерация public int Iter { set; get; } //предобусловливатель public Preconditioner Preconditioner { set; get; } //конструктор public BiConjugate_Gradient_Method(int MAX_ITER, double EPS) { Max_Iter = MAX_ITER; Eps = EPS; Iter = 0; } //реализация метода public Vector Start_Solver(CSlR_Matrix A, Vector F, Preconditioner.Type_Preconditioner PREC) { //реализация предобусловливателя switch (PREC) { //диагональный case Com_Methods.Preconditioner.Type_Preconditioner.Diagonal_Preconditioner: { Preconditioner = new Diagonal_Preconditioner(A); break; } //ILU(0) - декомпозиция case Com_Methods.Preconditioner.Type_Preconditioner.LU_Decomposition: { Preconditioner = new LU_Preconditioner(A); break; } } //размер системы int n = A.N; //результат var RES = new Vector(n); //вспомогательные векторы var r = new Vector(n); var p = new Vector(n); var r_ = new Vector(n); var p_ = new Vector(n); var vec = new Vector(n); var vec_ = new Vector(n); var vec_help = new Vector(n); //параметры метода 5 double alpha, beta, sc1, sc2; bool Flag = true; //норма невязки double Norma_r = 0; //невязка r = M^(-1) * (F - Ax) A.Mult_MV(RES, vec_help); for (int i = 0; i < n; i++) vec_help.Elem[i] = F.Elem[i] vec_help.Elem[i]; Preconditioner.Start_Preconditioner(vec_help, r); //выбор начальных значений векторов метода BiCG for (int i = 0; i < n; i++) { RES.Elem[i] = 0.0; p.Elem[i] = r.Elem[i]; r_.Elem[i] = r.Elem[i]; p_.Elem[i] = r.Elem[i]; } while (Flag && Iter < Max_Iter) { //скалярное произведение sc1 = (r; r_) sc1 = r * r_; //vec_help = A * p A.Mult_MV(p, vec_help); //vec = M^(-1) * A * p Preconditioner.Start_Preconditioner(vec_help, vec); //(M^(-1) * A * p; p_) sc2 = vec * p_; //коэффициент линейного поиска alpha = sc1 / sc2; //определим новый результат и невязку for (int i = 0; i < n; i++) { RES.Elem[i] += alpha * p.Elem[i]; r.Elem[i] -= alpha * vec.Elem[i]; } //переводим вектор p_ в преобусловленную систему Preconditioner.Start_Tr_Preconditioner(p_, vec_help); //vec_ = A_t * M^(-t) * p_ A.Mult_MtV(vec_help, vec_); //новая невязка r_ for (int i = 0; i < n; i++) r_.Elem[i] -= alpha * vec_.Elem[i]; //(r_new; (r_)_new) sc2 = r * r_; //коэффициент в выборе направления поиска на базе предыдущих beta = sc2 / sc1; //норма обычной невязки Norma_r = r.Norm(); //проверка завершения итерационного процесса if (Norma_r < Eps) Flag = false; //новые направления поиска if (Flag) { for (int i = 0; i < n; i++) { p.Elem[i] = r.Elem[i] + beta * p.Elem[i]; p_.Elem[i] = r_.Elem[i] + beta * p_.Elem[i]; } 6 } Iter++; Console.WriteLine("{0,-20} } return RES; {1,-20}", Iter, Norma_r.ToString("E")); } } } Задание №2 Два вида предобусловливания: диагональное и неполное LU-разложение. Класс предобусловливателя: using System; namespace Com_Methods { //абстрактный класс предобусловливателя abstract class Preconditioner { //реализация предобусловливателя abstract public void Start_Preconditioner(Vector X, Vector RES); //реализация транспонированного предобусловливателя abstract public void Start_Tr_Preconditioner(Vector X, Vector RES); //тип предобусловливателей public enum Type_Preconditioner { Diagonal_Preconditioner = 1, LU_Decomposition } } } Диагональное: using System; namespace Com_Methods { //диагональный предобусловливатель (Якоби) class Diagonal_Preconditioner : Preconditioner { //диагональ матрицы Vector Diag { get; } //конструктор диагонального преобусловливателя public Diagonal_Preconditioner(CSlR_Matrix A) { Diag = new Vector(A.N); for (int i = 0; i < A.N; i++) { if (Math.Abs(A.di[i]) < CONST.EPS) throw new Exception("Error in Diagonal_Preconditioner: di" + (i + 1).ToString() + " = " + A.di[i].ToString()); Diag.Elem[i] = A.di[i]; } } 7 //реализация преобусловливателя public override void Start_Preconditioner(Vector X, Vector RES) { for (int i = 0; i < Diag.N; i++) { RES.Elem[i] = X.Elem[i] / Diag.Elem[i]; } } //реализация транспонированного преобусловливателя public override void Start_Tr_Preconditioner(Vector X, Vector RES) { for (int i = 0; i < Diag.N; i++) { RES.Elem[i] = X.Elem[i] / Diag.Elem[i]; } } } } Неполное LU-разложение: using System; namespace Com_Methods { //LU-предобусловливатель (Incomplete LU-decomposition) class LU_Preconditioner : Preconditioner { //неполная LU-декомпозиция IIncomplete_LU_Decomposition ILU; //конструктор LU-преобусловливателя public LU_Preconditioner(CSlR_Matrix A) { ILU = new Incomplete_LU_Decomposition_CSlR(A); } //реализация преобусловливателя public override void Start_Preconditioner(Vector X, Vector RES) { //решаем СЛАУ с нижней треугольной матрицей ILU.SLAU_L(RES, X); //решаем СЛАУ с верхней треугольной матрицей ILU.SLAU_U(RES, RES); } //реализация транспонированного преобусловливателя public override void Start_Tr_Preconditioner(Vector X, Vector RES) { //решаем СЛАУ с нижней треугольной матрицей ILU.SLAU_Ut(RES, X); //решаем СЛАУ с верхней треугольной матрицей ILU.SLAU_Lt(RES, RES); } } } 8 Интерфейс LU-разложения: using System; namespace Com_Methods { /// <summary> /// интерфейс неполного LU-разложения /// </summary> public interface IIncomplete_LU_Decomposition { //решение СЛАУ L * x = F с нижним треугольником матрицы void SLAU_L(Vector X, Vector F); //решение СЛАУ Lt * x = F с нижним транспонированным треугольником матрицы void SLAU_Lt(Vector X, Vector F); //решение СЛАУ U * x = F с верхним треугольником матрицы void SLAU_U(Vector X, Vector F); //решение СЛАУ Ut * x = F с верхним транспонированным треугольником матрицы void SLAU_Ut(Vector X, Vector F); } /// <summary> /// неполное LU-разложение в формате CSlR /// </summary> class Incomplete_LU_Decomposition_CSlR : IIncomplete_LU_Decomposition { //матрица неполной LU-декомпозиции private CSlR_Matrix ILU; //конструктор public Incomplete_LU_Decomposition_CSlR(CSlR_Matrix A) { ILU = new CSlR_Matrix(); ILU.N = A.N; ILU.iptr = A.iptr; ILU.jptr = A.jptr; int N_autr = A.autr.Length; ILU.autr = new double[N_autr]; ILU.altr = new double[N_autr]; ILU.di = new double[A.N]; for (int i = 0; i < N_autr; i++) { ILU.altr[i] = A.altr[i]; ILU.autr[i] = A.autr[i]; } for (int i = 0; i < A.N; i++) { ILU.di[i] = A.di[i]; } //начинаем с i = 1, т.к. в первой строке нижнего треугольника только диагональный элемент for (int i = 1; i < A.N; i++) { for (int j = A.iptr[i] - 1; j < A.iptr[i + 1] - 1; j++) { for (int a = A.iptr[i] - 1; a < j; a++) { for (int b = A.iptr[A.jptr[j] - 1] - 1; b < A.iptr[A.jptr[j]] - 1; b++) { if (A.jptr[a] == A.jptr[b]) { ILU.altr[j] -= ILU.altr[a] * ILU.autr[b]; ILU.autr[j] -= ILU.autr[a] * ILU.altr[b]; } } 9 } ILU.autr[j] /= ILU.di[A.jptr[j] - 1]; ILU.di[i] -= ILU.autr[j] * ILU.altr[j]; } } } //решение СЛАУ L * x = F с нижним треугольником матрицы public void SLAU_L(Vector X, Vector F) { for (int i = 0; i < ILU.N; i++) { X.Elem[i] = F.Elem[i]; for (int j = ILU.iptr[i] - 1; j < ILU.iptr[i + 1] - 1; j++) X.Elem[i] -= X.Elem[ILU.jptr[j] - 1] * ILU.altr[j]; X.Elem[i] /= ILU.di[i]; } } //решение СЛАУ Lt * x = F с нижним транспонированным треугольником матрицы public void SLAU_Lt(Vector X, Vector F) { double[] V = new double[ILU.N]; for (int i = 0; i < ILU.N; i++) V[i] = F.Elem[i]; for (int i = ILU.N - 1; i >= 0; i--) { X.Elem[i] = V[i] / ILU.di[i]; for (int j = ILU.iptr[i] - 1; j < ILU.iptr[i + 1] - 1; j++) V[ILU.jptr[j] - 1] -= X.Elem[i] * ILU.altr[j]; } } //решение СЛАУ U * x = F с верхним треугольником матрицы public void SLAU_U(Vector X, Vector F) { for (int i = 0; i < ILU.N; i++) X.Elem[i] = F.Elem[i]; for (int i = ILU.N - 1; i >= 0; i--) { for (int j = ILU.iptr[i] - 1; j < ILU.iptr[i + 1] - 1; j++) { X.Elem[ILU.jptr[j] - 1] -= X.Elem[i] * ILU.autr[j]; } } } //решение СЛАУ Ut * x = F с верхним транспонированным треугольником матрицы public void SLAU_Ut(Vector X, Vector F) { for (int i = 0; i < ILU.N; i++) { X.Elem[i] = F.Elem[i]; for (int j = ILU.iptr[i] - 1; j < ILU.iptr[i + 1] - 1; j++) X.Elem[i] -= X.Elem[ILU.jptr[j] - 1] * ILU.autr[j]; } } } } 10 Задание №3 Подпрограмму чтения текстовых файлов СЛАУ, выданных преподавателем, в следующем разреженном строчно-столбцовом формате (CSlR): //Size.txt: размер системы (тип int) N //iptr.txt: индексный массив начала строк/столбцов (тип int) I1 I2 … IN+1 //jptr.txt: индексный массив столбцов/строк (тип int) I1 I2 … IN+1 //altr.txt: нижний треугольник (тип double) A1 A2 … AM //autr.txt: верхний треугольник (тип double) A1 A2 … AM //di.txt: диагональ (тип double) A1 A2 … AN //F.txt: вектор правой части (тип double) F1 F2 … FN //X.txt: вектор решения (тип double) X1 X2 … XN Матрица CSIR: using System; using System.IO; namespace Com_Methods { public interface ISparse_Matrix { //размер матрицы int N { set; get; } //умножение матрицы на вектор y = A * x void Mult_MV(Vector X, Vector Y); //умножение транспонированной матрицы на вектор y = At * x void Mult_MtV(Vector X, Vector Y); } //матрица в разреженном строчно-столбцовом формате CSlR class CSlR_Matrix : ISparse_Matrix { //размер матрицы public int N { set; get; } //диагональ матрицы public double[] di {set; get;} //нижний треугольник public double[] altr { set; get; } 11 //верхний треугольник public double[] autr { set; get; } //номера строк (столбцов) ненулевых элементов public int[] jptr { set; get; } //номера строк (столбцов), с которых начинается jptr public int[] iptr { set; get; } //конструктор по умолчанию public CSlR_Matrix() { } //конструктор по файлам public CSlR_Matrix(string PATH) { char[] Separator = new char[] {' '}; //размер системы using (var Reader = new StreamReader(File.Open(PATH + "Size.txt", FileMode.Open))) { N = Convert.ToInt32(Reader.ReadLine()); //выделение памяти под массивы di и iptr iptr = new int[N + 1]; di = new double[N]; } //диагональ матрицы using (var Reader = new StreamReader(File.Open(PATH + "di.txt", FileMode.Open))) { for (int i = 0; i < N; i++) { di[i] = Convert.ToDouble(Reader.ReadLine().Split(Separator, StringSplitOptions.RemoveEmptyEntries)[0]); } } //массив iptr using (var Reader = new StreamReader(File.Open(PATH + "iptr.txt", FileMode.Open))) { for (int i = 0; i <= N; i++) { iptr[i] = Convert.ToInt32(Reader.ReadLine().Split(Separator, StringSplitOptions.RemoveEmptyEntries)[0]); } } //выделение памяти под массивы jptr, altr, autr int Size = iptr[N] - 1; jptr = new int[Size]; altr = new double[Size]; autr = new double[Size]; var Reader1 = new StreamReader(File.Open(PATH + "jptr.txt", FileMode.Open)); var Reader2 = new StreamReader(File.Open(PATH + "altr.txt", FileMode.Open)); var Reader3 = new StreamReader(File.Open(PATH + "autr.txt", FileMode.Open)); for (int i = 0; i < Size; i++) { jptr[i] = Convert.ToInt32 (Reader1.ReadLine().Split(Separator, StringSplitOptions.RemoveEmptyEntries)[0]); 12 altr[i] = Convert.ToDouble(Reader2.ReadLine().Split(Separator, StringSplitOptions.RemoveEmptyEntries)[0]); autr[i] = Convert.ToDouble(Reader3.ReadLine().Split(Separator, StringSplitOptions.RemoveEmptyEntries)[0]); } Reader1.Close(); Reader2.Close(); Reader3.Close(); } //умножение матрицы на вектор y = A * x public void Mult_MV (Vector X, Vector Y) { for (int i = 0; i < N; i++) Y.Elem[i] = X.Elem[i] * di[i]; for (int i = 0; i < N; i++) for (int j = iptr[i] - 1; j < iptr[i + 1] - 1; j++) { Y.Elem[i] += X.Elem[jptr[j] - 1] * altr[j]; Y.Elem[jptr[j] - 1] += X.Elem[i] * autr[j]; } } //умножение транспонированной матрицы на вектор y = At * x public void Mult_MtV(Vector X, Vector Y) { for (int i = 0; i < N; i++) Y.Elem[i] = X.Elem[i] * di[i]; for (int i = 0; i < N; i++) for (int j = iptr[i] - 1; j < iptr[i + 1] - 1; j++) { Y.Elem[i] += X.Elem[jptr[j] - 1] * autr[j]; Y.Elem[jptr[j] - 1] += X.Elem[i] * altr[j]; } } } } Задание №4 Для каждого из реализованных методов выполнили замер времени решения СЛАУ, вычислили относительную погрешность численного решения по формуле: 𝛿𝑥∗ = ||𝑥−𝑥∗ ||2 ||𝑥∗ ||2 , где x – численное решение; x* – точное решение. Время замеряем несколько раз и считаем среднее арифметическое для более точного представления, также замеры осуществляем в Release. 13 Время работы: SPD Метод Сопряженные градиенты, диагональный предобуславливатель Сопряженные градиенты, LUпредобуславливатель Бисопряженные градиенты, диагональный предобуславливатель Бисопряженные градиенты, LUпредобуславливатель 1 система Относительная Время работы погрешность 1,59960309514331E13 00:00:00.2954789 1,41973634299459E13 00:00:00.8465890 4,59799129398446E13 00:00:00.7654896 3,08735884877162E14 00:00:00.3896367 SPD Метод Сопряженные градиенты, диагональный предобуславливатель Сопряженные градиенты, LUпредобуславливатель Бисопряженные градиенты, диагональный предобуславливатель Бисопряженные градиенты, LUпредобуславливатель 2 система Относительная Время работы погрешность 1,59960309514331E13 00:00:00.2695829 1,41973634299459E13 00:00:00.8965359 4,59799129398446E13 00:00:00.3964387 3,08735884877162E14 00:00:00.2709646 14 NonSPD Метод Сопряженные градиенты, диагональный предобуславливатель Сопряженные градиенты, LUпредобуславливатель Бисопряженные градиенты, диагональный предобуславливатель Бисопряженные градиенты, LUпредобуславливатель 1 система Относительная Время работы погрешность 0,0277260192425713 (вышел из итерации) 00:01:42.4314283 0,0149152378214524 (вышел из итерации) 00:02:09.1359347 3,72551998438032E-13 00:00:00.4712071 7,83160938986963E-15 00:00:00.3637586 NonSPD Метод Сопряженные градиенты, диагональный предобуславливатель Сопряженные градиенты, LUпредобуславливатель Бисопряженные градиенты, диагональный предобуславливатель Бисопряженные градиенты, LUпредобуславливатель 2 система Относительная Время работы погрешность 5230,11381040837 (вышел из итерций) 00:01:53.5321869 12,7318263287654 (вышел из итерций) 00:02:21.7235721 8,32167811952104E-14 00:00:04.2345714 1,71245299241268E-14 00:00:02.1123179 15 Задание №5 Для разработанных методов приводим графики зависимости величины нормы невязки от номера итерации: ось абсцисс – номер итерации, ось ординат – норма невязки 𝑟 (𝑘) . Использовать логарифмический масштаб, если норма невязки сильно осциллирует. Графики зависимости величины нормы невязки от номера итерации (метод сопряженных градиентов на SPD-матрице): 5,12E-01 5,12E-02 5,12E-03 5,12E-04 5,12E-05 5,12E-06 5,12E-07 5,12E-08 5,12E-09 5,12E-10 5,12E-11 5,12E-12 5,12E-13 1 7 13 19 25 31 37 43 49 55 61 67 73 79 85 91 97 103 109 115 121 127 133 139 145 151 157 163 169 НОРМА НЕВЯЗКИ График зависимости величины невязки от номера итерации (метод сопряженных градиентов на SPD-матрице) НОМЕР ИТЕРАЦИИ LU-предобусловливатель Диагональный предобусловливатель 16 Графики зависимости величины нормы невязки от номера итерации (метод бисопряженных градиентов на SPDматрице): 5,12E-01 5,12E-02 5,12E-03 5,12E-04 5,12E-05 5,12E-06 5,12E-07 5,12E-08 5,12E-09 5,12E-10 5,12E-11 5,12E-12 5,12E-13 1 7 13 19 25 31 37 43 49 55 61 67 73 79 85 91 97 103 109 115 121 127 133 139 145 151 157 163 169 НОРМА НЕВЯЗКИ График зависимости величины невязки от номера итерации (метод бисопряженных градиентов на SPD-матрице) НОМЕР ИТЕРАЦИИ LU-предобусловливатель Диагональный предобусловливатель 17 Графики зависимости величины нормы невязки от номера итерации (метод бисопряженных градиентов на NonSPDматрице): 7,64E-01 7,64E-04 7,64E-07 7,64E-10 7,64E-13 1 7 13 19 25 31 37 43 49 55 61 67 73 79 85 91 97 103 109 115 121 127 133 139 145 151 157 163 169 175 181 187 193 Норма невязки График зависимости величины нормы невязки от номера итерации (метод бисопряженных градиентов на NonSPDматрице) Номер итерации LU-предобусловливатель Диагональный предобусловливатель Вывод В процессе выполнения лабораторной работы мы изучили, реализовали и методы на подпространствах Крылова для решения СЛАУ с квадратной невырожденной матрицей. 18