<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 <center>
    <h1> Scientific Programming in Python  </h1>
    <h2> Topic 4
Step4:  En esta actividad implementaremos una conocida métrica para medir disimilitud entre conjuntos
Step8:  Paso 2.
Notar que los cambios son mínimos
Step9:  Paso 3.
<ASSISTANT_TASK:>
Python Code:
import numba
import numpy as np
import numexpr as ne
import matplotlib.pyplot as plt
Explanation: <center>
    <h1> Scientific Programming in Python  </h1>
    <h2> Topic 4: Just in Time Compilation: Numba and NumExpr </h2> 
</center>
Notebook created by Martín Villanueva - martin.villanueva@usm.cl - DI UTFSM - April 2017.
End of explanation
def metric_python(x, y):
    
    standard Euclidean distance
    
    ret = x-y
    ret *= ret
    return np.sqrt(ret).sum()
def inf_dist_python(x, Y):
    
    inf distance between row x and array Y
    
    m = Y.shape[0]
    inf = np.inf
    
    for i in range(m):
        dist = metric_python(x, Y[i])
        if dist < inf:
            inf = dist
    return inf
def hausdorff_python(X, Y):
    
    Hausdorff distance between arrays X and Y
    
    m = X.shape[0]
    n = Y.shape[0]
    sup1 = -1.
    sup2 = -1.
    
    for i in range(m):
        inf1 = inf_dist_python(X[i], Y)
        if inf1 > sup1:
            sup1 = inf1
    for i in range(n):
        inf2 = inf_dist_python(Y[i], X)
        if inf2 > sup2:
            sup2 = inf2
            
    return max(sup1, sup2)
Explanation: En esta actividad implementaremos una conocida métrica para medir disimilitud entre conjuntos: La métrica de Hausdorff. Esta corresponde a un métrica o distancia ocupada para medir cuán disímiles son dos subconjuntos dados. 
Esta tiene muchas aplicaciones, en particular para comparar el parecido entre imágenes. En el caso en donde los conjuntos son arreglos bidimensionales, la definición es la siguiente:
Sean $X \in \mathbb{R}^{m \times 3}$ e  $Y \in \mathbb{R}^{n \times 3}$ dos matrices, la métrica/distancia de Hausdorff sobre sobre estas como:
$$
d_H(X,Y) = \max \left(\ \max_{i\leq m} \min_{j \leq n} d(X[i],Y[j]), \ \max_{j\leq n} \min_{i \leq m} d(Y[j],X[i]) \ \right)
$$
donde $d$ es la distancia Euclideana clásica. ($X[i]$ indíca la i-ésima fila de X).
Ilustración unidimensional: Distancia entre funciones.
<img src='data/hausdorff.png' style="width: 600px;">
Implemente la métrica de Hausdorff en Python clásico.
Implemente la métrica de Hausdorff usando Numba (Forzando el modo nopython y definiendo explícitamente las signatures de las funciones).
Cree 10 arreglos $X,Y$ aleatorios, con cantidad creciente de filas, y realice análsis de tiempos de ejecuciones de las funciones anteriores sobre estos arreglos.
Concluya.
Paso 1.
End of explanation
@numba.jit('float64 (float64[:], float64[:])')
def metric_numba(x, y):
    
    standard Euclidean distance
    
    ret = x-y
    ret *= ret
    return np.sqrt(ret).sum()
@numba.jit('float64 (float64[:], float64[:,:])', nopython=True)
def inf_dist_numba(x, Y):
    
    inf distance between row x and array Y
    
    m = Y.shape[0]
    inf = np.inf
    
    for i in range(m):
        dist = metric_numba(x, Y[i])
        if dist < inf:
            inf = dist
    return inf
@numba.jit('float64 (float64[:,:], float64[:,:])', nopython=True)
def hausdorff_numba(X, Y):
    
    Hausdorff distance between arrays X and Y
    
    m = X.shape[0]
    n = Y.shape[0]
    sup1 = -1.
    sup2 = -1.
    
    for i in range(m):
        inf1 = inf_dist_numba(X[i], Y)
        if inf1 > sup1:
            sup1 = inf1
    for i in range(n):
        inf2 = inf_dist_numba(Y[i], X)
        if inf2 > sup2:
            sup2 = inf2
            
    return max(sup1, sup2)
Explanation: Paso 2.
Notar que los cambios son mínimos: Decoradores + nombres de funciones.
End of explanation
#nrows = [10**n for n in range(10)]
nrows = np.linspace(100,5000,10).astype(int)
for nrow in nrows:
    X = np.random.random((nrow,3))
    Y = np.random.random((nrow,3))
    tp = %timeit -o hausdorff_python(X,Y)
    tn = %timeit -o hausdorff_numba(X,Y)
    print("Number of rows: {0}".format(nrow))
    print("Best time in Python: {0}".format(tp.best))
    print("Best time in Numba: {0} \n".format(tn.best))
    del X,Y
Explanation: Paso 3.
End of explanation
<END_TASK>