何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

cmathモジュールのジレンマ

無何有の郷へ

Pythonには複素数を計算するための函数を収めた標準モジュールcmathが存在する。

9.3. cmath — 複素数のための数学関数 — Python 3.6.1 ドキュメント

ドキュメントにはこのような一節がある。

mathと同じような関数が選ばれていますが、全く同じではないので注意してください。機能を二つのモジュールに分けているのは、複素数に興味がなかったり、もしかすると複素数とは何かすら知らないようなユーザがいるからです。そういった人たちはむしろ、math.sqrt(-1)複素数を返すよりも例外を送出してほしいと考えます。また、cmathで定義されている関数は、たとえ結果が実数で表現可能な場合 (虚数部がゼロの複素数) でも、常に複素数を返すので注意してください。

複素数に興味がないプログラマ複素数を全く知らないプログラマはそれぞれ存在するのか気になるところである。 日々の業務において実数向けの数学モジュールmathすら使う機会がない。最近必要に迫られてmath.cosを利用したぐらいである。

一方で、複素数に興味があるプログラマ複素数体代数的閉体であることを知るプログラマも存在する。 複素数に関するプログラミングにニーズがあるとすれば、電気回路、信号理論、制御理論などのFourier変換やLaplace変換が必要となるエンジニア、正則函数と戯れたい数学者がもつだろう。 しかし、彼らが行いたいことを満たすにはcmathは力不足である。彼らが必要なのはNumPyやSciPy、SymPyなどの科学計算ライブラリ、Mathematicaなどの専用のソフトウェアである。

帯に短し襷に長し。 興味がないプログラマには不要の長物であり、必要とするプログラマには機能が足りない。

荘子の逍遥遊篇に次の一説がある。荘子が恵子に無用の用を説く場面である。

今、子に五石の瓠あり。何ぞこれ以て大樽を為りて江や湖に浮かぶことを慮えずして、其の瓠落して容るる所なきを憂うるや。すなわち夫子には猶ほ蓬れたる心有り」と。

一見役に立たないcmathをいかに役に立たせるかが第77回Python Mini Hack-a-thonのテーマであった。

複素数の性質

実数と複素数は似ている要素もあるが違う要素もある。 似ている要素としては、体をなす(四則演算ができる)こと、位相を入れることができる(収束や連続という概念が定義できる)、がある。 位相を入れることができるのでcmathには指数函数や三角函数が存在するのである。

一方で、決定的に異なる要素としては全順序を定義できない、つまり自然な大小関係を定義できないことである。 Pythonもこの事実を受けて、複素数同士を比較すると例外が送出される。

a = complex(1, 2)
b = complex(10, 10)
a < b
TypeError: unorderable types: complex() < complex()

異なる性質を持つので、実数と複素数を分けて考える必要がある。

複素数と画像変換

今回は、複素数が実数平面上の点として表せることに着目して、ある種の画像変換に活用することにした。 画像のピクセルを座標とみなして、適当に生成したランダムの複素数を掛け算して画像に何かしらの処理を加える。 画像の枠からはみ出ると面白くないので無理やり枠内に収める処理を入れてある。トーラスっぽくなっている。

import itertools
import random
import time

from PIL import Image

image = Image.open("sample.jpg").convert('RGB')
size = image.size

for _ in range(5):
    image2 = Image.new('RGB', size)
    c = complex(random.gauss(0,1), random.gauss(0, 1))

    for x, y in itertools.product(range(size[0]), range(size[1])):
        pixel_data = image.getpixel((x,y))
        convert_complex = complex(x, y) * c
        convert_x = int(convert_complex.real) % size[0]
        convert_y = int(convert_complex.imag) % size[1]
        try:
            image2.putpixel((convert_x, convert_y), pixel_data)
        except IndexError:
            pass
    else:
        image2.save(str(int(time.time())) + ".png")

複素数と大学入試

実際に興味があったのは複素積分である。 まともにやると手も足も出ない積分が複素積分だと鮮やかに解けるのを学部2年で知った。 それを再体験したいのであるが、どうやってプログラミングで行うのか見当もつかない(線積分となるので)。 そこで、適当な大学入試問題を解くことにした。

2002年一橋大学の入学試験の数学の問2である。 問題文をTeX形式で入力するのは骨が折れるので適当なサイトを参照されたい。

import sympy

r = sympy.Symbol("r", real=True, positive=True)
t = sympy.Symbol("t", real=True)
a = sympy.Symbol("a")
f = sympy.Symbol("f", real=True)

a = r * (sympy.cos(t) + sympy.I * sympy.sin(t))
f = sympy.im(a + (1/a))
sympy.simplify(f)

最後に解くべき不等式を導くところまでできた。 SymPyで不等式を扱う方法がイマイチ理解できず最後は手計算で解いた。 自分の受けた課程では複素数平面は範囲外であったが、習っていれば高校二年生でも解けそうな雰囲気はある。

結論

現状、複素数を使わない場合はcmathmathC言語で実装した高速版ではなく複素数向けであることを理解する、複素数を本格的に使いたい場合は科学技術計算ライブラリを検討する、が現実的な解である。まだまだ私は恵子のように固定概念に囚われているようである。

PythonicではなくPythonesqueなクラスを書く その2

It’s…

youtu.be

最近、いかに役に立たないプログラムを書くかということを考えている。 世の中では企業が自社の開発の成果の一部をオープンソースとして切り出してたくさんの人に使われている。 そうすると自分も何か公開するには何か役に立つものを公開しなくてはいけないのではないか、という強迫概念に囚われてしまう。 業務では業務のプログラムを書いているので、ここでは何も役に立たないプログラミングをしてみたい。

今回は、Pythonesqueなクラスとして毎回変化する文字列のクラスを実装する。 文字列が評価されるたびに内部で書き換わるという役に立たない代物である。 現在のPythonではstrを直接継承してクラスを実装することが可能であるが、collections.UserStringを継承するほうがイタズラがしやすいと思われる。

import collections
import random
import string


class StrangeString(collections.UserString):


    def __str__(self):
        self.data = "".join([random.choice(string.ascii_letters) for _ in range(random.randint(1, 10))])
        return str(self.data)


if __name__ == "__main__":
    s = StrangeString("abc")
    for _ in range(10):
        print(s, s.upper(), s.lower())

実行結果は以下のとおりである。 実装からわかるように結果はランダムに変化する。

GCPp UAaiHZoGk UaTvs
ZoBIW olrixlE FUfCS
GTR S Itsq
CImAlqaHY ZBNY JoYBhYe
wdNtMJIAa EvQgkA oSfSRkj
DfwsTVxuaT cRZRtmOl BRKZjSav
GZW jcXG bpw
GpeN ecM EbwyaDsqK
naU rpRCnwhe vP
ZCD tK vqwJrSHkg

なるほど無秩序な結果となったが、無秩序な中にも秩序を求めたい。 s.upper()としたら結果が毎回ランダムになろうとも大文字になって欲しい。s.title()はタイトルであるべきだ。

PythonicではなくPythonesqueなクラスを書く

It’s…

プログラミング言語Pythonにおいて、Pythonらしい、といった概念をPythonicと呼ぶことがある。 Pythonicらしい書き方を知るにはZen of Pythonや『Effective Python』の第一章を参照すればその一端を知ることができる。

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

一方で、Pythonesqueという言葉もある。Pythonesqueとはプログラミング言語Pythonの名称のもとになったイギリスのコメディグループMonty Pythonのスケッチの不条理さを表した造語である。 折角なので「チーズショップ」(第3シーズン第7話)スケッチを引用する。

youtu.be

普段はPythonicな書き方を目指すべきであるが、たまにはPythonesqueな書き方をしてもよいのではないだろうか。 今回は、Pythonの特殊メソッドを変な風に実装することでPythonesqueなクラスを書く。 先ほど引用した「チーズショップ」スケッチのような辞書クラスを定義してみよう。

import collections


class CheeseShop(collections.UserDict):
    def __len__(self):
        return 0

    def __getitem__(self, item):
        raise KeyError


if __name__ == "__main__":
    shop = CheeseShop(Caerphilly="here")
    print("len(shop) = {}".format(len(shop)))
    for key in ("Red Leicester", "Tilsit", "Caerphilly"):
        try:
            print(key, shop[key])
        except KeyError:
            print("{} is not here!".format(key))

出力結果は以下の通りになる。

len(shop) = 0
Red Leicester is not here!
Tilsit is not here!
Caerphilly is not here!

絶対にこのような実装をしてはいけない(普通は良心が否定するか、レビュワーに殴られる)。 しかし、Python言語を理解する上では中々面白いと思う。