Programación Lógica

La programación lógica está inspirada en la lógica de primer orden. El lenguaje más conocido es Prolog y si no lo conocéis os recomiendo que le echéis un ojo. Es una forma de programar bastante diferente a la habitual y aunque para cosas genéricas pueden ser más «cómodos» otros paradigmas de programación para ciertas ramas de la I.A. facilita el desarrollo de forma espectacular.

En programación lógica por lo general hay tres tipos de sentencias:

  • Hechos (facts): Serían los conocimientos de los que se parte. Son sentencias del estilo «Juan es padre de Pepe» que se escribiría como algo así: padre(juan, pepe).
  • Reglas (rules): Calculan nuevos hechos estableciendo relaciones entre ellos.Para ello se utilizan variables. Por ejemplo «Si X es padre de Y e Y es padre de Z entonces X es abuelo de Z» que en Prolog sería: abuelo(X,Z) :- padre(X,Y), padre(Y,Z).
  • Consultas (queries): Se usan para extraer información. Hay de dos tipos, la primera dice si un hecho es verdadero o falso. Por ejemplo «¿Es padre Juan de Pepe?» Que se escribiría (depende el intérprete de Prolog) padre(juan, pepe). El otro tipo son las que tiene que completar un hecho. Por ejemplo «¿Quien es el padre de Pepe?» padre(X, pepe).

Si queréis jugar con él sin tener que instalar nada podéis probar aquí.

Guardando el conocimiento

Supongo que ya os imaginareis como va a aprender este sistema. Convertiremos los datos a un conjunto de hechos y reglas. Para obtener la respuesta de la base de datos usaremos consultas.

¿Qué ventajas tiene este sistema?. Que no solo «aprende» datos, si no que también las relaciones entre ellos y la forma de calcularlas.

En el siguiente ejemplo vemos qué se define el concepto de abuelo y en lugar de añadir abuelo(juan, javi) añadimos la regla genérica para calcularlo.

padre(juan, jose). # juan padre de jose
padre(jose, javi). # jose pare de javi
abuelo(X,Y):-padre(X,Z), padre(Z,Y).

Probamos la query:

abuelo(juan, X)

Se comporta igual que si hubiéramos introducido el dato directamente abuelo(juan, javi).

Otra ventaja de la programación lógica es que es comprensible por los seres humanos, bueno al menos por algunos de ellos.

Usando Prolog desde Javascript

La cosa se complicó en este punto. Si quería que Chispas pudiera usar prolog necesitaba que se pudiera interpretar desde JS. El problema es que no encontré ningún intérprete de programación lógica que encajara con mi idea. O no tenían una sintaxis que me convenciera o no funcionaban bien cuando las reglas se complicaban o lo hacían pero eran muy lentos. Al final el que más me gustaba era el de la página que os he enlazado antes. El único problema es que la web era monolítica y tendría que separar la parte del interprete, encapsularlo y dotarle de una forma de introducir las reglas y leer los resultados que fuera amigable para el desarrollador. Pero eso era fácil de resolverlo programado. Al final el resultado podéis verlo aquí.

Histéresis

El ruido puede causar comportamiento errático. En este caso vamos a ver que problemas puede causar cuando hay un umbral de activación en un sistema. Por ejemplo una célula fotoeléctrica que determina cuando encender o apagar unas luces. Para ello hemos programado un microcontrolador «lea» el valor de la célula y cuando sea menor de 100 encienda la luz. Cuando el valor se aproxime a este punto puede devolver lecturas como estas:

100, 99, 98, 101, 102, 99, 100, 97

Que traducido a la luz que controla sería:

Off, On, On, Off, Off, On, Off, On

La bombilla parpadea sin parar, va parecer que los espíritus tratan de comunicarse con nosotros a través de ella. Para evitar eso se puede separar el umbral en dos. Uno para encender la luz y otro para apagarla. A este hueco se le llama histéresis. Por ejemplo se podría decidir que por debajo de 100 se enciende pero no se apaga hasta que el valor sea 105. Esta distancia sirve para evitar que la luz actúe erraticamente encendiéndose y apagándose sin parar cuando el valor es próximo al del umbral de activación.

El resultado seria:

100, 99, 98, 101, 102, 99, 100, 97

Off, On, On, On, On, On, On, On

Evitando que la luz parpadee todo el rato.

Reducir ruido usando la media y la desviación típica

Este metido es para detectar y reducir los ruidos intensos y poco frecuentes. La idea principal es que el valor leído más el ruido de baja intensidad se comportan como una función de normal. Se podría ver como que a mayor intensidad del ruido menos probable es que aparezca. Visto en forma de gráfica:

Standard deviation diagram micro.svg
De AinaliTrabajo propio, CC BY-SA 3.0, Enlace

En la imagen se puede ver que según un valor se aleja de la media (μ) menos probable es que pertenezca a la muestra. Así que según su distancia a la muestra podemos saber lo probable que es que pertenezca. Sabiendo esto se ponen una distancia máxima a la media de tal forma que habrá dos umbrales (μ+distancia y μ-distancia) los valores que queden fuera de esos umbrales de eliminan. Valores habituales de filtrado corresponde a μ±σμ±2σ y μ±3σ. O aproximadamente dejan fuera el 16%, 2% y 0.1% de los valores (a cada lado del umbral). Para filtrar valores fijamos la probabilidad de pertenencia a la muestra que les «exigimos». Por ejemplo si un valor solo puede pertenecer a la muestra con una seguridad del 5% lo descartamos.

Este proceso lo podemos aplica una vez o varias, repitiéndolo hasta que no se elimine ninguno de los valores.

Una vez eliminados los valores que quedan fuera del umbral podemos aplicar alguno de los filtros ya vista como la moda, mediana o media para calcular el valor.

Ejemplo:

Usamos como limite 2σ. repetiremos el filtrado hasta que no haya descartes en las muestras

Iteración 1

muestras iniciales: [23, 4, 5, 5, 6, 4, 5, 4, 5, 6, 4, 12]

media: 6.9167

desviación estándar: 5.5179

tolerancias: 6.9167+(2 * 5.5179) = 17.953         6.9167-(2 * 5.5179) = -4.1191

muestras admitidas: [4, 5, 5, 6, 4, 5, 4, 5, 6, 4, 12]

Iteración 2

muestras iniciales: [4, 5, 5, 6, 4, 5, 4, 5, 6, 4, 12]

media: 5.4545

desviación estándar: 2.2962

tolerancias: 5.4545 + (2 * 2.2962) = 10.047       5.4545 – (2 * 2.2962) = 0.8621

muestras admitidas: [4, 5, 5, 6, 4, 5, 4, 5, 6, 4]

Iteración 3

muestras iniciales: [4, 5, 5, 6, 4, 5, 4, 5, 6, 4]

media: 4.8

desviación estándar: 0.7888

tolerancia: 4.8 + (2 * 0.7888) = 6.3776        4.8 – (2 * 0.7888) = 3.2224

muestras admitidas: [4, 5, 5, 6, 4, 5, 4, 5, 6, 4]

Ahora calculamos los distintos valores usando al media, moda y mediana

media: 4.8 

moda: 4

mediana: 5

Comparamos entre los resultados entre los datos filtrados y sin filtrar:

Filtrado Sin filtrar
Media 4.8 6.9167
Mediana 5 5
Moda 4 4

Es normal que solo se haya visto afectada la media, ya que tanto la moda como la mediana están libres de la influencia de los valores en los extremos.

Reducir ruido usando la mediana

Otra idea para reducir el ruido es ordenar todos los valores y coger el central. La idea es muy parecida a la de la media. Se considera el ruido como la suma de una pequeña cantidad aleatoria cuyo valor oscila entre -e y e. Si el valor real es V las muestras se dividirán entre la que sumen el ruido (V+e) y la que lo resten (V-e) . Quedando el valor real V mas o menos en el centro de todos los valores. La mediana es precisamente eso, el valor en el centro de todos los valores. Para calcularlo es necesario ordenar la lista de muestras. Este es su punto débil ya que aumenta el coste computacional de calcular la mediana.

Tiene varias ventajas:

  • Es sencillo
  • Es insensible al ruido intenso pero poco frecuente.

También tiene sus inconvenientes:

  • Ordenar las muestras puede ser una tarea costosa a nivel computacional
  • Solo se puede usar cuando se puedan tomar varias muestras de la misma medida

Ejemplo:

Partimos de las siguientes muestras [23, 4, 5, 5, 6, 4, 5, 4, 5, 6, 4, 12]

Mediana: 5

El resultado no se ha visto influido por los valores extremos (12 y 23)

Reducir ruido usando la moda

El uso de la moda para evitar el ruido es parecida al caso de la media, pero en lugar de sumar todos los valores se selecciona el que más veces se repite. La idea en que se basa en que la mayoría del ruido es pequeño así que el valor que más veces aparezca es el más cercano al real.

Al igual que la media, esta técnica solo sirve si se pueden capturar suficiente numero de muestras sin que el valor cambie de forma apreciable. En este caso necesitamos mas valores que en caso de la media.

Tiene varias ventajas:

  • Es sencillo
  • Requiere poco «coste computacional»
  • Es insensible al ruido intenso pero poco frecuente.

También tiene sus inconvenientes:

  • Solo se puede usar cuando se puedan tomar varias muestras de la misma medida
  • Requiere muchas medidas para ser exacto
  • Por lo general supone perder «resolución» en la medida para poder «agrupar» las medidas y calcular la moda. Por ejemplo si tenemos los valores 4,13 y 4,18 hay que eliminar el último decimal para poder contar ambos como el mismo valor 4,1

Ejemplo:

Partimos de las siguientes muestras [23, 4, 5, 5, 6, 4, 5, 4, 5, 6, 4, 12]

Moda: 4

El resultado no se ha visto influido por los valores extremos (12 y 23)

Reducir ruido usando la media

Vamos a ver una técnica para ocuparnos del ruido habitual pero de baja intensidad. En el caso de que podamos tomar varias medidas sin que haya a penas variación del valor a medir. Es decir, estamos midiendo un valor que no cambia o lo hace tan lentamente que podemos capturar varias muestras sin que cambie.

Podemos ver el ruido como la suma de una pequeña cantidad aleatoria cuyo valor oscila entre -e y e. Eso significa que si tomamos suficiente muestras y las sumamos entre ellas lo mas probable es que los diferentes incrementos y decrementos que el ruido produce al valor medido se «neutralice» entre si. Ese calculo, sumar varios valores y dividir la suma entre el número de valores es precisamente la media.

Tiene varias ventajas:

  • Es sencillo
  • Requiere poco «coste computacional»
  • Se puede aplicar a cualquier número de muestras, aunque a mayor número de muestras mejores resultados.

También tiene sus inconvenientes:

  • Solo se puede usar cuando se puedan tomar varias muestras de la misma medida.
  • Hay que filtrar primero los valores de ruido más intenso ya que «desplazan» el valor de la media y son tan poco habituales que no se «neutralizan» al sumar las medidas
  • No todos los ruidos siguen el modelo propuesto de valor aleatorio entre e y -e

Ejemplo:

Partimos de las siguientes muestras [23, 4, 5, 5, 6, 4, 5, 4, 5, 6, 4, 12]

Media: 6.9167

Se puede ver como el 12 y el 23 han influido «desplazando» la media hacia su lado.

Combinar varias metaheurísticas

Si ajustar una metaheurísticas ya cuesta. ¿Por qué querríamos sufrir el doble para hacerlo con dos?. Hay varios motivos, el principal es que una metaheurística no se comportan bien en todo el espacio de soluciones del problema y por ello se usan varias.

Un caso es que una metaheurística se puede comportar muy bien para calcular parte de la solución pero no toda. Entonces algunos de sus valores los calcularemos usando una metaheurística dejando intactos aquellas partes de la solución para los que no funciona a los que posteriormente aplicaremos la segunda metaheurística que al igual que la anterior no alterará la parte para la que no funciona. Este proceso tiene que repetirse varias veces permitiendo que ambas partes evolucionen de forma conjunta.

Otro caso en que resulta útil aplicar una segunda metaheurística es cuando tenemos una que funciona muy bien explorando el espacio de soluciones y encontrando la zona donde se encuentra el máximo pero muy mal para precisar el valor de este. En ese caso se usa una metaheurística para acercarnos al máximo y cuando ha terminado aplicamos otra para precisar el resultado.

Ahora el problema es como combinar varias metaheurísticas ya que algunas funcionan con mutiles soluciones y otras con una sola.

Empezamos por el caso más intuitivo, el de que ambas metaheurísticas recurran a una población de varios individuos. Aquí las opciones son varias:

  • Cada metaheurística se ejecuta de forma separa con sus propia población de soluciones y cada cierto tiempo se intercambian soluciones entre ellas.
  • Ambas metaheurística actúan sobre las mismas soluciones y compiten entre ellas. Solo las mejores soluciones pasan a formar parte de la población
  • Intercalar metaheurísticas. Aplicamos una durante varias iteracciones  una y luego iteramos varias veces con la otra.

Para el caso de combinar dos metaheurísticas que trabajan con un solo resultado todas las opciones se limitan a usar primero una y luego la otra. Este proceso puede repetirse varias veces o realizarse solo una vez.

Para el caso de combinar metaheurísticas con población y las que trabajan mejorando una única solución:  

  • Tras cada ciclo de la metaheurística orientada a población se aplica la otra metaheurística a los individuos nuevos tratando de mejorarlos
  • Otra opción es usar la metaheristica individual como función de modificación/creación de nuevo individuos.

Un ejemplo muy habitual de combinar metahurísticas es en el caso de los algoritmos genéticos, tras haber generado los nuevos individuos usando los operadores de mutación y/o cruce  y antes de que compitan con el resto de la población se trata de mejorar los nuevos individuos usando alguna metaheurísticas que explore el espacio cercano a cada individuo tratando de encontrar el máximo local.

Trilateralización

Tras que medir la distancia al emisor no diera el resultado deseado pensé en no escribir esta entrada, pero como puede ser útil y ya la tenia medio escrita he decidido terminarla.

Ya hemos visto cómo calcular (más o menos) la distancia a un emisor. Si conocemos la situación del emisor podemos situarnos en algún punto de una circunferencia de radio r1 (r1 es igual a la distancia al emisor). Si añadiéramos un segundo emisor de coordenadas conocidas y al que calculamos la distancia r2. Esto nos sitúa en una segunda circunferencia que corta a la primera en dos puntos. Ahora ya sabemos que estamos situados en uno de esos dos puntos. Para saber en cual exactamente necesitamos un tercer emisor cuya posición conozcamos y del que podamos calcular la distancia r3. Esta tercera circunferencia coincidirá con las otras dos en un solo punto. En la imagen se ve mejor

Trilateration.svg
De Braindrain0000 de la Wikipedia en inglés, CC BY-SA 3.0, Enlace

Ahora toca ponerlo todo en lenguaje matemático. En lugar de usar circunferencias vamos a usar esferas, los cálculos son los mismo y nos permite no tener los tres emisores y el receptor en el mismo plano. La ecuación de una esfera cuyo centro está en las coordenadas (a,b,c) es:

(x ─ a) 2 + (y ─ b) 2 + (z ─ c) 2= r2

Esta ecuación representa a todos los puntos de la súperficie de una esfera. Cómo tenemos tres esferas que coinciden en un punto de sus superficies hay que buscar un punto (x,y,z) que cumpla las ecuaciones de las tres esferas.

Esfera1, vamos a ser amables con nosotros mismo y vamos a colocar esta esfera en el origen de coordenadas (0,0,0) por lo que su ecuación es:

x 2 + y 2 + z 2 = r12

Esfera2, también por simplificar nos la vida, colocamos el centro de esta esfera en línea con la anterior en las coordenadas (d,0,0). Su ecuación es:

(x – d)2 + y 2 + z 2 = r22

La tercer esfera la colocamos con el centro en el mismo plano pero con ambas coordenadas distintas.

(x – i)2 + (y – j) 2 + z 2 = r3

En el articulo de la wikipedia esta la resolución completa de las ecuaciones paso a paso, yo salto a la conclusión:

x = (r1 2 – r2 2 + d 2 ) / 2d

y = (r1 2 – r3 2 + i 2 + j 2) / 2j + (x * i / j)

z = √(r1 x 2 – y 2 )

Con estos cálculos ya tienes localizado el punto exacto. Desgraciadamente en la vida real las mediciones no suelen ser tan exactas por eso la mejor solución es detectar donde cortan dos circunferencias (por ejemplo la 1 con la 2 y la 1 con la 3) Buscar los puntos mas cercano y tomar el punto medo entre ambos, teniendo en cuenta que es una aproximación y que no será el punto exacto.

Medir distancia al emisor WiFi.

La idea es sencilla, mides la potencia de la señal de varias estaciones cuya posición conoces, con la potencia calculas la distancia a cada estación y a partir de ahí con geometría básica calculas tu posición. Parece fácil, hasta que lo intentas y vas viendo como todo se complica.

Como estación vamos a usar un emisor WiFi, eso nos permitirá realizar pruebas fácilmente ya que podemos usar cualquier router WiFi como estación y como receptor cualquier móvil. Vamos a empezar por lo mas sencillo, medir la distancia a un solo nodo cuya localización conocemos. En nuestro receptor podemos ver la potencia. ¿Como convertimos la potencia en distancia?. Las malas noticias es que no es lineal. Las buenas es que hay fórmula (Free-space path loss) para hacerlo de manera aproximada

dB = 20log10(d) + 20log10(f) + K

K varia segun las unidades de medida de la distancia d y la frecuencia f, siendo:

  • K = 92.45 si d está en km y f en Ghz
  • K = -87.55 si d está en m y f en Khz
  • K = -27.55 si d está en m y f en Mhz
  • K = 32.45 si d está en km y f en Mhz

Lo que nos interesa son metros y Mhz. Como frecuencia de la seña WiFi se puede usar siempre 2400 Mhz sin temer cometer un gran error (aunque ya hay algunas redes que operan a otras frecuencias como 5 Ghz)

Aunque no es del todo exacta en muchas aplicaciones usan directamente esta formula para calcular la distancia. En el receptor podemos conocer  la atenuación (dB), la frecuencia (f) y la constante (K). Ahora podemos resolver la distancia (d).

dB – (20log10(f) + K) = 20log10(d)

Cómo ni f ni K van a variar en nuestro caso vamos a remplazarlos por una constante C

dB – C = 20log10(d)

Despejando d

d = 10^((dB – C)/20)

Si queremos ser mas exactos no basta con leer la potencia de señal que se recibe en el receptor ya que la formula esta expresada en decibelios (dB) y la potencia de la señal WiFi nos la devuelve el sistema en decibelios-miliwatio (dBm). Que aunque aparentan ser lo mismo no lo son. Los dB son una medida relativa de potencia entre dos valores (en este caso serian la potencia de emisión del nodo WiFi y la potencia percibida en el receptor). Sin embargo los dBm indican un valor de potencia expresado en relación a 1 mW. Que para los que no estamos acostumbrados a este campo todo esto nos suena a chino y lo único que queremos saber es: ¿Como se convierte de una unidad a la otra?. Pues no es tan sencillo porque cada unidad mide cosas diferentes. Voy a tratar de explicarlo para este caso y perdonarme si meto mucho la pata, ya os digo que no es un tema que domine. Los dB serian la diferencia de potencia entre la señal original y el punto donde estas recibiéndola y se calcula como:

dB = 10log10(P/P0)

Siendo P la potencia medida y P0 la emitida o de referencia. Necesitamos obtener esas dos potencias. Para ello podemos usar los dBm, que son los dB medidos tomando como potencia de referencia 1mW por lo que la formula queda:

dBm = 10 log10(P/1mW)

Podemos calcular P en mW fácilmente.

P = 10 ^ (dBm/10)

¿Pero de donde sacamos P0?. Los que esperéis complicados cálculos siento decepcionaros. La forma de calcular P0 (de forma aproximada) es acercarse lo más posible al emisor y medir la potencia en dBm, despejar la formula anterior y usar ese valor como P0.

Con todo esto podemos calcular el valor en dB de la atenuación de la señal. Así que ahora podemos volver a la primera formula para calcular la distancia.

En javascript

function dBm2dB(dBm0, dBm){
  var P0 = Math.pow(10, dBm0/10);
  var P = Math.pow(10, dBm/10);
  var dB = 10 * Math.log10(P/P0);
  return dB;
}

function fspl(dB,f,K){
  var logF = 20*Math.log10(f) + K;
  var d = Math.pow(10, (Math.abs(dB) - logF)/20);
  return d;
}

//ejmeplo
fspl(dBm2dB(-29, -58), 2400, -27.55)*10;

¿Que tal resultado da? Pues por alguna extraña metedura de pata que no logro localizar necesito multiplicar el resultado por 100 para que las cifras tengan sentido. La aproximación es buena pero no lo suficiente, incluso sin moverse los valores de la señal varían mucho. Como sistema de localización, la potencia de la señal WiFi deja mucho que desear

Derivada numérica y gradiente

Derivada

La derivada df de una función f es otra función que expresa la tasa de cambio de f. Por ejemplo si tenemos una función que calcula la velocidad en el punto X y calculamos su derivada tendremos una función que calcula la aceleración (cambio de la velocidad) en el punto X. Otra forma de entenderla es como la pendiente en un punto. Si calculamos la derivada de la función fitness en un punto tendremos una función que nos dice cuánto varía y si aumenta o disminuye. Es decir nos da una pista clara de en qué sentido hay que moverse para aumentar o disminuir el valor de la función.

Esto también quiere decir que si la pendiente es 0 ya estás en un máximo o en un mínimo. Si calculas todos los puntos donde la derivada es 0 seguro que uno de ellos es el máximo o el mínimo que estás buscando. Desgraciadamente en muchos casos no es sencillo hallar esos puntos.

Siendo una herramienta tan útil vamos a ver cómo calcularlo. A muchos os sonará de haberlo estudiado y haber tenido que memorizar un montón de derivadas y luego aprender reglas de derivación (por partes, cambio de variables,…) Incluso puede que tener pesadillas con ellas. Pero los ordenadores no saben hacer ese tipo de derivadas. Con ellos hay que recurrir derivadas numéricas que son mucho más simples.

df(x) = f(x+h) – f(x-h) / 2*h

¿Cuanto es h? Lo ideal es que sea una cantidad lo más pequeña posible. Pero ojo con cantidades muy pequeñas y la precisión numérica de los lenguajes de programación que puede dar sorpresa. Cómo se puede ver en el código más abajo yo he fijado el valor por defecto a 0,001.

this.epsilon = epsilon || 0.001;

this.diff = function(x, f){
  return (f(x+this.epsilon)-f(x-this.epsilon))/(this.epsilon*2);
}

Para ir más allá de la primera derivada también hay fórmulas para calcularla numéricamente que son más exactas que el método que vamos a usar nosotros pero en este caso me ha parecido más interesante el siguiente método.

Tenemos una función diff(x,f) que calcula la derivada de f en el punto x. Si hacemos diff(3,diff(3,f)) calcularíamos la segunda derivada de la función f en el punto 3. Para la tercera tendríamos que hacer diff(3,diff(3,diff(3,f))). Para las siguientes derivadas creo que ya habéis entendido como funciona.

En lugar de escribir todas las funciones una detrás de otra en una larga cadena vamos a usar una función que lo haga por nosotros. Siendo el parámetro n el número de derivada que calculará.

this.diffN = function(n, x, f){
  if(n == 1)
    return this.diff(x,f);
  else
    return this.diffN(n-1, x, function(x){return that.diff(x,f)});
}

Gradiente

Ya hemos visto como funciona la derivada para una función f(x) en un punto A, ahora os estaréis preguntando «¿Cómo funciona la derivada para una función con más variables como f(x1, x2)?» ¿A qué os he leído la mente?. Esta vez el punto donde se calcula tiene dos coordenadas [A,B]. Se calculan dos derivadas respecto de x1 y respecto de x2. Lo de «respecto» significa que solo se va a calcular modificando esa varíable, la otra será estática.

df(x1,x2)/dx1 = f(x1-h,x2)-f(x1+h,x2)/2*h

Es decir dejamos el resto de las «equis» inalteradas.

El gradiente es el cálculo de la derivada respecto a «cada equis». Es decir que

G = [df(x1,x2)/dx1, df(x1,x2)/dx2];

Podemos calcular el segundo, tercer,…. gradiente calculando la correspondiente derivada.

Nuestra función para calcular el gradiente usando la función anterior para calcular la derivada numérica queda así:

this.gradN = function(n, x, f){
  G = [];
  for(var i = 0; i < x.length; ++i){
    var auxX = x.slice(0);
    var fdx = function(x){auxX[i] = x; return f(auxX);}
    G.push(this.diffN(n, auxX[i], fdx));
  }
  return G;
}

Para que el uso sea más intuitivo vamos a hacer una versión del primer gradiente sin el parámetro n (n=1)

this.grad = function(x, f){
return this.gradN(1,x,f);
}

Puedes ver todo el código en este repositorio de github.