3.2. Fonctions vectorisées

Les fonctions vectorisées sont des fonctions qui calculent automatiquement sur des tableaux, point par point. La plus part des opérateurs de python fonctionne sur des tableaux.

  • Opérateurs mathématiques. On peut tout utiliser (comme les puissances ou modulos…).

    def arctan(x, N_max=100):
         """ Calcule arctan par le développement limité"""
         n = np.arange(N_max)
         return np.sum((-1)**n * x**(2*n+1)/(2*n+1))
    
    print(arctan(1), np.arctan(1))
    
    0.782898225889638 0.7853981633974483
    
  • Opérateurs logiques:

    a = np.array([1, 2, 5, 3, 5])
    b = np.array([0, 2, 3, 7, 5])
    print(a==b)
    print(a>b)
    print( (a>b) | (a<b) )
    print(a!=b)
    print(~(a==b))
    print((a>b) & ((a%2)==1)) # nb impaires
    
    [False  True False False  True]
    [ True False  True False False]
    [ True False  True  True False]
    [ True False  True  True False]
    [ True False  True  True False]
    [ True False  True False False]
    

    Avertissement

    Ça n’a pas de sens d’utiliser les fonctions and ou or. Rappelons que ce sont des if implicites. Et dire, par exemple, « Si mon tableau est positif » n’a pas de sens. Seules les phrases du type : « si tous les élements sont positifs » ou « si au moins un élément est positif » ont un sens. C’est ce que le message d’erreur suivant dit :

    if (a>b):
       print(a)
    
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-4-bdbbc477d411> in <module>
    ----> 1 if (a>b):
          2    print(a)
    
    ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
    
  • Fonctions mathématiques: Les fonctions de bases sont dans le module numpy. Les fonctions spéciales peuvent se retrouver dans le module scipy.special

    Exemple : calcul de l”éclairement d’une tache d’Airy

    from scipy.special import jv
    def eclairement_airy(theta, d=1E-4, lamb=632E-9):
        """ Eclairement d'un disque """
        x = np.sin(theta)*d/lamb
        return (2*jv(1, np.pi*x)/(np.pi*x))**2
    
    print(eclairement_airy(theta=2E-3))
    
    0.7769307179570368
    
  • Fonctions définie par l’utilisateur. Il n’y a rien de magique : une fonction sera vectorisée, si sont contenu l’est.

    def position(x0, v0, t, g=9.81):
        """ Calcule la position d'un point après un temps de chute """
        return x0 + v0*t + g*t**2/2
    
    N = 10
    sigma_x = 100E-6
    sigma_v = 10E-3
    t = 100E-3
    x0 = np.random.normal(sigma_x, size=(N,3))
    v0 = np.random.normal(sigma_v, size=(N,3))
    xf = position(x0, v0, t)
    

    Par contre, le code suivant va créer une erreur :

    def valeur_absolue(x):
        if x>=0:
            return x
        else:
            return -x
    
    print(valeur_absolue(-1))
    print(valeur_absolue(np.array([-1, 1])))
    
    1
    
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-7-9d3584a1f876> in <module>
          6 
          7 print(valeur_absolue(-1))
    ----> 8 print(valeur_absolue(np.array([-1, 1])))
    
    <ipython-input-7-9d3584a1f876> in valeur_absolue(x)
          1 def valeur_absolue(x):
    ----> 2     if x>=0:
          3         return x
          4     else:
          5         return -x
    
    ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
    

    Si une fonction fonctionne avec des nombres scalaires, il est possible de la vectoriser automatiquement grâce à la fonction np.vectorize

    valeur_absolue_vec = np.vectorize(valeur_absolue)
    print(valeur_absolue_vec(np.array([-1, 1])))
    
    [1 1]
    

    Cette fonction va automatiser la création de la boucle for pour l’utilisateur. Elle sera lente comparée au fonction vectorisée native de numpy. On peut le voir en utilisant la commande “magic” de IPython %timeit

    x = np.random.normal(size=100000)
    
    %timeit -n 10 np.abs(x)
    %timeit -n 10 valeur_absolue_vec(x)
    
    36.5 µs ± 15.3 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
    12.3 ms ± 623 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    Il est possible d’utiliser une décorateur pour créer directement une fonction vectorisée :

    @np.vectorize
    def my_abs(x):
        if x>=0:
            return x
        else:
            return -x