С.И.Хашин
Вполне стандартные классы и методы, ничего оригинального.
ОГЛАВЛЕНИЕ
Visual C++ игнорирует тип long double. Однако стоит сохранить возможность работать с 10-байтовыми числам (long double), пусть и с другими компиляторами. Для этого вместо double будем использовать ldouble. Под Visual C++ это просто double, под другими компиляторами это может быть long double.
Этот тип данных определён в модуле vUtil. Из него же взяты ещё несколько несложных функций (сообщение об ошибке, возведение числа в квадрат, π, машинное epsilon). Если не хотите подключать лишние модули, можно просто перенести эти функции.
// memory, sizes void release(); // release memory void clear(); // обнулить все элементы // Если размер не изменился, то элементы остались прежние, если изменился, то все обнуляем void setSize (int mx_); // изменить размер void setSizeClear(int mx_); // изменить размер и обнулить void copyFrom(dvect &a); void copyFrom(int N, ldouble *x); // get/put int Mx() { return mx; } // размер int Size() { return mx; } // размер ldouble &operator() (int x); // обращение к элементу массива (с проверкой индексов) ldouble &operator[] (int x); // обращение к элементу массива (с проверкой индексов) // in/out void saveText (const char *fname, ...); // сохранить элементы в файле в виде строки void appendText(const char *fname, ...); // дописать элементы в файл в виде строки void loadText(const char *fname); // прочитать данные из текстового файле void inc(int x, ldouble value); // увеличить один элемент void set( ldouble value ); // заполнить все одним значением ldouble minV( int &x ); // наименьший элемент массива, x - его индекс ldouble maxV(int &x); // наибольший элемент массива, x - его индекс ldouble sum(); // сумма элементов void copyLine(dmatr &a, int y, int ya); // y-я строка(this) := ya-я строка(а) void copyCol (dmatr &a, int x, int xa); // x-й столбец(this) := xa-й столбец(а) void copyBlock(dmatr &a, int x0, int y0, int dx, int dy);// this := минор(a) размера dx*dy // algebra void add ( dvect &v1, ldouble z); // this += z*v1 void add ( ldouble *v1, ldouble z); // this += z*v1 void add(ldouble z); // все элементы += z void mult( ldouble z); // умножить все элементы на v void killZero(); // почти нули сделать нулями void normalize(); // сделать сумму квадратов коэффициентов =1 ldouble length(); // длина ldouble length2(); // квадрат длины ldouble dist(dvect &vv); // расстояние до вектора ldouble dist2(dvect &vv); // квадрат расстояния до вектора ldouble scProd(dvect &vv); // скалярное произведение // void setA(int mx_, ...); // установить все значения вектора void setRandom(ldouble mi, ldouble ma); // установить случайные значения из отрезка void append(ldouble z); // добавить число в конец вектора // sort void sort(); // сортировать по убываниюПример использования:
dvect v(7); v.saveText("v.txt", "Init values"); for (int i = 0; i < v.size(); i++) v(i) = i; v[ 3] = 7.7; v( 4) = 8.8; v(11) = 12; // Ошибка! v.appendText("v.txt", "New values"); v.setRandom(5, 7); // заполнить случайными значениями от 5.0 до 7.0 v.appendText("v.txt", "Random", "%6.3f");
В результате в файле v.txt получим:
7 Init values 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 7 New values 0.00000 1.00000 2.00000 7.70000 8.80000 5.00000 6.00000 7 Random 6.385 6.469 6.307 6.313 6.089 6.515 5.691
В С++ нельзя обращаться к массиву через две координаты:
double x = a[6,7]; // так нельзя!
Поэтому обращаться приходится через круглые скобки (как в Фортране):
double x = a(6,7); a(6,7) = x+3;
ВНИМАНИЕ! При обращении к матрице всегда сначала указывается x-координата, то есть номер столбца, затем y-координата, то есть номер строки.
Номера начинаются с 0!
Опишем подробнее некоторые методы.
Во многих случаях требуется специальная обработка "малых значений элементов матрицы". Что понимать по "малым значением"? У нас это будет сумма модулей всех элементов матрицы, умноженнам на "машинное epsilon", то есть на (2.2e-16):
ldouble setEps(); // установить подходящее для данной матрицы eps (почти 0)
Узнать текущее значение epsilon позволяет метод
ldouble getEps();
Метод
void killZero();сделает все "почти нули" (то есть элементы матрицы, по модулю меньшие epsilon) — нулями.
Записать все элементы матрицы в файл:
void saveText(const char *fname, const char *msg = NULL, const char *format = " %12.5f");
Например, после выполнения:
dmatr a(4, 3); for (int y = 0; y < a.My(); y++) for (int x = 0; x < a.Mx(); x++) a(x, y) = 1./(x + y+1); a.saveText("a.txt", "Matrix a");
В файле a.txt получим:
4 3 Matrix b 1.00000 0.50000 0.33333 0.25000 0.50000 0.33333 0.25000 0.20000 0.33333 0.25000 0.20000 0.16667
Похожий метод appendText не создает новый файл, а дописывает матрицу в уже существующий. Если такого файла нет, то он создается.
После оператора:
a.saveTextI("a1.txt", "Matrix a", "(%1d,%1d=>%2d), ");
В файле a1.txt получим:
4 3 Matrix b ( 0, 0=> 1.00000) ( 1, 0=> 0.50000) ( 2, 0=> 0.33333) ( 3, 0=> 0.25000) ( 0, 1=> 0.50000) ( 1, 1=> 0.33333) ( 2, 1=> 0.25000) ( 3, 1=> 0.20000) ( 0, 2=> 0.33333) ( 1, 2=> 0.25000) ( 2, 2=> 0.20000) ( 3, 2=> 0.16667)
Такой способ записи удобен, когда размеры матрицы велики (1000*1000) и в сравнительно небольшом окне на экране сложно понять, элементы с какими индексами мы сейчас видим.
Обратная операция - чтение матрицы из текстового файла:
void loadText( const char *fname );
В ней предполагается, что файл имеет именно ту структуру, что описана выше,
то есть в первой строке записаны два целых числа: mx и my, размеры матрицы по горизонтали
и по вертикали, всё чте идёт в первой строке после этих чисел рассматривается как комментарий.
Далее в файле должны быть записаны mx*my действительных чисел. На самом деле разбиение на
строки полностью игнорируется, числа могут быть разделены пробелами, табуляциями, концами строк.
Метод
ldouble isSymmetric(); // return difference for symmtricityвозвращает сумму модулей разностей |a(i,j)-a(j,i)|, то есть меру отклонения от симметричности.
Аналогичным обраом метод
ldouble isOrthonormal(); // возвращает величину отклонения от ортогональностивозвращает меру отклонения от ортогональности.
Метод
void setFourierBasis(int N);
в строках матрицы построить ортонормальный Фурье-базис длины N, например в результате
dmatr F; F.setFourierBasis(8); F.saveText("Fourier.txt");
в файле Fourier.txt получим:
8 8 Fourier basis 0.353553390593 0.353553390593 0.353553390593 0.353553390593 0.353553390593 0.353553390593 0.353553390593 0.353553390593 0.490392640202 0.415734806151 0.277785116510 0.097545161008 -0.097545161008 -0.277785116510 -0.415734806151 -0.490392640202 0.461939766256 0.191341716183 -0.191341716183 -0.461939766256 -0.461939766256 -0.191341716183 0.191341716183 0.461939766256 0.415734806151 -0.097545161008 -0.490392640202 -0.277785116510 0.277785116510 0.490392640202 0.097545161008 -0.415734806151 0.353553390593 -0.353553390593 -0.353553390593 0.353553390593 0.353553390593 -0.353553390593 -0.353553390593 0.353553390593 0.277785116510 -0.490392640202 0.097545161008 0.415734806151 -0.415734806151 -0.097545161008 0.490392640202 -0.277785116510 0.191341716183 -0.461939766256 0.461939766256 -0.191341716183 -0.191341716183 0.461939766256 -0.461939766256 0.191341716183 0.097545161008 -0.277785116510 0.415734806151 -0.490392640202 0.490392640202 -0.415734806151 0.277785116510 -0.097545161008
Метод
void GramShmidt();
Приводит СТРОКИ матрицы к ортонормальному виду.
Метод EigenQL находит собственные значения и собственные вектора СИММЕТРИЧНОЙ матрицы методом вращений Якоби.
void EigenQL(dvect &eval, dmatr &evec); // eigenvalues (increased) and eigenvectors of symmetric matrix, ev:=eigenvalues(this), v:=eigenvectors(this)
После работы метода исходная матрица сохраняется, в векторе eval лежат найденные
собственные значения ПО УБЫВАНИЮ, а в СТРОКАХ матрицы evec лежат соответствующие
собственные вектора, длина каждого из них равна 1.
Так как исходная матрица
симметрична, то эти вектора ортогальны и, следовательно. ортонормальны.
Пример работы:
int N = 9; dmatr a(N, N); for (int y = 0; y < N; y++) for (int x = 0; x < N; x++) a(x, y) = 1/(1.+x+y); a.saveText("a.txt", "a"); double de = a.det(); dmatr b; b.revers(a); b.appendText("a.txt", "revers", " %12.8f"); dvect eig; a.EigenQL(eig, b); b.appendText("a.txt", "eigen", " %12.8f"); eig.appendText("a.txt", "values", " %10g");