2025-3S 計算数理演習(東京大学理学部・教養学部) [齊藤宣一] [top] [0] [1] [2] [3] [4] [5] [6] [7] [8] [UTOL]
Google colaboratoryで,ノートブックを開く.$3+4$ を計算するには,以下のように操作する.
ノートブックを開いた直後は,答えが返って来るまでに,やや時間がかかる.2回目以降は,それほど,気にならないはずである.今後,本講義の資料では,上のように入力し,出力が得られることを,次のように書く.In [n]:とOut [n]:が対になっている.
In [1]: 3+4 Out [1]: 7
以下で,いろいろ例示をする.説明はいちいち不要であろう.
In [2]: 3-4 Out [2]: -1
In [3]: 3+(-4) Out [3]: -1
In [4]: 3*4 Out [4]: 12
In [5]: 3/4 Out [5]: 0.75
In [6]: 12/4 Out [6]: 3.0
In [7]: 12//4 Out [7]: 3
In [8]: 13//4 Out [8]: 3
In [9]: 13%4 Out [9]: 1
In [10]: 3**4 Out [10]: 81
(3^4ではない!)
In [11]: 4**-2 Out [11]: 0.0625
In [12]: 12/4*3 Out [12]: 9.0
In [13]: 12/(4*3) Out [13]: 1.0
In [14]: (1+2j)*(3+1j) Out [14]: (1+7j)
すなわち,j は虚数単位 $i$ を表す.3+j とは書かないことに注意せよ.
これも,例を見ながら色々試して,納得するのが近道である.
In [1]:
x=1.2
print(x)
print(y)
Out [1]:
1.2
---------------
NameError Traceback (most recent call last)
< ipython-input-27-1faa875c5937 > in < module >
1 x=1.2
2 print(x)
----> 3 print(y)
NameError: name 'y' is not defined
In [2]:
print("x is defined")
print('y is undefined')
print('文字列')
Out [2]:
x is defined
y is undefined
文字列
クォーテーションでもダブルクォーテーションでも同じ(私は,その場の気分で使ってしまうので,ご容赦ください).
In [3]: s="x is defined" print(s) print(type(s)) Out [3]: x is defined < class 'str' >
In [4]:
x=10.0
print('x =',x)
print('x = {}'.format(x))
print(f'x = {x}')
Out [4]:
x = 10.0
x = 10.0
x = 10.0
In [5]:
p=3.141592653589
print(f"{p:.3f}")
print(f"{p:9.3f}")
print(f"p={p:.3f}")
print(f"p={p:9.3f}")
Out [5]:
3.142
3.142
In [5]:の3行目と5行目では,$p$ の値を表示する際に, 合計9文字のフィールド幅で,小数点以下3桁の数値が右揃えされ,左側がスペースが埋められる. (この場合,小数点も合計の文字数に含まれる.)
In [6]:
x=12.345678
y=41
z='comment'
print(f'real={x:.5f}, integer={y:d}, string={z:s}')
print(f"real={x:.3e}, integer={y:5d}, string={z:s}")
print(f'real={x:.5f}, \n integer={y:d}, string={z:s}')
print(f'real={x:.5f}, \ninteger={y:d}, string={z:s}')
Out [6]:
real=12.34568, integer=41, string=comment
real=1.235e+01, integer= 41, string=comment
real=12.34568,
integer=41, string=comment
real=12.34568,
integer=41, string=comment
In [7]:
x=12.345678
y=41
z='comment'
print('real={:.5f}, integer={:d}, string={:s}'.format(x, y, z))
print('real={:.3e}, integer={:5d}, string={:s}'.format(x, y, z))
Out [7]:
real=12.34568, integer=41, string=comment
real=1.235e+01, integer= 41, string=comment
In [8]:
x=10.0
y=41
print(f'x = {x}, y = {y}')
print(f'x = {y}, y = {x}')
Out [8]:
x = 10.0, y = 41
x = 41, y = 10.0
変数には型というのものがある.
In [1]: a=1 b=2.0 c=1+2j print(type(a)) print(type(b)) print(type(c)) Out [1]: < class 'int' > < class 'float' > < class 'complex' >
intは整数型である.Pythonでは「任意」の桁の整数が扱える.floatは,64ビットを用いて表現される,いわゆる倍精度実数型浮動小数点数(binary64)である.これについては,2節で,やや詳しく説明する.しかし,講義を通じて,あまり細かいことは気にしないで,floatは実数型である,くらいに考えれば良い.今後の説明でも,そのように表現する.説明しなくてもわかるであろうが,complexは,2つの実数型の対として表現される複素数型である.(念の為:浮動小数点数=floating point number)
なお,例えば,C言語では,上の意味のfloatをdoubleと書き,floatは単精度実数型浮動小数点数(binary32)を意味している.Pythonでは,言語の仕様としては,単精度はサポートしていないようである.(ただし,数値を単精度で扱うことは可能である.)
上のOut [3]で出力された,str(string)は,文字列である.
In [2]: a=1 b=2.0 c=1+2j x=a+b y=b+c print(x) print(type(x)) print(y) print(type(y)) Out [2]: 3.0 < class 'float' > (3+2j) < class 'complex' >
整数型と実数型の和は実数型になる.実数型と複素数型の和は複素数型になる.差積商にについても同様である.
In [1]:
x = 10.0
y = 10.0
angle = atan(y/x)
print((angle/pi)*180)
Out [1]:
---------------------------------
NameError Traceback (most recent call last)
< ipython-input-39-84b2df14c98c > in < cell line: 3 > ()
1 x = 10.0
2 y = 10.0
----> 3 angle = atan(y/x)
4 print((angle/pi)*180)
NameError: name 'atan' is not definedPythonには,あらかじめ様々な関数が定義されている (https://docs.python.org/3/library/functions.html)が, 残念ながら,$\tan^{-1}$ はその中に含まれていない. $\sin,\exp$ なども含まれていない. $\pi$ の値も,そのままでは,利用できない.
特別な関数を利用するためには,モジュール(module)をインポートする必要がある. モジュールとは,Pythonにおいて,特定の機能や目的に応じて関数をまとめる単位のことを言う.
本講義でよく使うのは:
In [2]: from math import atan, pi x = 10.0 y = 10.0 angle = atan(y/x) print((angle/pi)*180) Out [2]: 45.0
あるいは,次のように書いても良い.詳しく説明しなくても,利用の仕方は理解できるであろう.
In [3]: import math x = 10.0 y = 10.0 angle = math.atan(y/x) print((angle/math.pi)*180) Out [3]: 45.0
これは,次のように書くと,より便利である.
In [4]: from math import * x = 10.0 y = 10.0 angle = atan(y/x) print((angle/pi)*180) Out [4]: 45.0
もちろん指数関数 $\exp$ も利用できる.
In [5]: from math import exp x = exp(1) print(x) Out [5]: 2.718281828459045
Numpyモジュールにおいても,指数関数expが利用できる.さらに,Numpyでは,リストを引数に与えることができる.
In [6]: from numpy import exp x = exp([0, 1, 2]) print(x) Out [6]: [1. 2.71828183 7.3890561 ]
しかし,mathモジュールでのexpは,引数としてはスカラー値しか受け付けない.
In [7]:
from math import exp
x = exp([0, 1, 2])
print(x)
Out [7]:
-------------
TypeError Traceback (most recent call last)
< ipython-input-45-6808b54d7130 > in < cell line: 2 > ()
1 from math import exp
----> 2 x = exp([0, 1, 2])
3 print(x)
TypeError: must be real number, not list
すなわち,同じ指数関数を計算する,同じ記号の関数expでも,定義されているモジュールが違えば,使い方が異なるのである.
以上を踏まえて,以下の2つを比較せよ.
In [8]:
from numpy import *
from math import *
y=cos(0)
print(y)
x = exp([0, 1, 2])
print(x)
Out [8]:
1.0
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
< ipython-input-47-fa0ec575be00 > in < cell line: 5 > ()
3 y=cos(0)
4 print(y)
----> 5 x = exp([0, 1, 2])
6 print(x)
TypeError: must be real number, not list
In [9]: from math import * from numpy import * y=cos(0) print(y) x = exp([0, 1, 2]) print(x) Out [9]: 1.0 [1. 2.71828183 7.3890561 ]
前者では,$\cos$ と $\exp$ は,mathモジュールのものが用いられているのに対し, 後者では,numpyモジュールのものが用いられているのである.
これらのことを踏まえて,本講義の例では(すなわち,普通は),モジュールの関数を以下のように利用することにする.
In [10]: import numpy as np y=np.cos(0) print(y) x = np.exp([0, 1, 2]) print(x) Out [10]: 1.0 [1. 2.71828183 7.3890561 ]
Google Colaboratoryを使っていると, 意図しないモジュールをロードしたまま, あるいは変数を二重三重に定義して,計算を続けてしまうことがある. このようなことを避けるために,適宜, 次のようにして,モジュールや変数のリセットとすると良い.
In [11]: %reset -f
1次元配列(ベクトル)の利用の仕方を説明する. それには配列(array)を利用する. 例えば,$\boldsymbol{x}=(1,2,4)$ (横ベクトル)は,次のように定義できる.
In [1]: x=[1,2,4] print(x[0]) print(x[1]) print(x[2]) Out [1]: 1 2 4
そうすると,x[0]=1,x[1]=2,x[2]=4となる.すなわち,配列の番号づけは,$0$からはじまる.
しかし,この配列の使い方は薦めない.例えば,
In [2]: 2*x Out [2]: [1, 2, 4, 1, 2, 4]
というように,あまり嬉しくない計算をされてしまう. 以後,本講義では,numpyの配列(ndarrayクラス)を利用する.
In [3]: import numpy as np x=np.array([1,2,4]) print(2*x) print(type(x)) Out [3]: [2 4 8] < class 'numpy.ndarray' >
実際,(いちいち検証しないが)ndarrayクラスの方が,いろいろな数学的操作を,容易かつ高速に実行できる.
次のlinspace関数は,今後とてもよく使う. 閉区間 $[0,5]$ の中に,両端も含めて $6$ の点を等間隔にとり,配列にする場合には,次のようにする.
In [4]: import numpy as np x=np.linspace(0,5,6) print(x) print(x[0]) print(x[3]) Out [4]: [0. 1. 2. 3. 4. 5.] 0.0 3.0
すなわち,z = linspace(a,b,N) とすることで,$\boldsymbol{z}=(z_0,\ldots,z_{N-1})$, $z_k=a+\frac{b-a}{N-1}k$ $(k=0,\ldots,N-1)$を生成する.
In [5]: print(x) In [6]: len(x) In [7]: type(x) Out [5]: [0. 1. 2. 3. 4. 5.] Out [6]: 6 Out [7]: numpy.ndarray
In [8]: x[6] Out [8]: ------------------------------------- IndexError Traceback (most recent call last) < ipython-input-65-04aa5bc9ecce > in < cell line: 1 > () ----> 1 x[6] IndexError: index 6 is out of bounds for axis 0 with size 6
配列のコピーには注意が必要である.例えば,
In [9]: y=x print(y) Out [9]: [0. 1. 2. 3. 4. 5.]
と x を y に「コピー」して,y の第0成分の値を上書きする.
In [10]: y[0]=-9 print(y) Out [10]: [-9. 1. 2. 3. 4. 5.]
ここまでは,想像通りであろう.しかし,念の為 x の値を確認してみると,
In [11]: print(x) Out [11]: [-9. 1. 2. 3. 4. 5.]
となり,y を操作しただけなのに,x の値も変わってしまっている.理由を説明すると,それなりに時間がかかるので,ここでは深入りしない.
配列を,文字通りにコピーする際には,copy関数を使うと良い.
In [12]: print(x) z=np.copy(x) print(z) z[0]=7.8 print(z) print(x) Out [12]: [-9. 1. 2. 3. 4. 5.] [-9. 1. 2. 3. 4. 5.] [7.8 1. 2. 3. 4. 5. ] [-9. 1. 2. 3. 4. 5.]
こんどは,xの値は影響を受けていない.
つぎのような配列の分割もよく使う.
In [13]: x=np.linspace(11,19,9) print(x) y=np.copy(x[1:5]) print(y) print(x) Out [13]: [11. 12. 13. 14. 15. 16. 17. 18. 19.] [12. 13. 14. 15.] [11. 12. 13. 14. 15. 16. 17. 18. 19.]
この計算では,配列xを生成したのちに,xの(最初の成分を第0成分と数えて)第1成分から第 $5-1=4$ 成分を,別の配列yにコピーしている.すなわち,x[n:m$+$1]で,配列xの第n成分から第m成分までからなる配列を表す.
なぜ,次のようにしないのか(このようにしても意図するy自体は生成できる),理由は各自で考えよ.
In [14]: y=x[1:5]
グラフを描画する際には,matplotlib.pyplotモジュール(https://matplotlib.org/stable/tutorials/introductory/pyplot.html)を用いると良い.
例として,$y=x-2x^2$ $(-1\le x \le 1)$のグラフを描いてみよう.そのために linspace関数を用いて, $x_k=-1+\frac{2}{N-1}k$ $(k=0,\ldots,N-1)$を生成して, $y_k=x_k-2x_k^2$を計算し, $(x_k,y_k)$ $(k=0,\ldots,N-1)$を順に線分で結ぶ.
次は,$N=41$に対して,この操作を行うプログラムである.
In [1]: import numpy as np import matplotlib.pyplot as plt x = np.linspace(-1, 1, 41) y = x - 2.0*x**2 plt.plot(x, y) plt.show()
複数のグラフを描画する,線に色をつける,描画方法(実線,破線,マーカーなど)を変える,グリッドを入れる,凡例をつける,軸に説明を入れる,タイトルをつける,などが可能である.詳しくは,「matplotlib color」などで検索せよ.
In [2]:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2*np.pi, 101)
y1 = np.sin(x)
y2 = np.sin(6*x)
plt.plot(x, y1,'r')
plt.plot(x, y2,'b--')
plt.legend(['sin(x)','sin(6x)'])
plt.title('Two functions (sin(x) and sin(6x))')
plt.grid('on')
plt.show()
In [2]は,次のように書いても全く同じ出力が得られる.
In [3]:
import numpy as np
import matplotlib.pyplot as plt
def func1(t):
return np.sin(t)
def func2(s):
m=6.0
return np.sin(m*s)
x = np.linspace(0, 2*np.pi, 101)
y1 = func1(x)
y2 = func2(x)
plt.plot(x, y1,'r')
plt.plot(x, y2,'b--')
plt.legend(['sin(x)','sin(6x)'])
plt.title('Two functions (sin(x) and sin(6x))')
plt.grid('on')
plt.show()
描画したイメージを自動的に保存する方法を説明する.まず,次を行う.
In [4]:
from google.colab import drive
drive.mount('/content/drive')すると,(やや間があって)次のようなウインドウが現れるので,次のように進む.
すると,次のようになる.
In [4]:
from google.colab import drive
drive.mount('/content/drive')
Out [4]:
Mounted at /content/drive
(これは必須ではないが,整理の目的で)Colab Notebooksの下にfigというフォルダを作っておく.
その上で,以下のプログラムを実行すると,Out[2]と同じイメージが,figにsin.pngとして保存される.この他に,例えば,sin.jpgやsin.pdfで保存も可能である.
In [5]:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2*np.pi, 101)
y1 = np.sin(x)
y2 = np.sin(6*x)
plt.plot(x, y1,'r')
plt.plot(x, y2,'b--')
plt.xlabel('x')
plt.ylabel('y')
plt.legend(['sin(x)','sin(6x)'])
plt.title('Two functions (sin(x) and sin(6x))')
plt.grid('on')
plt.savefig("/content/drive/MyDrive/Colab Notebooks/fig/sin.png")
plt.show()これまでに説明した描画方法は,いわば『pyplotインターフェース』を使用したものである.より,複雑なこと(凝ったこと)をしたい場合には,次のような『オブジェクト指向インターフェース』を利用した方が良い.
In [6]:
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x = np.linspace(0, 2*np.pi, 101)
y1 = np.sin(x)
y2 = np.sin(6*x)
ax.plot(x, y1,'r')
ax.plot(x, y2,'b--')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.legend(['sin(x)','sin(6x)'])
ax.set_title('Two functions (sin(x) and sin(6x))')
ax.grid('on')
plt.savefig("/content/drive/MyDrive/Colab Notebooks/fig/sin.png")
fig.show()本講義の例では,基本的には『pyplotインターフェース』を用いることにする.必要に応じて,『オブジェクト指向インターフェース』の利用を説明する.
まずは,for文を説明する.以下の,In [1]からIn [4]までは,全て同じである.
In [1]:
for i in range(10):
print(i)
Out [1]:
0
1
2
3
4
5
6
7
8
9
In [2]:
for m in range(0,10):
print(m)
In [3]:
for n in range(0,10,1):
print(n)
In [4]:
for k in [0,1,2,3,4,5,6,7,8,9]:
print(k)
for文を用いて, $S_n=\frac{1}{1}+\frac{1}{2}+\frac{1}{3}+\cdots+\frac{1}{n}$ を計算してみよう.以下の In[5 ]と In[6] は,共に,$S_{10}$ を計算している.
In [5]: n=10 sum=0.0 for i in range(n): sum=sum+1.0/(i+1) print(sum) Out [5]: 2.9289682539682538
In [6]: n=10 sum=0.0 for i in range(n): sum += 1.0/(i+1) print(sum) Out [6]: 2.9289682539682538
すなわち,a+=b は,a+b を新しく a とすることを意味している.
if文の使い方を,具体例で説明する.
方程式 $f(x)=e^x-2x-1=0$ の(唯一の)正の解 $x=x_*$ を,二分法(bisection method)で計算しよう.
初等的な考察により,$1< x_*<2$,かつ,$f(1)<0$,$f(2)>0$ であることがわかる.
まず,$[a_0,b_0]=[1,2]$ とする.そして,$k\ge 0$ に対して,$x_k=\frac{1}{2}(a_k+b_k)$,および, \[ [a_{k+1},b_{k+1}] =\begin{cases} [a_k,x_k] & (f(x_k)f(b_k)\ge 0\mbox{のとき})\\ [x_k,b_k] & (f(x_k)f(b_k)<0\mbox{のとき}) \end{cases} \] と定める.このとき, \[ |x_k-x_*|\le b_{k+1}-a_{k+1}=\left(\frac{1}{2}\right)^{k+1}(b_0-a_0) \] が成り立つ.これより,$|x_k-x_*|\le \varepsilon$ を満たす近似解が欲しい場合には,反復回数が $\displaystyle{k^*= \frac{1}{\log 2}\log\frac{|b-a|}{\varepsilon}}$ 程度必要となることもわかる.
これを実行するのが,次のプログラムである.ただし,1e-7$=10^{-7}$を意味する.
In [7]:
import numpy as np
def func(t):
return np.exp(t)-2.0*t-1.0
a=1.0
b=2.0
eps=1e-7
kmax=int((1.0/np.log(2.0))*np.log((b-a)/eps))
print('k, x, b-a, f(x)')
for k in range(kmax):
x=0.5*(b+a)
if func(x)*func(b)<0.0: a=x
else: b=x
print(f'{k:2d}, {x:.8f}, {b-a:.8f}, {func(x):.8f}')
Out [7]:
k, x, b-a, f(x)
0, 1.50000000, 0.50000000, 0.48168907
1, 1.25000000, 0.25000000, -0.00965704
2, 1.37500000, 0.12500000, 0.20507672
(...略...)
20, 1.25643110, 0.00000048, -0.00000016
21, 1.25643134, 0.00000024, 0.00000020
22, 1.25643122, 0.00000012, 0.00000002
次のプログラムは,上のプログラムと全く同じことをしているが,多少,python風である.
In [8]:
import numpy as np
def func(t):
return np.exp(t)-2.0*t-1.0
def bisection(a, b, eps):
kmax=int((1.0/np.log(2.0))*np.log((b-a)/eps))
print('k, x, b-a, f(x)')
for k in range(kmax):
x=0.5*(b+a)
if func(x)*func(b)<0.0: a=x
else: b=x
print(f'{k:2d}, {x:.8f}, {b-a:.8f}, {func(x):.8f}')
return 0
def main():
a=1.0
b=2.0
eps=1e-7
bisection(a,b,eps)
if __name__ == '__main__':
main()
Out [8]:
k, x, b-a, f(x)
0, 1.50000000, 0.50000000, 0.48168907
1, 1.25000000, 0.25000000, -0.00965704
2, 1.37500000, 0.12500000, 0.20507672
(...略...)
20, 1.25643110, 0.00000048, -0.00000016
21, 1.25643134, 0.00000024, 0.00000020
22, 1.25643122, 0.00000012, 0.00000002やや込み入っているので,論理的な塊のみ以下に示す.
ただし,if __name__ == '__main__':という行は,この講義の範囲内では,不要である.しかし,今後,作成したプログラムをモジュールとして,あるいは,単独のなんとか.pyとして使用する際に,付けておいた方が良い(というか,付けておく必要がある).なお,今後(次回以降)のサンプルプログラムでは,付けないことにする.
繰り返しには,for文の他に,while文も使える.
In [9]:
import numpy as np
def func(t):
return np.exp(t)-2.0*t-1.0
def bisection(a, b, eps):
print('k, x, b-a, f(x)')
k=0
while b-a>eps:
x=0.5*(b+a)
if func(x)*func(b)<0.0: a=x
else: b=x
print(f'{k:2d}, {x:.8f}, {b-a:.8f}, {func(x):.8f}')
k+=1
return 0
def main():
a=1.0
b=2.0
eps=1e-7
bisection(a,b,eps)
if __name__ == '__main__':
main()
Out [9]:
k, x, b-a, f(x)
0, 1.50000000, 0.50000000, 0.48168907
1, 1.25000000, 0.25000000, -0.00965704
2, 1.37500000, 0.12500000, 0.20507672
(...略...)
20, 1.25643110, 0.00000048, -0.00000016
21, 1.25643134, 0.00000024, 0.00000020
22, 1.25643122, 0.00000012, 0.00000002
23, 1.25643116, 0.00000006, -0.00000007
Out [8]では,k=22まで実行されていたが,Out [9]では,k=23まで実行されている.理由を考えよ.
次のプログラムでは,$S_{50}(x)$のグラフを描画している.
In [1]:
import numpy as np
import matplotlib.pyplot as plt
def func(x,n):
val = 0.0
for k in range(n):
theta=(2*k+1)*x
val += np.cos(theta)/((2*k+1)**2)
return np.pi/2.0 + (4.0/np.pi)*val
x = np.linspace(-1.5*np.pi, 1.5*np.pi, 100)
y = func(x,50)
plt.figure(figsize=(10, 10))
plt.plot(x, y,'b')
plt.title('convergence of Fourier series')
plt.grid('on')
plt.gca().set_aspect('equal', adjustable='box') # set aspect ratio as 1:1
plt.show()
さらに,$S_{1}(x)$,$S_{10}(x)$,$S_{20}(x)$,$S_{50}(x)$のグラフを並べて描き,収束の様子がよりわかりやすようにしてみよう.
In [2]:
import numpy as np
import matplotlib.pyplot as plt
def func(x,n):
val = 0.0
for k in range(n):
theta=(2*k+1)*x
val += np.cos(theta)/((2*k+1)**2)
return np.pi/2.0 + (4.0/np.pi)*val
def drawing(m):
x = np.linspace(-1.5*np.pi, 1.5*np.pi, m)
y1 = func(x,1)
y2 = func(x,10)
y3 = func(x,20)
y4 = func(x,50)
plt.figure(figsize=(10, 10)) # this can be dropped
plt.plot(x, y1,'b')
plt.plot(x, y2,'g')
plt.plot(x, y3,'c')
plt.plot(x, y4,'m')
plt.legend(['n=1','n=10','n=20','n=50'])
plt.title('convergence of Fourier series')
plt.grid('on')
plt.gca().set_aspect('equal', adjustable='box') # set aspect ratio as 1:1
plt.show()
return 0
m=100
drawing(m)
まずは,ベクトルの内積から.
In [1]: import numpy as np a=np.array([1,2,3,4]) b=np.array([5,6,7,8]) c=np.inner(a,b) d=np.dot(a,b) print(a) print(b) print(c) print(d) Out [1]: [1 2 3 4] [5 6 7 8] 70 70
In [2]: import numpy as np a=np.array([1+2j,2-1j]) b=np.array([2+1j,1+1j]) c=np.inner(a,b) d=np.dot(a,b) e=np.vdot(a,b) print(a) print(b) print(c) print(d) print(e) Out [2]: [1.+2.j 2.-1.j] [2.+1.j 1.+1.j] (3+6j) (3+6j) (5+0j)
すなわち,$\boldsymbol{x}=(x_1,\ldots,x_n)$ と $\boldsymbol{y}=(y_1,\ldots,y_n)$ に対して, これらが複素ベクトルであっても,inner と dot では,$\sum_{i=1}^nx_iy_i$ を計算する.一方で, vdotは,(複素ベクトルに対して,期待通りに)$\sum_{i=1}^nx_i\overline{y_i}$ を計算する.
ベクトルのノルムの値は,次のように計算できる.
In [3]: import numpy as np a=np.array([1,0,-3]) x=np.linalg.norm(a,ord=1) y=np.linalg.norm(a,ord=2) z=np.linalg.norm(a,ord=np.inf) w=np.linalg.norm(a,ord=0) print(x) print(y) print(z) print(w) Out [3]: 4.0 3.1622776601683795 3.0 2.0
すなわち,$1\le p<\infty$ と $\boldsymbol{x}=(x_1,\ldots,x_n)$ に対して, \[ \|\boldsymbol{x}\|_p=\left(\sum_{i=1}^n |x_i|^p\right)^{1/p} \] の値は,linalg.norm(a,ord=p) で計算できる. \[ \|\boldsymbol{x}\|_\infty=\max_{1\le i\le n}|x_i| \] の値は,linalg.norm(a,ord=np.inf) で計算できる. さらに,linalg.norm(a,ord=0) は,ベクトル $\boldsymbol{x}$ の非零成分の個数を計算する.
次のように使うと便利である.
In [4]: import numpy as np from numpy import linalg as la a=np.array([1,0,-3]) x=la.norm(a,ord=2) print(x) Out [4]: 3.1622776601683795
numpyのndarrayクラスでは,2次元の配列,すなわち,行列も利用できる.
In [5]: import numpy as np A=np.array([[1,2,3],[5,6,7]]) print(type(A)) print(A) Out [5]: < class 'numpy.ndarray' > [[1 2 3] [5 6 7]]
転置とエルミート転置は,次のようにする.
In [6]: import numpy as np A=np.array([[1, 2-1j],[3+2j, 4]]) D=A.T E=A.conj().T print(D) print(E) Out [6]: [[1.+0.j 3.+2.j] [2.-1.j 4.+0.j]] [[1.-0.j 3.-2.j] [2.+1.j 4.-0.j]]
和と差,スカラー倍,行列同士の積は次のように計算する.
In [7]:
import numpy as np
A=np.array([[1,2,3],[4,5,6],[-1,2,1]])
B=np.array([[0,-2,-1],[2,3,4],[2,1,1]])
C=A+B
D=A-B
E=3.0*A
F=A*B
G=np.dot(A,B)
print('A+B=\n',C)
print('A-B=\n',D)
print('3A=\n',E)
print('A*B=\n',F)
print('AB=\n',G)
Out [7]:
A+B=
[[ 1 0 2]
[ 6 8 10]
[ 1 3 2]]
A-B=
[[ 1 4 4]
[ 2 2 2]
[-3 1 0]]
3A=
[[ 3. 6. 9.]
[12. 15. 18.]
[-3. 6. 3.]]
A*B=
[[ 0 -4 -3]
[ 8 15 24]
[-2 2 1]]
AB=
[[10 7 10]
[22 13 22]
[ 6 9 10]]
この例でわかる通り,*では,通常の行列の積でなく,行列の成分ごとの積(アダマール積)を計算する.通常の行列の積を計算する場合には,dotを用いる.
なお,通常の行列の積を計算する場合には,dotの他に,matmulや@ も使える.
In [8]: import numpy as np A=np.array([[1,2,3],[4,5,6],[-1,2,1]]) B=np.array([[0,-2,-1],[2,3,4],[2,1,1]]) C=np.dot(A,B) D=np.matmul(A,B) E=A@B print(C) print(D) print(E) Out [8]: [[10 7 10] [22 13 22] [ 6 9 10]] [[10 7 10] [22 13 22] [ 6 9 10]] [[10 7 10] [22 13 22] [ 6 9 10]]
しかし,次のことに注意しなければならない. 行列を扱う際には,ndarrayクラスだけでなく,matrixクラスも利用できる. ところが,matrixクラスでは,通常の行列の積が*で,成分毎の積がmultiplyで計算されてしまう.実際,次のようになる.(matrixクラスとndarrayクラスを*で繋ぐと,通常の行列の積を計算するようである.)ただし,本講義のサンプルプログラムでは,matrixクラスは(多分)使わない.
In [9]:
import numpy as np
A=np.asmatrix('1,2;-5,3')
B=np.asmatrix('3,-1;4,6')
print(type(A))
print(type(B))
print(A*B)
print(np.multiply(A,B))
C=np.array([[1,2],[-5,3]])
D=np.array([[3,-1],[4,6]])
print(type(C))
print(type(D))
print(C*D)
print(type(A*C))
print(A*C)
Out [9]:
< class 'numpy.matrix' >
< class 'numpy.matrix' >
[[11 11]
[-3 23]]
[[ 3 -2]
[-20 18]]
< class 'numpy.ndarray' >
< class 'numpy.ndarray' >
[[ 3 -2]
[-20 18]]
< class 'numpy.matrix' >
[[ -9 8]
[-20 -1]]逆行列と行列式は次のように計算できる.
In [10]: import numpy as np A=np.array([[1,2,3],[4,5,6],[-1,2,1]]) B=np.linalg.inv(A) print(B) C=np.dot(A,B) print(C) a=np.linalg.det(A) print(a) Out [10]: [[-0.58333333 0.33333333 -0.25 ] [-0.83333333 0.33333333 0.5 ] [ 1.08333333 -0.33333333 -0.25 ]] [[ 1.00000000e+00 -1.11022302e-16 -5.55111512e-17] [ 0.00000000e+00 1.00000000e+00 1.11022302e-16] [-4.44089210e-16 1.11022302e-16 1.00000000e+00]] 11.999999999999995
指定したサイズの行列を,各成分を疑似乱数で生成することも可能である. このとき,各成分は $[0,1)$ の中から選ばれる. 一方で,random.randint(p,q,(m,n))では, 各成分が $[p,q]$ の中の整数値をとるような $m\times n$ 行列が生成される.
In [11]: import numpy as np A=np.random.rand(3, 4) B=np.random.randint(10,20,(3,4)) b=np.random.rand(3) print(A) print(B) print(b) Out [11]: [[0.90599422 0.44144026 0.95851401 0.35291246] [0.89705546 0.47500576 0.7160401 0.17252882] [0.75703266 0.94839887 0.26015848 0.06482541]] [[18 19 18 18] [14 17 19 17] [16 14 17 18]] [0.28397206 0.66572637 0.90254491]
零行列と単位行列の生成は重要である.
In [12]: import numpy as np A=np.zeros((4,5)) B=np.zeros(4) C=np.eye(3,5) D=np.eye(3) print(A) print(B) print(C) print(D) Out [12]: [[0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.]] [0. 0. 0. 0.] [[1. 0. 0. 0. 0.] [0. 1. 0. 0. 0.] [0. 0. 1. 0. 0.]] [[1. 0. 0.] [0. 1. 0.] [0. 0. 1.]]
何に使うか,ピンと来ないかもしれないが,与えられたベクトルに対して,それを対角行列やsubdiagonal(日本語でなんと言う?)な成分とする行列を生成できる.
In [13]: import numpy as np M=np.random.rand(4) A=np.diag(M) B=np.diag(M, 1) C=np.diag(M, -1) print(M) print(A) print(B) print(C) Out [13]: [0.30311831 0.94739795 0.07139463 0.09439237] [[0.30311831 0. 0. 0. ] [0. 0.94739795 0. 0. ] [0. 0. 0.07139463 0. ] [0. 0. 0. 0.09439237]] [[0. 0.30311831 0. 0. 0. ] [0. 0. 0.94739795 0. 0. ] [0. 0. 0. 0.07139463 0. ] [0. 0. 0. 0. 0.09439237] [0. 0. 0. 0. 0. ]] [[0. 0. 0. 0. 0. ] [0.30311831 0. 0. 0. 0. ] [0. 0.94739795 0. 0. 0. ] [0. 0. 0.07139463 0. 0. ] [0. 0. 0. 0.09439237 0. ]]
次は,簡単な応用である(この行列は何?).
In [14]: import numpy as np N=5 M=np.ones(N)*2.0 S=np.ones(N-1)*(-1.0) A = np.diag(M) + np.diag(S, 1) + np.diag(S, -1) print(A) Out [14]: [[ 2. -1. 0. 0. 0.] [-1. 2. -1. 0. 0.] [ 0. -1. 2. -1. 0.] [ 0. 0. -1. 2. -1.] [ 0. 0. 0. -1. 2.]]
行列のサイズを取得して,それと同じサイズの行列を作ることができる.
In [15]:
import numpy as np
A=np.random.rand(3, 4)
print('size',A.shape)
print('row',A.shape[0])
print('column',A.shape[1])
B=np.zeros(A.shape)
print(B)
C = np.zeros_like(A)
print(C)
Out [15]:
size (3, 4)
row 3
column 4
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
行列$A$に対して,A[i,:]で$A$の$i$行目のベクトルを,A[:,j]で $A$ の $j$ 列目のベクトルを取得できる.$i$ と $j$ が $0$ から始まることに注意せよ.
In [16]: import numpy as np A=np.random.rand(3, 4) print(A) print(A[0,:]) print(A[:,2]) Out [16]: [[0.13570483 0.34920421 0.72672949 0.35033207] [0.68598076 0.53217261 0.33295342 0.48206163] [0.47258342 0.53856068 0.94639199 0.2131011 ]] [0.13570483 0.34920421 0.72672949 0.35033207] [0.72672949 0.33295342 0.94639199]
連立一次方程式を解く際には,次のようにする./p>
In [17]: import numpy as np A=np.array([[1,2,-1],[4,5,6],[7,8,-3]]) x=np.ones(3) b=A@x print(A) print(x) print(b) xx=np.linalg.solve(A, b) print(xx) Out [17]: [[ 1 2 -1] [ 4 5 6] [ 7 8 -3]] [1. 1. 1.] [ 2. 15. 12.] [1. 1. 1.]
ただし,大規模な行列を係数行列とする連立一次方程式を解く際には,必ずしも,便利ではないようなので,以後,必要に応じて,他の方法も紹介する.
関数 \[ f(x)= \begin{cases} 1 & (0 < x < \pi)\\ 0 & (x=-\pi,0)\\ -1 & (-\pi < x < 0) \end{cases} \] のFourier級数展開は, \[ f(x)=\frac{4}{\pi} \sum_{k=0}^\infty \frac{\sin (2k+1)x}{2k+1} \] となる.Fourier級数展開の収束の様子を図示せよ. 具体的には,上の例を参考にして, \[ S_n(x)=\frac{4}{\pi} \sum_{k=0}^{n-1} \frac{\sin (2k+1)x}{2k+1} \] について,例えば,$n=1,10,20,50$などのときのグラフを描画せよ.