-- Introduction au Python scientifique --

Les bases du Python et de numpy

Introduction au python scientifique

-

simon.marie@lecnam.net

Ce notebook présente les bases du language Python qui seront utiles pour les TP du cours. Vous devez déjà avoir quelques notions en python. Ci ce n'est pas le cas vous pouvez par exemple regarder cette page.

Plan du Notebook:

  1. Exécution d'un script en Python
  2. Importation des modules
  3. Manipulation des Vecteurs et matrices
  4. Les boucles
  5. Les fonctions
  6. Représentations graphiques
  7. Conclusion

Exécution d'un script Python

Le langage Python est un langage précompilé ne nécessitant donc aucune compilation. Il y a 3 façons principales d'utiliser Python:

  1. Ecrire un script dans un fichier .py et le lancer en ligne de commande:
    \$> python script.py
    Pour éditer des scripts, on utilise un éditeur spécialisé qui reconnait la syntaxe par exemple Visual Studio Code, Atom, SublimeText, vim, emacs, gedit, notepad++...
  2. Utiliser directement Python en ligne de commande avec Ipython ou Spyder
  3. Utiliser un Notebook Jupyter (C'est ce moyen qui est utilisé ici):
    \$> jupyter notebook filename.ipynb

Pour un projet large et complexe, on préfèrera la première solution, pour faire des petits tests ou pour une utilisation locale et restreinte on préfèrera la seconde solution. Pour les TP, les rapports et tous les support nécessitant une diffusion large et une utilisation de texte, le Notebook constitue la solution idéale.

Importation des modules

Les premères lignes d'un script Python sont consacrées à l'appel des modules nécessaires aux calculs. Les modules sont des librairies de fonctions qui permettent une utilisation plus simple de certaines notions. On utilise pour cela la commande import. Pour les besoins des TP de simulation numérique, les modules numpy, matplotlib et math seront nécessaires. Le début du script ressemblera donc à:

In [1]:
import numpy as np # contitent des fonctions utiles pour le traitement des matrices
import matplotlib.pyplot as plt # contitent des fonctions utiles pour la representation graphique
import time # Pour évaluer les temps de calcul
%matplotlib inline 
# permet une manipulation des figures dans le notebook directement.

Les lignes ci-dessus importent le module numpy en le nommant np. Ainsi tous les appels à des fonctions propres aux modules numpy seront précédés de np.:

In [2]:
y=np.sin(np.pi) # calcul sin(pi)
print(y)
print(np.sqrt(np.log(np.exp(4))))
1.2246467991473532e-16
2.0

Pour obtenir de l'aide sur n'importe quelle fonction, il suffit de taper help(nom_fonction):

In [3]:
help(np.sin)
Help on ufunc object:

sin = class ufunc(builtins.object)
 |  Functions that operate element by element on whole arrays.
 |  
 |  To see the documentation for a specific ufunc, use `info`.  For
 |  example, ``np.info(np.sin)``.  Because ufuncs are written in C
 |  (for speed) and linked into Python with NumPy's ufunc facility,
 |  Python's help() function finds this page whenever help() is called
 |  on a ufunc.
 |  
 |  A detailed explanation of ufuncs can be found in the docs for :ref:`ufuncs`.
 |  
 |  **Calling ufuncs:** ``op(*x[, out], where=True, **kwargs)``
 |  
 |  Apply `op` to the arguments `*x` elementwise, broadcasting the arguments.
 |  
 |  The broadcasting rules are:
 |  
 |  * Dimensions of length 1 may be prepended to either array.
 |  * Arrays may be repeated along dimensions of length 1.
 |  
 |  Parameters
 |  ----------
 |  *x : array_like
 |      Input arrays.
 |  out : ndarray, None, or tuple of ndarray and None, optional
 |      Alternate array object(s) in which to put the result; if provided, it
 |      must have a shape that the inputs broadcast to. A tuple of arrays
 |      (possible only as a keyword argument) must have length equal to the
 |      number of outputs; use None for uninitialized outputs to be
 |      allocated by the ufunc.
 |  where : array_like, optional
 |      This condition is broadcast over the input. At locations where the
 |      condition is True, the `out` array will be set to the ufunc result.
 |      Elsewhere, the `out` array will retain its original value.
 |      Note that if an uninitialized `out` array is created via the default
 |      ``out=None``, locations within it where the condition is False will
 |      remain uninitialized.
 |  **kwargs
 |      For other keyword-only arguments, see the :ref:`ufunc docs <ufuncs.kwargs>`.
 |  
 |  Returns
 |  -------
 |  r : ndarray or tuple of ndarray
 |      `r` will have the shape that the arrays in `x` broadcast to; if `out` is
 |      provided, it will be returned. If not, `r` will be allocated and
 |      may contain uninitialized values. If the function has more than one
 |      output, then the result will be a tuple of arrays.
 |  
 |  Methods defined here:
 |  
 |  __call__(self, /, *args, **kwargs)
 |      Call self as a function.
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  __str__(self, /)
 |      Return str(self).
 |  
 |  accumulate(...)
 |      accumulate(array, axis=0, dtype=None, out=None)
 |      
 |      Accumulate the result of applying the operator to all elements.
 |      
 |      For a one-dimensional array, accumulate produces results equivalent to::
 |      
 |        r = np.empty(len(A))
 |        t = op.identity        # op = the ufunc being applied to A's  elements
 |        for i in range(len(A)):
 |            t = op(t, A[i])
 |            r[i] = t
 |        return r
 |      
 |      For example, add.accumulate() is equivalent to np.cumsum().
 |      
 |      For a multi-dimensional array, accumulate is applied along only one
 |      axis (axis zero by default; see Examples below) so repeated use is
 |      necessary if one wants to accumulate over multiple axes.
 |      
 |      Parameters
 |      ----------
 |      array : array_like
 |          The array to act on.
 |      axis : int, optional
 |          The axis along which to apply the accumulation; default is zero.
 |      dtype : data-type code, optional
 |          The data-type used to represent the intermediate results. Defaults
 |          to the data-type of the output array if such is provided, or the
 |          the data-type of the input array if no output array is provided.
 |      out : ndarray, None, or tuple of ndarray and None, optional
 |          A location into which the result is stored. If not provided or None,
 |          a freshly-allocated array is returned. For consistency with
 |          ``ufunc.__call__``, if given as a keyword, this may be wrapped in a
 |          1-element tuple.
 |      
 |          .. versionchanged:: 1.13.0
 |             Tuples are allowed for keyword argument.
 |      
 |      Returns
 |      -------
 |      r : ndarray
 |          The accumulated values. If `out` was supplied, `r` is a reference to
 |          `out`.
 |      
 |      Examples
 |      --------
 |      1-D array examples:
 |      
 |      >>> np.add.accumulate([2, 3, 5])
 |      array([ 2,  5, 10])
 |      >>> np.multiply.accumulate([2, 3, 5])
 |      array([ 2,  6, 30])
 |      
 |      2-D array examples:
 |      
 |      >>> I = np.eye(2)
 |      >>> I
 |      array([[1.,  0.],
 |             [0.,  1.]])
 |      
 |      Accumulate along axis 0 (rows), down columns:
 |      
 |      >>> np.add.accumulate(I, 0)
 |      array([[1.,  0.],
 |             [1.,  1.]])
 |      >>> np.add.accumulate(I) # no axis specified = axis zero
 |      array([[1.,  0.],
 |             [1.,  1.]])
 |      
 |      Accumulate along axis 1 (columns), through rows:
 |      
 |      >>> np.add.accumulate(I, 1)
 |      array([[1.,  1.],
 |             [0.,  1.]])
 |  
 |  at(...)
 |      at(a, indices, b=None, /)
 |      
 |      Performs unbuffered in place operation on operand 'a' for elements
 |      specified by 'indices'. For addition ufunc, this method is equivalent to
 |      ``a[indices] += b``, except that results are accumulated for elements that
 |      are indexed more than once. For example, ``a[[0,0]] += 1`` will only
 |      increment the first element once because of buffering, whereas
 |      ``add.at(a, [0,0], 1)`` will increment the first element twice.
 |      
 |      .. versionadded:: 1.8.0
 |      
 |      Parameters
 |      ----------
 |      a : array_like
 |          The array to perform in place operation on.
 |      indices : array_like or tuple
 |          Array like index object or slice object for indexing into first
 |          operand. If first operand has multiple dimensions, indices can be a
 |          tuple of array like index objects or slice objects.
 |      b : array_like
 |          Second operand for ufuncs requiring two operands. Operand must be
 |          broadcastable over first operand after indexing or slicing.
 |      
 |      Examples
 |      --------
 |      Set items 0 and 1 to their negative values:
 |      
 |      >>> a = np.array([1, 2, 3, 4])
 |      >>> np.negative.at(a, [0, 1])
 |      >>> a
 |      array([-1, -2,  3,  4])
 |      
 |      Increment items 0 and 1, and increment item 2 twice:
 |      
 |      >>> a = np.array([1, 2, 3, 4])
 |      >>> np.add.at(a, [0, 1, 2, 2], 1)
 |      >>> a
 |      array([2, 3, 5, 4])
 |      
 |      Add items 0 and 1 in first array to second array,
 |      and store results in first array:
 |      
 |      >>> a = np.array([1, 2, 3, 4])
 |      >>> b = np.array([1, 2])
 |      >>> np.add.at(a, [0, 1], b)
 |      >>> a
 |      array([2, 4, 3, 4])
 |  
 |  outer(...)
 |      outer(A, B, /, **kwargs)
 |      
 |      Apply the ufunc `op` to all pairs (a, b) with a in `A` and b in `B`.
 |      
 |      Let ``M = A.ndim``, ``N = B.ndim``. Then the result, `C`, of
 |      ``op.outer(A, B)`` is an array of dimension M + N such that:
 |      
 |      .. math:: C[i_0, ..., i_{M-1}, j_0, ..., j_{N-1}] =
 |         op(A[i_0, ..., i_{M-1}], B[j_0, ..., j_{N-1}])
 |      
 |      For `A` and `B` one-dimensional, this is equivalent to::
 |      
 |        r = empty(len(A),len(B))
 |        for i in range(len(A)):
 |            for j in range(len(B)):
 |                r[i,j] = op(A[i], B[j])  # op = ufunc in question
 |      
 |      Parameters
 |      ----------
 |      A : array_like
 |          First array
 |      B : array_like
 |          Second array
 |      kwargs : any
 |          Arguments to pass on to the ufunc. Typically `dtype` or `out`.
 |          See `ufunc` for a comprehensive overview of all available arguments.
 |      
 |      Returns
 |      -------
 |      r : ndarray
 |          Output array
 |      
 |      See Also
 |      --------
 |      numpy.outer : A less powerful version of ``np.multiply.outer``
 |                    that `ravel`\ s all inputs to 1D. This exists
 |                    primarily for compatibility with old code.
 |      
 |      tensordot : ``np.tensordot(a, b, axes=((), ()))`` and
 |                  ``np.multiply.outer(a, b)`` behave same for all
 |                  dimensions of a and b.
 |      
 |      Examples
 |      --------
 |      >>> np.multiply.outer([1, 2, 3], [4, 5, 6])
 |      array([[ 4,  5,  6],
 |             [ 8, 10, 12],
 |             [12, 15, 18]])
 |      
 |      A multi-dimensional example:
 |      
 |      >>> A = np.array([[1, 2, 3], [4, 5, 6]])
 |      >>> A.shape
 |      (2, 3)
 |      >>> B = np.array([[1, 2, 3, 4]])
 |      >>> B.shape
 |      (1, 4)
 |      >>> C = np.multiply.outer(A, B)
 |      >>> C.shape; C
 |      (2, 3, 1, 4)
 |      array([[[[ 1,  2,  3,  4]],
 |              [[ 2,  4,  6,  8]],
 |              [[ 3,  6,  9, 12]]],
 |             [[[ 4,  8, 12, 16]],
 |              [[ 5, 10, 15, 20]],
 |              [[ 6, 12, 18, 24]]]])
 |  
 |  reduce(...)
 |      reduce(array, axis=0, dtype=None, out=None, keepdims=False, initial=<no value>, where=True)
 |      
 |      Reduces `array`'s dimension by one, by applying ufunc along one axis.
 |      
 |      Let :math:`array.shape = (N_0, ..., N_i, ..., N_{M-1})`.  Then
 |      :math:`ufunc.reduce(array, axis=i)[k_0, ..,k_{i-1}, k_{i+1}, .., k_{M-1}]` =
 |      the result of iterating `j` over :math:`range(N_i)`, cumulatively applying
 |      ufunc to each :math:`array[k_0, ..,k_{i-1}, j, k_{i+1}, .., k_{M-1}]`.
 |      For a one-dimensional array, reduce produces results equivalent to:
 |      ::
 |      
 |       r = op.identity # op = ufunc
 |       for i in range(len(A)):
 |         r = op(r, A[i])
 |       return r
 |      
 |      For example, add.reduce() is equivalent to sum().
 |      
 |      Parameters
 |      ----------
 |      array : array_like
 |          The array to act on.
 |      axis : None or int or tuple of ints, optional
 |          Axis or axes along which a reduction is performed.
 |          The default (`axis` = 0) is perform a reduction over the first
 |          dimension of the input array. `axis` may be negative, in
 |          which case it counts from the last to the first axis.
 |      
 |          .. versionadded:: 1.7.0
 |      
 |          If this is None, a reduction is performed over all the axes.
 |          If this is a tuple of ints, a reduction is performed on multiple
 |          axes, instead of a single axis or all the axes as before.
 |      
 |          For operations which are either not commutative or not associative,
 |          doing a reduction over multiple axes is not well-defined. The
 |          ufuncs do not currently raise an exception in this case, but will
 |          likely do so in the future.
 |      dtype : data-type code, optional
 |          The type used to represent the intermediate results. Defaults
 |          to the data-type of the output array if this is provided, or
 |          the data-type of the input array if no output array is provided.
 |      out : ndarray, None, or tuple of ndarray and None, optional
 |          A location into which the result is stored. If not provided or None,
 |          a freshly-allocated array is returned. For consistency with
 |          ``ufunc.__call__``, if given as a keyword, this may be wrapped in a
 |          1-element tuple.
 |      
 |          .. versionchanged:: 1.13.0
 |             Tuples are allowed for keyword argument.
 |      keepdims : bool, optional
 |          If this is set to True, the axes which are reduced are left
 |          in the result as dimensions with size one. With this option,
 |          the result will broadcast correctly against the original `array`.
 |      
 |          .. versionadded:: 1.7.0
 |      initial : scalar, optional
 |          The value with which to start the reduction.
 |          If the ufunc has no identity or the dtype is object, this defaults
 |          to None - otherwise it defaults to ufunc.identity.
 |          If ``None`` is given, the first element of the reduction is used,
 |          and an error is thrown if the reduction is empty.
 |      
 |          .. versionadded:: 1.15.0
 |      
 |      where : array_like of bool, optional
 |          A boolean array which is broadcasted to match the dimensions
 |          of `array`, and selects elements to include in the reduction. Note
 |          that for ufuncs like ``minimum`` that do not have an identity
 |          defined, one has to pass in also ``initial``.
 |      
 |          .. versionadded:: 1.17.0
 |      
 |      Returns
 |      -------
 |      r : ndarray
 |          The reduced array. If `out` was supplied, `r` is a reference to it.
 |      
 |      Examples
 |      --------
 |      >>> np.multiply.reduce([2,3,5])
 |      30
 |      
 |      A multi-dimensional array example:
 |      
 |      >>> X = np.arange(8).reshape((2,2,2))
 |      >>> X
 |      array([[[0, 1],
 |              [2, 3]],
 |             [[4, 5],
 |              [6, 7]]])
 |      >>> np.add.reduce(X, 0)
 |      array([[ 4,  6],
 |             [ 8, 10]])
 |      >>> np.add.reduce(X) # confirm: default axis value is 0
 |      array([[ 4,  6],
 |             [ 8, 10]])
 |      >>> np.add.reduce(X, 1)
 |      array([[ 2,  4],
 |             [10, 12]])
 |      >>> np.add.reduce(X, 2)
 |      array([[ 1,  5],
 |             [ 9, 13]])
 |      
 |      You can use the ``initial`` keyword argument to initialize the reduction
 |      with a different value, and ``where`` to select specific elements to include:
 |      
 |      >>> np.add.reduce([10], initial=5)
 |      15
 |      >>> np.add.reduce(np.ones((2, 2, 2)), axis=(0, 2), initial=10)
 |      array([14., 14.])
 |      >>> a = np.array([10., np.nan, 10])
 |      >>> np.add.reduce(a, where=~np.isnan(a))
 |      20.0
 |      
 |      Allows reductions of empty arrays where they would normally fail, i.e.
 |      for ufuncs without an identity.
 |      
 |      >>> np.minimum.reduce([], initial=np.inf)
 |      inf
 |      >>> np.minimum.reduce([[1., 2.], [3., 4.]], initial=10., where=[True, False])
 |      array([ 1., 10.])
 |      >>> np.minimum.reduce([])
 |      Traceback (most recent call last):
 |          ...
 |      ValueError: zero-size array to reduction operation minimum which has no identity
 |  
 |  reduceat(...)
 |      reduceat(array, indices, axis=0, dtype=None, out=None)
 |      
 |      Performs a (local) reduce with specified slices over a single axis.
 |      
 |      For i in ``range(len(indices))``, `reduceat` computes
 |      ``ufunc.reduce(array[indices[i]:indices[i+1]])``, which becomes the i-th
 |      generalized "row" parallel to `axis` in the final result (i.e., in a
 |      2-D array, for example, if `axis = 0`, it becomes the i-th row, but if
 |      `axis = 1`, it becomes the i-th column).  There are three exceptions to this:
 |      
 |      * when ``i = len(indices) - 1`` (so for the last index),
 |        ``indices[i+1] = array.shape[axis]``.
 |      * if ``indices[i] >= indices[i + 1]``, the i-th generalized "row" is
 |        simply ``array[indices[i]]``.
 |      * if ``indices[i] >= len(array)`` or ``indices[i] < 0``, an error is raised.
 |      
 |      The shape of the output depends on the size of `indices`, and may be
 |      larger than `array` (this happens if ``len(indices) > array.shape[axis]``).
 |      
 |      Parameters
 |      ----------
 |      array : array_like
 |          The array to act on.
 |      indices : array_like
 |          Paired indices, comma separated (not colon), specifying slices to
 |          reduce.
 |      axis : int, optional
 |          The axis along which to apply the reduceat.
 |      dtype : data-type code, optional
 |          The type used to represent the intermediate results. Defaults
 |          to the data type of the output array if this is provided, or
 |          the data type of the input array if no output array is provided.
 |      out : ndarray, None, or tuple of ndarray and None, optional
 |          A location into which the result is stored. If not provided or None,
 |          a freshly-allocated array is returned. For consistency with
 |          ``ufunc.__call__``, if given as a keyword, this may be wrapped in a
 |          1-element tuple.
 |      
 |          .. versionchanged:: 1.13.0
 |             Tuples are allowed for keyword argument.
 |      
 |      Returns
 |      -------
 |      r : ndarray
 |          The reduced values. If `out` was supplied, `r` is a reference to
 |          `out`.
 |      
 |      Notes
 |      -----
 |      A descriptive example:
 |      
 |      If `array` is 1-D, the function `ufunc.accumulate(array)` is the same as
 |      ``ufunc.reduceat(array, indices)[::2]`` where `indices` is
 |      ``range(len(array) - 1)`` with a zero placed
 |      in every other element:
 |      ``indices = zeros(2 * len(array) - 1)``,
 |      ``indices[1::2] = range(1, len(array))``.
 |      
 |      Don't be fooled by this attribute's name: `reduceat(array)` is not
 |      necessarily smaller than `array`.
 |      
 |      Examples
 |      --------
 |      To take the running sum of four successive values:
 |      
 |      >>> np.add.reduceat(np.arange(8),[0,4, 1,5, 2,6, 3,7])[::2]
 |      array([ 6, 10, 14, 18])
 |      
 |      A 2-D example:
 |      
 |      >>> x = np.linspace(0, 15, 16).reshape(4,4)
 |      >>> x
 |      array([[ 0.,   1.,   2.,   3.],
 |             [ 4.,   5.,   6.,   7.],
 |             [ 8.,   9.,  10.,  11.],
 |             [12.,  13.,  14.,  15.]])
 |      
 |      ::
 |      
 |       # reduce such that the result has the following five rows:
 |       # [row1 + row2 + row3]
 |       # [row4]
 |       # [row2]
 |       # [row3]
 |       # [row1 + row2 + row3 + row4]
 |      
 |      >>> np.add.reduceat(x, [0, 3, 1, 2, 0])
 |      array([[12.,  15.,  18.,  21.],
 |             [12.,  13.,  14.,  15.],
 |             [ 4.,   5.,   6.,   7.],
 |             [ 8.,   9.,  10.,  11.],
 |             [24.,  28.,  32.,  36.]])
 |      
 |      ::
 |      
 |       # reduce such that result has the following two columns:
 |       # [col1 * col2 * col3, col4]
 |      
 |      >>> np.multiply.reduceat(x, [0, 3], 1)
 |      array([[   0.,     3.],
 |             [ 120.,     7.],
 |             [ 720.,    11.],
 |             [2184.,    15.]])
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  identity
 |      The identity value.
 |      
 |      Data attribute containing the identity element for the ufunc, if it has one.
 |      If it does not, the attribute value is None.
 |      
 |      Examples
 |      --------
 |      >>> np.add.identity
 |      0
 |      >>> np.multiply.identity
 |      1
 |      >>> np.power.identity
 |      1
 |      >>> print(np.exp.identity)
 |      None
 |  
 |  nargs
 |      The number of arguments.
 |      
 |      Data attribute containing the number of arguments the ufunc takes, including
 |      optional ones.
 |      
 |      Notes
 |      -----
 |      Typically this value will be one more than what you might expect because all
 |      ufuncs take  the optional "out" argument.
 |      
 |      Examples
 |      --------
 |      >>> np.add.nargs
 |      3
 |      >>> np.multiply.nargs
 |      3
 |      >>> np.power.nargs
 |      3
 |      >>> np.exp.nargs
 |      2
 |  
 |  nin
 |      The number of inputs.
 |      
 |      Data attribute containing the number of arguments the ufunc treats as input.
 |      
 |      Examples
 |      --------
 |      >>> np.add.nin
 |      2
 |      >>> np.multiply.nin
 |      2
 |      >>> np.power.nin
 |      2
 |      >>> np.exp.nin
 |      1
 |  
 |  nout
 |      The number of outputs.
 |      
 |      Data attribute containing the number of arguments the ufunc treats as output.
 |      
 |      Notes
 |      -----
 |      Since all ufuncs can take output arguments, this will always be (at least) 1.
 |      
 |      Examples
 |      --------
 |      >>> np.add.nout
 |      1
 |      >>> np.multiply.nout
 |      1
 |      >>> np.power.nout
 |      1
 |      >>> np.exp.nout
 |      1
 |  
 |  ntypes
 |      The number of types.
 |      
 |      The number of numerical NumPy types - of which there are 18 total - on which
 |      the ufunc can operate.
 |      
 |      See Also
 |      --------
 |      numpy.ufunc.types
 |      
 |      Examples
 |      --------
 |      >>> np.add.ntypes
 |      18
 |      >>> np.multiply.ntypes
 |      18
 |      >>> np.power.ntypes
 |      17
 |      >>> np.exp.ntypes
 |      7
 |      >>> np.remainder.ntypes
 |      14
 |  
 |  signature
 |      Definition of the core elements a generalized ufunc operates on.
 |      
 |      The signature determines how the dimensions of each input/output array
 |      are split into core and loop dimensions:
 |      
 |      1. Each dimension in the signature is matched to a dimension of the
 |         corresponding passed-in array, starting from the end of the shape tuple.
 |      2. Core dimensions assigned to the same label in the signature must have
 |         exactly matching sizes, no broadcasting is performed.
 |      3. The core dimensions are removed from all inputs and the remaining
 |         dimensions are broadcast together, defining the loop dimensions.
 |      
 |      Notes
 |      -----
 |      Generalized ufuncs are used internally in many linalg functions, and in
 |      the testing suite; the examples below are taken from these.
 |      For ufuncs that operate on scalars, the signature is None, which is
 |      equivalent to '()' for every argument.
 |      
 |      Examples
 |      --------
 |      >>> np.core.umath_tests.matrix_multiply.signature
 |      '(m,n),(n,p)->(m,p)'
 |      >>> np.linalg._umath_linalg.det.signature
 |      '(m,m)->()'
 |      >>> np.add.signature is None
 |      True  # equivalent to '(),()->()'
 |  
 |  types
 |      Returns a list with types grouped input->output.
 |      
 |      Data attribute listing the data-type "Domain-Range" groupings the ufunc can
 |      deliver. The data-types are given using the character codes.
 |      
 |      See Also
 |      --------
 |      numpy.ufunc.ntypes
 |      
 |      Examples
 |      --------
 |      >>> np.add.types
 |      ['??->?', 'bb->b', 'BB->B', 'hh->h', 'HH->H', 'ii->i', 'II->I', 'll->l',
 |      'LL->L', 'qq->q', 'QQ->Q', 'ff->f', 'dd->d', 'gg->g', 'FF->F', 'DD->D',
 |      'GG->G', 'OO->O']
 |      
 |      >>> np.multiply.types
 |      ['??->?', 'bb->b', 'BB->B', 'hh->h', 'HH->H', 'ii->i', 'II->I', 'll->l',
 |      'LL->L', 'qq->q', 'QQ->Q', 'ff->f', 'dd->d', 'gg->g', 'FF->F', 'DD->D',
 |      'GG->G', 'OO->O']
 |      
 |      >>> np.power.types
 |      ['bb->b', 'BB->B', 'hh->h', 'HH->H', 'ii->i', 'II->I', 'll->l', 'LL->L',
 |      'qq->q', 'QQ->Q', 'ff->f', 'dd->d', 'gg->g', 'FF->F', 'DD->D', 'GG->G',
 |      'OO->O']
 |      
 |      >>> np.exp.types
 |      ['f->f', 'd->d', 'g->g', 'F->F', 'D->D', 'G->G', 'O->O']
 |      
 |      >>> np.remainder.types
 |      ['bb->b', 'BB->B', 'hh->h', 'HH->H', 'ii->i', 'II->I', 'll->l', 'LL->L',
 |      'qq->q', 'QQ->Q', 'ff->f', 'dd->d', 'gg->g', 'OO->O']

Manipulation des vecteurs / matrices

Par défaut, les listes créées nativement en python à l'aide des crochets ne sont pas très utiles:

In [4]:
A=[0,1,2,3,4,5,6,7,8]
B=[[0,1,2],[3,4,5],[6,7,8]]

print(A)
print(B)
print(B[1][1])
[0, 1, 2, 3, 4, 5, 6, 7, 8]
[[0, 1, 2], [3, 4, 5], [6, 7, 8]]
4

Les objets A et B sont des listes on ne peut pas faire d'opérations avec:

In [5]:
print(type(A),type(B))
print(2*A)
print(B*B)
<class 'list'> <class 'list'>
[0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-9c966d6da90e> in <module>
      1 print(type(A),type(B))
      2 print(2*A)
----> 3 print(B*B)

TypeError: can't multiply sequence by non-int of type 'list'

Le module $\textit{numpy}$ permet la manipulation des matrices dans python. Avec numpy, on peut facilement transformer ces objets en matrices:

In [6]:
D=np.array(A)
E=np.array(B)
print(type(E))
print(E[1,1])
print()
print(2*D)
print()
print(E*E) # produit terme à terme ! ce n'est pas un produit matricielle
print()
print(np.dot(E,E)) # produit matriciel
<class 'numpy.ndarray'>
4

[ 0  2  4  6  8 10 12 14 16]

[[ 0  1  4]
 [ 9 16 25]
 [36 49 64]]

[[ 15  18  21]
 [ 42  54  66]
 [ 69  90 111]]

Pour créer des vecteurs et des matrices on pourra donc utiliser des fonction natives du module numpy. Ainsi pour initialiser et créer une matrice pleine de 0 on utilisera la fonction $\textit{zeros}$ de $\textit{numpy}$.

In [7]:
nx=10
ny=4
A=np.zeros((nx,ny)) # Matrice de 10 lignes et 4 colonnes ne contenant que des 0
print(A)
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]

La fonction arange(n) de $\textit{numpy}$ sert a créer une liste d'entier allant de $0$ à $n-1$.

In [8]:
B=np.arange(nx) # Vecteur de nx elements allant de 0 à 9
print(B)
[0 1 2 3 4 5 6 7 8 9]

Attention, en python les indices commencent à 0 ! Pour sélectionner un seul élément, il suffit de passer l'incie entre crochet:

$$ B[indice] $$
In [9]:
print(B[0]) # Le premier élément
print(B[1]) # Le deuxième élément
print(B[nx-1]) # Le dernier
print(B[-1]) # Le dernier
print(B[nx-2]) # L'avant dernier
print(B[-2]) # L'avant dernier
0
1
9
9
8
8

On peut aussi sélectionner ou modifier une partie de matrice en sélectionnant une tranche. Pour cela on utilise la syntaxe général:

$$ Matrice[ début : fin : pas ] $$

Si on ne précise pas de valeurs pour ces bornes, les valeurs extrêmes sont choisies par défaut comme présenté ci-dessous:

In [10]:
print(B[:]) # Tous les elements: équivalent de B[::]
print(B[0:])  # Equivalent à B[:]
print(B[0:nx])  # Equivalent à B[:]

print()

print(B[:5]) # 5 premiers Elements 
print(B[2:5]) # tranche du 3ème au 5ème 
print(B[-5:]) # 5 derniers

# B[n1:n2] # selection de n1 a n2-1 donc ligne n1+1 a n2
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]

[0 1 2 3 4]
[2 3 4]
[5 6 7 8 9]

On peut enfin faire varier la valeur du pas qui peut même prendre des valeurs négatives:

In [11]:
print(B[::2]) # 1 élément sur 2 => équivalent de B[0:nx:2]
print(B[1::2]) # 1 élément sur 2 en partant du deuxième => équivalent de B[1:nx:2]

print()

print(B[::-1]) # tous les élements dans l'ordre inverse : pas négatif
[0 2 4 6 8]
[1 3 5 7 9]

[9 8 7 6 5 4 3 2 1 0]

Pour créer des vecteurs contenant des suites de nombres régulièrement espacés (par exemple pour des maillages) on peut utiliser:

  • Soit la fonction $\textit{arange(start,end,step)}$ de $\textit{numpy}$ qui impose le début la fin et le pas:
In [12]:
A=np.arange(-2,2,0.5) # Vecteur contenant tous les nombres entre -2 et 1.5 par pas de 0.5
print(A)
[-2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5]
  • Soit la fonction linespace(start,end,nombre) de numpy qui impose le début, la fin et le nombre d'élément:
In [13]:
A=np.linspace(-2,2,10) # Vecteur contenant 10 élément régulièrement espacés entre -2 et 2
print(A)
[-2.         -1.55555556 -1.11111111 -0.66666667 -0.22222222  0.22222222
  0.66666667  1.11111111  1.55555556  2.        ]

Pour créer une matrice diagonale on utilise la fonction $\textit{eye(nx,ny)}$ de $\textit{numpy}$:

In [14]:
B=np.eye(3,3) # Matrice identite de 3*3 elements
print(B)
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

Enfin, pour créer une matrice complexe à partir d'un vecteur ou d'une autre matrice de taille différente, on peut utiliser une fonction très puissante, la fonction reshape. La seule condition est que le nombre d'élements total reste le même:

In [15]:
C=np.linspace(2.1,1.2,16)
D=C.reshape(4,4)
print(C)
print()
print(D)
[2.1  2.04 1.98 1.92 1.86 1.8  1.74 1.68 1.62 1.56 1.5  1.44 1.38 1.32
 1.26 1.2 ]

[[2.1  2.04 1.98 1.92]
 [1.86 1.8  1.74 1.68]
 [1.62 1.56 1.5  1.44]
 [1.38 1.32 1.26 1.2 ]]

Le premier objet est une matrice 1x16, le second une matrice 4x4.

Pour inverser une matrice on peut utiliser la fonction inv de linalg:

In [16]:
Dinv=np.linalg.inv(D)
print(np.dot(D,Dinv)) # produit matriciel avec l'inverse
[[ 1.  0. -4.  1.]
 [ 0.  1. -1.  1.]
 [ 1.  2.  2. -2.]
 [ 2.  1. -1. -2.]]

Les boucles python

L’ensemble de la syntaxe python est basée sur l'indentation du texte. Ainsi toutes les lignes devront commencer au même endroit. Le début et la fin d'une boucle (for, if, while...) sera repérée par une indentation différente. C'est une syntaxe propre au Python qui permet de s'affranchir des syntaxe de type end for mais qui impose d'être rigoureux dans l'écriture du code ! Par convention on utilise une indentation de 4 espaces. Ainsi, une boucle for s'écrira de la façon suivante:

In [17]:
A=np.arange(10)
j=1
for i in A:
    j+=2 # equivalent de j=j+2
    print(j,i,i**2,i**3)
3 0 0 0
5 1 1 1
7 2 4 8
9 3 9 27
11 4 16 64
13 5 25 125
15 6 36 216
17 7 49 343
19 8 64 512
21 9 81 729

Attention a ne pas oublier le signe $:$ à la fin de la première ligne de boucle.

Notes sur les Boucles et le slicing en Python

Si l'on doit effectuer des boucles sur un très grand nombre de points, le temps de restitution risque de devenir très long. Dans ce cas on préferrera faire du "slicing" c'est à dire crire une seule ligne en décalant les indices. On écrit des tranches d'indices (slice en anglais).

L'exemple suivant illustre le même calcul réalisé en boucle puis en slicing. Pour le slicing, on remplace i par la tranche 1:-1 (du deuxième indice à l'avant dernier). Donc:

i => 1:-1

i+1 => 2:

i-1 => 0:-2

In [20]:
nx=1000000 # On prend un nombre important de points pour que la démonstation soit convainquante:
f1=np.zeros(nx)
f2=np.zeros(nx)
g=np.ones(nx)

# boucle
t0=time.time()
for i in np.arange(1,nx-1):
    f1[i]=g[i+1]+g[i]+g[i-1]
t1=time.time()-t0

# slicing
t0=time.time()
f2[1:-1]=g[2:]+g[1:-1]+g[0:-2]
t2=time.time()-t0
print(t1,t2,t1/t2)
1.3908777236938477 0.003908395767211914 355.86921246873663

Ici le slicing est donc trois-cent fois plus rapide !!

On peut vérifier que les fonctions $f_1$ et $f_2$ sont bien identique

In [21]:
np.max(abs(f1-f2))
Out[21]:
0.0

Utilisation des fonctions

Dans tous les langages de programmation, il est possible d'effectuer une tache répétitive à l'aide d'une fonction. En python on utilise pour cela le mot clé def suivit du nom de la fonction que l'on veut créer. Essayons par exemple de créer une fonction qui calcul la valeur d'un polynôme en x:

In [22]:
def ma_fonction(x):
    return 2.*x**2-3.*x+1

print(ma_fonction(-1.))
print(ma_fonction(0.))
print(ma_fonction(1.))
6.0
1.0
0.0

On peut également créer des fonctions un peu plus évoluées comme par exemple la fonction factorielle qui utilise la récusrion (le fait d'appeller la fonction dans la définition de la fonction):

In [23]:
def fact(n):
    if n==0:
        return 1
    else:
        return n*fact(n-1)
    
fact(4)
Out[23]:
24

Représentation graphique

Le module matplotlib permet l'utilisation des fonctions graphiques de type plot(), pcolormesh() ou contour().

plot

On peut par exemple tracer la réprésentation graphique de la fonction créée précédemment:

In [24]:
x=np.arange(-2.,2.,0.1)
plt.plot(x,ma_fonction(x))
Out[24]:
[<matplotlib.lines.Line2D at 0x7f076c56c100>]

On peut également créer des axes plus riches, ajouter des légendes et utiliser les notations scientifiques:

In [25]:
x=np.arange(-10,10,0.5)
z=np.exp(-x**2/16)
z2=np.exp(-x**2/9)

fig=plt.figure(1,figsize=(12,6)) # Creation de l'objet figure 1 on force la taille de la figure
plt.plot(x,z,label="$z=exp(-x^2/16)$") # Courbe de y en fonction de x
plt.plot(x,z2,label="$z=exp(-x^2/9)$") # Courbe de y en fonction de x
plt.xlabel('On peut etiqueter les axes $\epsilon$',fontsize=20.)
plt.ylabel('Utiliser des symboles: $\sum_\infty \epsilon$',fontsize=20.)
plt.title('Titre du graphique',fontsize=20.)
plt.grid(True);plt.legend(fontsize=20.)
Out[25]:
<matplotlib.legend.Legend at 0x7f07a0763310>

Pour enregistrer une figure, on peut utiliser la fonction savefig:

In [26]:
fig.savefig('Ma_belle_figure.png', bbox_inches='tight',dpi=120) # Le format peut etre choisi librement (png,jpg,eps...)

pcolormesh

Pour les grandeurs 2D on utilise souvent des cartographies de couleur pour représenter l'évolution d'une grandeur dans le plan. Par exemple, en reprennant l'exemple précédent, on ajoute une dimension y et crée une donnée 2D z3:

In [27]:
y=np.arange(-10,10,0.5)
z3=np.einsum('i,j->ij',np.exp(-x**2/16),np.exp(-y**2/16))
print(z3.shape)
(40, 40)
In [28]:
fig=plt.figure(2,figsize=(16,6)) # Creation de l'objet figure 2 en specifiant la taille
fig.add_subplot(121) # On crée une subfigure dans une division de 1 ligne et 2 colonne 
plt.pcolormesh(x,y,z3.T,shading='nearest')
# Representation 2D de la matrice A avec la colormap "spectral"
# A.T signifie que l'on affiche la transposee de A
# vmin et vmax permettent de fixer l'echelle de couleur
plt.xlim((-10,10)) # Restriction de l'affichage a un domaine particulier 
plt.ylim((-10,10))
# !! Attention en dehors des notebook il faut utiliser plt.show() pour afficher une figure
plt.xlabel(r'$x$',fontsize=18.)
plt.ylabel(r'$y$',fontsize=18.)
plt.colorbar()

fig.add_subplot(122) # Sur la deuxieme subfigure on changhe les options d'affichage 
plt.pcolormesh(x,y,z3.T,shading='gouraud',cmap="jet") # on change la colormap et on lisse l'affichage
plt.xlim((-10,10)) # Restriction de l'affichage a un domaine particulier 
plt.ylim((-10,10))
plt.xlabel('$x$',fontsize=18.)
plt.colorbar()
Out[28]:
<matplotlib.colorbar.Colorbar at 0x7f07a06feca0>

contour()

Lorsque l'on souhaite représenter les isovaleurs d'une nappe de données 2D, on peut utiliser la fonction contour ou contourf (contour remplis).

In [29]:
fig=plt.figure(2,figsize=(16,6)) # Creation de l'objet figure 2 en specifiant la taille
fig.add_subplot(121) # On crée une subfigure dans une division de 1 ligne et 2 colonne 
plt.contour(x,y,z3,levels=[0.2,0.5,0.8],colors=['k','b','r'])
plt.axis('equal') # Restriction de l'affichage a un domaine particulier 
# !! Attention en dehors des notebook il faut utiliser plt.show() pour afficher une figure
plt.xlabel(r'$x$',fontsize=28.)
plt.ylabel(r'$y$',fontsize=28.)

fig.add_subplot(122) # Sur la deuxieme subfigure on changhe les options d'affichage 
plt.contourf(x,y,z3.T,cmap="Greys") # on change la colormap et on lisse l'affichage
plt.axis('equal') # Restriction de l'affichage a un domaine particulier 
# !! Attention en dehors des notebook il faut utiliser plt.show() pour afficher une figure
plt.xlabel(r'$x$',fontsize=28.)
plt.colorbar()
Out[29]:
<matplotlib.colorbar.Colorbar at 0x7f07a02f84f0>

Conclusion

Le python est un language simple très utilisé en science et dans plein d'autres domaines. Il permet de se familiariser facilement avec les problématiques liées à la programmation scientifique, au calcul mathématique et à la simulation numérique.

Note: Vous pouvez télécharger ce notebook (fichier ipynb) et l'executer (modifier) à votre guise sur une machine disposant d'une version de Python (3.x) ou bien l'éxecuter sur le JupyterHUB du CNAM

Pour aller plus loin voici quelques liens utiles:

In [1]:
from IPython.core.display import HTML
style=open('notebooks.css', "r").read()
HTML(style)
Out[1]: