MODULOS PARA INTELIGENCIA ARTIFICIAL - NUMPY
1. Concepto
NumPy es un paquete de Python que significa “Numerical Python”, es la librería principal para la informática científica, proporciona potentes estructuras de datos, implementando matrices y matrices multidimensionales. Estas estructuras de datos garantizan cálculos eficientes con matrices.
2. Proporciona
* Paquetes de procesamiento de matrices de uso general.
* Proporciona un objeto de matriz multidimensional de alto rendimiento, y
herramientas para trabajar con estas matrices.
* Paquete fundamental para la computación científica con Python.
* Puede ser usado como un eficiente contenedor multidimensional de datos
genéricos.
* Enriquece el lenguaje de programación de Python con potentes
estructuras de datos, implementando matrices y arreglos
multidimensional.
* Estas estructuras de datos garantizan cálculos eficientes con matrices.
* La implementación apunta incluso a matrices enormes, mejor conocidas bajo el título de “grandes datos”.
* El módulo suministra funciones matemáticas de alto nivel para operar en estas matrices.
3. Array NumPy
NumPy array es un potente objeto de matriz N-dimensional que tiene forma de filas y columnas, en la que
tenemos varios elementos que están almacenados en sus respectivas ubicaciones de memoria.
3.1. Array Unidimensional
Todos los elementos que se almacenan en el array deben ser del mismo tipo. Esto implica que el array e un
bloque de datos homogéneos.
import numpy as np
a = np.array([1,2,3])
print(a)
--------
[1 2 3]
3.2. Array multidimensional
Tiene más de una columna. Podemos considerar un arreglo multidisciplinario como una hoja de cálculo de
Excel, tiene columnas y filas. Cada columna puede ser considerada como una dimensión.
import numpy as np
a = np.array([[1,2],[10,20]])
print(a)
--------
[[ 1 2]
[10 20]]
4. Crear Arrays en MunPy
4.1 Array con ceros
Para crear un array unidimensional con 2 elementos, ambos ceros utilizamos el siguiente código:
import numpy as np
a = np.zeros(2)
print(a)
--------
[0. 0.]
Por su parte para crear un array bidimensional debes indicar el número de files y columnas que tendrá el
array.
import numpy as np
a = np.zeros([2,3])
print(a)
--------
[[0. 0. 0.]
[0. 0. 0.]]
4.2 Array con unos
Para crear un array unidimensional con 2 elementos, ambos unos utilizamos el siguiente código:
import numpy as np
a = np.ones(2)
print(a)
--------
[1. 1.]
Por su parte para crear un array bidimensional debes indicar el número de files y columnas que tendrá el
array.
import numpy as np
a = np.ones([2,3])
print(a)
--------
[[1. 1. 1.]
[1. 1. 1.]]
4.3 Array con rango de elementos
Para crear un array con rango de elementos, por ejemplo 3, escribimos el siguiente código:
import numpy as np
a = np.arange(3)
print(a)
--------
[0 1 2]
Si quieres crear un array con un rango de elementos, con números entre 2 y 7, por ejemplo, escribimos el
siguiente código:
import numpy as np
a = np.arange(3,7)
print(a)
--------
[3 4 5 6]
4.4 Array con elementos aleatorios
Para crear un array con elementos aleatorios debemos utilizar la siguiente instrucción e indicar el número
de filas y columnas que tendrá el array.
import numpy as np
a = np.random.rand(3,2)
print(a)
--------
[[0.51800425 0.13125493]
[0.15577916 0.1678749 ]
[0.12214038 0.09268094]]
5. Conceptos básicos de la estadística descriptiva
En estadística descriptiva se utilizan distintas medidas para intentar describir las propiedades de
nuestros datos, algunos de los conceptos básicos, son:
- Media aritmética: La media aritmética es el valor obtenido al sumar
todos los datos y dividir el resultado entre el número total elementos.
Se suele representar con la letra griega μ. Si tenemos una muestra de n
valores, xi, la media aritmética, μ, es la suma de los valores divididos
por el numero de elementos; en otras palabras:
\[
\mu=\frac{1}{n} \sum_{i} x_{i}
\]
# Ejemplos de estadistica descriptiva con python
import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas
np.random.seed(2131982) # para poder replicar el random
datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")
print("Media Aritmética por columna:\n")
datos.mean(axis=0) # media aritmetica de cada columna
--------
Datos:
[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
[-0.1074033 -0.88138082 -0.34466623 -0.28320214]
[ 0.94051171 0.86693793 1.20947882 -0.16894118]
[-0.12790177 -0.58099931 -0.46188426 -0.18148302]
[-0.76959435 -1.37414587 1.37696874 -0.18040537]]
Media Aritmética por columna:
array([ 0.0791985 , -0.61180267, 0.23061642, -0.2894644 ])
- Varianza: La varianza es la media aritmética del cuadrado de las
desviaciones respecto a la media de una distribución estadística. La
varianza intenta describir la dispersión de los datos. Se representa
como σ2.
\[
\sigma^{2}=\frac{\sum_{i=1}^{n}\left(x_{i}-\mu\right)^{2}}{n}
\]
# Ejemplos de estadistica descriptiva con python
import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas
np.random.seed(2131982) # para poder replicar el random
datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")
print("Varianza por columna:\n")
np.var(datos, 0) # varianza de cada columna
--------
Datos:
[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
[-0.1074033 -0.88138082 -0.34466623 -0.28320214]
[ 0.94051171 0.86693793 1.20947882 -0.16894118]
[-0.12790177 -0.58099931 -0.46188426 -0.18148302]
[-0.76959435 -1.37414587 1.37696874 -0.18040537]]
Varianza por columna:
array([0.337064 , 0.6139171 , 0.76359823, 0.03126703])
- Desviación típica: La desviación típica es la raíz cuadrada de la
varianza. Se representa con la letra griega σ.
\[
\sigma=\sqrt{\frac{\sum_{i=1}^{n}\left(x_{i}-\mu\right)^{2}}{n}}
\]
# Ejemplos de estadistica descriptiva con python
import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas
np.random.seed(2131982) # para poder replicar el random
datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")
print("Desviación típica por columna:\n")
np.std(datos, 0) # Desviación típica de cada columna
--------
Datos:
[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
[-0.1074033 -0.88138082 -0.34466623 -0.28320214]
[ 0.94051171 0.86693793 1.20947882 -0.16894118]
[-0.12790177 -0.58099931 -0.46188426 -0.18148302]
[-0.76959435 -1.37414587 1.37696874 -0.18040537]]
Desviación típica por columna:
array([0.58057213, 0.78352862, 0.87384108, 0.17682485])
- Moda: La moda es el valor que tiene mayor frecuencia absoluta. Se
representa con M0
# Ejemplos de estadistica descriptiva con python
import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas
np.random.seed(2131982) # para poder replicar el random
datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")
print("Moda por columna:\n")
# moda
stats.mode(datos) # Calcula la moda de cada columna
# el 2do array devuelve la frecuencia.
--------
Datos:
[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
[-0.1074033 -0.88138082 -0.34466623 -0.28320214]
[ 0.94051171 0.86693793 1.20947882 -0.16894118]
[-0.12790177 -0.58099931 -0.46188426 -0.18148302]
[-0.76959435 -1.37414587 1.37696874 -0.18040537]]
Moda por columna:
ModeResult(mode=array([[-0.76959435, -1.37414587, -0.62681496, -0.63329028]]), count=array([[1, 1, 1, 1]]))
- Mediana: La mediana es el valor que ocupa el lugar central de todos
los datos cuando éstos están ordenados de menor a mayor. Se representa
con x˜.
# Ejemplos de estadistica descriptiva con python
import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas
np.random.seed(2131982) # para poder replicar el random
datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")
print("Mediana por columna:\n")
np.median(datos, 0) # media aritmetica de cada columna
--------
Datos:
[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
[-0.1074033 -0.88138082 -0.34466623 -0.28320214]
[ 0.94051171 0.86693793 1.20947882 -0.16894118]
[-0.12790177 -0.58099931 -0.46188426 -0.18148302]
[-0.76959435 -1.37414587 1.37696874 -0.18040537]]
Mediana por columna:
array([-0.1074033 , -0.88138082, -0.34466623, -0.18148302])
- Correlación: La correlación trata de establecer la relación o
dependencia que existe entre las dos variables que intervienen en una
distribución bidimensional. Es decir, determinar si los cambios en una
de las variables influyen en los cambios de la otra. En caso de que
suceda, diremos que las variables están correlacionadas o que hay
correlación entre ellas. La correlación es positiva cuando los valores
de las variables aumenta juntos; y es negativa cuando un valor de una
variable se reduce cuando el valor de la otra variable aumenta.
# Ejemplos de estadistica descriptiva con python
import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas
np.random.seed(2131982) # para poder replicar el random
datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")
# correlacion
print("Crea matriz de correlación:\n")
print(np.corrcoef(datos)) # Crea matriz de correlación.
print("\n")
# calculando la correlación entre dos vectores.
print("calculando la correlación entre dos vectores.:\n")
np.corrcoef(datos[0], datos[1])
--------
Datos:
[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
[-0.1074033 -0.88138082 -0.34466623 -0.28320214]
[ 0.94051171 0.86693793 1.20947882 -0.16894118]
[-0.12790177 -0.58099931 -0.46188426 -0.18148302]
[-0.76959435 -1.37414587 1.37696874 -0.18040537]]
Crea matriz de correlación:
[[ 1. 0.82333743 0.15257202 0.78798675 -0.02292073]
[ 0.82333743 1. -0.13709662 0.86873632 0.41234875]
[ 0.15257202 -0.13709662 1. -0.47691376 0.21216856]
[ 0.78798675 0.86873632 -0.47691376 1. -0.03445705]
[-0.02292073 0.41234875 0.21216856 -0.03445705 1. ]]
calculando la correlación entre dos vectores.:
array([[1. , 0.82333743],
[0.82333743, 1. ]])
- Covarianza: La covarianza es el equivalente de la varianza aplicado
a una variable bidimensional. Es la media aritmética de los productos de
las desviaciones de cada una de las variables respecto a sus medias
respectivas.La covarianza indica el sentido de la correlación entre las
variables; Si σxy>0 la correlación es directa; Si σxy<0 la
correlación es inversa.
# Ejemplos de estadistica descriptiva con python
import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas
np.random.seed(2131982) # para poder replicar el random
datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")
# Covarianza de dos vectores
print("Covarianza de dos vectores:\n")
np.cov(datos[0], datos[1])
--------
Datos:
[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
[-0.1074033 -0.88138082 -0.34466623 -0.28320214]
[ 0.94051171 0.86693793 1.20947882 -0.16894118]
[-0.12790177 -0.58099931 -0.46188426 -0.18148302]
[-0.76959435 -1.37414587 1.37696874 -0.18040537]]
Covarianza de dos vectores:
array([[0.43350958, 0.18087281],
[0.18087281, 0.11132485]])
LS0tDQp0aXRsZTogIlNpbnRheGlzIELDoXNpY2EgTWFya2Rvd24iDQphdXRob3I6ICJKZWFuIFAuIE0uIEh1YW1hbiBRdWlzcGUiDQpkYXRlOiAiMjAyMi8wOC8wMSINCnN1YnRpdGxlOiBBcGxpY8OhbmRvIE1hcmtkb3duIA0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCi0tLQ0KDQo8IS0tIEHDsWFkaXIgY29tZW50YXJpb3MgYSBudWVzdHJvIGRvY3VtZW50byBNYXJrZG93biAtLT4NCg0KPGNlbnRlcj4NCiFbXShpbWcvbnVtcHkuanBnKXt3aWR0aD00MDB9DQoNCiMgTU9EVUxPUyBQQVJBIElOVEVMSUdFTkNJQSBBUlRJRklDSUFMIC0gTlVNUFkNCjwvY2VudGVyPg0KDQojIyMgMS4gQ29uY2VwdG8gDQogICAgTnVtUHkgZXMgdW4gcGFxdWV0ZSBkZSBQeXRob24gcXVlIHNpZ25pZmljYSDigJxOdW1lcmljYWwgUHl0aG9u4oCdLCBlcyBsYSBsaWJyZXLDrWEgcHJpbmNpcGFsIHBhcmEgbGEgaW5mb3Jtw6F0aWNhIGNpZW50w61maWNhLCBwcm9wb3JjaW9uYSBwb3RlbnRlcyBlc3RydWN0dXJhcyBkZSBkYXRvcywgaW1wbGVtZW50YW5kbyBtYXRyaWNlcyB5IG1hdHJpY2VzIG11bHRpZGltZW5zaW9uYWxlcy4gRXN0YXMgZXN0cnVjdHVyYXMgZGUgZGF0b3MgZ2FyYW50aXphbiBjw6FsY3Vsb3MgZWZpY2llbnRlcyBjb24gbWF0cmljZXMuDQoNCiMjIyAyLiBQcm9wb3JjaW9uYQ0KDQogICAgKiBQYXF1ZXRlcyBkZSBwcm9jZXNhbWllbnRvIGRlIG1hdHJpY2VzIGRlIHVzbyBnZW5lcmFsLg0KICAgIA0KICAgICogUHJvcG9yY2lvbmEgdW4gb2JqZXRvIGRlIG1hdHJpeiBtdWx0aWRpbWVuc2lvbmFsIGRlIGFsdG8gcmVuZGltaWVudG8sIHkNCiAgICAgIGhlcnJhbWllbnRhcyBwYXJhIHRyYWJhamFyIGNvbiBlc3RhcyBtYXRyaWNlcy4NCiAgICAgIA0KICAgICogUGFxdWV0ZSBmdW5kYW1lbnRhbCBwYXJhIGxhIGNvbXB1dGFjacOzbiBjaWVudMOtZmljYSBjb24gUHl0aG9uLg0KICAgIA0KICAgICogUHVlZGUgc2VyIHVzYWRvIGNvbW8gdW4gZWZpY2llbnRlIGNvbnRlbmVkb3IgbXVsdGlkaW1lbnNpb25hbCBkZSBkYXRvcw0KICAgICAgZ2Vuw6lyaWNvcy4NCiAgICAgICAgICAgDQogICAgKiBFbnJpcXVlY2UgZWwgbGVuZ3VhamUgZGUgcHJvZ3JhbWFjacOzbiBkZSBQeXRob24gY29uIHBvdGVudGVzDQogICAgICBlc3RydWN0dXJhcyBkZSBkYXRvcywgaW1wbGVtZW50YW5kbyBtYXRyaWNlcyB5IGFycmVnbG9zICANCiAgICAgIG11bHRpZGltZW5zaW9uYWwuDQogICAgICAgICAgIA0KICAgICogRXN0YXMgZXN0cnVjdHVyYXMgZGUgZGF0b3MgZ2FyYW50aXphbiBjw6FsY3Vsb3MgZWZpY2llbnRlcyBjb24gbWF0cmljZXMuDQogICAgICAgICANCiAgICAqIExhIGltcGxlbWVudGFjacOzbiBhcHVudGEgaW5jbHVzbyBhIG1hdHJpY2VzIGVub3JtZXMsIG1lam9yIGNvbm9jaWRhcyBiYWpvIGVsIHTDrXR1bG8gZGUg4oCcZ3JhbmRlcyBkYXRvc+KAnS4NCiAgICAgICAgIA0KICAgICogRWwgbcOzZHVsbyBzdW1pbmlzdHJhIGZ1bmNpb25lcyBtYXRlbcOhdGljYXMgZGUgYWx0byBuaXZlbCBwYXJhIG9wZXJhciBlbiBlc3RhcyBtYXRyaWNlcy4NCg0KIyMjIDMuIEFycmF5IE51bVB5DQogICAgTnVtUHkgYXJyYXkgZXMgdW4gcG90ZW50ZSBvYmpldG8gZGUgbWF0cml6IE4tZGltZW5zaW9uYWwgcXVlIHRpZW5lIGZvcm1hIGRlIGZpbGFzIHkgY29sdW1uYXMsIGVuIGxhIHF1ZQ0KICAgIHRlbmVtb3MgdmFyaW9zIGVsZW1lbnRvcyBxdWUgZXN0w6FuIGFsbWFjZW5hZG9zIGVuIHN1cyByZXNwZWN0aXZhcyB1YmljYWNpb25lcyBkZSBtZW1vcmlhLiANCg0KIyMjIDMuMS4gQXJyYXkgVW5pZGltZW5zaW9uYWwNCiAgICBUb2RvcyBsb3MgZWxlbWVudG9zIHF1ZSBzZSBhbG1hY2VuYW4gZW4gZWwgYXJyYXkgZGViZW4gc2VyIGRlbCBtaXNtbyB0aXBvLiBFc3RvIGltcGxpY2EgcXVlIGVsIGFycmF5IGUgdW4NCiAgICBibG9xdWUgZGUgZGF0b3MgaG9tb2fDqW5lb3MuDQoNCjxjZW50ZXI+ICAgIA0KIVtdKGltZy91bmlkaW1lbnNpb25hbC5qcGcpe3dpZHRoPTQwMH0NCjwvY2VudGVyPg0KDQpgYGBQeXRob24NCmltcG9ydCBudW1weSBhcyBucA0KYSA9IG5wLmFycmF5KFsxLDIsM10pDQpwcmludChhKQ0KLS0tLS0tLS0NClsxIDIgM10NCmBgYA0KDQojIyMgMy4yLiBBcnJheSBtdWx0aWRpbWVuc2lvbmFsDQogICAgVGllbmUgbcOhcyBkZSB1bmEgY29sdW1uYS4gUG9kZW1vcyBjb25zaWRlcmFyIHVuIGFycmVnbG8gbXVsdGlkaXNjaXBsaW5hcmlvIGNvbW8gdW5hIGhvamEgZGUgY8OhbGN1bG8gZGUNCiAgICBFeGNlbCwgdGllbmUgY29sdW1uYXMgeSBmaWxhcy4gQ2FkYSBjb2x1bW5hIHB1ZWRlIHNlciBjb25zaWRlcmFkYSBjb21vIHVuYSBkaW1lbnNpw7NuLg0KDQo8Y2VudGVyPiAgICANCiFbXShpbWcvbXVsdGlkaW1lbnNpb25hbC5qcGcpe3dpZHRoPTQwMH0NCjwvY2VudGVyPg0KDQpgYGBQeXRob24NCmltcG9ydCBudW1weSBhcyBucA0KYSA9IG5wLmFycmF5KFtbMSwyXSxbMTAsMjBdXSkNCnByaW50KGEpDQotLS0tLS0tLQ0KW1sgMSAgMl0NCiBbMTAgMjBdXQ0KYGBgDQoNCiMjIyA0LiBDcmVhciBBcnJheXMgZW4gTXVuUHkNCiMjIyA0LjEgQXJyYXkgY29uIGNlcm9zDQogICAgUGFyYSBjcmVhciB1biBhcnJheSB1bmlkaW1lbnNpb25hbCBjb24gMiBlbGVtZW50b3MsIGFtYm9zIGNlcm9zIHV0aWxpemFtb3MgZWwgc2lndWllbnRlIGPDs2RpZ286DQoNCmBgYFB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQphID0gbnAuemVyb3MoMikNCnByaW50KGEpDQotLS0tLS0tLQ0KWzAuIDAuXQ0KYGBgDQogICAgUG9yIHN1IHBhcnRlIHBhcmEgY3JlYXIgdW4gYXJyYXkgYmlkaW1lbnNpb25hbCBkZWJlcyBpbmRpY2FyIGVsIG7Dum1lcm8gZGUgZmlsZXMgeSBjb2x1bW5hcyBxdWUgdGVuZHLDoSBlbA0KICAgIGFycmF5Lg0KDQpgYGBQeXRob24NCmltcG9ydCBudW1weSBhcyBucA0KYSA9IG5wLnplcm9zKFsyLDNdKQ0KcHJpbnQoYSkNCi0tLS0tLS0tDQpbWzAuIDAuIDAuXQ0KIFswLiAwLiAwLl1dDQpgYGANCg0KIyMjIDQuMiBBcnJheSBjb24gdW5vcw0KICAgIFBhcmEgY3JlYXIgdW4gYXJyYXkgdW5pZGltZW5zaW9uYWwgY29uIDIgZWxlbWVudG9zLCBhbWJvcyB1bm9zIHV0aWxpemFtb3MgZWwgc2lndWllbnRlIGPDs2RpZ286DQoNCmBgYFB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQphID0gbnAub25lcygyKQ0KcHJpbnQoYSkNCi0tLS0tLS0tDQpbMS4gMS5dDQpgYGANCiAgICBQb3Igc3UgcGFydGUgcGFyYSBjcmVhciB1biBhcnJheSBiaWRpbWVuc2lvbmFsIGRlYmVzIGluZGljYXIgZWwgbsO6bWVybyBkZSBmaWxlcyB5IGNvbHVtbmFzIHF1ZSB0ZW5kcsOhIGVsDQogICAgYXJyYXkuDQogICAgDQpgYGBQeXRob24NCmltcG9ydCBudW1weSBhcyBucA0KYSA9IG5wLm9uZXMoWzIsM10pDQpwcmludChhKQ0KLS0tLS0tLS0NCltbMS4gMS4gMS5dDQogWzEuIDEuIDEuXV0NCmBgYA0KICAgIA0KIyMjIDQuMyBBcnJheSBjb24gcmFuZ28gZGUgZWxlbWVudG9zDQogICAgUGFyYSBjcmVhciB1biBhcnJheSBjb24gcmFuZ28gZGUgZWxlbWVudG9zLCBwb3IgZWplbXBsbyAzLCBlc2NyaWJpbW9zIGVsIHNpZ3VpZW50ZSBjw7NkaWdvOg0KICAgIA0KYGBgUHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmEgPSBucC5hcmFuZ2UoMykNCnByaW50KGEpDQotLS0tLS0tLQ0KWzAgMSAyXQ0KYGBgDQogICAgU2kgcXVpZXJlcyBjcmVhciB1biBhcnJheSBjb24gdW4gcmFuZ28gZGUgZWxlbWVudG9zLCBjb24gbsO6bWVyb3MgZW50cmUgMiB5IDcsIHBvciBlamVtcGxvLCBlc2NyaWJpbW9zIGVsDQogICAgc2lndWllbnRlIGPDs2RpZ286DQoNCmBgYFB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQphID0gbnAuYXJhbmdlKDMsNykNCnByaW50KGEpDQotLS0tLS0tLQ0KWzMgNCA1IDZdDQpgYGAgICAgDQogICAgDQojIyMgNC40IEFycmF5IGNvbiBlbGVtZW50b3MgYWxlYXRvcmlvcw0KICAgIFBhcmEgY3JlYXIgdW4gYXJyYXkgY29uIGVsZW1lbnRvcyBhbGVhdG9yaW9zIGRlYmVtb3MgdXRpbGl6YXIgbGEgc2lndWllbnRlIGluc3RydWNjacOzbiBlIGluZGljYXIgZWwgbsO6bWVybw0KICAgIGRlIGZpbGFzIHkgY29sdW1uYXMgcXVlIHRlbmRyw6EgZWwgYXJyYXkuDQogICAgDQpgYGBQeXRob24NCmltcG9ydCBudW1weSBhcyBucA0KYSA9IG5wLnJhbmRvbS5yYW5kKDMsMikNCnByaW50KGEpDQotLS0tLS0tLQ0KW1swLjUxODAwNDI1IDAuMTMxMjU0OTNdDQogWzAuMTU1Nzc5MTYgMC4xNjc4NzQ5IF0NCiBbMC4xMjIxNDAzOCAwLjA5MjY4MDk0XV0NCmBgYCAgICAgICANCiAgICANCiMjIyA1LiBDb25jZXB0b3MgYsOhc2ljb3MgZGUgbGEgZXN0YWTDrXN0aWNhIGRlc2NyaXB0aXZhDQogICAgRW4gZXN0YWTDrXN0aWNhIGRlc2NyaXB0aXZhIHNlIHV0aWxpemFuIGRpc3RpbnRhcyBtZWRpZGFzIHBhcmEgaW50ZW50YXIgZGVzY3JpYmlyIGxhcyBwcm9waWVkYWRlcyBkZQ0KICAgIG51ZXN0cm9zIGRhdG9zLCBhbGd1bm9zIGRlIGxvcyBjb25jZXB0b3MgYsOhc2ljb3MsIHNvbjoNCg0KKiBNZWRpYSBhcml0bcOpdGljYTogIExhIG1lZGlhIGFyaXRtw6l0aWNhIGVzIGVsIHZhbG9yIG9idGVuaWRvIGFsIHN1bWFyIHRvZG9zIGxvcyBkYXRvcyB5IGRpdmlkaXIgZWwgcmVzdWx0YWRvIGVudHJlIGVsIG7Dum1lcm8gdG90YWwgZWxlbWVudG9zLiBTZSBzdWVsZSByZXByZXNlbnRhciBjb24gbGEgbGV0cmEgZ3JpZWdhIM68LiBTaSB0ZW5lbW9zIHVuYSBtdWVzdHJhIGRlIG4gdmFsb3JlcywgeGksIGxhIG1lZGlhIGFyaXRtw6l0aWNhLCDOvCwgZXMgbGEgc3VtYSBkZSBsb3MgdmFsb3JlcyBkaXZpZGlkb3MgcG9yIGVsIG51bWVybyBkZSBlbGVtZW50b3M7IGVuIG90cmFzIHBhbGFicmFzOg0KDQokJA0KXG11PVxmcmFjezF9e259IFxzdW1fe2l9IHhfe2l9DQokJA0KDQpgYGBQeXRob24NCiMgRWplbXBsb3MgZGUgZXN0YWRpc3RpY2EgZGVzY3JpcHRpdmEgY29uIHB5dGhvbg0KDQppbXBvcnQgbnVtcHkgYXMgbnAgIyBpbXBvcnRhbmRvIG51bXB5DQpmcm9tIHNjaXB5IGltcG9ydCBzdGF0cyAjIGltcG9ydGFuZG8gc2NpcHkuc3RhdHMNCmltcG9ydCBwYW5kYXMgYXMgcGQgIyBpbXBvcnRhbmRvIHBhbmRhcw0KDQpucC5yYW5kb20uc2VlZCgyMTMxOTgyKSAjIHBhcmEgcG9kZXIgcmVwbGljYXIgZWwgcmFuZG9tDQoNCmRhdG9zID0gbnAucmFuZG9tLnJhbmRuKDUsIDQpICMgZGF0b3Mgbm9ybWFsbWVudGUgZGlzdHJpYnVpZG9zDQpwcmludCgiRGF0b3M6XG4iKQ0KcHJpbnQoZGF0b3MpDQpwcmludCgiXG4iKQ0KcHJpbnQoIk1lZGlhIEFyaXRtw6l0aWNhIHBvciBjb2x1bW5hOlxuIikNCmRhdG9zLm1lYW4oYXhpcz0wKSAjIG1lZGlhIGFyaXRtZXRpY2EgZGUgY2FkYSBjb2x1bW5hDQoNCi0tLS0tLS0tDQpEYXRvczoNCg0KW1sgMC40NjAzODAyMiAtMS4wODk0MjUyOCAtMC42MjY4MTQ5NiAtMC42MzMyOTAyOF0NCiBbLTAuMTA3NDAzMyAgLTAuODgxMzgwODIgLTAuMzQ0NjY2MjMgLTAuMjgzMjAyMTRdDQogWyAwLjk0MDUxMTcxICAwLjg2NjkzNzkzICAxLjIwOTQ3ODgyIC0wLjE2ODk0MTE4XQ0KIFstMC4xMjc5MDE3NyAtMC41ODA5OTkzMSAtMC40NjE4ODQyNiAtMC4xODE0ODMwMl0NCiBbLTAuNzY5NTk0MzUgLTEuMzc0MTQ1ODcgIDEuMzc2OTY4NzQgLTAuMTgwNDA1MzddXQ0KDQoNCk1lZGlhIEFyaXRtw6l0aWNhIHBvciBjb2x1bW5hOg0KDQphcnJheShbIDAuMDc5MTk4NSAsIC0wLjYxMTgwMjY3LCAgMC4yMzA2MTY0MiwgLTAuMjg5NDY0NCBdKQ0KYGBgIA0KDQoqIFZhcmlhbnphOiBMYSB2YXJpYW56YSBlcyBsYSBtZWRpYSBhcml0bcOpdGljYSBkZWwgY3VhZHJhZG8gZGUgbGFzIGRlc3ZpYWNpb25lcyByZXNwZWN0byBhIGxhIG1lZGlhIGRlIHVuYSBkaXN0cmlidWNpw7NuIGVzdGFkw61zdGljYS4gTGEgdmFyaWFuemEgaW50ZW50YSBkZXNjcmliaXIgbGEgZGlzcGVyc2nDs24gZGUgbG9zIGRhdG9zLiBTZSByZXByZXNlbnRhIGNvbW8gz4MyLg0KDQokJA0KXHNpZ21hXnsyfT1cZnJhY3tcc3VtX3tpPTF9XntufVxsZWZ0KHhfe2l9LVxtdVxyaWdodCleezJ9fXtufQ0KJCQNCg0KYGBgUHl0aG9uDQojIEVqZW1wbG9zIGRlIGVzdGFkaXN0aWNhIGRlc2NyaXB0aXZhIGNvbiBweXRob24NCg0KaW1wb3J0IG51bXB5IGFzIG5wICMgaW1wb3J0YW5kbyBudW1weQ0KZnJvbSBzY2lweSBpbXBvcnQgc3RhdHMgIyBpbXBvcnRhbmRvIHNjaXB5LnN0YXRzDQppbXBvcnQgcGFuZGFzIGFzIHBkICMgaW1wb3J0YW5kbyBwYW5kYXMNCg0KbnAucmFuZG9tLnNlZWQoMjEzMTk4MikgIyBwYXJhIHBvZGVyIHJlcGxpY2FyIGVsIHJhbmRvbQ0KDQpkYXRvcyA9IG5wLnJhbmRvbS5yYW5kbig1LCA0KSAjIGRhdG9zIG5vcm1hbG1lbnRlIGRpc3RyaWJ1aWRvcw0KcHJpbnQoIkRhdG9zOlxuIikNCnByaW50KGRhdG9zKQ0KcHJpbnQoIlxuIikNCnByaW50KCJWYXJpYW56YSBwb3IgY29sdW1uYTpcbiIpDQpucC52YXIoZGF0b3MsIDApICMgdmFyaWFuemEgZGUgY2FkYSBjb2x1bW5hDQotLS0tLS0tLQ0KRGF0b3M6DQoNCltbIDAuNDYwMzgwMjIgLTEuMDg5NDI1MjggLTAuNjI2ODE0OTYgLTAuNjMzMjkwMjhdDQogWy0wLjEwNzQwMzMgIC0wLjg4MTM4MDgyIC0wLjM0NDY2NjIzIC0wLjI4MzIwMjE0XQ0KIFsgMC45NDA1MTE3MSAgMC44NjY5Mzc5MyAgMS4yMDk0Nzg4MiAtMC4xNjg5NDExOF0NCiBbLTAuMTI3OTAxNzcgLTAuNTgwOTk5MzEgLTAuNDYxODg0MjYgLTAuMTgxNDgzMDJdDQogWy0wLjc2OTU5NDM1IC0xLjM3NDE0NTg3ICAxLjM3Njk2ODc0IC0wLjE4MDQwNTM3XV0NCg0KDQpWYXJpYW56YSBwb3IgY29sdW1uYToNCg0KYXJyYXkoWzAuMzM3MDY0ICAsIDAuNjEzOTE3MSAsIDAuNzYzNTk4MjMsIDAuMDMxMjY3MDNdKQ0KYGBgIA0KDQoqIERlc3ZpYWNpw7NuIHTDrXBpY2E6IExhIGRlc3ZpYWNpw7NuIHTDrXBpY2EgZXMgbGEgcmHDrXogY3VhZHJhZGEgZGUgbGEgdmFyaWFuemEuIFNlIHJlcHJlc2VudGEgY29uIGxhIGxldHJhIGdyaWVnYSDPgy4NCg0KJCQNClxzaWdtYT1cc3FydHtcZnJhY3tcc3VtX3tpPTF9XntufVxsZWZ0KHhfe2l9LVxtdVxyaWdodCleezJ9fXtufX0NCiQkDQoNCmBgYFB5dGhvbg0KIyBFamVtcGxvcyBkZSBlc3RhZGlzdGljYSBkZXNjcmlwdGl2YSBjb24gcHl0aG9uDQoNCmltcG9ydCBudW1weSBhcyBucCAjIGltcG9ydGFuZG8gbnVtcHkNCmZyb20gc2NpcHkgaW1wb3J0IHN0YXRzICMgaW1wb3J0YW5kbyBzY2lweS5zdGF0cw0KaW1wb3J0IHBhbmRhcyBhcyBwZCAjIGltcG9ydGFuZG8gcGFuZGFzDQoNCm5wLnJhbmRvbS5zZWVkKDIxMzE5ODIpICMgcGFyYSBwb2RlciByZXBsaWNhciBlbCByYW5kb20NCg0KZGF0b3MgPSBucC5yYW5kb20ucmFuZG4oNSwgNCkgIyBkYXRvcyBub3JtYWxtZW50ZSBkaXN0cmlidWlkb3MNCnByaW50KCJEYXRvczpcbiIpDQpwcmludChkYXRvcykNCnByaW50KCJcbiIpDQpwcmludCgiRGVzdmlhY2nDs24gdMOtcGljYSBwb3IgY29sdW1uYTpcbiIpDQpucC5zdGQoZGF0b3MsIDApICMgRGVzdmlhY2nDs24gdMOtcGljYSBkZSBjYWRhIGNvbHVtbmENCi0tLS0tLS0tDQpEYXRvczoNCg0KW1sgMC40NjAzODAyMiAtMS4wODk0MjUyOCAtMC42MjY4MTQ5NiAtMC42MzMyOTAyOF0NCiBbLTAuMTA3NDAzMyAgLTAuODgxMzgwODIgLTAuMzQ0NjY2MjMgLTAuMjgzMjAyMTRdDQogWyAwLjk0MDUxMTcxICAwLjg2NjkzNzkzICAxLjIwOTQ3ODgyIC0wLjE2ODk0MTE4XQ0KIFstMC4xMjc5MDE3NyAtMC41ODA5OTkzMSAtMC40NjE4ODQyNiAtMC4xODE0ODMwMl0NCiBbLTAuNzY5NTk0MzUgLTEuMzc0MTQ1ODcgIDEuMzc2OTY4NzQgLTAuMTgwNDA1MzddXQ0KDQoNCkRlc3ZpYWNpw7NuIHTDrXBpY2EgcG9yIGNvbHVtbmE6DQoNCmFycmF5KFswLjU4MDU3MjEzLCAwLjc4MzUyODYyLCAwLjg3Mzg0MTA4LCAwLjE3NjgyNDg1XSkNCmBgYCANCg0KKiBNb2RhOiBMYSBtb2RhIGVzIGVsIHZhbG9yIHF1ZSB0aWVuZSBtYXlvciBmcmVjdWVuY2lhIGFic29sdXRhLiBTZSByZXByZXNlbnRhIGNvbiBNMA0KDQpgYGBQeXRob24NCiMgRWplbXBsb3MgZGUgZXN0YWRpc3RpY2EgZGVzY3JpcHRpdmEgY29uIHB5dGhvbg0KDQppbXBvcnQgbnVtcHkgYXMgbnAgIyBpbXBvcnRhbmRvIG51bXB5DQpmcm9tIHNjaXB5IGltcG9ydCBzdGF0cyAjIGltcG9ydGFuZG8gc2NpcHkuc3RhdHMNCmltcG9ydCBwYW5kYXMgYXMgcGQgIyBpbXBvcnRhbmRvIHBhbmRhcw0KDQpucC5yYW5kb20uc2VlZCgyMTMxOTgyKSAjIHBhcmEgcG9kZXIgcmVwbGljYXIgZWwgcmFuZG9tDQoNCmRhdG9zID0gbnAucmFuZG9tLnJhbmRuKDUsIDQpICMgZGF0b3Mgbm9ybWFsbWVudGUgZGlzdHJpYnVpZG9zDQpwcmludCgiRGF0b3M6XG4iKQ0KcHJpbnQoZGF0b3MpDQpwcmludCgiXG4iKQ0KcHJpbnQoIk1vZGEgcG9yIGNvbHVtbmE6XG4iKQ0KIyBtb2RhDQpzdGF0cy5tb2RlKGRhdG9zKSAjIENhbGN1bGEgbGEgbW9kYSBkZSBjYWRhIGNvbHVtbmENCiMgZWwgMmRvIGFycmF5IGRldnVlbHZlIGxhIGZyZWN1ZW5jaWEuDQotLS0tLS0tLQ0KRGF0b3M6DQoNCltbIDAuNDYwMzgwMjIgLTEuMDg5NDI1MjggLTAuNjI2ODE0OTYgLTAuNjMzMjkwMjhdDQogWy0wLjEwNzQwMzMgIC0wLjg4MTM4MDgyIC0wLjM0NDY2NjIzIC0wLjI4MzIwMjE0XQ0KIFsgMC45NDA1MTE3MSAgMC44NjY5Mzc5MyAgMS4yMDk0Nzg4MiAtMC4xNjg5NDExOF0NCiBbLTAuMTI3OTAxNzcgLTAuNTgwOTk5MzEgLTAuNDYxODg0MjYgLTAuMTgxNDgzMDJdDQogWy0wLjc2OTU5NDM1IC0xLjM3NDE0NTg3ICAxLjM3Njk2ODc0IC0wLjE4MDQwNTM3XV0NCg0KDQpNb2RhIHBvciBjb2x1bW5hOg0KDQpNb2RlUmVzdWx0KG1vZGU9YXJyYXkoW1stMC43Njk1OTQzNSwgLTEuMzc0MTQ1ODcsIC0wLjYyNjgxNDk2LCAtMC42MzMyOTAyOF1dKSwgY291bnQ9YXJyYXkoW1sxLCAxLCAxLCAxXV0pKQ0KYGBgIA0KDQoqIE1lZGlhbmE6IExhIG1lZGlhbmEgZXMgZWwgdmFsb3IgcXVlIG9jdXBhIGVsIGx1Z2FyIGNlbnRyYWwgZGUgdG9kb3MgbG9zIGRhdG9zIGN1YW5kbyDDqXN0b3MgZXN0w6FuIG9yZGVuYWRvcyBkZSBtZW5vciBhIG1heW9yLiBTZSByZXByZXNlbnRhIGNvbiB4y5wuDQoNCmBgYFB5dGhvbg0KIyBFamVtcGxvcyBkZSBlc3RhZGlzdGljYSBkZXNjcmlwdGl2YSBjb24gcHl0aG9uDQoNCmltcG9ydCBudW1weSBhcyBucCAjIGltcG9ydGFuZG8gbnVtcHkNCmZyb20gc2NpcHkgaW1wb3J0IHN0YXRzICMgaW1wb3J0YW5kbyBzY2lweS5zdGF0cw0KaW1wb3J0IHBhbmRhcyBhcyBwZCAjIGltcG9ydGFuZG8gcGFuZGFzDQoNCm5wLnJhbmRvbS5zZWVkKDIxMzE5ODIpICMgcGFyYSBwb2RlciByZXBsaWNhciBlbCByYW5kb20NCg0KZGF0b3MgPSBucC5yYW5kb20ucmFuZG4oNSwgNCkgIyBkYXRvcyBub3JtYWxtZW50ZSBkaXN0cmlidWlkb3MNCnByaW50KCJEYXRvczpcbiIpDQpwcmludChkYXRvcykNCnByaW50KCJcbiIpDQpwcmludCgiTWVkaWFuYSBwb3IgY29sdW1uYTpcbiIpDQpucC5tZWRpYW4oZGF0b3MsIDApICMgbWVkaWEgYXJpdG1ldGljYSBkZSBjYWRhIGNvbHVtbmENCi0tLS0tLS0tDQpEYXRvczoNCg0KW1sgMC40NjAzODAyMiAtMS4wODk0MjUyOCAtMC42MjY4MTQ5NiAtMC42MzMyOTAyOF0NCiBbLTAuMTA3NDAzMyAgLTAuODgxMzgwODIgLTAuMzQ0NjY2MjMgLTAuMjgzMjAyMTRdDQogWyAwLjk0MDUxMTcxICAwLjg2NjkzNzkzICAxLjIwOTQ3ODgyIC0wLjE2ODk0MTE4XQ0KIFstMC4xMjc5MDE3NyAtMC41ODA5OTkzMSAtMC40NjE4ODQyNiAtMC4xODE0ODMwMl0NCiBbLTAuNzY5NTk0MzUgLTEuMzc0MTQ1ODcgIDEuMzc2OTY4NzQgLTAuMTgwNDA1MzddXQ0KDQoNCk1lZGlhbmEgcG9yIGNvbHVtbmE6DQoNCmFycmF5KFstMC4xMDc0MDMzICwgLTAuODgxMzgwODIsIC0wLjM0NDY2NjIzLCAtMC4xODE0ODMwMl0pDQpgYGAgDQoNCiogQ29ycmVsYWNpw7NuOiBMYSBjb3JyZWxhY2nDs24gdHJhdGEgZGUgZXN0YWJsZWNlciBsYSByZWxhY2nDs24gbyBkZXBlbmRlbmNpYSBxdWUgZXhpc3RlIGVudHJlIGxhcyBkb3MgdmFyaWFibGVzIHF1ZSBpbnRlcnZpZW5lbiBlbiB1bmEgZGlzdHJpYnVjacOzbiBiaWRpbWVuc2lvbmFsLiBFcyBkZWNpciwgZGV0ZXJtaW5hciBzaSBsb3MgY2FtYmlvcyBlbiB1bmEgZGUgbGFzIHZhcmlhYmxlcyBpbmZsdXllbiBlbiBsb3MgY2FtYmlvcyBkZSBsYSBvdHJhLiBFbiBjYXNvIGRlIHF1ZSBzdWNlZGEsIGRpcmVtb3MgcXVlIGxhcyB2YXJpYWJsZXMgZXN0w6FuIGNvcnJlbGFjaW9uYWRhcyBvIHF1ZSBoYXkgY29ycmVsYWNpw7NuIGVudHJlIGVsbGFzLiBMYSBjb3JyZWxhY2nDs24gZXMgcG9zaXRpdmEgY3VhbmRvIGxvcyB2YWxvcmVzIGRlIGxhcyB2YXJpYWJsZXMgYXVtZW50YSBqdW50b3M7IHkgZXMgbmVnYXRpdmEgY3VhbmRvIHVuIHZhbG9yIGRlIHVuYSB2YXJpYWJsZSBzZSByZWR1Y2UgY3VhbmRvIGVsIHZhbG9yIGRlIGxhIG90cmEgdmFyaWFibGUgYXVtZW50YS4NCg0KYGBgUHl0aG9uDQojIEVqZW1wbG9zIGRlIGVzdGFkaXN0aWNhIGRlc2NyaXB0aXZhIGNvbiBweXRob24NCg0KaW1wb3J0IG51bXB5IGFzIG5wICMgaW1wb3J0YW5kbyBudW1weQ0KZnJvbSBzY2lweSBpbXBvcnQgc3RhdHMgIyBpbXBvcnRhbmRvIHNjaXB5LnN0YXRzDQppbXBvcnQgcGFuZGFzIGFzIHBkICMgaW1wb3J0YW5kbyBwYW5kYXMNCg0KbnAucmFuZG9tLnNlZWQoMjEzMTk4MikgIyBwYXJhIHBvZGVyIHJlcGxpY2FyIGVsIHJhbmRvbQ0KDQpkYXRvcyA9IG5wLnJhbmRvbS5yYW5kbig1LCA0KSAjIGRhdG9zIG5vcm1hbG1lbnRlIGRpc3RyaWJ1aWRvcw0KcHJpbnQoIkRhdG9zOlxuIikNCnByaW50KGRhdG9zKQ0KcHJpbnQoIlxuIikNCg0KIyBjb3JyZWxhY2lvbg0KcHJpbnQoIkNyZWEgbWF0cml6IGRlIGNvcnJlbGFjacOzbjpcbiIpDQpwcmludChucC5jb3JyY29lZihkYXRvcykpICMgQ3JlYSBtYXRyaXogZGUgY29ycmVsYWNpw7NuLg0KcHJpbnQoIlxuIikNCg0KIyBjYWxjdWxhbmRvIGxhIGNvcnJlbGFjacOzbiBlbnRyZSBkb3MgdmVjdG9yZXMuDQpwcmludCgiY2FsY3VsYW5kbyBsYSBjb3JyZWxhY2nDs24gZW50cmUgZG9zIHZlY3RvcmVzLjpcbiIpDQpucC5jb3JyY29lZihkYXRvc1swXSwgZGF0b3NbMV0pDQotLS0tLS0tLQ0KRGF0b3M6DQoNCltbIDAuNDYwMzgwMjIgLTEuMDg5NDI1MjggLTAuNjI2ODE0OTYgLTAuNjMzMjkwMjhdDQogWy0wLjEwNzQwMzMgIC0wLjg4MTM4MDgyIC0wLjM0NDY2NjIzIC0wLjI4MzIwMjE0XQ0KIFsgMC45NDA1MTE3MSAgMC44NjY5Mzc5MyAgMS4yMDk0Nzg4MiAtMC4xNjg5NDExOF0NCiBbLTAuMTI3OTAxNzcgLTAuNTgwOTk5MzEgLTAuNDYxODg0MjYgLTAuMTgxNDgzMDJdDQogWy0wLjc2OTU5NDM1IC0xLjM3NDE0NTg3ICAxLjM3Njk2ODc0IC0wLjE4MDQwNTM3XV0NCg0KDQpDcmVhIG1hdHJpeiBkZSBjb3JyZWxhY2nDs246DQoNCltbIDEuICAgICAgICAgIDAuODIzMzM3NDMgIDAuMTUyNTcyMDIgIDAuNzg3OTg2NzUgLTAuMDIyOTIwNzNdDQogWyAwLjgyMzMzNzQzICAxLiAgICAgICAgIC0wLjEzNzA5NjYyICAwLjg2ODczNjMyICAwLjQxMjM0ODc1XQ0KIFsgMC4xNTI1NzIwMiAtMC4xMzcwOTY2MiAgMS4gICAgICAgICAtMC40NzY5MTM3NiAgMC4yMTIxNjg1Nl0NCiBbIDAuNzg3OTg2NzUgIDAuODY4NzM2MzIgLTAuNDc2OTEzNzYgIDEuICAgICAgICAgLTAuMDM0NDU3MDVdDQogWy0wLjAyMjkyMDczICAwLjQxMjM0ODc1ICAwLjIxMjE2ODU2IC0wLjAzNDQ1NzA1ICAxLiAgICAgICAgXV0NCg0KDQpjYWxjdWxhbmRvIGxhIGNvcnJlbGFjacOzbiBlbnRyZSBkb3MgdmVjdG9yZXMuOg0KDQphcnJheShbWzEuICAgICAgICAsIDAuODIzMzM3NDNdLA0KICAgICAgIFswLjgyMzMzNzQzLCAxLiAgICAgICAgXV0pDQpgYGAgDQoNCiogQ292YXJpYW56YTogTGEgY292YXJpYW56YSBlcyBlbCBlcXVpdmFsZW50ZSBkZSBsYSB2YXJpYW56YSBhcGxpY2FkbyBhIHVuYSB2YXJpYWJsZSBiaWRpbWVuc2lvbmFsLiBFcyBsYSBtZWRpYSBhcml0bcOpdGljYSBkZSBsb3MgcHJvZHVjdG9zIGRlIGxhcyBkZXN2aWFjaW9uZXMgZGUgY2FkYSB1bmEgZGUgbGFzIHZhcmlhYmxlcyByZXNwZWN0byBhIHN1cyBtZWRpYXMgcmVzcGVjdGl2YXMuTGEgY292YXJpYW56YSBpbmRpY2EgZWwgc2VudGlkbyBkZSBsYSBjb3JyZWxhY2nDs24gZW50cmUgbGFzIHZhcmlhYmxlczsgU2kgz4N4eT4wIGxhIGNvcnJlbGFjacOzbiBlcyBkaXJlY3RhOyBTaSDPg3h5PDAgbGEgY29ycmVsYWNpw7NuIGVzIGludmVyc2EuDQoNCmBgYFB5dGhvbg0KIyBFamVtcGxvcyBkZSBlc3RhZGlzdGljYSBkZXNjcmlwdGl2YSBjb24gcHl0aG9uDQoNCmltcG9ydCBudW1weSBhcyBucCAjIGltcG9ydGFuZG8gbnVtcHkNCmZyb20gc2NpcHkgaW1wb3J0IHN0YXRzICMgaW1wb3J0YW5kbyBzY2lweS5zdGF0cw0KaW1wb3J0IHBhbmRhcyBhcyBwZCAjIGltcG9ydGFuZG8gcGFuZGFzDQoNCm5wLnJhbmRvbS5zZWVkKDIxMzE5ODIpICMgcGFyYSBwb2RlciByZXBsaWNhciBlbCByYW5kb20NCg0KZGF0b3MgPSBucC5yYW5kb20ucmFuZG4oNSwgNCkgIyBkYXRvcyBub3JtYWxtZW50ZSBkaXN0cmlidWlkb3MNCnByaW50KCJEYXRvczpcbiIpDQpwcmludChkYXRvcykNCnByaW50KCJcbiIpDQoNCg0KIyBDb3ZhcmlhbnphIGRlIGRvcyB2ZWN0b3Jlcw0KcHJpbnQoIkNvdmFyaWFuemEgZGUgZG9zIHZlY3RvcmVzOlxuIikNCm5wLmNvdihkYXRvc1swXSwgZGF0b3NbMV0pDQotLS0tLS0tLQ0KRGF0b3M6DQoNCltbIDAuNDYwMzgwMjIgLTEuMDg5NDI1MjggLTAuNjI2ODE0OTYgLTAuNjMzMjkwMjhdDQogWy0wLjEwNzQwMzMgIC0wLjg4MTM4MDgyIC0wLjM0NDY2NjIzIC0wLjI4MzIwMjE0XQ0KIFsgMC45NDA1MTE3MSAgMC44NjY5Mzc5MyAgMS4yMDk0Nzg4MiAtMC4xNjg5NDExOF0NCiBbLTAuMTI3OTAxNzcgLTAuNTgwOTk5MzEgLTAuNDYxODg0MjYgLTAuMTgxNDgzMDJdDQogWy0wLjc2OTU5NDM1IC0xLjM3NDE0NTg3ICAxLjM3Njk2ODc0IC0wLjE4MDQwNTM3XV0NCg0KDQpDb3ZhcmlhbnphIGRlIGRvcyB2ZWN0b3JlczoNCg0KYXJyYXkoW1swLjQzMzUwOTU4LCAwLjE4MDg3MjgxXSwNCiAgICAgICBbMC4xODA4NzI4MSwgMC4xMTEzMjQ4NV1dKQ0KYGBgIA0KDQoNCg0KICAgIA0KDQoNCg0KIA0K