¿Qué es el «álgebra lineal»?
Hay muchas definiciones técnicas: en [Wikipedia][linalg-wiki], en los libros de texto y en sitios excelentes como [3Blue1Brown][3blue1brown]. Pero para lo que nos interesa aquí, podemos ser más informales.
El álgebra lineal trata de muchas cosas interesantes (y muy útiles) que puedes hacer con vectores y matrices.
A quienes mantienen Julia (y Python) les encanta este tipo de cosas. A la dirección de Exercism, no tanto.
Este Concepto se limitará a los aspectos más sencillos de un tema enorme, pero te advierto: esto es, inevitablemente, un concepto bastante matemático.
Depende de a quién le preguntes y de cómo quieras visualizar algo más bien abstracto.
Vector{T} no es más que un alias de Array{T, 1}, y el eltype T puede ser cualquier cosa.length y una direction en un espacio de N dimensiones, pero sin una posición fija.point en un espacio de N dimensiones, donde los N elementos representan la distancia al origen a lo largo de cada uno de los N ejes.Para lo que nos ocupa ahora, ignora los vectores de strings o de caracteres.
En este Concepto trabajaremos con tipos numéricos: Int, Float o Complex.
De nuevo, encontrarás respuestas muy variadas.
linear combination de vectores columna, apilados uno al lado del otro.transformation que se puede aplicar a un vector, igual que una function se aplica a otros tipos de entrada.Algunos tipos de matriz cuadrada son lo bastante comunes como para tener nombres especiales.
Matriz diagonal: Todas las entradas distintas de cero están en la main diagonal (de arriba a la izquierda a abajo a la derecha).
julia> [1 0 0; 0 2 0; 0 0 3]
3×3 Matrix{Int64}:
1 0 0
0 2 0
0 0 3
# a convenient shortcut
julia> diagm(1:3)
3×3 Matrix{Int64}:
1 0 0
0 2 0
0 0 3
Matriz identidad: Una matriz diagonal con solo unos en la diagonal (por razones que quedarán más claras después).
A menudo se abrevia como I.
julia> Matrix{Float64}(I, 3, 3)
3×3 Matrix{Float64}:
1.0 0.0 0.0
0.0 1.0 0.0
0.0 0.0 1.0
Triangular superior: Valores distintos de cero en la diagonal y por encima de ella, y ceros por debajo. La triangular inferior ya te la imaginas.
Si de verdad quieres intercambiar filas por columnas, la función permutedims() hará eso y te dará una matriz nueva.
Esto implica copiar, algo lento y que consume mucha memoria cuando trabajas con matrices grandes.
Para fines del álgebra lineal, la función transpose() es más útil, ya que crea rápidamente un envoltorio perezoso alrededor de la matriz original.
Todavía más útil es la función adjoint(), que además invierte el signo de la parte imaginaria de los números complejos.
Esta operación es lo bastante común como para que baste con añadir un apóstrofo ' al nombre de la variable para crear la adjunta.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
# new, full copy
julia> permutedims(m)
3×2 Matrix{Int64}:
1 4
2 5
3 6
# lazy version
julia> transpose(m)
3×2 transpose(::Matrix{Int64}) with eltype Int64:
1 4
2 5
3 6
julia> mc = [1+2im 2+3im; 3+2im 1+2im]
2×2 Matrix{Complex{Int64}}:
1+2im 2+3im
3+2im 1+2im
# lazy conjugate transpose
julia> adjoint(mc)
2×2 adjoint(::Matrix{Complex{Int64}}) with eltype Complex{Int64}:
1-2im 3-2im
2-3im 1-2im
# syntactic sugar for adjoint
julia> mc'
2×2 adjoint(::Matrix{Complex{Int64}}) with eltype Complex{Int64}:
1-2im 3-2im
2-3im 1-2im
El resto de este documento incluye funcionalidad del módulo LinearAlgebra.
Una línea using LinearAlgebra lo traerá al espacio de nombres, pero no lo repetiremos en los ejemplos (demasiado ruido visual).
Esto se trató en el Concepto [Operaciones con vectores][vector-ops].
El operador es .*, que opera sobre los vectores de entrada por pares y da una salida del mismo tamaño y tipo que las entradas.
julia> [1, 2] .* [3, 4]
2-element Vector{Int64}:
3
8
Esta operación sumamente común se escribe en los libros de texto como u ⋅ v y se llama producto «punto».
Un producto punto equivale a la suma del producto elemento a elemento.
Dos vectores se pueden multiplicar con el operador * habitual, pero solo si el vector de la izquierda es un adjoint: cómodamente escrito u' * v.
Esto debería quedar más claro en la sección posterior sobre multiplicación de matrices.
El punto elevado (centrado) está disponible en Julia (se escribe \cdot y luego tabulador) como azúcar sintáctico para la función dot().
No hace falta especificar la adjunta, porque este detalle se maneja automáticamente.
julia> using LinearAlgebra
# with element-wise syntax
julia> sum([1, 2] .* [3, 4])
11
# with u' * v syntax
julia> [1, 2]' * [3, 4]
11
# with dot()
julia> dot([1, 2], [3, 4])
11
# with \cdot syntax
julia> [1, 2] ⋅ [3, 4]
11
Para vectores con valores complejos, el vector de la izquierda tiene que ser el conjugado (con el signo de la parte imaginaria invertido).
La función dot hace esto automáticamente, la sintaxis u' lo hace de forma explícita, pero sum(u .* v) fallará si u y v son complejos.
¿Qué tan «grande» es un vector?
La norm es un intento de capturar esto, reduciendo el vector a un escalar adecuado.
Se define toda una familia de normas, pero la más común con diferencia es la 2-norma, que es √(v ⋅ v).
Esta operación de raíz cuadrática media es la distancia pitagórica desde el origen (en un espacio de N dimensiones). Si visualizamos el vector como una flecha con su cola en el origen, la 2-norma es la longitud de la flecha.
Cualquier p-norma se puede calcular pasando p como segundo argumento.
La 1-norma a veces resulta útil: es simplemente la suma de los valores absolutos de las entradas (así que es muy rápida y fácil de calcular).
# defaults to the 2-norm
julia> norm([1, 2, 3])
3.7416573867739413
# the 1-norm
julia> norm([1, -2, 3], 1)
6.0
A veces parece que todos los matemáticos aplicados del mundo han pasado buena parte de los últimos 80 años convirtiendo cada cálculo en una serie de multiplicaciones de matrices.
Esta es una operación en la que las computadoras son muy buenas:
Los detalles son bastante sencillos, aunque no muy intuitivos a primera vista.
Considera una matriz A que multiplica un vector v para dar una salida w (por convención del álgebra lineal, usaremos letras mayúsculas para las matrices y minúsculas para los vectores).
Se calcula el producto punto de la primera fila de A con v para obtener el primer elemento de w, la segunda fila da el segundo elemento, y así sucesivamente hacia abajo.
julia> A = [1 2; 3 4]
2×2 Matrix{Int64}:
1 2
3 4
julia> v = [5, 6]
2-element Vector{Int64}:
5
6
julia> A * v
2-element Vector{Int64}:
17 # equals [1, 2] ⋅ [5, 6]
39 # equals [3, 4] ⋅ [5, 6]
Una multiplicación matriz * matriz extiende esto a lo largo de las columnas de la matriz de la derecha.
Para C = A * B, podemos pensar que A multiplica cada columna de B para dar la columna correspondiente de C: una serie de multiplicaciones de matriz por vector.
De manera equivalente, podríamos decir que se calcula el producto punto de la primera fila de A con cada columna de B para dar la primera fila de C, la segunda fila da la segunda fila, y así sucesivamente hacia abajo.
julia> A
2×2 Matrix{Int64}:
1 2
3 4
julia> B = [5 7; 6 8]
2×2 Matrix{Int64}:
5 7
6 8
# left column is the same as A*v previously
julia> A * B
2×2 Matrix{Int64}:
17 23
39 53
julia> B * A
2×2 Matrix{Int64}:
26 38
30 44
Como se muestra en el ejemplo anterior, la multiplicación de matrices no es conmutativa: no hay una relación simple entre A*B y B*A.
Probablemente sea difícil visualizar la multiplicación de matrices para quien la ve por primera vez y solo lee el texto. YouTube tiene muchos videos que la demuestran gráficamente, así que busca «multiplicación de matrices» y elige uno con tu estilo, nivel de detalle e idioma preferidos.
El producto punto de dos vectores depende de que tengan la misma longitud.
Por extensión, para la multiplicación de matrices, el número de columnas de la matriz de la izquierda debe coincidir con el número de filas de la matriz de la derecha.
Si expresamos los tamaños como tuplas (nrows, ncols), tal como los devuelve size(A), tenemos (a, b) * (b, c) -> (a, c).
Las dimensiones «internas», aquí b y b, son compatibles para calcular productos punto.
Las dimensiones «externas», aquí a y c, determinan las dimensiones de la salida.
Un ejemplo con matrices rectangulares:
julia> D = reshape(1:6, 2, 3)
2×3 reshape(::UnitRange{Int64}, 2, 3) with eltype Int64:
1 3 5
2 4 6
julia> E = reshape(1:12, 3, 4)
3×4 reshape(::UnitRange{Int64}, 3, 4) with eltype Int64:
1 4 7 10
2 5 8 11
3 6 9 12
julia> D * E
2×4 Matrix{Int64}:
22 49 76 103
28 64 100 136
# (3, 4) * (2, 3) not possible
julia> E * D
ERROR: DimensionMismatch: matrix A has axes (Base.OneTo(3),Base.OneTo(4)), matrix B has axes (Base.OneTo(2),Base.OneTo(3))
Multiplicar pares de vectores al estilo de las matrices tiene dos posibilidades.
Convencionalmente, usaríamos u' * v como equivalente del producto punto.
Las dimensiones son (1, 3) * (3, 1), y Julia simplifica la salida (1, 1) a un escalar (a diferencia, por ejemplo, de R).
Como alternativa, podríamos usar u * v', con dimensiones (3, 1) * (1, 3) -> (3, 3), para multiplicar todos los pares de elementos posibles y expandir los vectores a una matriz.
julia> u = [1, 2]
2-element Vector{Int64}:
1
2
julia> v = [3, 4]
2-element Vector{Int64}:
3
4
julia> u' * v
11
julia> u * v'
2×2 Matrix{Int64}:
3 4
6 8
u' * v a veces se llama el producto interno.
Un tipo particularmente común de multiplicación de matrices involucra matrices de rotación.
En 2D, hay una matriz relativamente simple para rotar un vector θ radianes en sentido antihorario.
julia> rot2d(θ, vec) = [cos(θ) -sin(θ); sin(θ) cos(θ)] * vec
rot2d (generic function with 1 method)
# unit vector in the x direction
julia> i_hat = [1, 0]
2-element Vector{Int64}:
1
0
# rotate 45 degrees
julia> rot2d(π/4, i_hat)
2-element Vector{Float64}:
0.7071067811865476
0.7071067811865475
# rotate 90 degrees -> unit vector in the y direction, j_hat
julia> rot2d(π/2, i_hat)
2-element Vector{Float64}:
6.123233995736766e-17 # zero, within numerical error
1.0
Trabajas para una startup de robótica que está desarrollando un robot sencillo como prueba de concepto. Te encargaron implementar una funcionalidad para controlar el movimiento del robot.
Para llevar un registro de la orientación y la extensión del robot, este tiene tres marcadores que están a 1 unidad de distancia de su centro. Para inicializar su posición, debemos tomar los tres vectores de dirección, normalizarlos y colocarlos en una matriz.
Implementa la función orientrobot(vectors), que recibe un vector de tres vectores.
Devuelve una matriz 2x3 cuyas columnas son los vectores normalizados.
julia> orientrobot([[-1,1],[1,0],[-1,-1]])
2×3 Matrix{Float64}:
-0.707107 1.0 -0.707107
0.707107 0.0 -0.707107
Ahora necesitamos una funcionalidad para cambiar la dirección del movimiento. Para lograrlo, debemos rotar el robot para que mire hacia donde quiere ir.
Implementa la función rotaterobot(orientation, θ), que recibe la matriz de orientación del robot y un ángulo θ para rotar en sentido antihorario.
Devuelve la nueva matriz de orientación.
julia> orientmatrix = initialize([[-1,1],[1,0],[-1,-1]]);
julia> rotaterobot(orientmatrix, π/2)
2×3 Matrix{Float64}:
-0.707107 6.12323e-17 0.707107
-0.707107 1.0 -0.707107
Para mover el robot de un lugar a otro, primero debemos verificar que tenga la orientación correcta antes de moverlo. La segunda columna de la matriz de orientación representa la dirección hacia la que mira de frente.
Implementa la función robotoriented(orientation, direction), que recibe una matriz de orientación y un vector de posición relativa.
Devuelve true si el robot está orientado en la misma dirección que el vector de posición relativa (dentro del error de redondeo).
julia> orientmatrix = initialize([[-1,1],[1,0],[-1,-1]]);
julia> robotoriented(orientmatrix, [5, 0])
true
julia> robotoriented(orientmatrix, [0, 5])
false
julia> robotoriented(orientmatrix, [-5, 0])
false
Como la matriz de orientación también registra la forma del robot, necesitamos saber dónde quedan esos puntos con respecto al origen después de mover el robot. Esto ayudará al robot a evitar colisiones con otros objetos cuando se desplaza.
Implementa la función bodylocation(orientation, position), que recibe una matriz de orientación y la posición actual del centro del robot.
Devuelve la matriz de orientación trasladada.
julia> orientmatrix = initialize([[-1,1],[1,0],[-1,-1]]);
julia> bodylocation(orientmatrix, [5, 3])
2×3 Matrix{Float64}:
4.29289 6.0 4.29289
3.70711 3.0 2.29289
Regístrate en Exercism para aprender y dominar Julia con 35 conceptos128 ejercicios y mentoría humana real, todo gratis.