Português
preview
Factorización de matrices: lo básico

Factorización de matrices: lo básico

MetaTrader 5Ejemplos | 31 julio 2024, 16:29
13 0
Daniel Jose
Daniel Jose

Introducción

Hola a todos y bienvenidos a otro de mis artículos, orientado a ofrecer un contenido didáctico.

En este artículo hablaremos sobre cálculos matriciales. Antes de que tú, mi querido lector, desistas siquiera de leer el artículo, imaginando que vamos a hablar de algo extremadamente complicado que solo existe para fastidiar a los estudiantes en las clases de matemáticas, quiero que entiendas algo. A diferencia de lo que muchos dicen, predican y afirman, un buen programador no es aquel que escribe un programa gigantesco que solo él puede entender. O aquel que programa en el lenguaje de moda. El verdadero y buen programador es aquel que entiende que una computadora no es más que una calculadora a la que podemos indicar cómo deben hacerse los cálculos. Da igual si estás creando esto o aquello. Puede ser un simple editor de texto que, en apariencia, no contiene código matemático. Pero esto es una ilusión. Incluso un editor de texto tiene bastante matemática en su código, especialmente si incluye un corrector ortográfico.

Básicamente, existen dos maneras de efectuar factorizaciones o cálculos, si así prefieres llamarlos, en programación. La primera manera es realizar el cálculo de forma escalar. Es decir, usando las funciones que proporciona el lenguaje, escribes la fórmula tal como se presenta. Sería algo así como lo mostrado a continuación:


Esta fórmula, que todos reconocen, nos permite calcular las raíces de una función cuadrática y puede escribirse fácilmente en cualquier lenguaje de programación, utilizando las funciones y recursos disponibles en el lenguaje. Pero también existe otra forma de hacer cálculos, que es la forma matricial o vectorial, como algunos prefieren llamarla. En este artículo, la llamaremos "matricial", debido al hecho de que usaremos matrices para representar las cosas. Este tipo de factorización normalmente tiene un aspecto como el mostrado a continuación.


Muchos aficionados, que a menudo están empezando en el área de la programación, no tienen ni idea de cómo escribirlo en código. Entonces, terminan traduciendo esta notación matricial a una notación escalar para obtener el mismo resultado.

La cuestión no es si esto está bien o mal. Si el resultado obtenido es correcto, poco importa. En este caso, esta matriz muestra cómo se realiza exactamente la rotación de algo alrededor de un punto indicado. Lo que quiero mostrarte en este artículo, mi querido y entusiasta lector, es... es cómo puedes, usando MQL5 o cualquier otro lenguaje, conseguir escribir esta forma matricial directamente en tu código, sin necesidad de convertirla en una forma escalar. Hacer esto no es complicado, como muchos piensan. En realidad, es mucho más simple de lo que parece. Acompáñame en este artículo y aprende a hacerlo.


Por qué usar matrices y no la forma escalar

Antes de comenzar a ver cómo se hace la codificación, veamos cómo elegir una u otra forma de implementar la factorización.

Obviamente, si buscas información sobre un lenguaje de programación, seguramente te encontrarás con el modo escalar de escribir el código, pero ¿por qué? El motivo es que su uso es un poco más confuso a la hora de escribir código. Para entender esto, intenta escribir la matriz mostrada anteriormente, o cualquier otra matriz como si fueran líneas de código. Verás que resulta bastante incómodo. El código queda con una apariencia extraña y no tiene mucho sentido. Sin embargo, en modo escalar, el código será mucho más sencillo de comprender. Por eso, no ves a nadie codificando factorizaciones en forma matricial.

Sin embargo, diversas factorizaciones son mucho más sencillas de codificar de manera matricial que en su equivalente escalar. Por ejemplo, si necesitas manejar grandes cantidades de datos que pueden expresarse fácilmente en un array multidimensional. Para entenderlo, pensemos primero en una imagen vectorial. En lugar de pintar la pantalla píxel a píxel, usamos vectores. Esto nos permite rotar, cambiar la escala, hacer una deformación por cizallamiento, entre diversas otras manipulaciones, de forma extremadamente simple, siempre y cuando uses matrices. O, dicho de otro modo, codificar los cálculos para usar matrices. Podrás hacer las mismas transformaciones de manera escalar, pero usar matrices será mucho más sencillo.

Si una imagen vectorial no te parece lo suficientemente complicada, piensa en objetos 3D. Tal vez ahora entiendas por qué son tan interesantes las factorizaciones usando matrices.

En un objeto 3D, cualquier transformación es mucho más fácil de realizar con matrices. Hacer lo mismo de forma escalar es extremadamente complicado y laborioso. Se puede hacer, pero es tan complicado que resulta desalentador. Piensa en la necesidad de hacer una proyección ortogonal de un objeto 3D usando fórmulas como las vistas al principio del artículo. Sería una pesadilla programar algo así. Sin embargo, crear un código que use matrices hace que la complejidad del objeto 3D sea irrelevante: hacer la proyección ortogonal es muy simple y fácil.

Y esto es precisamente lo que se utiliza en las tarjetas gráficas y en los programas de modelado 3D. Básicamente, utilizan matrices en sus cálculos, incluso si no te das cuenta. Entonces, ¿cuándo usar una forma y cuándo la otra para codificar las factorizaciones? Bien, esto dependerá de tu experiencia y conocimientos. No existe una regla rígida que te obligue a usar uno u otro método. Sin embargo, saber programar ambos métodos es importante, para que puedas hacer las cosas de la manera más rápida, simple y eficaz posible.

Como la forma escalar está muy difundida y se programa literalmente, aquí nos centraremos únicamente en la manera de codificar la forma matricial. Para separar las cosas y hacer más sencilla su comprensión, lo dividiremos en temas muy específicos. Empecemos ahora.


La forma escalar de hacer las cosas

Aquí quiero mostrar algo muy simple y de fácil comprensión. Recuerda que este artículo tiene como finalidad ser didáctico. No pretende programar nada en concreto.

Para comenzar, vamos a crear un pequeño programa en MQL5. Lo que mostraré aquí puede aplicarse a cualquier otro lenguaje que quieras usar. Todo depende de lo familiarizado que estés con el lenguaje. Empezaremos con un código muy simple, como se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. //+------------------------------------------------------------------+
06. #include <Canvas\Canvas.mqh>
07. //+------------------------------------------------------------------+
08. CCanvas canvas;
09. //+------------------------------------------------------------------+
10. int OnInit()
11. {
12.     
13.     int px, py;
14.     
15.     px = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
16.     py = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
17. 
18.     canvas.CreateBitmapLabel("BL", 0, 0, px, py, COLOR_FORMAT_ARGB_NORMALIZE);
19.     canvas.Erase(ColorToARGB(clrWhite, 255));
20.         
21.     canvas.Update(true);
22.     
23.     return INIT_SUCCEEDED;
24. }
25. //+------------------------------------------------------------------+
26. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
27. {
28.     return rates_total;
29. }
30. //+------------------------------------------------------------------+
31. void OnDeinit(const int reason)
32. {
33.     canvas.Destroy();
34. }
35. //+------------------------------------------------------------------+

Como puedes ver, este código es un indicador. Podríamos usar cualquier otro, pero un indicador será más interesante por varias razones: podemos hacer algunas cosas con él y permite interceptar determinadas llamadas provenientes del MetaTrader 5.

Bien, este código es muy simple y prácticamente no hace nada. Solo cambia el gráfico a blanco o al color que desees usar. Observa que, para simplificar al máximo el código generado, en la línea seis estoy usando la biblioteca estándar de MQL5. Es decir, en esta línea le estoy diciendo al compilador que añada el archivo de cabecera Canvas.mqh, que se crea por defecto durante la instalación de MetaTrader 5. Esto simplificará bastante lo que será necesario explicar en este artículo. Después, en la línea ocho, declaramos una variable global para poder acceder a la clase CCanvas. Vale, como se trata de una clase, necesitamos usarla de una determinada manera. Un detalle aquí: normalmente me gusta usar clases como punteros, pero para simplificar al máximo, ya que muchos podrían tener dudas sobre por qué estoy haciendo esto o aquello, voy a usar la clase como todos normalmente la usan, es decir, una variable simple que nos permite referenciar algo.

Muy bien, en las líneas 15 y 16 capturamos el tamaño de la ventana del gráfico donde se coloca el indicador. Observa que no estamos creando una ventana para él; esto se indica en la línea 3. Como el indicador no va a trazar absolutamente nada, evitamos que el compilador nos genere mensajes de alerta. Para hacer esto, usamos la línea 4. Por lo tanto, si aplicamos este indicador en una subventana, la información capturada en las líneas 15 y 16 será diferente.

En la línea 18, le decimos a la clase Canvas que queremos crear una región donde pintar o trazar algo. Esta región va de la esquina superior izquierda (segundo y tercer parámetro) a la esquina inferior izquierda (los dos siguientes parámetros). El primer parámetro es el nombre que tendrá el objeto creado por la clase CCanvas. El último parámetro indica qué tipo de combinación se usará para pintar en la región. Consulta la documentación para obtener más detalles sobre estos modos de pintura. En este caso, vamos a utilizar el modelo que tiene un alpha en el color, es decir, podemos crear una transparencia sobre lo que estemos pintando en la región seleccionada.

La siguiente acción es limpiar la región. Esto se hace en la línea 19 y justo después, en la línea 21, se indica que la información presente en la memoria se muestre en la pantalla. Es importante que entiendas esto: la información que coloques en la región usando CCanvas no aparecerá en la pantalla en el mismo momento en que la estás creando. Toda la pintura se hace primero en la memoria. No se mostrará en la pantalla hasta que se ejecute la línea 21. Así de simple. Ahora que ya tenemos la base inicial, veamos qué pintaremos en la pantalla. Esto se muestra en el código a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. //+------------------------------------------------------------------+
06. #include <Canvas\Canvas.mqh>
07. //+------------------------------------------------------------------+
08. CCanvas canvas;
09. //+------------------------------------------------------------------+
10. #define _ToRadians(A) (A * (M_PI / 180.0))
11. //+------------------------------------------------------------------+
12. void Arrow(const int x, const int y, const ushort angle)
13. {
14.     int ax[] = {0, 150, 100, 150},
15.         ay[] = {0, -75,   0,  75};
16.     int dx[ax.Size()], dy[ax.Size()];
17.     
18.     for (int c = 0; c < (int)ax.Size(); c++)
19.     {
20.         dx[c] = (int)((ax[c] * cos(_ToRadians(angle))) + (ay[c] * sin(_ToRadians(angle)))) + x;
21.         dy[c] = (int)((ax[c] * (-sin(_ToRadians(angle)))) + (ay[c] * cos(_ToRadians(angle)))) + y;
22.     }
23. 
24.     canvas.FillPolygon(dx, dy, ColorToARGB(clrBlue, 255));
25.     canvas.FillCircle(x, y, 5, ColorToARGB(clrRed, 255));
26. }
27. //+------------------------------------------------------------------+
28. int OnInit()
29. {    
30.     int px, py;
31.     
32.     px = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
33.     py = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
34. 
35.     canvas.CreateBitmapLabel("BL", 0, 0, px, py, COLOR_FORMAT_ARGB_NORMALIZE);
36.     canvas.Erase(ColorToARGB(clrWhite, 255));
37.         
38.     Arrow(px / 2, py / 2, 60);
39. 
40.     canvas.Update(true);
41.     
42.     return INIT_SUCCEEDED;
43. }
44. //+------------------------------------------------------------------+
45. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
46. {
47.     return rates_total;
48. }
49. //+------------------------------------------------------------------+
50. void OnDeinit(const int reason)
51. {
52.     canvas.Destroy();
53. }
54. //+------------------------------------------------------------------+

Ten en cuenta que aquí estamos realizando la traducción escalar de la fórmula que se muestra al inicio del artículo, donde se utiliza una notación matricial. Es decir, haremos la rotación de un objeto. Pero, ¿qué objeto? Para empezar a entenderlo, mira la siguiente imagen. Es lo que se verá cuando se ejecute el código en MetaTrader 5.


Es decir, el objeto que estamos rotando es una flecha. Pero, al mirar este código, ¿puedes ver dónde se está dibujando dicha flecha? Bien, fíjate en la línea 38, donde hay una llamada a un procedimiento llamado ARROW (FLECHA). Este procedimiento está en la línea 12. Por lo tanto, obviamente, la flecha debe estar en algún punto dentro del procedimiento de la línea 12. Pero, ¿dónde? Bien, la flecha está definida en las líneas 14 y 15. Ojo: tenemos dos arrays para dibujar la flecha. Aquí la estamos dibujando de forma muy simple. Ahora comienza la parte divertida.

Esta flecha se está definiendo en términos absolutos. Es decir, la estamos dibujando tal y como se representaría en el mundo real. Sin embargo, también podríamos representarla de manera virtual. Mejor dicho, en lugar de usar valores enteros, podríamos usar valores de coma flotante. Así, al multiplicar los valores que dibujan la flecha por un escalar cualquiera, podríamos definir su tamaño. Sin embargo, para no complicar las cosas y no confundirme, no he hecho esto aquí. Quiero dejar las cosas lo más simples posible para que tú, mi querido lector, consigas entender cómo implementar una factorización matricial.

Vale, en la línea 16 hay algo que puede resultar confuso para muchos, pero el motivo de que esté escrito de esa manera es sencillo de entender. Como no sabemos de antemano cuántos puntos se van a utilizar para dibujar el objeto y necesitamos un array intermedio para poder dibujarlo en la línea 24, necesitamos alguna forma de decirle a la aplicación que asigne memoria para nosotros. Al utilizar la forma vista en la línea 16, le decimos al compilador que asigne la misma cantidad de espacio que fue necesario para dibujar los puntos. Es decir, tenemos un array dinámico que se usa, pero que se asigna de forma estática. Si no utilizáramos esta forma de codificar, tendríamos que crear el mismo código visto en el procedimiento Arrow, montado como se muestra en el fragmento a continuación.

11. //+------------------------------------------------------------------+
12. void Arrow(const int x, const int y, const ushort angle)
13. {
14.     int ax[] = {0, 150, 100, 150},
15.         ay[] = {0, -75,   0,  75};
16.     int dx[], dy[];
17.     
18.     ArrayResize(dx, ax.Size());
19.     ArrayResize(dy, ax.Size());
20.     
21.     for (int c = 0; c < (int)ax.Size(); c++)
22.     {
23.         dx[c] = (int)((ax[c] * cos(_ToRadians(angle))) + (ay[c] * sin(_ToRadians(angle)))) + x;
24.         dy[c] = (int)((ax[c] * (-sin(_ToRadians(angle)))) + (ay[c] * cos(_ToRadians(angle)))) + y;
25.     }
26. 
27.     canvas.FillPolygon(dx, dy, ColorToARGB(clrBlue, 255));
28.     canvas.FillCircle(x, y, 5, ColorToARGB(clrRed, 255));
29. 
30.     ArrayFree(dx);
31.     ArrayFree(dy);
32. }
33. //+------------------------------------------------------------------+

Ten en cuenta que en la línea 16 tenemos ahora una indicación de array dinámico. Este tipo de array define el espacio que se va a usar en tiempo de ejecución. Necesitamos hacer los siguientes cambios. En las líneas 18 y 19 del fragmento, debemos asignar el espacio necesario para que el programa no falle durante la ejecución. Además, en el fragmento, debemos devolver la memoria asignada al sistema operativo en las líneas 30 y 31. Esto es una buena práctica en programación. Muchos programadores suelen no devolver los recursos asignados, lo que convierte la aplicación en un verdadero consumidor de recursos, ya que asigna recursos y no los libera, siendo una aplicación mal gestionada. Pero, dejando de lado estas pequeñas diferencias que puedes ver entre el fragmento y el código principal, todo lo demás funciona de la misma manera.

Muy bien, volvamos al código principal. Observa que el bucle de la línea 18 recorrerá todos los puntos que forman parte de la figura que queremos, en este caso, rotar.

Ahora presta atención a algo. Estamos rotando la figura y moviéndola a un lugar concreto. En concreto, la estamos moviendo al punto X e Y, que en este caso es el centro de la pantalla. Observa la línea 38, donde definimos la ubicación y el ángulo de giro, que en este caso es de 60 grados.

Como, por defecto, todas las funciones trigonométricas usan valores en radianes y no en grados, es necesario convertir los grados en radianes. Esto es precisamente lo que se hace en la definición de la línea diez. De esta manera, es más natural decir cuánto debe girar una figura, que tener que expresar el valor en radianes, lo que en muchos casos puede ser bastante confuso.

Así, para cada uno de los puntos del array, aplicamos la fórmula que se muestra a continuación.


Esto hará que la imagen gire en sentido antihorario, de modo que cero grados sería la posición de las nueve en un reloj. Para que lo entiendas mejor, 90 grados serían las seis, 180 grados serían las tres y 270 grados serían las doce. Recuerda que la rotación se produce en sentido antihorario. Para cambiar este comportamiento, sería necesario modificar la forma en que se hace el cálculo. No es algo complicado; de hecho, es bastante simple. En el caso de girar en sentido horario, la fórmula tendría que cambiarse a la que se muestra a continuación.


Observa que es muy fácil de hacer. En este caso, las tres serían el ángulo de cero grados, las seis el de 90 grados, las nueve el de 180 grados y las doce el de 270 grados. Aunque parece que solo hemos cambiado el valor del ángulo de cero con el de 180 grados, hemos hecho más que eso. Ahora el sentido de giro ha cambiado de antihorario a horario.

Pero no es eso lo que quiero mostrar. Sin embargo, esto que se ha hecho solo sirve como curiosidad. Para explicar realmente lo que queremos mostrar, primero debemos presentar la forma en que todos normalmente hacen las cosas. Justamente porque es más fácil y sencillo en muchos casos. Sin embargo, no es algo que podamos hacer que se adapte fácilmente a cualquier situación.

Pero no voy a entrar en detalles sobre esto en este momento, ya que el enfoque del artículo no es ese, sino cómo modelar cálculos matriciales. Así que veamos cómo suelen lidiar los programadores con estas cuestiones. La manera más sencilla es transformar las cosas en unidades virtuales. Hacemos todos los cálculos y luego transformamos el resultado en unidades gráficas de pantalla. Al crear algo con unidades virtuales, no usaremos valores enteros, sino valores flotantes, normalmente del tipo double. Así, el mismo código visto antes queda como se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. //+------------------------------------------------------------------+
06. #include <Canvas\Canvas.mqh>
07. //+------------------------------------------------------------------+
08. CCanvas canvas;
09. //+------------------------------------------------------------------+
10. #define _ToRadians(A) (A * (M_PI / 180.0))
11. //+------------------------------------------------------------------+
12. void Arrow(const int x, const int y, const ushort angle, const uchar size = 100)
13. {
14.     double ax[] = {0.0,  1.5, 1.0, 1.5},
15.            ay[] = {0.0, -.75, 0.0, .75};
16.     int dx[ax.Size()], dy[ax.Size()];
17.     
18.     for (int c = 0; c < (int)ax.Size(); c++)
19.     {
20.         ax[c] *= size;
21.         ay[c] *= size;
22.         dx[c] = (int) ((ax[c] * cos(_ToRadians(angle))) + (ay[c] * sin(_ToRadians(angle)))) + x;
23.         dy[c] = (int) ((ax[c] *(-sin(_ToRadians(angle)))) + (ay[c] * cos(_ToRadians(angle)))) + y;
24.     }
25. 
26.     canvas.FillPolygon(dx, dy, ColorToARGB(clrGreen, 255));
27.     canvas.FillCircle(x, y, 5, ColorToARGB(clrRed, 255));
28. }
29. //+------------------------------------------------------------------+
30. int OnInit()
31. {    
32.     int px, py;
33.     
34.     px = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
35.     py = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
36. 
37.     canvas.CreateBitmapLabel("BL", 0, 0, px, py, COLOR_FORMAT_ARGB_NORMALIZE);
38.     canvas.Erase(ColorToARGB(clrWhite, 255));
39.         
40.     Arrow(px / 2, py / 2, 60);
41. 
42.     canvas.Update(true);
43.     
44.     return INIT_SUCCEEDED;
45. }
46. //+------------------------------------------------------------------+
47. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
48. {
49.     return rates_total;
50. }
51. //+------------------------------------------------------------------+
52. void OnDeinit(const int reason)
53. {
54.     canvas.Destroy();
55. }
56. //+------------------------------------------------------------------+

Observa que casi nada ha cambiado, salvo el hecho de que ahora podemos hacer zoom en el objeto creado. Esto se hace en las líneas 20 y 21. El objeto se está creando en las líneas 14 y 15. Todo el código permanece básicamente idéntico. Sin embargo, este simple cambio ya ayuda mucho a portar la aplicación a otros escenarios. Por ejemplo, si el usuario cambia las dimensiones del gráfico, puedes ajustar únicamente el parámetro size en la línea 12 para mantener los objetos dentro de una proporción determinada.

Pero aún no entiendo cómo esto me ayudaría a trabajar con matrices. Si puedo hacerlo así, ¿por qué complicarlo más? De acuerdo, veamos si realmente sería más complicado hacer este tipo de cosas usando matrices. Para ello, empezaremos con un nuevo tema.


Ahora usando factorización por matriz

Como el objetivo aquí es ser didáctico, intentaré mantener las cosas lo más simples posible. Así que implementaremos solo lo necesario para demostrar lo que necesitamos, es decir, la multiplicación de matrices en este caso específico que estamos mostrando. Pero tú, mi querido lector, verás que incluso haciendo solo esto tendremos suficiente para efectuar la multiplicación de una matriz por un escalar. La gran dificultad que muchas personas tienen a la hora de implementar un código utilizando la factorización de matrices es que, a diferencia de una factorización escalar, donde en casi todos los casos el orden de los factores no altera el resultado, cuando se usan matrices, la cosa no es así. Entonces, ¿cómo sería el mismo código visto en el tema anterior, solo que usando matrices? No te asustes, mi querido lector, pero el código se implementa como se muestra a continuación, para que la factorización se haga usando matrices.
01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. //+------------------------------------------------------------------+
06. #include <Canvas\Canvas.mqh>
07. //+------------------------------------------------------------------+
08. CCanvas canvas;
09. //+------------------------------------------------------------------+
10. #define _ToRadians(A) (A * (M_PI / 180.0))
11. //+------------------------------------------------------------------+
12. void MatrixA_x_MatrixB(const double &A[][], const double &B[][], double &R[][], const int nDim)
13. {
14.     for (int c = 0, size = (int)(B.Size() / nDim); c < size; c++)
15.     {
16.         R[c][0] = (A[0][0] * B[c][0]) + (A[0][1] * B[c][1]);
17.         R[c][1] = (A[1][0] * B[c][0]) + (A[1][1] * B[c][1]);
18.     }
19. }
20. //+------------------------------------------------------------------+
21. void Arrow(const int x, const int y, const ushort angle, const uchar size = 100)
22. {
23.     double M_1[2][2]{
24.                         cos(_ToRadians(angle)), sin(_ToRadians(angle)),
25.                         -sin(_ToRadians(angle)), cos(_ToRadians(angle))
26.                     },
27.            M_2[][2] {
28.                         0.0,  0.0,
29.                         1.5, -.75,
30.                         1.0,  0.0,
31.                         1.5,  .75
32.                     },
33.            M_3[M_2.Size() / 2][2];
34. 
35.     int dx[M_2.Size() / 2], dy[M_2.Size() / 2];
36.     
37.     MatrixA_x_MatrixB(M_1, M_2, M_3, 2);
38.     ZeroMemory(M_1);
39.     M_1[0][0] = M_1[1][1] = size;
40.     MatrixA_x_MatrixB(M_1, M_3, M_2, 2);
41. 
42.     for (int c = 0; c < (int)M_2.Size() / 2; c++)
43.     {
44.         dx[c] = x + (int) M_2[c][0];
45.         dy[c] = y + (int) M_2[c][1];
46.     }
47. 
48.     canvas.FillPolygon(dx, dy, ColorToARGB(clrPurple, 255));
49.     canvas.FillCircle(x, y, 5, ColorToARGB(clrRed, 255));
50. }
51. //+------------------------------------------------------------------+
52. int OnInit()
53. {    
54.     int px, py;
55.     
56.     px = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
57.     py = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
58. 
59.     canvas.CreateBitmapLabel("BL", 0, 0, px, py, COLOR_FORMAT_ARGB_NORMALIZE);
60.     canvas.Erase(ColorToARGB(clrWhite, 255));
61.         
62.     Arrow(px / 2, py / 2, 160);
63. 
64.     canvas.Update(true);
65.     
66.     return INIT_SUCCEEDED;
67. }
68. //+------------------------------------------------------------------+
69. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
70. {
71.     return rates_total;
72. }
73. //+------------------------------------------------------------------+
74. void OnDeinit(const int reason)
75. {
76.     canvas.Destroy();
77. }
78. //+------------------------------------------------------------------+

Dejé el código lo más simple posible, sin muchos ajustes, para que tú, mi querido y entusiasta lector, puedas entenderlo. Normalmente, cuando escribo una factorización que se hará usando cálculos matriciales, lo hago de una forma un poco diferente, pero esto se verá en un próximo artículo, ya que será necesario explicar algunos detalles y cuidados que hay que tomar. Así que debo hacer otro solo para explicar estos detalles, porque, aunque parezca simple, si no tomas los debidos cuidados, acabarás en un agujero sin fondo. De acuerdo, pero ¿qué está haciendo este código tan loco? La respuesta es que está haciendo lo mismo que en el tema anterior. Solo que aquí estamos utilizando la multiplicación de matrices para realizar los mismos cálculos. Tal vez al mirar este código encuentres que es muy complicado y difícil de entender. Pero no, es mucho más sencillo que el anterior. Es mucho más simple, siempre y cuando entiendas cómo se multiplica una matriz por otra. A pesar de todo, la multiplicación no se está haciendo de la forma correcta. O, dicho de otro modo, no nos permitirá hacer algunas cosas si es necesario modificar el código. Pero, como dije, esta versión es solo para demostrar. La versión correcta se verá en el próximo artículo. No obstante, la factorización dará el resultado esperado, aunque se haga de una manera algo extraña.

Este es un detalle que necesitas saber antes de querer utilizar esta modelización en cualquier actividad. Sin embargo, si ya entiendes mínimamente bien cómo se factorizan las matrices, el resto resulta mucho más sencillo de comprender. Fíjate en que en la línea 12 se implementa el código que se encarga de efectuar la factorización matricial. Algo simple. Básicamente, multiplicamos la primera matriz por la segunda y colocamos el resultado en una tercera. Como la multiplicación se realiza por columnas de una matriz y por filas de la otra, la factorización se lleva a cabo de manera bastante simple. Aquí debemos tener algunos cuidados, como el hecho de que el número de filas de una matriz debe ser igual al número de columnas de la otra. Esto es matemáticas básicas sobre matrices, así que procura entenderlo antes, en caso de que no sepas cómo proceder o por qué debe hacerse así. En la matriz A colocamos las columnas y en la matriz B las filas. Así, en nuestro caso, tendríamos algo equivalente a lo que se ve a continuación.

Sé que esto puede parecer muy confuso la primera vez que se mira. Pero es muy sencillo. Se multiplicará la matriz de elementos A por la matriz de elementos B. Esto dará como resultado la matriz de elementos R. Básicamente, es así:
R11 = (A11 x B11) + (A12 x B12);  R12 = (A21 x B11) + (A22 x B12) R21 = (A11 x B21) + (A12 x B22);  R22 = (A21 x B21) + (A22 x B22) R31 = (A11 x B31) + (A12 x B32);  R32 = (A21 x B31) + (A22 x B32)
y así sucesivamente. Ten en cuenta que es exactamente como se haría si el cálculo se montara de forma escalar. Esto es precisamente lo que hace el procedimiento de la línea 12. Ahora observa que, en el momento de construir los cálculos, tenemos una libertad mucho mayor. Para percibirlo, veamos el procedimiento de la línea 21, donde se construye y se presenta la flecha. Observa que aquí se definen 5 matrices. Podríamos usar menos. Pero comprendamos esto primero. En el futuro, quién sabe, mostraré cómo podríamos modelar esto de una forma mucho más intuitiva y comprensible. Pero, incluso en este modelo, sigue siendo bastante sencillo de entender. Entre las líneas 23 y 33, dibujamos nuestras matrices. Estas se utilizarán en los cálculos. Ahora, en la línea 35, declaramos las que usaremos para presentar la flecha en la pantalla. Entre las líneas 37 y 40, efectuamos los cálculos deseados. Ahora presta atención, porque lo que vamos a hacer es muy interesante. Esto desde el punto de vista práctico y en términos de libertad a la hora de factorizar las cosas. En la línea 37, primero multiplicamos la matriz M_1 por la matriz M_2 y el resultado se va a la matriz M_3. ¿Qué está haciendo esta multiplicación? Está girando la imagen. Sin embargo, no podemos presentarla aún en la pantalla porque la escala todavía es la utilizada en el modelo encontrado en la matriz M_2. Para poder presentar la imagen en la escala correcta, necesitamos hacer otro cálculo. Pero antes, en la línea 38, limpiamos el contenido de la matriz M_1 y, justo después, en la línea 39, indicamos cómo se modificará la escala de la imagen. En este punto, tenemos diversas opciones nuevas. Sin embargo, nos centraremos solo en el cambio de escala. En la línea 40, multiplicamos los datos del objeto, que ya está rotado, por la nueva matriz, que ya contiene la escala que se va a utilizar. Esto se puede ver en la imagen a continuación.

Aquí Sx es la nueva dimensión en el eje X y Sy es la nueva dimensión en el eje Y. Como estamos poniendo ambos valores como "Size", el cambio será como si estuviéramos haciendo zoom en el objeto. Por eso dije que podemos hacer muchas más cosas. Muy bien, ahora, como el procedimiento FillPolygon no sabe lidiar con esta estructura en forma de matriz, necesitamos separar los datos para que FillPolygon pueda dibujar la flecha para nosotros. Por eso tenemos el bucle for en la línea 48 y el resultado es igual al que se generó en el tema anterior.


Consideraciones finales

En este artículo, presento un método bastante sencillo para manejar cálculos matriciales. He intentado aportar algo interesante y, al mismo tiempo, explicar de la forma más sencillo posible cómo se hace dicho cálculo. Sin embargo, el modelo de matrices visto en este artículo no es el más adecuado para permitirnos hacer todos los tipos de factorización posibles. Así que publicaré pronto un nuevo artículo en el que explicaré un modelo más adecuado para representar estas matrices. También daré una explicación orientada a que puedas aprovechar esta forma de factorizar datos. Hasta pronto. Nos vemos en un próximo artículo.


Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/13646

Archivos adjuntos |
App1.mq5 (1.87 KB)
App2.mq5 (1.97 KB)
App3.mq5 (2.62 KB)
Red neuronal en la práctica: Recta secante Red neuronal en la práctica: Recta secante
Como se explicó en la parte teórica, necesitamos usar regresiones lineales y derivadas cuando trabajamos con redes neuronales. ¿Pero por qué? La razón es que la regresión lineal es una de las fórmulas más simples que existen. Básicamente, una regresión lineal es solo una función afín. Sin embargo, cuando hablamos de redes neuronales, no nos interesan los efectos de la recta de regresión lineal. Lo que nos interesa es la ecuación que genera dicha recta. La recta generada poco importa. ¿Pero sabes cuál es la ecuación principal que hay que comprender? Si no, lee este artículo para empezar a comprenderlo.
Inferencia causal en problemas de clasificación de series temporales Inferencia causal en problemas de clasificación de series temporales
En este artículo, examinaremos la teoría de la inferencia causal utilizando el aprendizaje automático, así como la implementación del enfoque personalizado en Python. La inferencia causal y el pensamiento causal tienen sus raíces en la filosofía y la psicología y desempeñan un papel importante en nuestra comprensión de la realidad.
Algoritmos de optimización de la población: Objetos artificiales de búsqueda multisocial (artificial Multi-Social search Objects, MSO) Algoritmos de optimización de la población: Objetos artificiales de búsqueda multisocial (artificial Multi-Social search Objects, MSO)
Continuación del artículo anterior como desarrollo de la idea de grupos sociales. El nuevo artículo investiga la evolución de los grupos sociales mediante algoritmos de reubicación y memoria. Los resultados ayudarán a comprender la evolución de los sistemas sociales y a aplicarlos a la optimización y la búsqueda de soluciones.
Introducción a MQL5 (Parte 4): Estructuras, clases y funciones de tiempo Introducción a MQL5 (Parte 4): Estructuras, clases y funciones de tiempo
En esta serie, seguiremos desvelando los secretos de la programación. En nuestro nuevo artículo, aprenderemos los fundamentos de las estructuras, las clases y las funciones de tiempo y adquiriremos nuevas habilidades para lograr una programación eficiente. Esta guía será probablemente útil no solo para los principiantes, sino también para los desarrolladores experimentados, ya que simplifica conceptos complejos, ofreciendo información valiosa para dominar MQL5. Así que hoy podrá seguir aprendiendo cosas nuevas, mejorando sus conocimientos de programación y dominando el mundo del trading algorítmico.