2018年6月21日木曜日

複素数

Pythonは複素数を直接書くことができます。

Python 2.7.12 (default, Dec  4 2017, 14:50:18)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a=0+1j
>>> a
1j

実数部が0のとき省略できます。

>>> a=1j
>>> a
1j

虚数部が0のときこれも省略できます。

>>> b=1
>>> b
1

これはただの実数ですが複素数と組み合わせるとちゃんと複素数の計算をしてくれます。

>>> a-b
(-1+1j)

複素数は距離の計算をとても楽にしてくれます。

a,b 2点間の距離は差をとって絶対値を計算すればよい。

>>> d=abs(a-b)
>>> d
1.4142135623730951

これは√2です。
これをbに代入します。

>>> b=d
>>> b
1.4142135623730951
>>> d=abs(a-b)
>>> d
1.7320508075688774

これは√3です。
このように計算結果を次々に代入していけば全ての整数の平方根がもとめられます。
(複素数の計算の内部で sqrt を使っているのでは? というツッコミはあると思いますが無視してください)

Haskellではこのような記法はできません。
Data.Complex モジュールの

(:+)

というコンストラクターで複素数のインスタンスをつくります。
Haskellではコンストラクターは関数であり、(:+)は演算子として定義されているので次のようにします。

GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help
Prelude> import Data.Complex
Prelude Data.Complex> let a=0:+1
Prelude Data.Complex> a
0 :+ 1
Prelude Data.Complex> let b=1:+0
Prelude Data.Complex> b
1 :+ 0

ちょっと不格好ですが・・・

Haskell は、複素数の絶対値を複素数で返します。

Prelude Data.Complex> abs(a-b)
1.4142135623730951 :+ 0.0

これは abs が
abs :: Num a => a -> a
と定義されているからです。
かわりにmagnitudeをつかいます。

Prelude Data.Complex> :t magnitude
magnitude :: RealFloat a => Complex a -> a

Prelude Data.Complex> magnitude(a-b)
1.4142135623730951

ちなみに Data.Complex では abs を
abs z               =  magnitude z :+ 0
と再定義しているようです。(^_^;)

N−1 の平方根がわかれば N の平方根が計算できることから再帰的に定義できます。

--r.hs
import Data.Complex
dist a b = magnitude (a-b)
a = 0 :+ 1
r 0 = 0
r n = dist a b
    where b = r (n-1) :+ 0


Prelude> :l r.hs
[1 of 1] Compiling Main             ( r.hs, interpreted )
Ok, modules loaded: Main.
*Main> map r [0..9]
[0.0,1.0,1.4142135623730951,1.7320508075688774,2.0,2.23606797749979,2.4494897427831783,2.6457513110645907,2.8284271247461903,3.0000000000000004]

容易に想像できると思いますがNが大きくなると時間がかかるようになります。

*Main> :set +s
*Main> r 1000000
1000.0000000000299
(7.74 secs, 1,673,363,512 bytes)

このような再帰は fold で置き換えることができます。

*Main> let r' n = foldl (\x _ -> dist a (x :+ 0)) 0 [1..n]
*Main> r' 1000000
1000.0000000000299
(5.28 secs, 1,474,441,216 bytes)

さらに Data.List モジュールの foldl'  を使うと

*Main> import Data.List
*Main Data.List> let r'' n = foldl' (\x _ -> dist a (x :+ 0)) 0 [1..n]
*Main Data.List> r'' 1000000
1000.0000000000299
(2.52 secs, 1,293,766,728 bytes)

magnitude 自体が重い処理なのでそこそこかかります。

fold を使ったコードは最初に説明した手続き的処理をそのまま置き換えている点に注目です。

手続き的なscanlの説明
参照

余談ですが (:+) の型は 

Prelude Data.Complex> :t (:+)
(:+) :: a -> a -> Complex a

です。引数の型は何でもいいようです。ですので

Prelude Data.Complex> "hello" :+ "world"
"hello" :+ "world"
Prelude Data.Complex> :t it
it :: Complex [Char]

もちろん演算とかはできません。

Haskell Process

Haskellの System.Processは便利ですが、問題もあります。 単一スレッドでの逐次処理を保証していない。(想像です。) 次のようなスクリプトを書いてみた。 --a.hs main = print [1..10] --t.hs import Sy...