Il est écrit pour les débutants de Python et NumPy. Il peut être particulièrement utile pour ceux qui visent le calcul numérique de systèmes physiques correspondant à "Je peux utiliser le langage C mais j'ai récemment commencé Python" ou "Je ne comprends pas comment écrire comme Python". Peut être.
De plus, il peut y avoir des descriptions incorrectes en raison de mon manque d'étude. Veuillez me pardonner.
Python possède une bibliothèque très riche de calculs numériques et est facile à utiliser. Beaucoup d'entre eux sont des wrappers d'héritage écrits en Fortran, mais ils sont beaucoup plus faciles que de les appeler depuis C / C ++ etc. Par exemple, si vous appelez LAPACK [^ 1] à partir de C / C ++ et essayez de calculer le problème de valeur propre
info = LAPACKE_zheevr(LAPACK_COL_MAJOR, 'V', 'A', 'L', H, (lapack_complex_double*)a, lda, vl, vu, il, iu, abstol, &mm, w, (lapack_complex_double*)z, ldz, isuppz);
Il y a trop d'arguments et c'est compliqué. Bien que plus il y a d'arguments, plus il y a de liberté de calcul, cela ne ressemble pas à un débutant. De plus, C / C ++ ne supporte pas nativement les complexes. Depuis, des types tels que lapack_complex_double
sont définis à l'intérieur du wrapper, ce qui peut être déroutant. D'un autre côté, c'est très concis en Python:
w, v = numpy.linalg.eig(H)
Les arguments comme ceux de LAPACK sont également préparés correctement [^ 2], mais la valeur par défaut est définie, donc si vous voulez simplement le résoudre sans penser aux détails, vous pouvez le faire. Simple et bon À propos, divers solveurs semblent avoir des performances plus élevées dans SciPy que dans NumPy.
Lorsque cela se produit, il devient tentant de laisser divers calculs numériques à Python, mais cet enfant présente également des inconvénients. ** Les boucles For sont extrêmement lentes. ** Dans les opérations dites matricielles, des triples boucles se produisent fréquemment. Cependant, le calcul numérique est fondamentalement "finement différencié et bouclé", donc c'est assez fatal [^ 3]. Bien qu'il ne s'agisse pas d'un langage de compilation, il semble être plus lent que les autres langages LL Pour les opérations matricielles
for i in range(N):
for j in range(N):
for k in range(N):
c[i][j] = a[i][k] * b[k][j]
Je l'ai écrit et c'est fini.
Donc, ce qu'il faut faire est de tirer parti de la fonction universelle de NumPy. ** La fonction intégrée de NumPy fonctionne sur chaque élément du tableau NumPy ndarray
et renvoie un nouveau ndarray
. . ** Ceci est très important et a le potentiel de chasser les boucles pour les opérations tensorielles. Par exemple, math.sin (cmath.sin)
ne peut transmettre que des scalaires, mais numpy.sin
>>> theta = numpy.array([numpy.pi/6, numpy.pi/4, numpy.pi])
>>> numpy.sin(theta)
array([ 5.00000000e-01, 7.07106781e-01, 1.22464680e-16])
Lancer un tableau NumPy fait agir numpy.sin sur chaque élément, et souvent ce type de traitement est aussi rapide que C / C ++. L'implémentation principale de NumPy est C. C'est parce que BLAS [^ 4] / LAPACK fait de son mieux dans les opérations linéaires.
De plus, les quatre règles de fonctionnement définies par «ndarray» sont également rapides et diverses.
Écrivons une opération linéaire liée à un vecteur sans boucle for. Comme on s'attend à ce que des fonctions telles que le produit interne et le produit externe soient correctement préparées, comment la somme, le produit, le quotient, etc. pour ndarray
sont-ils principalement décrits ci-dessous? Voyons s'il est défini. ** L'opération sur ndarray
est complètement différente de l'opération sur liste
construite en Python **
Après cela, les alias sont définis comme ʻimport numpy as np`.
Un "ndarray" unidimensionnel peut être considéré comme une sorte de vecteur.
Scalar (complex, float, int) fonctionne sur tous les éléments de ndarray
:
>>> a = np.array([1, 2, 3])
>>> a + 2
array([3, 4, 5])
>>> a * (3 + 4j)
array([ 3. +4.j, 6. +8.j, 9.+12.j])
ndarray
fonctionne avec des éléments avec des index correspondants:
>>> a = np.array([1, 2, 3])
>>> b = -np.array([1, 2, 3])
>>> a + b
array([0, 0, 0])
>>> a * b
array([-1, -4, -9])
>>> a / b
array([-1., -1., -1.])
Il est intéressant de noter qu'il ne s'agit ni d'un produit intérieur ni d'un produit extérieur. Il est possible de définir une division paire. Il est souvent utilisé lors de l'exécution d'une opération différentielle sur une certaine fonction différenciée. La somme / le produit de «ndarray» avec des dimensions différentes n'est pas défini.
ndarray
a une méthode appelée reshape
, par exemple, vous pouvez réorganiser un vecteur 3x1 en 1x3. C'est comme un vecteur horizontal pour un vecteur vertical, mais un produit (*). Il n'y a pas de correspondance simple car la définition de est différente de celle de l'espace vectoriel. Le produit avec ce vecteur reshape
est très intéressant:
>>> c = a.reshape(3, 1)
>>> c
array([[1],
[2],
[3]])
>>> c + b
array([[ 0, -1, -2],
[ 1, 0, -1],
[ 2, 1, 0]])
>>> c * b
array([[-1, -2, -3],
[-2, -4, -6],
[-3, -6, -9]])
Tout en ayant des règles comme (3 × 1) + ou * (1 × 3) = (3 × 3), mais comme l'opération précédente, ** cette opération est réalisable **. Cela semble étrange à première vue. C'est possible, mais si vous regardez, vous pouvez voir que cela peut être expliqué en utilisant les règles de la section précédente. Avec cela, il est possible que même l'initialisation de la matrice ** n'ait pas besoin d'utiliser la boucle for. Masu **. Il est très utile pour des tâches telles que l'initialisation d'une énorme matrice à chaque fois que les paramètres sont modifiés et la poursuite du calcul.
Personnellement, j'aime les spécifications très flexibles qui ne sont pas liées à ces types [^ 5].
Ce n'est pas une opération, mais il est inutile d'utiliser une boucle for pour préparer un vecteur en premier lieu. Il existe de nombreuses fonctions qui créent ndarray
, mais voici quelques-unes de celles que j'utilise le plus souvent:
>>> L = 1
>>> N = 10
>>> np.linspace(-L/2, L/2, N)
array([-0.5 , -0.38888889, -0.27777778, -0.16666667, -0.05555556,
0.05555556, 0.16666667, 0.27777778, 0.38888889, 0.5 ])
>>> dL = 0.2
>>> np.arange(-L/2, L/2, dL)
array([-0.5, -0.3, -0.1, 0.1, 0.3])
>>> np.logspace(0, 1 ,10, base=np.e)
array([ 1. , 1.11751907, 1.24884887, 1.39561243, 1.5596235 ,
1.742909 , 1.94773404, 2.17662993, 2.43242545, 2.71828183])
Si vous combinez des opérations avec des scalaires et des vecteurs, cela semble tout à fait le cas même sans boucle for.
Le déroulement de l'histoire est presque le même que celui du vecteur. Puisque le produit matriciel a une fonction dédiée, ce qui suit est aussi l'histoire de l'opération. Je pense que le résultat de l'opération peut être prédit.
>>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> a
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
>>> a + (2 - 3j)
array([[ 3.-3.j, 4.-3.j, 5.-3.j],
[ 6.-3.j, 7.-3.j, 8.-3.j],
[ 9.-3.j, 10.-3.j, 11.-3.j]])
>>> a * 0.2
array([[ 0.2, 0.4, 0.6],
[ 0.8, 1. , 1.2],
[ 1.4, 1.6, 1.8]])
>>> b = np.array([1, 2, 3])
>>> a + b
array([[ 2, 4, 6],
[ 5, 7, 9],
[ 8, 10, 12]])
>>> a * b
array([[ 1, 4, 9],
[ 4, 10, 18],
[ 7, 16, 27]])
>>> a / b
array([[ 1. , 1. , 1. ],
[ 4. , 2.5, 2. ],
[ 7. , 4. , 3. ]])
>>> b = np.array([[-1, 2, -3], [4, -5, 6], [-7, 8, -9]])
>>> a + b
array([[ 0, 4, 0],
[ 8, 0, 12],
[ 0, 16, 0]])
>>> a * b
array([[ -1, 4, -9],
[ 16, -25, 36],
[-49, 64, -81]])
Il existe également des fonctions utiles pour les matrices. En voici quelques-unes:
>>> np.identity(4)
array([[ 1., 0., 0., 0.],
[ 0., 1., 0., 0.],
[ 0., 0., 1., 0.],
[ 0., 0., 0., 1.]])
>>> np.eye(4, 3)
array([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.],
[ 0., 0., 0.]])
>>> c = np.array([1, 2, 3, 4])
>>> np.diag(c)
array([[1, 0, 0, 0],
[0, 2, 0, 0],
[0, 0, 3, 0],
[0, 0, 0, 4]])
Je pense que d'autres personnes ont écrit de meilleurs articles sur les fonctions pratiques et les règles arithmétiques détaillées de NumPy. Si vous regardez cet article et que vous l'écrivez comme NumPy, vous pouvez faire divers calculs sans utiliser de boucles for. Nous espérons que vous vous sentirez capable de le faire. De nombreux algorithmes manuels sont déjà disponibles dans SciPy et autres.
Cela fait un peu longtemps, je voudrais donc résumer l'application spécifique au calcul numérique dans un autre article.
** Addendum (2016/11/25): Suite ci-dessous ** Accélération du calcul numérique à l'aide de NumPy / SciPy: Application 1 Accélération du calcul numérique à l'aide de NumPy / SciPy: Application 2
[^ 1]: LAPACK (Linear Algebra PACKage) est une bibliothèque d'opérations d'algèbre linéaire écrites en Fortran 90. Certains wrappers pour C / C ++ sont appelés Lapacke
. Parce qu'il a une interface compatible MKL, Le manuel MKL vous sera utile.
[^ 2]: Pour plus d'informations, voir NumPy et [SciPy](https://docs.scipy.org/doc/scipy-0.18. Veuillez vous référer au manuel 1 / reference /).
[^ 3]: A côté de ça, j'ai l'impression que la surcharge de l'appel de fonction est également importante. Dans le cas de Procon, il arrive souvent que "l'écriture en méthode de planification dynamique passe, mais en récurrence mémorielle TLE".
[^ 4]: Une bibliothèque qui contient un ensemble d'opérations linéaires plus basique que LAPACK. Dans LAPACK, nous appelons BLAS.
[^ 5]: Certaines personnes n'aiment pas "aucun type". Contrairement aux programmes conçus pour répondre à "certaines spécifications", les calculs numériques en physique sont si faciles à vérifier si la sortie est correcte. Ce n'est pas le cas. Et il est encore plus difficile de déboguer si "le type de variable change implicitement" est autorisé pendant l'exécution du programme. Puisque le montant observable est fixé à float, le typage statique crée des bogues. Je comprends l'affirmation selon laquelle c'est difficile, mais j'aime Python.
Recommended Posts