Матрицы и векторы в MQL5

MetaQuotes | 11 февраля, 2022

Для работы с упорядоченными однотипными данными обычно используются массивы, которые позволяет обращаться к каждому элементу по индексу. Массивы широко используются при решении множества задач из линейной алгебры, математического моделирования, в машинном обучении и т.д. Решение этих задач, в общем виде, основывается на математических операциях с использованием матриц и векторов, которые позволяют компактно записывать в виде простой формулы очень сложные преобразования. Для программирования таких операций нужен не только хороший уровень знания математики, но и умение писать сложные вложенные циклы. Отладка и поиск ошибок в таких программах могут быть очень изнуряющими. 

Специальные типы данных matrix и vector позволяют писать код, приближенный к математической записи, и избавляют от необходимости создавать вложенные циклы. Программисту уже не нужно помнить о правильной индексации массивов, которые участвуют в вычислении. В этой статье мы покажем, как создавать, инициализировать и применять объекты matrix и vector в MQL5.


Тип vector

vector представляет из себя одномерный массив типа double. Над векторами определены операции сложения и умножения, а также введено понятие "Норма" для получения длины или модуля вектора. В программировании векторы обычно представлены массивами однородных элементов, над которыми может быть не задано обычных векторных операций, то есть массивы нельзя складывать друг с другом, умножать друг на друга, а также нет понятия нормы.

В математике векторы могут быть представлены как вектор-строка, то есть как массив из одной строки и n столбцов, или как вектор-столбец как матрица из одного столбца и n строк. В MQL5 тип "vector" не разделяется на векторы-строки и векторы-столбцы, поэтому программист сам должен понимать, какой тип вектора используется при выполнении той или иной операции.

Создать и инициализировать вектор можно с помощью встроенных методов.

Методы
Аналог NumPy
Описание
void vector.Init( ulong size);   Создает вектор указанной длины, значения в котором не определены
static vector vector::Ones(ulong size);
 ones
Создает вектор указанной длины и заполняет его единицами
static vector vector::Zeros(ulong size);
 zeros
Создает вектор указанной длины и заполняет его нулями
static vector vector::Full(ulong size,double value);
 full
Создает вектор указанной длины и заполняет его заданным значением
оператор =

Возвращает копию вектора
void vector.Resize(const vector v);
  Изменяет размер вектора путем добавления новых значений с конца 


Примеры создания вектора:

void OnStart()
 {
//--- примеры инициализации векторов
  vector v;
  v.Init(7);
  Print("v = ", v); 

  vector v1=vector::Ones(5);
  Print("v1 = ", v1);

  vector v2=vector::Zeros(3);
  Print("v2 = ", v2);

  vector v3=vector::Full(6, 2.5);
  Print("v3 = ", v3);

  vector v4{1, 2, 3};
  Print("v4 = ", v4);  
  v4.Resize(5);
  Print("after Resize(5) v4 = ", v4);  
  
  vector v5=v4;
  Print("v5 = ", v5);  
  v4.Fill(7);
  Print("v4 = ", v4, "   v5 =",v5);  
   
 }
 
 
/*
Результат выполнения

v = [4,5,6,8,10,12,12]
v1 = [1,1,1,1,1]
v2 = [0,0,0]
v3 = [2.5,2.5,2.5,2.5,2.5,2.5]
v4 = [1,2,3]
after Resize(5) v4 = [1,2,3,7,7]
v5 = [1,2,3,7,7]
v4 = [7,7,7,7,7]   v5 =[1,2,3,7,7]

*/ 

Метод Init может использоваться не только для распределения памяти под вектор, но также и для инициализации элементов вектора значениями с помощью функции. В этом случае в Init первым параметром передается размер вектора, а вторым — имя функции. Если функция в свою очередь имеет параметры, то эти параметры указываются сразу за именем функции через запятую.

Сама функция должна содержать первым параметром ссылку на вектор, который передается в неё, но при вызове Init вектор передавать не нужно. Покажем это на примере функции Arange, имитирующей numpy.arange.

void OnStart()
  {
//---
   vector v;
   v.Init(7,Arange,10,0,0.5); // при вызове Arange передаются 3 параметра
   Print("v = ", v);
   Print("v.size = ",v.Size());
  }
//+------------------------------------------------------------------+
//|  Values are generated within the half-open interval [start, stop)|
//+------------------------------------------------------------------+
void Arange(vector& v, double stop, double start = 0, double step = 1) // функция имеет 4 параметра
  {
   if(start >= stop)
     {
      PrintFormat("%s wrong parameters! start=%G  stop=%G", __FILE__,start, stop);
      return;
     }
//---
   int size = (int)((stop - start) / step);
   v.Resize(size);
   double value = start;
   for(ulong i = 0; i < v.Size(); i++)
     {
      v[i] = value;
      value += step;
     }
  }
  
/*
Результат выполнения

v = [0,0.5,1,1.5,2,2.5,3,3.5,4,4.5,5,5.5,6,6.5,7,7.5,8,8.5,9,9.5]
v.size = 20

*/

При этом функция Arange имеет два необязательных параметра — "start" и "step". Поэтому допустим такой способ вызова Init(7,Arange,10) с соответствующим результатом:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   vector v;
   v.Init(7,Arange,10);
   Print("v = ", v);
   Print("v.size = ",v.Size());
  }
...

/*

v = [0,1,2,3,4,5,6,7,8,9]
v.size = 10

*/


Операции с векторами

Над векторами можно производить обычные операции сложения, вычитания, умножения и деления с использованием скаляра.

//+------------------------------------------------------------------+
//|                                              vector2_article.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
 {
//---
  vector v= {1, 2, 3, 4, 5};
  Print("Примеры без сохранения изменений вектора");
  Print("v = ", v);
  Print("v+5 = ", v+5);
  Print("v-Pi= ", v-M_PI);
  Print("v*2.0= ", v*2);
  Print("v/3.0= ", v/3.0);

  Print("Cохраняем все изменения вектора");
  Print("v = ", v);
  Print("v+5 = ", v=v+5);
  Print("v-Pi= ", v=v-M_PI);
  Print("v*2.0= ", v= v*2);
  Print("v/3.0= ", v= v/3.0);
 }
/*
Результат выполнения
   
Примеры без сохранения изменений вектора
v = [1,2,3,4,5]
v+5 = [6,7,8,9,10]
v-Pi= [-2.141592653589793,-1.141592653589793,-0.1415926535897931,0.8584073464102069,1.858407346410207]
v*2.0= [2,4,6,8,10]
v/3.0= [0.3333333333333333,0.6666666666666666,1,1.333333333333333,1.666666666666667]
Cохраняем все изменения вектора
v = [1,2,3,4,5]
v+5 = [6,7,8,9,10]
v-Pi= [2.858407346410207,3.858407346410207,4.858407346410207,5.858407346410207,6.858407346410207]
v*2.0= [5.716814692820414,7.716814692820414,9.716814692820414,11.71681469282041,13.71681469282041]
v/3.0= [1.905604897606805,2.572271564273471,3.238938230940138,3.905604897606805,4.572271564273471]

*/
//+------------------------------------------------------------------+

Определены поэлементные операции сложения, вычитания, умножения и деления двух векторов одинакового размера.

void OnStart()
  {
//---
   vector a = {1, 2, 3};
   vector b = {2, 4, 6};
   Print("a + b = ", a + b);
   Print("a - b = ", a - b);
   Print("a * b = ", a * b);
   Print("b / a = ", b / a);
  }

/*
Результат выполнения

a + b = [3,6,9]
a - b = [-1,-2,-3]
a * b = [2,8,18]
b / a = [2,2,2]

*/

Также определены четыре операции произведения векторов.

void OnStart()
 {
//---
  vector a={1, 2, 3};
  vector b={4, 5, 6};
  Print("a = ", a);
  Print("b = ", b);
  Print("1) a.Dot(b) = ", a.Dot(b));
  Print("2) a.MatMul(b) = ", a.MatMul(b));
  Print("3) a.Kron(b) = ", a.Kron(b));
  Print("4) a.Outer(b) = \n", a.Outer(b));
 }
/*
Результат выполнения

a = [1,2,3]
b = [4,5,6]
1) a.Dot(b) = 32.0
2) a.MatMul(b) = 32.0
3) a.Kron(b) = [[4,5,6,8,10,12,12,15,18]]
4) a.Outer(b) = 
[[4,5,6]
 [8,10,12]
 [12,15,18]]

*/

Как видно из примера, метод Outer() возвращает матрицу, в которой количество строк и столбцов соответствуют размерам умножаемых векторов. Методы Dot() и MatMul() работают одинаково. При этом предполагается, что в операции Outer() вектор слева является вертикальным, а правый вектор — горизонтальный. Для методов Dot() и MatMul() порядок векторов обратный — слева горизонтальный вектор,  а справа — вертикальный.


Норма вектора

Над векторами и матрицами определено понятие нормы, которое представляет понятие длины или абсолютного значения вектора. Всего существует три варианта вычисления нормы вектора, которые перечислены в ENUM_VECTOR_NORM.
void OnStart()
 {
//---
  struct str_vector_norm
   {
    ENUM_VECTOR_NORM  norm;
    int               value;
   };
  str_vector_norm vector_norm[]=
   {
     {VECTOR_NORM_INF,       0},
     {VECTOR_NORM_MINUS_INF, 0},
     {VECTOR_NORM_P,         0},
     {VECTOR_NORM_P,         1},
     {VECTOR_NORM_P,         2},
     {VECTOR_NORM_P,         3},
     {VECTOR_NORM_P,         4},
     {VECTOR_NORM_P,         5},
     {VECTOR_NORM_P,         6},
     {VECTOR_NORM_P,         7},
     {VECTOR_NORM_P,        -1},
     {VECTOR_NORM_P,        -2},
     {VECTOR_NORM_P,        -3},
     {VECTOR_NORM_P,        -4},
     {VECTOR_NORM_P,        -5},
     {VECTOR_NORM_P,        -6},
     {VECTOR_NORM_P,        -7}
   };
  vector v{1, 2, 3, 4, 5, 6, 7};
  double norm;
  Print("v = ", v);
//---
  for(int i=0; i<ArraySize(vector_norm); i++)
   {
    switch(vector_norm[i].norm)
     {
      case VECTOR_NORM_INF :
        norm=v.Norm(VECTOR_NORM_INF);
        Print("v.Norm(VECTOR_NORM_INF) = ", norm);
        break;
      case VECTOR_NORM_MINUS_INF :
        norm=v.Norm(VECTOR_NORM_MINUS_INF);
        Print("v.Norm(VECTOR_NORM_MINUS_INF) = ", norm);
        break;
      case VECTOR_NORM_P :
        norm=v.Norm(VECTOR_NORM_P, vector_norm[i].value);
        PrintFormat("v.Norm(VECTOR_NORM_P,%d) = %G", vector_norm[i].value, norm);
     }
   }
 }
/*

v = [1,2,3,4,5,6,7]
v.Norm(VECTOR_NORM_INF) = 7.0
v.Norm(VECTOR_NORM_MINUS_INF) = 1.0
v.Norm(VECTOR_NORM_P,0) = 7
v.Norm(VECTOR_NORM_P,1) = 28
v.Norm(VECTOR_NORM_P,2) = 11.8322
v.Norm(VECTOR_NORM_P,3) = 9.22087
v.Norm(VECTOR_NORM_P,4) = 8.2693
v.Norm(VECTOR_NORM_P,5) = 7.80735
v.Norm(VECTOR_NORM_P,6) = 7.5473
v.Norm(VECTOR_NORM_P,7) = 7.38704
v.Norm(VECTOR_NORM_P,-1) = 0.385675
v.Norm(VECTOR_NORM_P,-2) = 0.813305
v.Norm(VECTOR_NORM_P,-3) = 0.942818
v.Norm(VECTOR_NORM_P,-4) = 0.980594
v.Norm(VECTOR_NORM_P,-5) = 0.992789
v.Norm(VECTOR_NORM_P,-6) = 0.99714
v.Norm(VECTOR_NORM_P,-7) = 0.998813

*/

С помощью нормы можно измерять расстояние между двумя векторами:

void OnStart()
 {
//---
   vector a{1,2,3};
   vector b{2,3,4};
   double distance=(b-a).Norm(VECTOR_NORM_P,2);
   Print("a = ",a);
   Print("b = ",b);
   Print("|a-b| = ",distance);   
 }
/*
Результат выполнения

a = [1,2,3]
b = [2,3,4]
|a-b| = 1.7320508075688772

*/


Тип matrix

Вектор является частным случаем матрицы, которая фактически представляет из себя двумерный массив типа double. Таким образом, можно сказать, что матрица — это массив векторов одинакового размера. Количество строк в матрице равно количеству векторов, а количество столбцов — длине векторов. 

Над матрицами также определены операции сложения и умножения. В обычных языках программирования для представления матриц используются массивы. Но обычные массивы нельзя складывать друг с другом, умножать друг на друга, а также для них не определено понятие нормы. В математике рассматривается множество различных типов и видов матриц. Например, единичная, симметричная, кососимметричная, верхнетреугольная (нижнетреугольная) и другие матрицы.

Создать и инициализировать матрицу можно с помощью встроенных методов, аналогичных методам вектора.

Метод

Аналог в NumPy

Описание

void static matrix.Eye(const int rows, const int cols, const int ndiag=0)

eye

Создает матрицу с единицами по указанной диагонали и нулями в остальных местах

void matrix.Identity()

identity

Заполняет матрицу единицами на главной диагонали и нулями в остальных местах

void static matrix.Ones(const int rows, const int cols)

ones

Создает новую матрицу по числу строк и столбцов, заполненную единицами

void static matrix.Zeros(const int rows, const int cols)

zeros

Создает новую матрицу по числу строк и столбцов, заполненную нулями

void static matrix.Tri(const int rows, const int cols, const int ndiag=0)
tri  Создает матрицу с единицами на указанной диагонали и ниже и с нулями в остальных местах 
void matrix.Diag(const vector v, const int ndiag=0)  diag  Извлекает диагональ или создает диагональную матрицу 

void matrix.Full(const int rows, const int cols, const scalar value)

full

Создает новую матрицу по числу строк и столбцов, заполненную скалярным значением

void matrix.Fill(const scalar value)     Заполняет матрицу указанным значением


Примеры создания и заполнения матриц:

void OnStart()
 {
//---
  matrix m{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
  Print("m = \n", m);
  matrix ones=matrix::Ones(4, 4);
  Print("ones = \n", ones);
  matrix zeros=matrix::Zeros(4, 4);
  Print("zeros = \n", zeros);
  matrix eye=matrix::Eye(4, 4);
  Print("eye = \n", eye);

  matrix identity(4, 5);
  Print("matrix_identity\n", identity);
  identity.Identity();
  Print("matrix_identity\n", identity);

  matrix tri=matrix::Tri(3, 4);
  Print("tri = \n", tri);
  Print("tri.Transpose() = \n", tri.Transpose()); // транспонируем матрицу

  matrix diag(5, 5);
  Print("diag = \n", diag);
  vector d{1, 2, 3, 4, 5};
  diag.Diag(d);
  Print("diag = \n", diag); // вставим по диагонали матрицы значения из вектора

  matrix fill(5, 5);
  fill.Fill(10);
  Print("fill = \n", fill);

  matrix full =matrix::Full(5, 5, 100);
  Print("full = \n", full);


  matrix init(5, 7);
  Print("init = \n", init);
  m.Init(4, 6);
  Print("init = \n", init);
  
  matrix resize=matrix::Full(2, 2, 5);  
  resize.Resize(5,5);
  Print("resize = \n", resize);  
 }
/*
Результат выполнения

m =
[[1,2,3]
 [4,5,6]
 [7,8,9]]
ones =
[[1,1,1,1]
 [1,1,1,1]
 [1,1,1,1]
 [1,1,1,1]]
zeros =
[[0,0,0,0]
 [0,0,0,0]
 [0,0,0,0]
 [0,0,0,0]]
eye =
[[1,0,0,0]
 [0,1,0,0]
 [0,0,1,0]
 [0,0,0,1]]
matrix_identity
[[1,0,0,0,0]
 [0,1,0,0,0]
 [0,0,1,0,0]
 [0,0,0,1,0]]
matrix_identity
[[1,0,0,0,0]
 [0,1,0,0,0]
 [0,0,1,0,0]
 [0,0,0,1,0]]
tri =
[[1,0,0,0]
 [1,1,0,0]
 [1,1,1,0]]
tri.Transpose() =
[[1,1,1]
 [0,1,1]
 [0,0,1]
 [0,0,0]]
diag =
[[0,0,0,0,0]
 [0,0,0,0,0]
 [0,0,0,0,0]
 [0,0,0,0,0]
 [0,0,0,0,0]]
diag =
[[1,0,0,0,0]
 [0,2,0,0,0]
 [0,0,3,0,0]
 [0,0,0,4,0]
 [0,0,0,0,5]]
fill =
[[10,10,10,10,10]
 [10,10,10,10,10]
 [10,10,10,10,10]
 [10,10,10,10,10]
 [10,10,10,10,10]]
full =
[[100,100,100,100,100]
 [100,100,100,100,100]
 [100,100,100,100,100]
 [100,100,100,100,100]
 [100,100,100,100,100]]
resize = 
[[5,5,0,0,0]
 [5,5,0,0,0]
 [0,0,0,0,0]
 [0,0,0,0,0]
 [0,0,0,0,0]]
*/

Следующий пример показывает, как можно использовать собственные функции при заполнении матриц:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
 {
//---
  matrix random(4, 5, MatrixRandom);
  Print("random = \n",random);
  
  matrix init(3, 6, MatrixSetValues);  
  Print("init = \n", init);
  
 }
//+------------------------------------------------------------------+
//| Заполняет матрицу случайными значениями                          |
//+------------------------------------------------------------------+
void MatrixRandom(matrix& m)
 {
  for(ulong r=0; r<m.Rows(); r++)
   {
    for(ulong c=0; c<m.Cols(); c++)
     {
      m[r][c]=double(MathRand())/32767.;
     }
   }
 }
//+------------------------------------------------------------------+
//| Заполняет матрицу степенями числа                                |
//+------------------------------------------------------------------+
void MatrixSetValues(matrix& m, double initial=1)
 {
  double value=initial;
  for(ulong r=0; r<m.Rows(); r++)
   {
    for(ulong c=0; c<m.Cols(); c++)
     {
      m[r][c]=value;
      value*=2;
     }
   }
 } 
 
/*
Результат выполнения

random = 
[[0.4200262459181494,0.5014496292001098,0.7520371105075229,0.652058473464156,0.08783227027191992]
 [0.5991088595233008,0.4311960203863643,0.8718832972197638,0.1350138859218116,0.901882992034669]
 [0.4964445936460463,0.8354747154148991,0.5258339182714317,0.6055482650227363,0.5952940458388012]
 [0.3959166234321116,0.8146916104617451,0.2053590502639851,0.2657551805169835,0.3672292245246742]]
init = 
[[1,2,4,8,16,32]
 [64,128,256,512,1024,2048]
 [4096,8192,16384,32768,65536,131072]]

*/ 

Кроме того, обратите, внимание, что создать матрицу без инициализации значениями можно двумя способами. 

//--- создание матрицы заданного размера rows x cols
  matrix m(3, 3);

// ------ эквивалентно
  matrix m;
  m.Resize(3, 3);


Норма матрицы

Всего предусмотрено девять вариантов вычисления нормы матрицы. Они перечислены в ENUM_MATRIX_NORM.

void OnStart()
  {
//---
   ENUM_MATRIX_NORM matrix_norm[]= {MATRIX_NORM_FROBENIUS,
                                    MATRIX_NORM_SPECTRAL,
                                    MATRIX_NORM_NUCLEAR,
                                    MATRIX_NORM_INF,
                                    MATRIX_NORM_MINUS_INF,
                                    MATRIX_NORM_P1,
                                    MATRIX_NORM_MINUS_P1,
                                    MATRIX_NORM_P2,
                                    MATRIX_NORM_MINUS_P2
                                   };
   matrix m{{1,2,3},{4,5,6},{7,8,9}};
   Print("matrix m:\n",m);
//--- вычислим норму всеми способами
   double norm;
   for(int i=0; i<ArraySize(matrix_norm); i++)
     {
      norm=m.Norm(matrix_norm[i]);
      PrintFormat("%d. Norm(%s) = %.6f",i+1, EnumToString(matrix_norm[i]),norm);
     }
//---
   return;
  }

/*
Результат выполнения

matrix m:
[[1,2,3]
[4,5,6]
[7,8,9]]
1. Norm(MATRIX_NORM_FROBENIUS) = 16.881943
2. Norm(MATRIX_NORM_SPECTRAL) = 14.790157
3. Norm(MATRIX_NORM_NUCLEAR) = 17.916473
4. Norm(MATRIX_NORM_INF) = 24.000000
5. Norm(MATRIX_NORM_MINUS_INF) = 6.000000
6. Norm(MATRIX_NORM_P1) = 18.000000
7. Norm(MATRIX_NORM_MINUS_P1) = 12.000000
8. Norm(MATRIX_NORM_P2) = 16.848103
9. Norm(MATRIX_NORM_MINUS_P2) = 0.000000

*/


Операции с матрицами и векторами

Для решения математических задач матрицы содержат специальные методы:

Разложение матрицы:

Метод

Аналог в NumPy

Описание
bool matrix.Cholesky(matrix& L) cholesky Вычисляет декомпозицию Холецкого
bool matrix.QR(matrix& Q, matrix& R) qr

Вычисляет QR-декомпозиция

bool matrix.SVD(matrix& U, matrix& V, vector& singular_values)

svd

Вычисляет SVD-декомпозицию

bool matrix.Eig(matrix& eigen_vectors, vector& eigen_values)

eig

Вычисляет собственные значения и правые собственные векторы квадратной матрицы

bool matrix.EigVals(vector& eigen_values)

eigvals

Вычисляет собственные значения общей матрицы

bool matrix.LU(matrix& L, matrix& U)

 

Выполняет LU-декомпозицию матрицы как произведение нижней треугольной матрицы и верхней треугольной матрицы

bool matrix.LUP(matrix& L, matrix& U, matrix& P)

 

Выполняет LUP-декомпозицию с частичным поворотом, которая является LU-факторизацией с перестановками строк: PA = LU


Произведение матриц и векторов

Для выполнения матричного произведения матриц и векторов определен метод MatMul(). Он часто используется в решении многих математических задач. При умножении матрицы и вектора допустимы только два варианта:

Если длина вектора не равна количеству столбцов матрицы, то будет вызвана критическая ошибка исполнения.

Умножать между собой можно матрицы вида A[M,N] * B[N,K] = С[M,K], то есть количество столбцов в матрице слева должно быть равно количеству строк в матрице справа. Если размеры не согласованы, результатом будет пустая матрица. Покажем все варианты матричного произведения на примерах.

void OnStart()
  {
//--- инициализируем матрицы
   matrix m35, m52;
   m35.Init(3,5,Arange);
   m52.Init(5,2,Arange);
//---
   Print("1. Произведение горизонтального вектора v[3] на матрицу m[3,5]");
   vector v3 = {1,2,3};
   Print("Слева v3 = ",v3);
   Print("Справа m35 = \n",m35);
   Print("v3.MatMul(m35) = горизонтальный вектор v[5] \n",v3.MatMul(m35));
//--- покажем, что это действительно горизонтальный вектор
   Print("\n2. Произведение матрицы m[1,3] на матрицу m[3,5]");
   matrix m13;
   m13.Init(1,3,Arange,1);
   Print("Слева m13 = \n",m13);
   Print("Справа m35 = \n",m35);
   Print("m13.MatMul(m35) = матрица m[1,5] \n",m13.MatMul(m35));

   Print("\n3. Произведение матрицы m[3,5] на вертикальный вектор v[5]");
   vector v5 = {1,2,3,4,5};
   Print("Слева m35 = \n",m35);
   Print("Справа v5 = ",v5);
   Print("m35.MatMul(v5) = вертикальный вектор v[3] \n",m35.MatMul(v5));
//--- покажем, что это действительно вертикальный вектор
   Print("\n4. Произведение матрицы m[3,5] на матрицу m[5,1]");
   matrix m51;
   m51.Init(5,1,Arange,1);
   Print("Слева m35 = \n",m35);
   Print("Справа m51 = \n",m51);
   Print("m35.MatMul(m51) = матрица v[3] \n",m35.MatMul(m51));

   Print("\n5. Произведение матрицы m[3,5] на матрицу m[5,2]");
   Print("Слева m35 = \n",m35);
   Print("Справа m52 = \n",m52);
   Print("m35.MatMul(m52) = матрица m[3,2] \n",m35.MatMul(m52));

   Print("\n6. Произведение горизонтального вектора v[5] на матрицу m[5,2]");
   Print("Слева v5 = \n",v5);
   Print("Справа m52 = \n",m52);
   Print("v5.MatMul(m52) = горизонтальный вектор v[2] \n",v5.MatMul(m52));

   Print("\n7. Произведение Outer() горизонтального вектора v[5] на вертикальный вектор v[3]");
   Print("Слева v5 = \n",v5);
   Print("Справа v3 = \n",v3);
   Print("v5.Outer(v3) = матрица m[5,3] \n",v5.Outer(v3));
//--- покажем, что произведение матриц дает такой же результат
   Print("\n8. Произведение Outer() матрицы m[1,5] на матрицу m[3,1]");
   matrix m15,m31;
   m15.Init(1,5,Arange,1);
   m31.Init(3,1,Arange,1);
   Print("Слева m[1,5] = \n",m15);
   Print("Справа m31 = \n",m31);
   Print("m15.Outer(m31) = матрица m[5,3] \n",m15.Outer(m31));
  }
//+------------------------------------------------------------------+
//|  Заполнение матрицы нарастающими значениями                      |
//+------------------------------------------------------------------+
void Arange(matrix & m, double start = 0, double step = 1) // функция имеет 3 параметра
  {
//---
   ulong cols = m.Cols();
   ulong rows = m.Rows();
   double value = start;
   for(ulong r = 0; r < rows; r++)
     {
      for(ulong c = 0; c < cols; c++)
        {
         m[r][c] = value;
         value += step;
        }
     }
//---     
  }
/*
Результат выполнения

1. Произведение горизонтального вектора v[3] на матрицу m[3,5]
Слева v3 = [1,2,3]
Справа m35 =
[[0,1,2,3,4]
 [5,6,7,8,9]
 [10,11,12,13,14]]
v3.MatMul(m35) = горизонтальный вектор v[5]
[40,46,52,58,64]

2. Произведение матрицы m[1,3] на матрицу m[3,5]
Слева m13 =
[[1,2,3]]
Справа m35 =
[[0,1,2,3,4]
 [5,6,7,8,9]
 [10,11,12,13,14]]
m13.MatMul(m35) = матрица m[1,5]
[[40,46,52,58,64]]

3. Произведение матрицы m[3,5] на вертикальный вектор v[5]
Слева m35 =
[[0,1,2,3,4]
 [5,6,7,8,9]
 [10,11,12,13,14]]
Справа v5 = [1,2,3,4,5]
m35.MatMul(v5) = вертикальный вектор v[3]
[40,115,190]

4. Произведение матрицы m[3,5] на матрицу m[5,1]
Слева m35 =
[[0,1,2,3,4]
 [5,6,7,8,9]
 [10,11,12,13,14]]
Справа m51 =
[[1]
 [2]
 [3]
 [4]
 [5]]
m35.MatMul(m51) = матрица v[3]
[[40]
 [115]
 [190]]

5. Произведение матрицы m[3,5] на матрицу m[5,2]
Слева m35 =
[[0,1,2,3,4]
 [5,6,7,8,9]
 [10,11,12,13,14]]
Справа m52 =
[[0,1]
 [2,3]
 [4,5]
 [6,7]
 [8,9]]
m35.MatMul(m52) = матрица m[3,2]
[[60,70]
 [160,195]
 [260,320]]

6. Произведение горизонтального вектора v[5] на матрицу m[5,2]
Слева v5 =
[1,2,3,4,5]
Справа m52 =
[[0,1]
 [2,3]
 [4,5]
 [6,7]
 [8,9]]
v5.MatMul(m52) = горизонтальный вектор v[2]
[80,95]

7. Произведение Outer() горизонтального вектора v[5] на вертикальный вектор v[3]
Слева v5 =
[1,2,3,4,5]
Справа v3 =
[1,2,3]
v5.Outer(v3) = матрица m[5,3]
[[1,2,3]
 [2,4,6]
 [3,6,9]
 [4,8,12]
 [5,10,15]]

8. Произведение Outer() матрицы m[1,5] на матрицу m[3,1]
Слева m[1,5] =
[[1,2,3,4,5]]
Справа m31 =
[[1]
 [2]
 [3]]
m15.Outer(m31) = матрица m[5,3]
[[1,2,3]
 [2,4,6]
 [3,6,9]
 [4,8,12]
 [5,10,15]]

*/

Для лучшего понимания внутреннего устройства типов matrix и vector в этих примерах показано, как можно использовать матрицы вместо векторов или, другими словами, что векторы можно представлять в виде матриц.


Комплексные числа — тип complex

Для решения некоторых математических задач требуется новый тип данных — комплексные числа. Тип complex представляет из себя структуру:
struct complex
  {
   double             real;   // вещественная часть
   double             imag;   // мнимая часть
  };
Тип "complex" может передаваться по значению в качестве параметра для MQL5-функций (в отличие от обычных структур, которые передаются только по ссылке). Для функций, импортируемых из DLL, тип "complex" должен передаваться только по ссылке.

Для описания комплексных констант используется суффикс 'i':
complex square(complex c)
  {
   return(c*c);
  }
  
void OnStart()
  {
   Print(square(1+2i));  // в качестве параметра передается константа
  }

// будет выведено "(-3,4)" - это строковое представление комплексного числа
Для комплексных чисел доступны только простые операции: =, +, -, *, /, +=, -=, *=, /=, ==, !=.

Скоро будут добавлена поддержка комплексных чисел в матрицах и векторах, а также дополнительные математические функции: получение абсолютного значения, синуса, косинуса и многие другие.