# Algebra Lineal con Python

*Esta notebook fue creada originalmente como un blog post por [Raúl E. López Briega](http://relopezbriega.com.ar/) en [Mi blog sobre Python](http://relopezbriega.github.io). El contenido esta bajo la licencia BSD.*

## Introducción

Una de las herramientas matemáticas más utilizadas en [machine learning](http://es.wikipedia.org/wiki/Machine_learning) y [data mining](http://es.wikipedia.org/wiki/Miner%C3%ADa_de_datos) es el [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal); por tanto, si queremos incursionar en el fascinante mundo del aprendizaje automático y el análisis de datos es importante reforzar los conceptos que forman parte de sus cimientos.

El [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal) es una rama de las [matemáticas](http://es.wikipedia.org/wiki/Matem%C3%A1ticas) que es sumamente utilizada en el estudio de una gran variedad de ciencias, como ser, ingeniería, finanzas, investigación operativa, entre otras. Es una extensión del [álgebra](http://es.wikipedia.org/wiki/%C3%81lgebra) que aprendemos en la escuela secundaria, hacia un mayor número de dimensiones; en lugar de trabajar con incógnitas a nivel de escalares comenzamos a trabajar con matrices y [vectores](http://es.wikipedia.org/wiki/Vector).

El estudio del [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal) implica trabajar con varios objetos matemáticos, como ser:

* **Los Escalares**: Un *escalar* es un solo número, en contraste con la mayoría de los otros objetos estudiados en [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal), que son generalmente una colección de múltiples números.

* **Los [Vectores](http://es.wikipedia.org/wiki/Vector)**:Un *vector* es una serie de números. Los números tienen una orden preestablecido, y podemos identificar cada número individual por su índice en ese orden. Podemos pensar en los  *vectores* como la identificación de puntos en el espacio, con cada elemento que da la coordenada a lo largo de un eje diferente. Existen dos tipos de *vectores*, los *vectores de fila* y los *vectores de columna*. Podemos representarlos de la siguiente manera, dónde *f* es un vector de fila y *c* es un vector de columna:
$$f=\begin{bmatrix}0&1&-1\end{bmatrix} ;       c=\begin{bmatrix}0\\1\\-1\end{bmatrix}$$

* **Las Matrices**: Una *matriz* es un arreglo bidimensional de números (llamados entradas de la matriz) ordenados en filas (o renglones) y columnas, donde una fila es cada una de las líneas horizontales de la matriz y una columna es cada una de las líneas verticales. En una *matriz* cada elemento puede ser identificado utilizando dos índices, uno para la fila y otro para la columna en que se encuentra. Las podemos representar de la siguiente manera, *A* es una matriz de 3x2.
$$A=\begin{bmatrix}0 & 1& \\-1 & 2 \\ -2 & 3\end{bmatrix}$$

* **Los [Tensores](http://es.wikipedia.org/wiki/C%C3%A1lculo_tensorial)**:En algunos casos necesitaremos una matriz con más de dos ejes. En general, una serie de números dispuestos en una cuadrícula regular con un número variable de ejes es conocido como un *tensor*.

Sobre estos objetos podemos realizar las operaciones matemáticas básicas, como ser [adición](http://es.wikipedia.org/wiki/Adici%C3%B3n), [multiplicación](http://es.wikipedia.org/wiki/Multiplicaci%C3%B3n), [sustracción](http://es.wikipedia.org/wiki/Sustracci%C3%B3n) y división, es decir que vamos a poder sumar [vectores](http://es.wikipedia.org/wiki/Vector) con matrices, multiplicar escalares a [vectores](http://es.wikipedia.org/wiki/Vector) y demás.

(Tensores) T grado 0 = es un escalar; T grado 1 = vector; T grado 2 = matriz

## Librerías de Python para álgebra lineal

Los principales módulos que [Python](http://python.org/) nos ofrece para realizar operaciones de [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal) son los siguientes:

* **[Numpy](http://www.numpy.org/)**: El popular paquete matemático de [Python](http://python.org/), nos va a permitir crear *[vectores](http://es.wikipedia.org/wiki/Vector)*, *matrices* y *[tensores](http://es.wikipedia.org/wiki/C%C3%A1lculo_tensorial)* con suma facilidad.

* **[numpy.linalg](http://docs.scipy.org/doc/numpy/reference/routines.linalg.html)**: Este es un submodulo dentro de [Numpy](http://www.numpy.org/) con un gran número de funciones para resolver ecuaciones de [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal).

* **[scipy.linalg](http://docs.scipy.org/doc/scipy/reference/tutorial/linalg.html)**: Este submodulo del paquete científico [Scipy](http://docs.scipy.org/doc/scipy/reference/index.html) es muy similar al anterior, pero con algunas más funciones y optimaciones.

* **[Sympy](http://www.sympy.org/es/)**: Esta librería nos permite trabajar con matemática simbólica, convierte a [Python](http://python.org/) en un [sistema algebraico computacional](http://es.wikipedia.org/wiki/Sistema_algebraico_computacional). Nos va a permitir trabajar con ecuaciones y fórmulas simbólicamente, en lugar de numéricamente.

* **[CVXOPT](http://cvxopt.org/)**: Este módulo nos permite resolver problemas de optimizaciones de [programación lineal](http://es.wikipedia.org/wiki/Programaci%C3%B3n_lineal).

* **[PuLP](http://pythonhosted.org//PuLP/)**: Esta librería nos permite crear modelos de [programación lineal](http://es.wikipedia.org/wiki/Programaci%C3%B3n_lineal) en forma muy sencilla con [Python](http://python.org/).

## Operaciones básicas

### Vectores

Un [vector](http://es.wikipedia.org/wiki/Vector) de largo `n` es una secuencia (o *array*, o *tupla*) de `n` números. La solemos escribir como x=(x1,...,xn) o x=[x1,...,xn]

En [Python](http://python.org/), un [vector](http://es.wikipedia.org/wiki/Vector) puede ser representado con una simple *lista*, o con un *array* de [Numpy](http://www.numpy.org/); siendo preferible utilizar esta última opción.

```python
# Vector como lista de Python
v1 = [2, 4, 6]
v1
```

    [2, 4, 6]

```python
# Vectores con numpy
import numpy as np

v2 = np.ones(3) # vector de solo unos.
v2
```

    array([1., 1., 1.])

```python
v3 = np.array([1, 3, 5]) # pasando una lista a las arrays de numpy
v3
```

    array([1, 3, 5])

```python
np.arange(3,5,2)
```

    array([3])

```python
lind = np.linspace(1, 50, num=100) #esta función te devuelve los valores pedidos entre dos números el número de veces determinado)
lind
```

    array([ 1.        ,  1.49494949,  1.98989899,  2.48484848,  2.97979798,
            3.47474747,  3.96969697,  4.46464646,  4.95959596,  5.45454545,
            5.94949495,  6.44444444,  6.93939394,  7.43434343,  7.92929293,
            8.42424242,  8.91919192,  9.41414141,  9.90909091, 10.4040404 ,
           10.8989899 , 11.39393939, 11.88888889, 12.38383838, 12.87878788,
           13.37373737, 13.86868687, 14.36363636, 14.85858586, 15.35353535,
           15.84848485, 16.34343434, 16.83838384, 17.33333333, 17.82828283,
           18.32323232, 18.81818182, 19.31313131, 19.80808081, 20.3030303 ,
           20.7979798 , 21.29292929, 21.78787879, 22.28282828, 22.77777778,
           23.27272727, 23.76767677, 24.26262626, 24.75757576, 25.25252525,
           25.74747475, 26.24242424, 26.73737374, 27.23232323, 27.72727273,
           28.22222222, 28.71717172, 29.21212121, 29.70707071, 30.2020202 ,
           30.6969697 , 31.19191919, 31.68686869, 32.18181818, 32.67676768,
           33.17171717, 33.66666667, 34.16161616, 34.65656566, 35.15151515,
           35.64646465, 36.14141414, 36.63636364, 37.13131313, 37.62626263,
           38.12121212, 38.61616162, 39.11111111, 39.60606061, 40.1010101 ,
           40.5959596 , 41.09090909, 41.58585859, 42.08080808, 42.57575758,
           43.07070707, 43.56565657, 44.06060606, 44.55555556, 45.05050505,
           45.54545455, 46.04040404, 46.53535354, 47.03030303, 47.52525253,
           48.02020202, 48.51515152, 49.01010101, 49.50505051, 50.        ])

```python
v4 = np.arange(1, 8) # utilizando la funcion arange de numpy
v4
```

    array([1, 2, 3, 4, 5, 6, 7])

### Representación gráfica

Tradicionalmente, los [vectores](http://es.wikipedia.org/wiki/Vector) son representados visualmente como flechas que parten desde el origen hacia un punto.

Por ejemplo, si quisiéramos representar graficamente a los vectores v1=[2, 4], v2=[-3, 3] y v3=[-4, -3.5], podríamos hacerlo de la siguiente manera.

```python
import matplotlib.pyplot as plt
from warnings import filterwarnings

%matplotlib inline
filterwarnings('ignore') # Ignorar warnings
```

```python
def move_spines():
    """Crea la figura de pyplot y los ejes. Mueve las lineas de la izquierda y de abajo
    para que se intersecten con el origen. Elimina las lineas de la derecha y la de arriba.
    Devuelve los ejes."""
    fix, ax = plt.subplots()
    for spine in ["left", "bottom"]:
        ax.spines[spine].set_position("zero")

    for spine in ["right", "top"]:
        ax.spines[spine].set_color("none")

    return ax

def vect_fig():
    """Genera el grafico de los vectores en el plano"""
    ax = move_spines()

    ax.set_xlim(-5, 5)
    ax.set_ylim(-5, 5)
    ax.grid()
    vecs = [[2, 4], [-3, 3], [-4, -3.5]] # lista de vectores
    for v in vecs:
        ax.annotate(" ", xy=v, xytext=[0, 0],
                   arrowprops=dict(facecolor="blue",
                                  shrink=0,
                                  alpha=0.7,
                                  width=0.5))
        ax.text(1.1 * v[0], 1.1 * v[1], v)
```

```python
vect_fig() # crea el gráfico
```

### Operaciones con vectores

Las operaciones más comunes que utilizamos cuando trabajamos con [vectores](http://es.wikipedia.org/wiki/Vector) son la *suma*, la *resta* y la *multiplicación por escalares*.

Cuando *sumamos* dos [vectores](http://es.wikipedia.org/wiki/Vector), vamos sumando elemento por elemento de cada
[vector](http://es.wikipedia.org/wiki/Vector).

$$ \begin{split}x + y
=
\left[
\begin{array}{c}
    x_1 \\
    x_2 \\
    \vdots \\
    x_n
\end{array}
\right]
+
\left[
\begin{array}{c}
     y_1 \\
     y_2 \\
    \vdots \\
     y_n
\end{array}
\right]
:=
\left[
\begin{array}{c}
    x_1 + y_1 \\
    x_2 + y_2 \\
    \vdots \\
    x_n + y_n
\end{array}
\right]\end{split}$$

De forma similar funciona la operación de resta.

$$ \begin{split}x - y
=
\left[
\begin{array}{c}
    x_1 \\
    x_2 \\
    \vdots \\
    x_n
\end{array}
\right]
-
\left[
\begin{array}{c}
     y_1 \\
     y_2 \\
    \vdots \\
     y_n
\end{array}
\right]
:=
\left[
\begin{array}{c}
    x_1 - y_1 \\
    x_2 - y_2 \\
    \vdots \\
    x_n - y_n
\end{array}
\right]\end{split}$$

La *multiplicación por escalares* es una operación que toma a un número $\gamma$, y a un [vector](http://es.wikipedia.org/wiki/Vector) $x$ y produce un nuevo [vector](http://es.wikipedia.org/wiki/Vector) donde cada elemento del vector $x$ es multiplicado por el número $\gamma$.

$$\begin{split}\gamma x
:=
\left[
\begin{array}{c}
    \gamma x_1 \\
    \gamma x_2 \\
    \vdots \\
    \gamma x_n
\end{array}
\right]\end{split}$$

En [Python](http://python.org/) podríamos realizar estas operaciones en forma muy sencilla:

```python
# Ejemplo en Python
x = np.arange(1, 5)
y = np.array([2, 4, 6, 8])
x, y
```

    (array([1, 2, 3, 4]), array([2, 4, 6, 8]))

```python
# sumando dos vectores numpy
x + y
```

    array([ 3,  6,  9, 12])

```python
# restando dos vectores
x - y
```

    array([-1, -2, -3, -4])

```python
# multiplicando por un escalar
x * 2
```

    array([2, 4, 6, 8])

```python
y * 3
```

    array([ 6, 12, 18, 24])

```python
ax = move_spines()
ax.set_xlim(-6, 6)
ax.set_ylim(-6, 6)
ax.grid()

v = np.array([2, 3])
vect_fig(v, "blue")

v = v * 2
vect_fig(v, "red")
```

### Producto escalar o interior

El [producto escalar](https://es.wikipedia.org/wiki/Producto_escalar) de dos [vectores](http://es.wikipedia.org/wiki/Vector) se define como la suma de los productos de sus elementos, suele representarse matemáticamente como < x, y > o x'y, donde x e y son dos vectores. Tambien puede definirse como el módulo de ambos vectores por el coseno del ángulo que forma los dos vectores.

$$< x, y > := \sum_{i=1}^n x_i y_i = \| x \| \|y \| cos(\alpha)$$

Dos [vectores](http://es.wikipedia.org/wiki/Vector) son ortogonales o perpendiculares cuando forman ángulo recto entre sí. Si el producto escalar de dos vectores es cero, ambos vectores son ortogonales.

Adicionalmente, todo [producto escalar](https://es.wikipedia.org/wiki/Producto_escalar) induce una [norma](https://es.wikipedia.org/wiki/Norma_vectorial) sobre el espacio en el que está definido, de la siguiente manera:

$$\| x \| := \sqrt{< x, x>} := \left( \sum_{i=1}^n x_i^2 \right)^{1/2}$$

En [Python](http://python.org/) lo podemos calcular de la siguiente forma:

Multiplicar un vector por sí mismo da la norma de un vector
Dos vectores son ortonormales siempre que el ángulo que formen entre ellos sumen π/2 y que el módulo de cada vector mida 1; si es ortogonal solo sume π/2

Como la multiplicación de dos vectores unitarios depende del coseno (el ángulo que forman entre ellos), dos vectores paralelos tendrán un valor 0.
cos0=1 cosπ/2=0 sen0=0 senπ/2=1

Un vector tiene módulo, mide algo (ocupa un espacio), tiene una dirección y un sentido (esto lo da la flecha).

```python
# Calculando el producto escalar de los vectores x e y
np.dot(x, y)
```

    60

```python
# o lo que es lo mismo, que:
sum(x * y)
```

    60

```python
# Calculando la norma del vector X
np.linalg.norm(x)
```

    5.4772255750516612

```python
# otra forma de calcular la norma de x
np.sqrt(np.dot(x, x))
```

    5.4772255750516612

```python
# vectores ortogonales
v1 = np.array([3, 4])
v2 = np.array([4, -3])

np.dot(v1, v2)
```

    0

```python
# vectores ortonormales
v1 = np.array([1, 0])
v2 = np.array([0, 1])

np.dot(v1, v2)
```

    0

### Producto vectorial

El [producto vectorial](https://es.wikipedia.org/wiki/Producto_vectorial) de dos [vectores](http://es.wikipedia.org/wiki/Vector) se define como:

$$\vec{x} \times \vec{y} =  \| x \| \|y \| sin(\alpha)

\|

## Combinaciones lineales

Cuando trabajamos con [vectores](http://es.wikipedia.org/wiki/Vector), nos vamos a encontrar con dos operaciones fundamentales, la *suma* o adición; y la multiplicación por escalares. Cuando *sumamos* dos vectores $v$ y $w$, sumamos elemento por elemento, del siguiente modo:

$$v + w
=
\left[
\begin{array}{c}
    v_1 \\
    v_2 \\
    \vdots \\
    v_n
\end{array}
\right]
+
\left[
\begin{array}{c}
     w_1 \\
     w_2 \\
    \vdots \\
     w_n
\end{array}
\right] =
\left[
\begin{array}{c}
    v_1 + w_1 \\
    v_2 + w_2 \\
    \vdots \\
    v_n + w_n
\end{array}
\right]$$

Geométricamente lo podemos ver representado del siguiente modo:

```python
ax = move_spines()
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.grid()
vecs = [[2, 4], [2, -2]] # lista de vectores
for v in vecs:
    vect_fig(v, "blue")

v = np.array([2, 4]) + np.array([2, -2])
vect_fig(v, "red")

ax.plot([2, 4], [-2, 2], linestyle='--')
a =ax.plot([2, 4], [4, 2], linestyle='--' )
```

Cuando combinamos estas dos operaciones, formamos lo que se conoce en [Álgebra lineal](http://relopezbriega.github.io/tag/algebra.html) como [combinaciones lineales](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal). Es decir que una [combinación lineal](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal) va a ser una expresión matemática construida sobre un conjunto de [vectores](http://es.wikipedia.org/wiki/Vector), en el que cada vector es *multiplicado por un escalar* y los resultados son luego *sumados*. Matemáticamente lo podemos expresar de la siguiente forma:

$$w = \alpha_1 v_1 + \alpha_2 v_2 + \dots + \alpha_n v_n = \sum_{i=1}^n \alpha_i v_i
$$

en donde, $v_n$ son [vectores](http://es.wikipedia.org/wiki/Vector) y $\alpha_n$ son escalares.

### Matrices

Las matrices son una forma clara y sencilla de organizar los datos para su uso en operaciones lineales.

Una matriz `n × k` es una agrupación rectangular de números con n filas y k columnas; se representa de la siguiente forma:

$$\begin{split}A =
\left[
\begin{array}{cccc}
    a_{11} & a_{12} & \cdots & a_{1k} \\
    a_{21} & a_{22} & \cdots & a_{2k} \\
    \vdots & \vdots &  & \vdots \\
    a_{n1} & a_{n2} & \cdots & a_{nk}
\end{array}
\right]\end{split}$$

En la matriz A, el símbolo $a_{nk}$ representa el elemento  n-ésimo de la fila en la k-ésima columna. La matriz A también puede ser llamada un [vector](http://es.wikipedia.org/wiki/Vector) si cualquiera de n o k son iguales a 1. En el caso de n=1, A se llama un [vector](http://es.wikipedia.org/wiki/Vector) fila, mientras que en el caso de k=1 se denomina un [vector](http://es.wikipedia.org/wiki/Vector) columna.

Las matrices se utilizan para múltiples aplicaciones y sirven, en particular, para representar los coeficientes de los sistemas de ecuaciones lineales o para representar transformaciones lineales dada una base. Pueden sumarse, multiplicarse y descomponerse de varias formas.

### Operaciones con matrices

Al igual que con los [vectores](http://es.wikipedia.org/wiki/Vector), que no son más que un caso particular, las matrices se pueden *sumar*, *restar* y la *multiplicar por escalares*.

Multiplicacion por escalares:
$$\begin{split}\gamma A
\left[
\begin{array}{ccc}
    a_{11} &  \cdots & a_{1k} \\
    \vdots & \vdots  & \vdots \\
    a_{n1} &  \cdots & a_{nk} \\
\end{array}
\right]
:=
\left[
\begin{array}{ccc}
    \gamma a_{11} & \cdots & \gamma a_{1k} \\
    \vdots & \vdots & \vdots \\
    \gamma a_{n1} & \cdots & \gamma a_{nk} \\
\end{array}
\right]\end{split}$$

Suma de matrices: $$\begin{split}A + B =
\left[
\begin{array}{ccc}
    a_{11} & \cdots & a_{1k} \\
    \vdots & \vdots & \vdots \\
    a_{n1} & \cdots & a_{nk} \\
\end{array}
\right]
+
\left[
\begin{array}{ccc}
    b_{11} & \cdots & b_{1k} \\
    \vdots & \vdots & \vdots \\
    b_{n1} & \cdots & b_{nk} \\
\end{array}
\right]
:=
\left[
\begin{array}{ccc}
    a_{11} + b_{11} &  \cdots & a_{1k} + b_{1k} \\
    \vdots & \vdots & \vdots \\
    a_{n1} + b_{n1} &  \cdots & a_{nk} + b_{nk} \\
\end{array}
\right]\end{split}$$

Resta de matrices: $$\begin{split}A - B =
\left[
\begin{array}{ccc}
    a_{11} & \cdots & a_{1k} \\
    \vdots & \vdots & \vdots \\
    a_{n1} & \cdots & a_{nk} \\
\end{array}
\right]-
\left[
\begin{array}{ccc}
    b_{11} & \cdots & b_{1k} \\
    \vdots & \vdots & \vdots \\
    b_{n1} & \cdots & b_{nk} \\
\end{array}
\right]
:=
\left[
\begin{array}{ccc}
    a_{11} - b_{11} &  \cdots & a_{1k} - b_{1k} \\
    \vdots & \vdots & \vdots \\
    a_{n1} - b_{n1} &  \cdots & a_{nk} - b_{nk} \\
\end{array}
\right]\end{split}$$

Para los casos de suma y resta, hay que tener en cuenta que solo se pueden sumar o restar matrices que tengan las mismas dimensiones, es decir que si tengo una matriz A de dimensión 3x2 (3 filas y 2 columnas) solo voy a poder sumar o restar la matriz B si esta también tiene 3 filas y 2 columnas.

```python
# Ejemplo en Python
A = np.array([[1, 3, 2],
              [1, 0, 0],
              [1, 2, 2]])

B = np.array([[1, 0, 5],
              [7, 5, 0],
              [2, 1, 1]])
```

```python
# resta de matrices
A - B
```

    array([[ 0,  3, -3],
           [-6, -5,  0],
           [-1,  1,  1]])

```python
# multiplicando matrices por escalares
A * 2
```

    array([[2, 6, 4],
           [2, 0, 0],
           [2, 4, 4]])

```python
B * 3
```

    array([[ 3,  0, 15],
           [21, 15,  0],
           [ 6,  3,  3]])

```python
# ver la dimension de una matriz
A.shape
```

    (3, 3)

```python
# ver cantidad de elementos de una matriz
A.size
```

    9

Una matriz es un arreglo bidimensional de números ordenados en filas y columnas, donde una fila es cada una de las líneas horizontales de la matriz y una columna es cada una de las líneas verticales. En una matriz cada elemento puede ser identificado utilizando dos índices, uno para la fila y otro para la columna en que se encuentra. Las podemos representar de la siguiente manera:

$$A=\begin{bmatrix}a_{11} & a_{12} & \dots & a_{1n}\\a_{21} & a_{22} & \dots & a_{2n}
\\ \vdots & \vdots & \ddots & \vdots \\
a_{n1} & a_{n2} & \dots & a_{nn}\end{bmatrix}$$

Las matrices se utilizan para múltiples aplicaciones y sirven, en particular, para representar los coeficientes de los [sistemas de ecuaciones lineales](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) o para representar [combinaciones lineales](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal).

Supongamos que tenemos los siguientes 3 vectores:

$$x_1
=
\left[
\begin{array}{c}
     1 \\
    -1 \\
     0
\end{array}
\right]
\
x_2 =
\left[
\begin{array}{c}
     0 \\
     1 \\
    -1
\end{array}
\right] \
x_3 =
\left[
\begin{array}{c}
    0 \\
    0 \\
    1
\end{array}
\right]$$

su [combinación lineal](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal) en el espacio de 3 dimensiones va a ser igual a $\alpha_1 x_1 + \alpha_2 x_2 + \alpha_3 x_3$; lo que es lo mismo que decir:

$$\alpha_1
\left[
\begin{array}{c}
     1 \\
    -1 \\
     0
\end{array}
\right]
+ \alpha_2
\left[
\begin{array}{c}
     0 \\
     1 \\
    -1
\end{array}
\right] + \alpha_3
\left[
\begin{array}{c}
    0 \\
    0 \\
    1
\end{array}
\right] = \left[
\begin{array}{c}
    \alpha_1 \\
    \alpha_2 - \alpha_1 \\
    \alpha_3 - \alpha_2
\end{array}
\right]$$

Ahora esta [combinación lineal](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal) la podríamos reescribir en forma matricial. Los vectores $x_1, x_2$ y $x_3$, pasarían a formar las columnas de la matriz $A$ y los escalares $\alpha_1, \alpha_2$ y $\alpha_3$ pasarían a ser los componentes del [vector](http://es.wikipedia.org/wiki/Vector) $x$ del siguiente modo:

$$\begin{bmatrix}1 & 0 & 0\\-1 & 1 & 0
\\ 0 & -1 & 1\end{bmatrix}\begin{bmatrix} \alpha_1 \\ \alpha_2 \\ \alpha_3\end{bmatrix}=
\begin{bmatrix}\alpha_1 \\ \alpha_2 - \alpha_1 \\ \alpha_3 - \alpha_2 \end{bmatrix}$$

De esta forma la matriz $A$ multiplicada por el [vector](http://es.wikipedia.org/wiki/Vector) $x$, nos da como resultado la misma [combinación lineal](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal) $b$. De esta forma, arribamos a una de las ecuaciones más fundamentales del [Álgebra lineal](http://relopezbriega.github.io/tag/algebra.html):

$$Ax = b$$

Esta ecuación no solo nos va a servir para expresar [combinaciones lineales](https://es.wikipedia.org/wiki/Combinaci%C3%B3n_lineal), sino que también se vuelve de suma importancia a la hora de resolver [sistemas de ecuaciones lineales](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales), en dónde $b$ va a ser conocido y la incógnita pasa a ser $x$. Por ejemplo, supongamos que queremos resolver el siguiente [sistemas de ecuaciones](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) de 3 incógnitas:

$$ 2x_1 + 3x_2 + 5x_3 = 52 \\
3x_1 + 6x_2 + 2x_3 = 61 \\
8x_1 + 3x_2 + 6x_3 = 75
$$

Podemos ayudarnos de [SymPy](http://www.sympy.org/es/) para expresar a la matriz $A$ y $b$ para luego arribar a la solución del [vector](http://es.wikipedia.org/wiki/Vector) $x$.

#### Multiplicacion o Producto de matrices

La regla para la [multiplicación de matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices) generaliza la idea del [producto interior](https://es.wikipedia.org/wiki/Producto_escalar) que vimos con los [vectores](http://es.wikipedia.org/wiki/Vector); y esta diseñada para facilitar las operaciones lineales básicas.
Cuando [multiplicamos matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices), el número de columnas de la primera matriz debe ser igual al número de filas de la segunda matriz; y el resultado de esta multiplicación va a tener el mismo número de filas que la primer matriz y el número de la columnas de la segunda matriz. Es decir, que si yo tengo una matriz A de dimensión 3x4 y la multiplico por una matriz B de dimensión 4x2, el resultado va a ser una matriz C de dimensión 3x2.

Algo a tener en cuenta a la hora de [multiplicar matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices) es que la propiedad [connmutativa](https://es.wikipedia.org/wiki/Conmutatividad) no se cumple. AxB no es lo mismo que BxA.

Veamos los ejemplos en [Python](http://python.org/).

```python
# Ejemplo multiplicación de matrices
A = np.arange(1, 13).reshape(3, 4) #matriz de dimension 3x4
A
```

    array([[ 1,  2,  3,  4],
           [ 5,  6,  7,  8],
           [ 9, 10, 11, 12]])

```python
B = np.arange(8).reshape(4,2) #matriz de dimension 4x2
B
```

    array([[0, 1],
           [2, 3],
           [4, 5],
           [6, 7]])

```python
# Multiplicando A x B
A.dot(B) #resulta en una matriz de dimension 3x2
```

    array([[ 40,  50],
           [ 88, 114],
           [136, 178]])

```python
# Multiplicando B x A
B.dot(A)
```

Este ultimo ejemplo vemos que la propiedad conmutativa no se cumple, es más, [Python](http://python.org/) nos arroja un error, ya que el número de columnas de B no coincide con el número de filas de A, por lo que ni siquiera se puede realizar la multiplicación de B x A.

Para una explicación más detallada del proceso de [multiplicación de matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices), pueden consultar el siguiente [tutorial](http://www.mathsisfun.com/algebra/matrix-multiplying.html).

```python
#Las matrices no son conmutativas :(
a = np.array([1,2,-2,-5]).reshape(2,2)
b = np.array([1,-3, 2,1]).reshape(2,2)
print(a.dot(b))
print(b.dot(a))
print(a.dot(b) - b.dot(a)) #para comprobar la propiedad conmutativa
```

    [[  5  -1]
     [-12   1]]
    [[ 7 17]
     [ 0 -1]]
    [[ -2 -18]
     [-12   2]]

```python
import sympy
```

```python
# Resolviendo sistema de ecuaciones con SymPy
A = sympy.Matrix(( (2, 3, 5), (3, 6, 2), (8, 3, 6) ))
A
```

$\displaystyle \left[\begin{matrix}2 & 3 & 5\\3 & 6 & 2\\8 & 3 & 6\end{matrix}\right]$

```python
b = sympy.Matrix(3,1,(52,61,75))
b
```

$\displaystyle \left[\begin{matrix}52\\61\\75\end{matrix}\right]$

```python
help(A.LUsolve)
```

    Help on method LUsolve in module sympy.matrices.matrices:

    LUsolve(rhs, iszerofunc=<function _iszero at 0x11cb45d40>) method of sympy.matrices.dense.MutableDenseMatrix instance
        Solve the linear system ``Ax = rhs`` for ``x`` where ``A = self``.

        This is for symbolic matrices, for real or complex ones use
        mpmath.lu_solve or mpmath.qr_solve.

        See Also
        ========

        lower_triangular_solve
        upper_triangular_solve
        gauss_jordan_solve
        cholesky_solve
        diagonal_solve
        LDLsolve
        QRsolve
        pinv_solve
        LUdecomposition

```python
# Resolviendo Ax = b
x = A.LUsolve(b)
x
```

$\displaystyle \left[\begin{matrix}3\\7\\5\end{matrix}\right]$

```python
# Comprobando la solución
A*x
```

$\displaystyle \left[\begin{matrix}52\\61\\75\end{matrix}\right]$

#### La matriz identidad,  la matriz inversa,  la matrix transpuesta y el determinante

La [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) es el elemento neutro en la [multiplicación de matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices), es el equivalente al número 1. Cualquier matriz multiplicada por la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) nos da como resultado la misma matriz. La [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) es una [matriz cuadrada](https://es.wikipedia.org/wiki/Matriz_cuadrada) (tiene siempre el mismo número de filas que de columnas); y su diagonal principal se compone de todos elementos 1 y el resto de los elementos se completan con 0. Suele representase con la letra I

Por ejemplo la matriz identidad de 3x3 sería la siguiente:

$$I=\begin{bmatrix}1 & 0 & 0 & \\0 & 1 & 0\\ 0 & 0 & 1\end{bmatrix}$$

Ahora que conocemos el concepto de la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad), podemos llegar al concepto de la [matriz inversa](https://es.wikipedia.org/wiki/Matriz_invertible). Si tenemos una matriz A, la [matriz inversa](https://es.wikipedia.org/wiki/Matriz_invertible) de A, que se representa como $A^{-1}$ es aquella [matriz cuadrada](https://es.wikipedia.org/wiki/Matriz_cuadrada) que hace que la multiplicación $A$x$A^{-1}$ sea igual a la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) I. Es decir que es la matriz recíproca de A.

$$A × A^{-1} = A^{-1} × A = I$$

Tener en cuenta que esta [matriz inversa](https://es.wikipedia.org/wiki/Matriz_invertible) en muchos casos puede no existir.En este caso se dice que la matriz es singular o degenerada. Una matriz es singular si y solo si su determinante es nulo.

El determinante es un número especial que puede calcularse sobre las [matrices cuadradas](https://es.wikipedia.org/wiki/Matriz_cuadrada). Se calcula como la suma de los productos de las diagonales de la matriz en una dirección menos la suma de los productos de las diagonales en la otra dirección. Se represente con el símbolo |A|.

$$A=\begin{bmatrix}a_{11} & a_{12} & a_{13} & \\a_{21} & a_{22} & a_{23} & \\ a_{31} & a_{32} & a_{33} & \end{bmatrix}$$

$$|A| =
     (a_{11} a_{22} a_{33}
   + a_{12} a_{23} a_{31}
   + a_{13} a_{21} a_{32} )
   - (a_{31} a_{22} a_{13}
   + a_{32} a_{23} a_{11}
   + a_{33} a_{21} a_{12})
 $$

Por último, la [matriz transpuesta](http://es.wikipedia.org/wiki/Matriz_transpuesta) es aquella en que las filas se transforman en columnas y las columnas en filas. Se representa con el símbolo $A^\intercal$

$$\begin{bmatrix}a & b & \\c & d & \\ e & f & \end{bmatrix}^T:=\begin{bmatrix}a & c & e &\\b & d & f & \end{bmatrix}$$

Algunas de las propiedades de las [matrices transpuestas](http://es.wikipedia.org/wiki/Matriz_transpuesta) son:

a. $(A^T)^T = A$

b. $(A + B)^T = A^T + B^T$

c. $k(A)^T = k(A^T)$

d. $(AB)^T = B^T A^T$

e. $(A^r)^T = (A^T)^r$ para todos los $r$ no negativos.

f. Si $A$ es una [matriz cuadrada](https://es.wikipedia.org/wiki/Matriz_cuadrada), entonces $A + A^T$ es una [matriz simétrica](https://es.wikipedia.org/wiki/Matriz_sim%C3%A9trica).

g. Para cualquier matriz $A$, $A A^T$ y $A^T A$ son [matrices simétricas](https://es.wikipedia.org/wiki/Matriz_sim%C3%A9trica).

Veamos algunos ejemplos en [Python](http://python.org/

```python
# Creando una matriz identidad de 2x2
I = np.eye(2)
I
```

    array([[ 1.,  0.],
           [ 0.,  1.]])

```python
# Multiplicar una matriz por la identidad nos da la misma matriz
A = np.array([[4, 7],
              [2, 6]])
A
```

    array([[4, 7],
           [2, 6]])

```python
A.dot(I) # AxI = A
```

    array([[ 4.,  7.],
           [ 2.,  6.]])

```python
# Calculando el determinante de la matriz A
np.linalg.det(A)
```

    10.000000000000002

```python
# Calculando la inversa de A.
A_inv = np.linalg.inv(A)
A_inv
```

    array([[ 0.6, -0.7],
           [-0.2,  0.4]])

```python
# A x A_inv nos da como resultado I.
A.dot(A_inv)
```

    array([[ 1.,  0.],
           [ 0.,  1.]])

```python
# Trasponiendo una matriz
A = np.arange(6).reshape(3, 2)
A
```

    array([[0, 1],
           [2, 3],
           [4, 5]])

```python
A = np.array([[2,3,5], [3,6,2], [8,3,6]])
b = np.array([[52, 61,75]]).reshape(3,1)
print(A)
print(b)
np.linalg.det(A) !=0
np.linalg.inv(A).dot(b)
```

    [[2 3 5]
     [3 6 2]
     [8 3 6]]
    [[52]
     [61]
     [75]]

    array([[3.],
           [7.],
           [5.]])

```python
np.transpose(A)
```

    array([[2, 3, 8],
           [3, 6, 3],
           [5, 2, 6]])

```python
# Matriz transpuesta
A = sympy.Matrix( [[ 2,-3,-8, 7],
                   [-2,-1, 2,-7],
                   [ 1, 0,-3, 6]] )
A
```

$$\left[\begin{matrix}2 & -3 & -8 & 7\\-2 & -1 & 2 & -7\\1 & 0 & -3 & 6\end{matrix}\right]$$

```python
A.transpose()
A.T #también transpone
```

$$\left[\begin{matrix}2 & -2 & 1\\-3 & -1 & 0\\-8 & 2 & -3\\7 & -7 & 6\end{matrix}\right]$$

```python
# transpuesta de transpuesta vuelve a A.
A.transpose().transpose()
```

$$\left[\begin{matrix}2 & -3 & -8 & 7\\-2 & -1 & 2 & -7\\1 & 0 & -3 & 6\end{matrix}\right]$$

```python
# creando matriz simetrica
As = A*A.transpose()
As
```

$$\left[\begin{matrix}126 & -66 & 68\\-66 & 58 & -50\\68 & -50 & 46\end{matrix}\right]$$

```python
# comprobando simetria.
As.transpose()

```

$$\left[\begin{matrix}126 & -66 & 68\\-66 & 58 & -50\\68 & -50 & 46\end{matrix}\right]$$

La [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible) es muy importante, ya que esta relacionada con la ecuación $Ax = b$. Si tenemos una [matriz cuadrada](https://es.wikipedia.org/wiki/Matriz_cuadrada) $A$ de $n \times n$, entonces la [matriz inversa](https://es.wikipedia.org/wiki/Matriz_invertible) de $A$ es una matriz $A'$ o $A^{-1}$ de $n \times n$ que hace que la multiplicación $A A^{-1}$ sea igual a la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) $I$. Es decir que es la matriz recíproca de $A$.

$A A^{-1} = I$ o $A^{-1} A = I$

En caso de que estas condiciones se cumplan, decimos que la [matriz es invertible](https://es.wikipedia.org/wiki/Matriz_invertible).

Que una matriz sea [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) tiene importantes implicaciones, como ser:

a. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible), entonces su [matriz inversa](https://es.wikipedia.org/wiki/Matriz_invertible) es única.

b. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible) de $n \times n$, entonces el [sistemas de ecuaciones lineales](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) dado por $Ax = b$ tiene una única solución $x = A^{-1}b$ para cualquier $b$ en $\mathbb{R}^n$.

c. Una matriz va a ser [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) si y solo si su determinante es distinto de cero. En el caso de que el determinante sea cero se dice que la matriz es singular.

d. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible), entonces el [sistema](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) $Ax = 0$ solo tiene una solución *trivial*. Es decir, en las que todas las incógnitas son ceros.

e. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible), entonces su [forma escalonada](https://es.wikipedia.org/wiki/Matriz_escalonada) va a ser igual a la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad).

f. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible), entonces $A^{-1}$ es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) y:

$$(A^{-1})^{-1} = A$$

g. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible) y $\alpha$ es un  escalar distinto de cero, entonces $\alpha A$ es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) y:

$$(\alpha A)^{-1} = \frac{1}{\alpha}A^{-1}$$.

h. Si $A$ y $B$ son [matrices invertibles](https://es.wikipedia.org/wiki/Matriz_invertible) del mismo tamaño, entonces $AB$ es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) y:

$$(AB)^{-1} = B^{-1} A^{-1}$$.

i. Si $A$ es una [matriz invertible](https://es.wikipedia.org/wiki/Matriz_invertible), entonces $A^T$ es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) y:

$$(A^T)^{-1} = (A^{-1})^T$$.

Con [SymPy](http://www.sympy.org/es/) podemos trabajar con las [matrices invertibles](https://es.wikipedia.org/wiki/Matriz_invertible) del siguiente modo:

```python
# Matriz invertible
A = sympy.Matrix( [[1,2],
                   [3,9]] )
A
```

$$\left[\begin{matrix}1 & 2\\3 & 9\end{matrix}\right]$$

```python
A_inv = A.inv()
A_inv
```

$$\left[\begin{matrix}3 & - \frac{2}{3}\\-1 & \frac{1}{3}\end{matrix}\right]$$

```python
# A * A_inv = I
A*A_inv
```

$$\left[\begin{matrix}1 & 0\\0 & 1\end{matrix}\right]$$

```python
# forma escalonada igual a indentidad.
A.rref()
```

$$\left ( \left[\begin{matrix}1 & 0\\0 & 1\end{matrix}\right], \quad \left [ 0, \quad 1\right ]\right )$$

```python
# la inversa de A_inv es A
A_inv.inv()
```

$$\left[\begin{matrix}1 & 2\\3 & 9\end{matrix}\right]$$

### Sistemas de ecuaciones lineales

Una de las principales aplicaciones del [Álgebra lineal](http://es.wikipedia.org/wiki/%C3%81lgebra_lineal) consiste en resolver problemas de sistemas de ecuaciones lineales.

Una [ecuación lineal](https://es.wikipedia.org/wiki/Ecuaci%C3%B3n_de_primer_grado) es una ecuación que solo involucra sumas y restas de una variable o mas variables a la primera potencia. Es la ecuación de la línea recta.Cuando nuestro problema esta representado por más de una [ecuación lineal](https://es.wikipedia.org/wiki/Ecuaci%C3%B3n_de_primer_grado), hablamos de un [sistema de ecuaciones lineales](http://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales). Por ejemplo, podríamos tener un sistema de dos ecuaciones con dos incógnitas como el siguiente:

$$ x - 2y = 1$$
$$3x + 2y = 11$$

La idea es encontrar el valor de $x$ e $y$ que resuelva ambas ecuaciones. Una forma en que podemos hacer esto, puede ser representando graficamente ambas rectas y buscar los puntos en que las rectas se cruzan.

En [Python](http://python.org/) esto se puede hacer en forma muy sencilla con la ayuda de [matplotlib](http://matplotlib.org/).

```python
# graficando el sistema de ecuaciones.
x_vals = np.linspace(0, 5, 50) # crea 50 valores entre 0 y 5
plt.plot(x_vals, (1 - x_vals)/-2) # grafica x - 2y = 1
plt.plot(x_vals, (11 - (3*x_vals))/2) # grafica 3x + 2y = 11
plt.axis(ymin = 0)
```

Luego de haber graficado las funciones, podemos ver que ambas rectas se cruzan en el punto (3, 1), es decir que la solución de nuestro sistema sería $x=3$ e $y=1$. En este caso, al tratarse de un sistema simple y con solo dos incógnitas, la solución gráfica puede ser de utilidad, pero para sistemas más complicados se necesita una solución numérica, es aquí donde entran a jugar las matrices.

Ese mismo sistema se podría representar como una ecuación matricial de la siguiente forma:

$$\begin{bmatrix}1 & -2 & \\3 & 2 & \end{bmatrix} \begin{bmatrix}x & \\y & \end{bmatrix} = \begin{bmatrix}1 & \\11 & \end{bmatrix}$$

Lo que es lo mismo que decir que la matriz A por la matriz $x$ nos da como resultado el [vector](http://es.wikipedia.org/wiki/Vector) b.

$$ Ax = b$$

En este caso, ya sabemos el resultado de $x$, por lo que podemos comprobar que nuestra solución es correcta realizando la [multiplicación de matrices](https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices).

```python
# Comprobando la solucion con la multiplicación de matrices.
A = np.array([[1., -2.],
              [3., 2.]])
x = np.array([[3.],[1.]])

A.dot(x)
```

    array([[  1.],
           [ 11.]])

Para resolver en forma numérica los [sistema de ecuaciones](http://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales), existen varios métodos:

* **El método de sustitución**: El cual consiste en despejar en una de las ecuaciones cualquier incógnita, preferiblemente la que tenga menor coeficiente y a continuación sustituirla en otra ecuación por su valor.

* **El método de igualacion**: El cual se puede entender como un caso particular del método de sustitución en el que se despeja la misma incógnita en dos ecuaciones y a continuación se igualan entre sí la parte derecha de ambas ecuaciones.

* **El método de reduccion**: El procedimiento de este método consiste en transformar una de las ecuaciones (generalmente, mediante productos), de manera que obtengamos dos ecuaciones en la que una misma incógnita aparezca con el mismo coeficiente y distinto signo. A continuación, se suman ambas ecuaciones produciéndose así la reducción o cancelación de dicha incógnita, obteniendo una ecuación con una sola incógnita, donde el método de resolución es simple.

* **El método gráfico**: Que consiste en construir el gráfica de cada una de las ecuaciones del sistema. Este método (manualmente aplicado) solo resulta eficiente en el plano cartesiano (solo dos incógnitas).

* **El método de Gauss**: El método de eliminación de Gauss o simplemente método de Gauss consiste en convertir un sistema lineal de n ecuaciones con n incógnitas, en uno escalonado, en el que la primera ecuación tiene n incógnitas, la segunda ecuación tiene n - 1 incógnitas, ..., hasta la última ecuación, que tiene 1 incógnita. De esta forma, será fácil partir de la última ecuación e ir subiendo para calcular el valor de las demás incógnitas.

* **El método de Eliminación de Gauss-Jordan**: El cual es una variante del método anterior, y consistente en triangular la matriz aumentada del sistema mediante transformaciones elementales, hasta obtener ecuaciones de una sola incógnita.

* **El método de Cramer**: El cual consiste en aplicar la [regla de Cramer](http://es.wikipedia.org/wiki/Regla_de_Cramer) para resolver el sistema. Este método solo se puede aplicar cuando la matriz de coeficientes del sistema es cuadrada y de determinante no nulo.

La idea no es explicar cada uno de estos métodos, sino saber que existen y que [Python](http://python.org/) nos hacer la vida mucho más fácil, ya que para resolver un [sistema de ecuaciones](http://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) simplemente debemos llamar a la función `solve()`.

Por ejemplo, para resolver este sistema de 3 ecuaciones y 3 incógnitas.

$$ x + 2y + 3z = 6$$
$$ 2x + 5y + 2z = 4$$
$$ 6x - 3y + z = 2$$

Primero armamos la matriz A de coeficientes y la matriz b de resultados y luego utilizamos `solve()` para resolverla.

```python
# Creando matriz de coeficientes
A = np.array([[1, 2, 3],
              [2, 5, 2],
              [6, -3, 1]])
A
```

    array([[ 1,  2,  3],
           [ 2,  5,  2],
           [ 6, -3,  1]])

```python
# Creando matriz de resultados
b = np.array([6, 4, 2])
b
```

    array([6, 4, 2])

```python
# Resolviendo sistema de ecuaciones
x = np.linalg.solve(A, b)
x
```

    array([0., 0., 2.])

```python
# Comprobando la solucion
A.dot(x) == b
```

    array([ True,  True,  True])

## Independencia lineal

La [independencia lineal](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal) es un concepto aparentemente simple con consecuencias que se extienden profundamente en muchos aspectos del análisis. Si deseamos entender cuando una matriz puede ser [invertible](https://es.wikipedia.org/wiki/Matriz_invertible), o cuando un [sistema de ecuaciones lineales](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) tiene una única solución, o cuando una estimación por [mínimos cuadrados](https://es.wikipedia.org/wiki/M%C3%ADnimos_cuadrados) se define de forma única, la idea fundamental más importante es la de [independencia lineal](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal) de [vectores](http://es.wikipedia.org/wiki/Vector).

Dado un conjunto finito de [vectores](http://es.wikipedia.org/wiki/Vector) $x_1, x_2, \dots, x_n$ se dice que los mismos son *[linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*, si y solo si, los únicos escalares $\alpha_1, \alpha_2, \dots, \alpha_n$ que satisfacen la ecuación:

$$\alpha_1 x_1 + \alpha_2 x_2 + \dots + \alpha_n x_n = 0$$

son todos ceros, $\alpha_1 = \alpha_2 = \dots = \alpha_n = 0$.

En caso de que esto no se cumpla, es decir, que existe una solución a la ecuación de arriba en que no todos los escalares son ceros, a esta solución se la llama *no trivial* y se dice que los [vectores](http://es.wikipedia.org/wiki/Vector) son *[linealmente dependientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*.

Para ilustrar la definición y que quede más clara, veamos algunos ejemplos. Supongamos que queremos determinar si los siguientes [vectores](http://es.wikipedia.org/wiki/Vector) son *[linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*:

$$\begin{split}x_1
=
\left[
\begin{array}{c}
    1.2 \\
1.1 \\
\end{array}
\right] \  \  \ x_2 =
\left[
\begin{array}{c}
    -2.2 \\
1.4 \\
\end{array}
\right]\end{split}$$

Para lograr esto, deberíamos resolver el siguiente [sistema de ecuaciones](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) y verificar si la única solución es aquella en que los escalares sean ceros.

$$\begin{split}\alpha_1
\left[
\begin{array}{c}
    1.2 \\
1.1 \\
\end{array}
\right] + \alpha_2
\left[
\begin{array}{c}
    -2.2 \\
1.4 \\
\end{array}
\right]\end{split} = 0
$$

```python
# Resolviendo el sistema de ecuaciones.
A = np.array([[1.2, -2.2],
              [1.1, 1.4]])

b = np.array([0., 0.])

x = np.linalg.solve(A, b)
x
```

    array([ 0.,  0.])

```python
# <!-- collapse=True -->
# Solución gráfica.
x_vals = np.linspace(-5, 5, 50) # crea 50 valores entre 0 y 5

ax = move_spines()
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.grid()

ax.plot(x_vals, (1.2 * x_vals) / -2.2) # grafica 1.2x_1 - 2.2x_2 = 0
a = ax.plot(x_vals, (1.1 * x_vals) / 1.4) # grafica 1.1x + 1.4x_2 = 0
```

Como podemos ver, tanto por la solución numérica como por la solución gráfica, estos vectores son *[linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*, ya que la única solución a la ecuación $\alpha_1 x_1 + \alpha_2 x_2 + \dots + \alpha_n x_n = 0$, es aquella en que los escalares son cero.

Determinemos ahora si por ejemplo, los siguientes [vectores](http://es.wikipedia.org/wiki/Vector) en $\mathbb{R}^4$ son *[linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*: $\{(3, 2, 2, 3), (3, 2, 1, 2), (3, 2, 0, 1)\}$. Aquí, ahora deberíamos resolver la siguiente ecuación:

$$\alpha_1 (3, 2, 2, 3) +\alpha_2 (3, 2, 1, 2) + \alpha_3 (3, 2, 0, 1) = (0, 0, 0, 0)$$

Para resolver este sistema de ecuaciones que no es cuadrado (tiene 4 ecuaciones y solo 3 incógnitas); podemos utilizar [SymPy](http://www.sympy.org/es/).

```python
# Sympy para resolver el sistema de ecuaciones lineales
a1, a2, a3 = sympy.symbols('a1, a2, a3')
A = sympy.Matrix(( (3, 3, 3, 0), (2, 2, 2, 0), (2, 1, 0, 0), (3, 2, 1, 0) ))
A
```

$$\left[\begin{matrix}3 & 3 & 3 & 0\\2 & 2 & 2 & 0\\2 & 1 & 0 & 0\\3 & 2 & 1 & 0\end{matrix}\right]$$

```python
sympy.solve_linear_system(A, a1, a2, a3)
```

$$\left \{ a_{1} : a_{3}, \quad a_{2} : - 2 a_{3}\right \}$$

Como vemos, esta solución es *no trivial*, ya que por ejemplo existe la solución $\alpha_1 = 1, \ \alpha_2 = -2 , \  \alpha_3 = 1$ en la que los escalares no son ceros. Por lo tanto este sistema es *[linealmente dependiente](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*.

Por último, podríamos considerar si los siguientes [polinomios](https://es.wikipedia.org/wiki/Polinomio) son *[linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal)*: $1 -2x -x^2$, $1 + x$, $1 + x + 2x^2$. En este caso, deberíamos resolver la siguiente ecuación:

$$\alpha_1 (1 − 2x − x^2) + \alpha_2 (1 + x) + \alpha_3 (1 + x + 2x^2) = 0$$

y esta ecuación es equivalente a la siguiente:

$$(\alpha_1 + \alpha_2 + \alpha_3 ) + (−2 \alpha_1 + \alpha_2 + \alpha_3 )x + (−\alpha_1 + 2 \alpha_2 )x^2 = 0$$

Por lo tanto, podemos armar el siguiente [sistema de ecuaciones](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales):

$$\alpha_1 + \alpha_2 + \alpha_3 = 0, \\
-2 \alpha_1 + \alpha_2 + \alpha_3 = 0, \\
-\alpha_1 + 2 \alpha_2  = 0.
$$

El cual podemos nuevamente resolver con la ayuda de [SymPy](http://www.sympy.org/es/).

```python
A = sympy.Matrix(( (1, 1, 1, 0), (-2, 1, 1, 0), (-1, 2, 0, 0) ))
A
```

$$\left[\begin{matrix}1 & 1 & 1 & 0\\-2 & 1 & 1 & 0\\-1 & 2 & 0 & 0\end{matrix}\right]$$

```python
sympy.solve_linear_system(A, a1, a2, a3)
```

$$\left \{ a_{1} : 0, \quad a_{2} : 0, \quad a_{3} : 0\right \}$$

## Rango

Otro concepto que también esta ligado a la [independencia lineal](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal) es el de rango. Los números de columnas $m$ y filas $n$ pueden darnos el tamaño de una matriz, pero esto no necesariamente representa el verdadero tamaño del [sistema lineal](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales), ya que por ejemplo si existen dos filas iguales en una matriz $A$, la segunda fila desaparecía en el proceso de [eliminación](https://es.wikipedia.org/wiki/Eliminaci%C3%B3n_de_Gauss-Jordan). El verdadero tamaño de $A$ va a estar dado por su rango. El rango de una matriz es el número máximo de columnas (filas respectivamente) que son [linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal). Por ejemplo si tenemos la siguiente matriz de 3 x 4:

$$A = \begin{bmatrix}1 & 1 & 2 & 4\\1 & 2 & 2 & 5
\\ 1 & 3 & 2 & 6\end{bmatrix}$$

Podemos ver que la tercer columna $(2, 2, 2)$ es un múltiplo de la primera y que la cuarta columna $(4, 5, 6)$ es la suma de las primeras 3 columnas. Por tanto el rango de $A$ va a ser igual a 2; ya que la tercer y cuarta columna pueden ser eliminadas.

Obviamente, el rango también lo podemos calcular con la ayuda de [Python](http://python.org/).

```python
# Calculando el rango con SymPy
A = sympy.Matrix([[1, 1, 2, 4],
                  [1, 2, 2, 5],
                  [1, 3, 2, 6]])
A
```

$$\left[\begin{matrix}1 & 1 & 2 & 4\\1 & 2 & 2 & 5\\1 & 3 & 2 & 6\end{matrix}\right]$$

```python
# Rango con SymPy
A.rank()
```

$$2$$

```python
# Rango con numpy
A = np.array([[1, 1, 2, 4],
              [1, 2, 2, 5],
              [1, 3, 2, 6]])
np.linalg.matrix_rank(A)
```

    2

Una útil aplicación de calcular el rango de una matriz es la de determinar el número de soluciones al [sistema de ecuaciones lineales](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales), de acuerdo al enunciado del [Teorema de Rouché–Frobenius](https://es.wikipedia.org/wiki/Teorema_de_Rouch%C3%A9%E2%80%93Frobenius). El sistema tiene por lo menos una solución si el rango de la matriz de coeficientes equivale al rango de la [matriz aumentada](https://es.wikipedia.org/wiki/Matriz_aumentada). En ese caso, ésta tiene exactamente una solución si el rango equivale al número de incógnitas.

## La norma y la Ortogonalidad

Si quisiéramos saber cual es el *largo* del un [vector](http://es.wikipedia.org/wiki/Vector), lo único que necesitamos es el famoso [teorema de Pitágoras](https://es.wikipedia.org/wiki/Teorema_de_Pit%C3%A1goras). En el plano $\mathbb{R}^2$, el *largo* de un [vector](http://es.wikipedia.org/wiki/Vector) $v=\begin{bmatrix}a \\ b \end{bmatrix}$ va a ser igual a la distancia desde el origen $(0, 0)$ hasta el punto $(a, b)$. Esta distancia puede ser fácilmente calculada gracias al [teorema de Pitágoras](https://es.wikipedia.org/wiki/Teorema_de_Pit%C3%A1goras) y va ser igual a $\sqrt{a^2 + b^2}$, como se puede ver en la siguiente figura:

```python
# <!-- collapse=True -->
# Calculando largo de un vector
# forma un triángulo rectángulo

ax = move_spines()
ax.set_xlim(-6, 6)
ax.set_ylim(-6, 6)
ax.grid()
v = np.array([4, 6])

vect_fig(v, "blue")

a = ax.vlines(x=v[0], ymin=0, ymax = 6, linestyle='--', color='g')
```

En esta definición podemos observar que $a^2 + b^2 = v \cdot v$, por lo que ya estamos en condiciones de poder definir lo que en [Álgebra lineal](http://relopezbriega.github.io/tag/algebra.html) se conoce como [norma](https://es.wikipedia.org/wiki/Norma_vectorial).

El *largo* o [norma](https://es.wikipedia.org/wiki/Norma_vectorial) de un [vector](http://es.wikipedia.org/wiki/Vector) $v = \begin{bmatrix} v_1 \\ v_2 \\ \vdots \\ v_n \end{bmatrix}$, en $\mathbb{R}^n$ va a ser igual a un número no negativo $||v||$ definido por:

$$||v|| = \sqrt{v \cdot v} = \sqrt{v_1^2 + v_2^2 + \dots + v_n^2}$$

Es decir que la [norma](https://es.wikipedia.org/wiki/Norma_vectorial) de un [vector](http://es.wikipedia.org/wiki/Vector) va a ser igual a la raíz cuadrada de la suma de los cuadrados de sus componentes.

### Ortogonalidad

El concepto de [perpendicularidad](https://es.wikipedia.org/wiki/Perpendicularidad) es fundamental en [geometría](https://es.wikipedia.org/wiki/Geometr%C3%ADa). Este concepto llevado a los [vectores](http://es.wikipedia.org/wiki/Vector) en $\mathbb{R}^n$ se llama ortogonalidad.

Dos [vectores](http://es.wikipedia.org/wiki/Vector) $v$ y $w$ en $\mathbb{R}^n$ van a ser ortogonales el uno al otro si su [producto interior](https://es.wikipedia.org/wiki/Producto_escalar) es igual a cero. Es decir, $v \cdot w = 0$.

Geométricamente lo podemos ver de la siguiente manera:

```python
# <!-- collapse=True -->
# Vectores ortogonales

ax = move_spines()
ax.set_xlim(-6, 6)
ax.set_ylim(-6, 6)
ax.grid()
vecs = [np.array([4, 6]), np.array([-3, 2])]

for v in vecs:
    vect_fig(v, "blue")

a = ax.plot([-3, 4], [2, 6], linestyle='--', color='g')
```

```python
# comprobando su producto interior.
v = np.array([4, 6])
w = np.array([-3, 2])
v.dot(w)
```

    0

Un [conjunto](http://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/) de [vectores](http://es.wikipedia.org/wiki/Vector) en $\mathbb{R}^n$ va a ser ortogonal si todo los pares de los distintos [vectores](http://es.wikipedia.org/wiki/Vector) en el [conjunto](http://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/) son ortogonales entre sí. O sea:

$v_i \cdot v_j = 0$ para todo $i, j = 1, 2, \dots, k$ y donde $i \ne j$.

Por ejemplo, si tenemos el siguiente [conjunto](http://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/) de [vectores](http://es.wikipedia.org/wiki/Vector) en $\mathbb{R}^3$:

$$v1 = \begin{bmatrix} 2 \\ 1 \\ -1\end{bmatrix} \
v2 = \begin{bmatrix} 0 \\ 1 \\ 1\end{bmatrix} \
v3 = \begin{bmatrix} 1 \\ -1 \\ 1\end{bmatrix}$$

En este caso, deberíamos combrobar que:

$$v1 \cdot v2 = 0 \\
v2 \cdot v3 = 0 \\
v1 \cdot v3 = 0 $$

```python
# comprobando ortogonalidad del conjunto

v1 = np.array([2, 1, -1])
v2 = np.array([0, 1, 1])
v3 = np.array([1, -1, 1])

v1.dot(v2), v2.dot(v3), v1.dot(v3)
```

    (0, 0, 0)

```python
a = np.array([1,2,3,5]).reshape(2,2)
print(a)
np.linalg.det(a)
np.linalg.det(np.linalg.inv(a))
```

    [[1 2]
     [3 5]]

    -0.9999999999999993

```python
a = np.array([3,5,1,2]).reshape(2,2)
print(a)
np.linalg.det(a)
np.linalg.det(np.linalg.inv(a))
```

    [[3 5]
     [1 2]]

    0.9999999999999988

Como vemos, este conjunto es ortogonal. Una de las principales ventajas de trabajar con [conjuntos](http://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/) de [vectores](http://es.wikipedia.org/wiki/Vector) ortogonales es que los mismos son necesariamente [linealmente independientes](https://es.wikipedia.org/wiki/Dependencia_e_independencia_lineal).

El concepto de ortogonalidad es uno de los más importantes y útiles en [Álgebra lineal](http://relopezbriega.github.io/tag/algebra.html) y surge en muchas situaciones prácticas, sobre todo cuando queremos calcular distancias.

## Determinante

El determinante es un número especial que puede calcularse sobre las [matrices cuadradas](https://es.wikipedia.org/wiki/Matriz_cuadrada). Este número nos va a decir muchas cosas sobre la matriz. Por ejemplo, nos va decir si la matriz es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible) o no. Si el determinante es igual a cero, la matriz no es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible). Cuando la matriz es [invertible](https://es.wikipedia.org/wiki/Matriz_invertible), el determinante de $A^{-1}= 1/(\det \ A)$. El determinante también puede ser útil para calcular áreas.

Para obtener el determinante de una matriz debemos calcular la suma de los productos de las diagonales de la matriz en una dirección menos la suma de los productos de las diagonales en la otra dirección. Se represente con el símbolo $|A|$ o $\det A$.

Algunas de sus propiedades que debemos tener en cuenta son:

a. El determinante de la [matriz identidad](https://es.wikipedia.org/wiki/Matriz_identidad) es igual a 1. $\det I = 1$.

b. Una matriz $A$ es *singular* (no tiene [inversa](https://es.wikipedia.org/wiki/Matriz_invertible)) si su determinante es igual a cero.

c. El determinante cambia de signo cuando dos columnas(o filas) son intercambiadas.

d. Si dos filas de una matriz $A$ son iguales, entonces el determinante es cero.

e. Si alguna fila de la matriz $A$ son todos ceros, entonces el determinante es cero.

f. La [matriz transpuesta](http://es.wikipedia.org/wiki/Matriz_transpuesta) $A^T$, tiene el mismo determinante que $A$.

g. El determinante de $AB$ es igual al determinante de $A$ multiplicado por el determinante de $B$. $\det (AB) = \det A \cdot \det B$.

h. El determinante es una [función lineal](https://es.wikipedia.org/wiki/Funci%C3%B3n_lineal) de cada una de las filas en forma separada. Si multiplicamos solo una fila por $\alpha$, entonces el determinante también es multiplicado por $\alpha$.

Veamos como podemos obtener el determinante con la ayuda de [Python](http://python.org/)

```python
# Determinante con sympy
A = sympy.Matrix( [[1, 2, 3],
                   [2,-2, 4],
                   [2, 2, 5]] )
A.det()
```

$$2$$

```python
# Determinante con numpy
A = np.array([[1, 2, 3],
              [2,-2, 4],
              [2, 2, 5]] )
np.linalg.det(A)
```

$$2.0$$

```python
# Determinante como funcion lineal de fila
A[0] = A[0:1]*5
np.linalg.det(A)
```

$$10.0$$

```python
# cambio de signo de determinante
A = sympy.Matrix( [[2,-2, 4],
                   [1, 2, 3],
                   [2, 2, 5]] )
A.det()
```

$$-2$$

## Eigenvalores y Eigenvectores

Cuando estamos resolviendo [ecuaciones lineales](https://es.wikipedia.org/wiki/Sistema_de_ecuaciones_lineales) del tipo $Ax = b$, estamos trabajando con problemas *estáticos*. ¿Pero qué pasa si quisiéramos trabajar con problemas *dinámicos*?. Es en este tipo de situaciones donde los [Eigenvalores y Eigenvectores](https://es.wikipedia.org/wiki/Vector_propio_y_valor_propio) tienen su mayor importancia.

Supongamos que tenemos una [matriz cuadrada](https://es.wikipedia.org/wiki/Matriz_cuadrada) $A$ de $n \times n$. Una pregunta natural que nos podríamos hacer sobre $A$ es: ¿Existe algún [vector](http://es.wikipedia.org/wiki/Vector) $x$ distinto de cero para el cual $Ax$ es un escalar múltiplo de $x$?. Si llevamos esta pregunta al lenguaje matemático nos vamos a encontrar con la siguiente ecuación:

$$Ax = \lambda x$$

Cuando esta ecuación es válida y $x$ no es cero, decimos que $\lambda$ es el [Eigenvalor o valor propio](https://es.wikipedia.org/wiki/Vector_propio_y_valor_propio) de $A$ y $x$ es su correspondiente [Eigenvector o vector propio](https://es.wikipedia.org/wiki/Vector_propio_y_valor_propio).

Muchos problemas en ciencia derivan en problemas de [Eigenvalores](https://es.wikipedia.org/wiki/Vector_propio_y_valor_propio), en los cuales la principal pregunta es: ¿Cuáles son los [Eigenvalores](https://es.wikipedia.org/wiki/Vector_propio_y_valor_propio) de una matriz dada, y cuáles son sus correspondientes [Eigenvectores](https://es.wikipedia.org/wiki/Vector_propio_y_valor_propio). Un área donde nos va a ser de mucha utilidad esta teoría, es en problemas con sistemas de [ecuaciones diferenciales lineales](https://es.wikipedia.org/wiki/Ecuaci%C3%B3n_diferencial_lineal).

### Calculando Eigenvalores

Hasta aquí todo muy bien, pero dada una [matriz cuadrada](https://es.wikipedia.org/wiki/Matriz_cuadrada) $A$ de $n \times n$, ¿cómo podemos obtener sus [Eigenvalores](https://es.wikipedia.org/wiki/Vector_propio_y_valor_propio)?.

Podemos comenzar por observar que la ecuación $Ax = \lambda x$ es equivalente a $(A - \lambda I)x = 0$. Dado que estamos interesados en soluciones a esta ecuación que sean distintas de cero, la matriz $A - \lambda I$ debe ser *singular*, no [invertible](https://es.wikipedia.org/wiki/Matriz_invertible), por lo tanto su determinante debe ser cero, $\det (A - \lambda I) = 0$. De esta forma, podemos utilizar esta ecuación para encontrar los [Eigenvalores](https://es.wikipedia.org/wiki/Vector_propio_y_valor_propio) de $A$. Particularmente, podríamos formar el [polinomio característico](https://es.wikipedia.org/wiki/Polinomio_caracter%C3%ADstico) de la matriz $A$, el cual va a tener *grado* $n$ y por lo tanto va a tener $n$ soluciones, es decir que vamos a encontrar $n$ [Eigenvalores](https://es.wikipedia.org/wiki/Vector_propio_y_valor_propio).
Algo que debemos tener en cuenta es, que a pesar de que la matriz $A$ sea [real](https://es.wikipedia.org/wiki/N%C3%BAmero_real), debemos estar preparados para encontrar [Eigenvalores](https://es.wikipedia.org/wiki/Vector_propio_y_valor_propio) que sean [complejos](http://relopezbriega.github.io/blog/2015/10/12/numeros-complejos-con-python/).

Para que quede más claro, veamos un ejemplo de como podemos calcular los [Eigenvalores](https://es.wikipedia.org/wiki/Vector_propio_y_valor_propio). Supongamos que tenemos la siguiente matriz:

$$A = \begin{bmatrix} 3 & 2 \\ 7 & -2 \end{bmatrix}$$

Su [polinomio característico](https://es.wikipedia.org/wiki/Polinomio_caracter%C3%ADstico) va a ser igual a:

$$p(\lambda) = \det (A - \lambda I) = \det \begin{bmatrix}3 - \lambda & 2 \\ 7  & -2-\lambda\end{bmatrix} = (3 - \lambda)(-2-\lambda) - 14  \\ =\lambda^2 - \lambda - 20 = (\lambda - 5) (\lambda + 4)$$

Por lo tanto los [Eigenvalores](https://es.wikipedia.org/wiki/Vector_propio_y_valor_propio) de $A$ van a ser $5$ y $-4$.

Obviamente, también los podemos obtener mucho más fácilmente con la ayuda de [Python](http://python.org/).

```python
# Eigenvalores con numpy
A = np.array([[3, 2],
              [7, -2]])

x, v = np.linalg.eig(A)

# x Eigenvalor, v Eigenvector
x, v
```

    (array([ 5., -4.]), array([[ 0.70710678, -0.27472113],
            [ 0.70710678,  0.96152395]]))

```python
# Eigenvalores con SymPy
A = sympy.Matrix([[3, 2],
                  [7, -2]])

# Eigenvalor
A.eigenvals()
```

$$\left \{ -4 : 1, \quad 5 : 1\right \}$$

```python
# Eigenvector
A.eigenvects()
```

$$\left [ \left ( -4, \quad 1, \quad \left [ \left[\begin{matrix}- \frac{2}{7}\\1\end{matrix}\right]\right ]\right ), \quad \left ( 5, \quad 1, \quad \left [ \left[\begin{matrix}1\\1\end{matrix}\right]\right ]\right )\right ]$$

```python
# comprobando la solución Ax = λx
# x eigenvector, v eigenvalue
x = A.eigenvects()[0][2][0]
v = A.eigenvects()[0][0]

# Ax == vx
A*x, v*x
```

$$\left ( \left[\begin{matrix}\frac{8}{7}\\-4\end{matrix}\right], \quad \left[\begin{matrix}\frac{8}{7}\\-4\end{matrix}\right]\right )$$

Con esto termino con este recorrido por los principales conceptos del [Álgebra lineal](http://relopezbriega.github.io/tag/algebra.html), muchos de los cuales veremos en próximos artículos que tienen muchas aplicaciones interesantes. Espero que les sea de utilidad y les sirva de referencia.

```python
det(A-øI)=0 ecuación
```


```python
"""Header cell, contains modules and functions to make the whole notebook experience better"""
%matplotlib inline
# plots graphs within the notebook

from IPython.display import display,Image, Latex
from sympy.interactive import printing
printing.init_printing(use_latex='mathjax')
from IPython.display import clear_output

import time

from IPython.display import display,Image, Latex

from IPython.display import clear_output

import matplotlib.pyplot as plt
import numpy as np
import math
import scipy.constants as sc

import sympy as sym

font = {'family' : 'serif',
        #'color'  : 'black',
        'weight' : 'normal',
        'size'   : 12,
        }
fontlabel = {'family' : 'serif',
        #'color'  : 'black',
        'weight' : 'normal',
        'size'   : 12,
        }

from matplotlib.ticker import FormatStrFormatter
plt.rc('font', **font)

class PDF(object):
  def __init__(self, pdf, size=(200,200)):
    self.pdf = pdf
    self.size = size

  def _repr_html_(self):
    return ''.format(self.pdf, self.size)

  def _repr_latex_(self):
    return r'\includegraphics[width=1.0\textwidth]{{{0}}}'.format(self.pdf)

class ListTable(list):
    """ Overridden list class which takes a 2-dimensional list of
        the form [[1,2,3],[4,5,6]], and renders an HTML Table in
        IPython Notebook. """

    def _repr_html_(self):
        html = ["<table>"]
        for row in self:
            html.append("<tr>")

            for col in row:
                html.append("<td>{0}</td>".format(col))

            html.append("</tr>")
        html.append("</table>")
        return ''.join(html)

```

```python
PDF('bot-sketch.pdf',size = (550,400))
```

# Differential Drive Robot - 1

Alphabot 2 is a differential drive robot: Two independent motors drive the left and right wheels and the differential speed between the two wheels control the speed and direction of the robot. The following code is copied from http://enesbot.me/kinematic-model-of-a-differential-drive-robot.html

## Objective
This notebook focuses on the control of a simulated differential drive robot for prescribed simple tasks.

## Theoretical Background

The robot parameters are:

 Wheel radius: $r$.

 Length between wheels: $L$

 Angular velocity of the left and right wheels: $\omega_L$, $\omega_R$, respectively.

 Angle from horizontal: $\alpha$.

 Position vector of the robot: $(x,y)$.

 Velocity vector of the robot: $V=(\dot{x},\dot{y})$.

Hereafter $\dot{a}$ refers to the time derivative of variable $a$:

$$
\dot{a}=\frac{da}{dt}
$$

The velocities of the wheels are therefore defined as:

$$
V_L=\omega_Lr\text{ and }V_R=\omega_Rr.
$$

The velocity of the robot, taken at the center of the wheels, is simply:

$$
\vec{V}=\frac{V_R+V_L}{2}(\cos(\alpha)\vec{e}_x+\sin(\alpha\vec{e}_y),
$$

yielding the following equations of motions:

$$
\dot{x}=r\frac{\omega_R+\omega_L}{2}\cos{\alpha}
$$

$$
\dot{y}=r\frac{\omega_R+\omega_L}{2}\sin{\alpha}
$$

and

$$
\dot{\alpha}=\frac{r}{L}(\omega_R-\omega_L)
$$

Numerically, these solutions of these equations requires their discretization. Remember that the derivative $da/dt$ of a variable $a=f(t)$ is the slope $s$ of the local tangent. Over a small enough time interval $\Delta t$, any continuous function $f$ can be approximated by a linear function based on the slope $a(t+\Delta t$)=a(t)+s(t)\Delta t$. The derivative is then estimated as:

$$
\frac{da}{dt}\approx\frac{\Delta a}{\Delta t}
$$

where $\Delta a=a(t+\Delta t$)-a(t)$. The discretized equations of motion may be recast as an integration of the trajectory of the robot:

$$
x(t+\Delta t) = x(t) +\Delta t\left(r\frac{\omega_R+\omega_L}{2}\cos{\alpha}\right),
$$

$$
y(t+\Delta t) = y(t) +\Delta t\left(r\frac{\omega_R+\omega_L}{2}\sin{\alpha}\right),
$$

$$
\alpha(t+\Delta t) = \alpha(t) +\Delta t\left(r\frac{\omega_R-\omega_L}{L}\right).
$$

As long as, the initial conditions are prescribed at $t=0$, the motion of the robot is predictable providing that $\Delta t$ is not too large, as we shall see later. The present discretization is the simplest one, and also the most rudimentary. Even though the above equations carry fairly large numerical errors, no robot is perfect and these numerical errors will not be discussed here.

## Code description

The code below defines a class, named Robot, which includes the robot dimensions, position variables ($x,y,\alpha$), the time step $\Delta t$, the left/right angular velocities and various functions necessary to integrate the robot motion. First execute the cell below.

```python
import matplotlib.pyplot as plt
import numpy as np
import math
import scipy.constants as sc

import sympy as sym

from math import sin, cos

class Robot(object):
    """Defines basic mobile robot properties"""
    def __init__(self):
        self.pos_x  = 0.0
        self.pos_y  = 0.0
        self.angle  = 0.0
        self.plot   = False
        self._delta = 0.01
        self.step_plot = int(5)
        self.mag_plot = 1.

    # Movement
    def step(self):
        """ updates the x,y and angle """
        self.deltax()
        self.deltay()
        self.deltaa()

    def move(self, seconds):
        """ Moves the robot for an 's' amount of seconds"""
        for i in range(int(seconds/self._delta)):
            self.step()
            if i % self.step_plot == 0 and self.plot: # plot path every 3 steps
                self.plot_xya()

    # Printing-and-plotting:
    def print_xya(self):
        """ prints the x,y position and angle """
        print ("x = " + str(self.pos_x) +" "+ "y = " + str(self.pos_y))
        print ("a = " + str(self.angle))

    def plot_robot(self):
        """ plots a representation of the robot """
        plt.arrow(self.pos_x, self.pos_y, 0.001
                  * cos(self.angle), 0.001 * sin(self.angle),
                  head_width=self.mag_plot*self.length, head_length=self.mag_plot*self.length,
                  fc='k', ec='k')

    def plot_xya(self):
        """ plots a dot in the position of the robot """
        plt.scatter(self.pos_x, self.pos_y, c='r', edgecolors='r')

class DDRobot(Robot):
    """Defines a differential drive robot"""

    def __init__(self):
        Robot.__init__(self)
        self.radius = 0.1
        self.length = 0.4

        self.rt_spd_left = 0.0
        self.rt_spd_right = 0.0

    def deltax(self):
        """ update x depending on l and r angular speeds """
        self.pos_x += self._delta * (self.radius*0.5) \
        * (self.rt_spd_right + self.rt_spd_left)*cos(self.angle)

    def deltay(self):
        """ update y depending on l and r angular speeds """
        self.pos_y += self._delta * (self.radius*0.5) \
        * (self.rt_spd_right + self.rt_spd_left)*sin(self.angle)

    def deltaa(self):
        """ update z depending on l and r angular speeds """
        self.angle += self._delta * (self.radius/self.length) \
        * (self.rt_spd_right - self.rt_spd_left)
# function to convert degrees to radians
def D2R(a):
    return math.pi*a/180
def R2D(a):
    return 180*a/math.pi
# mybot = DDRobot()            # robot called 'enesbot'

# mybot.angle = 3.1416/4        # 45 degrees
# mybot.plot = True             # plot the robot!
# mybot.plot_robot()

# mybot.rt_spd_left = 10
# mybot.rt_spd_right = 10       # straight line
# mybot.move(2)                 # move for 2 seconds

# mybot.rt_spd_left = 12.5664
# mybot.rt_spd_right = 18.8496  # (2m diameter circle)
# mybot.move(1)                 # move for 1 second

# mybot.rt_spd_left = 18.8496
# mybot.rt_spd_right = 12.5664  # (2m diameter circle)
# mybot.move(2.5)               # move for 2.5 second

# mybot.rt_spd_left = 12.5664
# mybot.rt_spd_right = 18.8496  # (2m diameter circle)
# mybot.move(3.5)               # move for 2.5 second

# mybot.plot_robot()

# plt.xlim([-1, 6])               # axis limits
# plt.ylim([-1, 6])

# plt.show()
```

## Quick Tutorial

Now you can create your first robot.

```python
mybot = DDRobot()
```

For simplicity, assume that all units are based on the metric system. Your new robot, mybot, is by default located at $(x=0,y=0)$, its angle from the horizontal axis $x$ is $0$ and the wheel radius and length between wheel are $r=0.1$m and $L=0.4$m (see class DDRobot). The default time step is $\Delta t=0.01$s. All these variables and more can be easily customized to your problem:

```python
mybot.pos_x = 0 #x
mybot.pos_y = 0 #y
mybot.angle = D2R(45) #alpha
mybot.length =0.4 #L
mybot.radius = 0.1 #r
mybot.rt_spd_left = 10 # omega_L
mybot.rt_spd_right = 10 # omega_R

```

Now that our initial conditions and rotational speed is set, let's move our robot in a straight line and plot its trajectory.

```python
mybot.plot = True #True if you want to plot the robot's trajectory
mybot.plot_robot() #draw an arrow for the location of the robot at t=0

mybot.move(10) #move the robot for 10s
mybot.plot_robot() #draw the new location

plt.xlim(-1,11)
plt.ylim(-1,11)
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.show()
```

You may change the size of the arrow and the spacing of the dots making the trajectory using the following variables:

```python
mybot.mag_plot = 2 #coefficient of magnification of the arrow
mybot.step_plot = 100 # plot location every 100 iterations

mybot.pos_x = 0 #x
mybot.pos_y = 0 #y
mybot.angle = D2R(45) #alpha
mybot.length =0.4 #L
mybot.radius = 0.1 #r
mybot.rt_spd_left = 10 # omega_L
mybot.rt_spd_right = 10 # omega_R

mybot.plot = True #True if you want to plot the robot's trajectory
mybot.plot_robot() #draw an arrow for the location of the robot at t=0

mybot.move(10) #move the robot for 10s
mybot.plot_robot() #draw the new location

plt.xlim(-1,11)
plt.ylim(-1,11)
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.show()
```

You may print the location of your robot using:

```python
mybot.print_xya()
```

    x = 7.071067811865343 y = 7.071067811865343
    a = 0.7853981633974483

which you can compare to the theoretical location of the robot. Since the robot's wheel radius is $0.1$m, the rotational speed is $\omega=10$ (1/s), therefore the robot speed is $1$m/s. Over $10$s of travel time, starting at an angle of $45^\circ$, the location of the robot should be $x=10\cos(\pi/4)$ and $y=10\sin(\pi/4)$.

```python
print('absolute error x: %1.1e m' %(abs(mybot.pos_x - 10*math.cos(math.pi/4))))
print('absolute error y: %1.1e m' %(abs(mybot.pos_y - 10*math.sin(math.pi/4))))
print('absolute error alpha: %1.1e rad' %(abs(mybot.angle - math.pi/4)))
```

    absolute error x: 1.3e-13 m
    absolute error y: 1.3e-13 m
    absolute error alpha: 0.0e+00 rad

To turn, the left and right angular velocities must be different, as shown below

```python

mybot.mag_plot = 2 #coefficient of magnification of the arrow
mybot.step_plot = 10 # plot location every 100 iterations

mybot.pos_x = 0 #x
mybot.pos_y = 0 #y
mybot.angle = D2R(45) #alpha
mybot.length =0.4 #L
mybot.radius = 0.1 #r

mybot.plot = True #True if you want to plot the robot's trajectory
mybot.plot_robot() #draw an arrow for the location of the robot at t=0

# move straight for 10m
mybot.rt_spd_left = 10 # omega_L
mybot.rt_spd_right = 10 # omega_R
mybot.move(10) #move the robot for 10s

# 2m radius turn
mybot.rt_spd_left = 18.8496
mybot.rt_spd_right = 12.5664
mybot.move(2.5)

mybot.plot_robot() #draw the new location

plt.xlim(-1,11)
plt.ylim(-1,11)
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.show()
```

## Assignment

### 1 Iterative turn
In this first assignment the robot must start at $(0,0)$, drive horizontally to $(10,0)$, then vertically to $(10,10)$, horizontally to $0,10$ and finally vertically to $(0,0)$. Write a code using a for loop to move your robot. Within the for loop, turn your robot using a while loop. Use the same robot dimensions as above and an absolute angular velocity $\omega = 10$.

```python
mybot = DDRobot()            # robot called 'enesbot'
mybot.mag_plot = 2 #coefficient of magnification of the arrow
mybot.step_plot = 50 # plot location every 100 iterations

mybot.pos_x = 0 #x
mybot.pos_y = 0 #y
mybot.angle = 0 #alpha
mybot.length =0.4 #L
mybot.radius = 0.1 #r
omega = 10
rot_time_increment = 0.01 #time increment for rotation
rotation_angle = D2R(90)

mybot.plot = True #True if you want to plot the robot's trajectory
mybot.plot_robot() #draw an arrow for the location of the robot at t=0

for i in range(4):

    # move straight for 10m
    mybot.rt_spd_left = omega # omega_L
    mybot.rt_spd_right = omega # omega_R
    mybot.move(10) #move the robot for 10s
    # turn 90 degrees to the left
    mybot.rt_spd_left = -omega # omega_L
    mybot.rt_spd_right = omega # omega_R
    alpha_0 = mybot.angle
    while (mybot.angle - alpha_0) < rotation_angle:
        mybot.move(rot_time_increment)

    mybot.plot_robot() #draw the new location

plt.xlim(-1,11)
plt.ylim(-1,11)
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.show()

```

As you can see, the robot does not return to its origin. Quantify the error.

```python
print('absolute error x: %1.1e m' %(math.sqrt(mybot.pos_x**2 +mybot.pos_y**2)))
```

    absolute error x: 8.1e-01 m

### 2 Exact rotation

Derive and implement an equation to determine the exact time needed for the robot to achieve a rotation of a given angle around its center. Quantify the error

Integrating the angular velocity equation:

$$
\dot{\alpha}=\frac{d\alpha}{dt}=\frac{r}{L}(\omega_R-\omega_L)
$$

from $0$ to a time $T$ yields

$$
\int_0^T\frac{d\alpha}{dt}dt=\int_0^T\frac{r}{L}(\omega_R-\omega_L)dt
$$

Since the left hand side function is constant with respect to time, the result is simply:

$$
\alpha(T) - \alpha(0) =Tr\frac{\omega_R-\omega_L}{L}
$$

Therefore the time to execute a rotation of angle $\alpha_o=\alpha(T) - \alpha(0)$ is

$$
T = \frac{\alpha_oL}{r(\omega_R-\omega_L)}
$$
Note: Vary the time step used to move the robot to see the effect on the error. You should see that error decreases at your reduce the time step from myplot._delta=0.1 to 0.0001

```python
mybot = DDRobot()            # robot called 'enesbot'
mybot.mag_plot = 2 #coefficient of magnification of the arrow

mybot._delta = 0.1 #time step of integration
mybot.step_plot = 2*int(1/mybot._delta) # plot location every n iterations
mybot.pos_x = 0 #x
mybot.pos_y = 0 #y
mybot.angle = 0 #alpha
mybot.length =0.4 #L
mybot.radius = 0.1 #r
omega = 10
rotation_angle = D2R(90)

mybot.plot = True #True if you want to plot the robot's trajectory
mybot.plot_robot() #draw an arrow for the location of the robot at t=0

for i in range(4):

    # move straight for 10m
    mybot.rt_spd_left = omega # omega_L
    mybot.rt_spd_right = omega # omega_R
    mybot.move(10) #move the robot for 10s
    # turn 90 degrees to the left
    mybot.rt_spd_left = -omega/10 # omega_L
    mybot.rt_spd_right = omega/10 # omega_R
    t_rotation = rotation_angle*mybot.length/(mybot.radius*(mybot.rt_spd_right-mybot.rt_spd_left))
    mybot.move(t_rotation)

    mybot.plot_robot() #draw the new location
    print("angle at position %i: %0.2f"%(i,R2D(mybot.angle)))

plt.xlim(-1,11)
plt.ylim(-1,11)
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.show()
print('absolute error x: %1.1e m' %(math.sqrt(mybot.pos_x**2 +mybot.pos_y**2)))
```

```python

```

    absolute error x: 5.9e-01 m

### 3 Circular motion

Derive and implement an equation to determine the paramters (angular velocities and motion time) needed for the robot to achieve an arc of a given radius and angle (from origin to end).

To describe an arc, one wheel must rotate faster than the other. The solid body rotation of the robot center on a circle of radius $R$ dictates that the velocity at the center of the robot $V$ is proportional to $R$:

$$
V=\Omega R
$$

where $\omega$ is the rotation speed. Assuming that the robot is turning counter clock-wise, the right and left wheel speeds are

$$
V_R = \Omega\left(R+\frac{L}{2}\right)
$$
$$
V_L = \Omega\left(R-\frac{L}{2}\right)
$$

Therefore,

$$
\omega_R = \frac{\Omega}{r}\left(R+\frac{L}{2}\right)
$$
$$
\omega_L = \frac{\Omega}{r}\left(R-\frac{L}{2}\right)
$$
The duration to rotate by $\alpha_o$ is the same as above. The code below describes an arc of radius $10$, and angle $60^\circ$.

```python
Omega = 10
R = 10
alpha_o = D2R(60)
mybot = DDRobot()            # robot called 'enesbot'
mybot.mag_plot = 2 #coefficient of magnification of the arrow

mybot._delta = 0.0001 #time step of integration
mybot.step_plot = 50 # plot location every n iterations
mybot.pos_x = 10 #x
mybot.pos_y = 0 #y
mybot.angle = D2R(90) #alpha
mybot.length =0.4 #L
mybot.radius = 0.1 #r
rotation_direction = 1 #1 for counter clockwise -1 for clockwise
omega_R = Omega/mybot.radius*(R+rotation_direction*mybot.length/2)
omega_L = Omega/mybot.radius*(R-rotation_direction*mybot.length/2)
t_rotation = alpha_o*mybot.length/(mybot.radius*(omega_R-omega_L))
mybot.plot = True #True if you want to plot the robot's trajectory
mybot.plot_robot() #draw an arrow for the location of the robot at t=0
mybot.rt_spd_left = omega_L
mybot.rt_spd_right = omega_R
mybot.move(t_rotation)

mybot.plot_robot() #draw the new location
print("arc angle %i: %0.2f"%(i,R2D(mybot.angle-D2R(90))))

plt.xlim(-1,11)
plt.ylim(-1,11)
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.show()

```

```python

```


# Mathematical Typesetting With Latex

This will only serve as a brief introduction to mathematical typesetting with Latex, and will not cover many other aspects of the typesetting language. This tutorial will cover the basics of a few common mistakes and how to fix them, and provide you with a link to a cheat sheet.

1. To put a variable in-line, surround it in dollar signs like so: `$x$`, which renders as $x$.
1. To typeset an equation, surround it (on a new line) in double dollar signs: `$$equation$$`
1. You can also use this syntax:
```latex
\begin{equation}
       your_equation_here
\end{equation}
  ```
1. Special symbols start with a backslash (`\`). For example, the greek letter $\alpha$ is typed as `$\alpha$`.

1. Fractions can be typeset with `\frac{numerator}{denominator}`
1. Square roots are `\sqrt{stuff inside square root}`

If you want special symbols in line, simply call it by its name, such as $\alpha$, $\gamma$, $\epsilon$, $\Delta$, $\nabla$ and so on are called by typing `$\alpha$`, `$\gamma$`, `$\epsilon$`, `$\Delta$`, and `$\nabla$` etc. [This link leads](http://tug.ctan.org/info/symbols/comprehensive/symbols-a4.pdf) to a comprehensive symbol list and their Latex commands. Of course, sometimes a Google search is faster.

# Latex Basics

#### Double Click Me
You can start equations easily:
$$ c  = \sqrt{a^2 + b^2}$$
notice how in the above, the variables appearing under the square root are surrounded by curly brackets (`{ }`), and powers are done with carrots (`^`).

#### Double Click Me

You can also type equations using this syntax:
\begin{equation}
a = \frac{x^3 +\Gamma}{\int e^{-x^2} dx}
\end{equation}

Notice here how the numerator of the fraction appears in curly braces, and the denominator appears in separate curly braces. Note that capital Greek letters are found by capitalizing the first letter in your Latex command.

#### Double Click Me

Also notice the exponent, this time we included curly brackets. This is because without them only the first character will become a superscript, as demonstrated below.

\begin{equation}
a = x^-10
\end{equation}

#### Double Click Me

The above doesn't render as you would expect. However, we can fix this by adding curly braces around the exponent as follows
\begin{equation}
a = x^{-10}
\end{equation}

#### Double Click Me

You can also include vectors on variables
\begin{equation}
\vec{F} = m \vec{a}
\end{equation}

#### Double Click Me

The asterisk symbol may not render how you hope for multiplication in Latex, and it is often better to use `\times`. For example, compare

\begin{equation}
a * b = ab
\end{equation}

versus

\begin{equation}
a \times b = ab
\end{equation}

#### Double Click Me

If you need to put parenthesis around a fraction, sometimes your equation may not render nicely, for example

\begin{equation}
f(x) = a \times ( \frac{ x^2 }{ \sqrt{ x^2 + \tan(x) } } )
\end{equation}

In order to make your parenthesis larger, you need to include the `\left` and `\right` commands besides your parenthesis as follows

\begin{equation}
f(x) = a \times \left( \frac{ x^2 }{ \sqrt{ x^2 + \tan(x) } } \right)
\end{equation}

# Latex Common Errors

### Long subscripts need to be wrapped in curly braces to render correctly

##### Double Click Me

Notice how the equation
\begin{equation}
a = x_10
\end{equation}

Doesn't render how you want it to, but
\begin{equation}
a = x_{10}
\end{equation}
does once you include the curly braces.

### Spaces in equations are ignored

##### Double Click Me
Notice what happens to words included in the math environment
$$
If you use words as a variable, they become hard to read = x_a^2
$$

If you need to include words in your equations, you can do so with `\text{}`
$$
\text{Now we can read this easily once we've rendered the math} = x_a^2
$$

### You cannot include percent signs without a little extra work

##### Double Click Me

If you want to include a percent sign in a mathematical equation, you need to escape it with `\` as a percent sign denotes a comment in Latex

$$
100 % That's because a percent sign is a comment in latex!
$$
To escape the comment, simply add a backslash
$$ 100 \% $$

### Missing a Curly Bracket

If you're missing a curly bracket when using a Latex command, say in a fraction, your equation won't render as shown in a simple example below

\begin{equation}
x = \frac{ a } { y
\end{equation}

To fix this, simply add the missing parenthesis

\begin{equation}
x = \frac{ a } { y }
\end{equation}

Note however that anytime you have an error in your Latex it will simply not render as shown above, and sometimes its a bit of work finding the offending parenthesis.

Latex is very extensive for mathematical typesetting, and we can't cover it all here. However, it's fairly intuitive and with a little practice significantly faster than using the equation editors in other tools. Feel free to try creating your own equations now.

---
### Exercises
1. Fix the following to correctly represent the formula for calculating the surface area of a sphere

$$
Area = 4 pi r2
$$

2. Fix the following to correctly represent the formula for calculating the volume of a sphere
\begin{equation}
Volume = \frac{4} / {3} pi r3
\end{equation}
3. Make sure pi is showing up as a symbol in questions 1 & 2.
4. In this sentence, change the variables x, Y, and y to ensure they render as LateX variables.