[Python][Gnuplot] ロジスティック写像の分岐図を描く

環境
Windows10 + Anaconda 4.8.1 + Python 3.7.6 + gnuplot 5.2(patchlevel 8)

はじめに

Pythonでロジスティック写像を計算し、Gnuplotを使って分岐図を描いてみた。Wikipediaの図ほど綺麗には描けなかったので気が向いたらリベンジしたい。

ロジスティック写像についてはwikiに詳しい説明がある。
ja.wikipedia.org

結果

f:id:cyanatlas:20200715223443p:plain

ソースコード

実行には少し時間がかかる。自分の環境だと3分くらいかかった。

import subprocess

import numpy as np
from tqdm import tqdm


def func(x, a):
    return a * x * (1 - x)


def calc(*, a, x0, n, n_run):
    res = np.ones(n + n_run) * np.nan
    for i in range(n + n_run):
        if i == 0:
            res[i] = func(x=x0, a=a)
        else:
            res[i] = func(x=res[i - 1], a=a)
    return res[n_run:]


N = 2000
N_run = 1000
x0 = 0.5
RESOLUTION = 6000

FILENAME = 'logistic_map.csv'


def main():
    a_list = np.linspace(2.4, 4.0, RESOLUTION)
    with open(FILENAME, mode='w') as f:
        for a in tqdm(a_list):
            xs = np.ones(N) * a
            ys = calc(a=a, x0=x0, n=N, n_run=N_run)
            for x, y in zip(xs, ys):
                f.write('{:f}, {:f}\n'.format(x, y))

    cmd = '; '.join(
        [
            r'set terminal png size 3840, 2880',
            r'set output \"logistic_map_gnuplot.png\"',
            r'set datafile separator \",\"',
            r'set xrange [2.4:4.0]',
            r'set yrange [0.0:1.0]',
            r'set noborder',
            r'set noxtics',
            r'set noytics',
            r'plot \"{}\" using 1:2 notitle pt 7 ps 0.1'.format(FILENAME),
            r'exit'
        ]
    )
    cmd = 'gnuplot -e "{}"'.format(cmd)
    subprocess.run(cmd, text=True)
    return


if __name__ == '__main__':
    main()

Gnuplotを使った他の記事

cyanatlas.hatenablog.com

[Python][Gnuplot] PythonからGnuplotを使ってグモウスキー・ミラの写像を作図する

環境
Windows10 + Anaconda 4.8.1 + Python 3.7.6 + gnuplot 5.2(patchlevel 8)

はじめに

以前、PythonでMatplotlibを使ってグモウスキー・ミラの写像を作図したのだが、小さな点の打ち方が分からず、あまり綺麗な図にならなかった。

cyanatlas.hatenablog.com

そのリベンジとして今回は、Matplotlibの代わりにGnuplotを使って作図してみる。

結果

f:id:cyanatlas:20200713220837p:plain

ソースコード

import numpy as np
from tqdm import tqdm
import subprocess


x0 = 0.1
y0 = 0.1
N = 1000000
FILENAME = 'gm.csv'


def F(x, mu):
    return mu * x + 2 * (1 - mu) * x**2 / (1 + x**2)


def func(x, y, alpha, sigma, mu):
    x_next = y + alpha * (1 - sigma * y**2) * y + F(x, mu)
    y_next = - x + F(x_next, mu)
    return (x_next, y_next)


def calc(params):
    x, y = (x0, y0)
    with open(FILENAME, mode='w') as f:
        for _ in tqdm(list(range(N))):
            f.write('{:.8f}, {:.8f}\n'.format(x, y))
            x, y = func(x, y, **params)
    return


def plot():
    cmd = '; '.join(
        [
            r'set terminal png size 3840, 2880',
            r'set output \"test.png\"',
            r'set datafile separator \",\"',
            r'set xrange [-20.0:24.0]',
            r'set yrange [-14.5:12.5]',
            r'set noborder',
            r'set noxtics',
            r'set noytics',
            r'plot \"gm.csv\" using 1:2 notitle pt 7 ps 0.1',
            r'exit'
        ]
    )
    cmd = 'gnuplot -e "{}"'.format(cmd)
    subprocess.run(cmd, text=True)
    return


def main():
    params = {
        'mu': -0.8,
        'alpha': 0.008,
        'sigma': 0.05
    }
    calc(params=params)
    plot()
    return


if __name__ == '__main__':
    main()

解説

簡単にですが。

  • def calc():returnでグモウスキー・ミラの写像N回計算し、計算結果をgm.csv(カンマ区切り)に出力。
  • def plot():returnGnuplotを使ってgm.csvをプロット。
  • コマンドプロンプトgnuplot -e "command1;command2..."と入力すると、gnuplotに一連のコマンドを実行させることができる。Pythonでからsubprocess.run関数でこのコマンドを入力すればGnuplotを使うことができる(記事最下部の参考ページを参照のこと)。
  • gnuplotに送るコマンドの中ではダブルクオーテーションをエスケープする必要があることに注意。
  • 以下はgnuplotのコマンドの解説
    • set datafile separator ","は、カンマ区切りのファイルを読み込むための設定。
    • set noborderで枠・軸を削除。set noxticksset noyticksで目盛りを削除。
    • plot "gm.csv" using 1:2 notitle pt 7 ps 0.1でプロット。notitleでlegendを削除。pt 7でマーカーを丸に設定。ps 0.1はマーカーサイズの設定。

参考ページ

PythonからGnuplotを使う方法を参考にさせていただいた。
auewe.hatenablog.com

[Python][Matplotlib] グモウスキー・ミラの写像を描く

はじめに

不思議な模様が現れることで有名な差分方程式系の一つに、グモウスキー・ミラの写像というものがある。

ja.wikipedia.org

以前、PythonのMatplotlibを使って作図したコードを発見したので貼ってみる。本当はもっときれいに描きたかったのだが、Matplotlibでの方法がよく分からなかった。

出力結果

f:id:cyanatlas:20200707223436j:plain
figure1.jpg
f:id:cyanatlas:20200707223439j:plain
figure2.jpg

figure2の方は、五枚の翼を持つ鳥に見えるということで、写像の提案者の一人であるミラは「神話の鳥 (mythic bird)」と呼んだらしい。

ソースコード

import os.path

import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm


x0 = 0.1
y0 = 0.1
T = 20000
N = 1000
marker = '.'
markersize = 1
fillstyle = 'full'
DPI = 1200


def F(x, mu):
    return mu * x + 2 * (1 - mu) * x**2 / (1 + x**2)


def func(x, y, alpha, sigma, mu):
    x_next = y + alpha * (1 - sigma * y**2) * y + F(x, mu)
    y_next = - x + F(x_next, mu)
    return (x_next, y_next)


def plot_times(*, x_init, y_init, ax, N, params):
    xs, ys = [np.ones(N) for _ in range(2)]
    xs[0], ys[0] = func(x_init, y_init, **params)
    for t in np.arange(N - 1):
        xs[t + 1], ys[t + 1] = func(xs[t], ys[t], **params)
    ax.plot(
        xs, ys, fillstyle=fillstyle, markerfacecolor='C0', markeredgecolor='C0',
        marker=marker, markersize=markersize, linewidth=0
    )
    return xs[-1], ys[-1]


def plot(params, T, filename):
    x, y = (x0, y0)
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(x, y)

    times_now = 0
    while True:
        if times_now >= T:
            break
        elif times_now + N < T:
            x, y = plot_times(x_init=x, y_init=y, N=N, ax=ax, params=params)
            times_now += N
        elif (times_now < T) and (times_now + N >= T):
            n = T - times_now
            x, y = plot_times(x_init=x, y_init=y, N=n, ax=ax, params=params)
            times_now += n
        else:
            raise ValueError

    fig.savefig(filename, dpi=DPI)
    return


def main():
    params = {
        'mu': -0.38,
        'alpha': 0.0083,
        'sigma': 0.1
    }
    plot(params=params, T=T, filename='figure1.jpg')
    params = {
        'mu': -0.8,
        'alpha': 0.008,
        'sigma': 0.05
    }
    plot(params=params, T=T, filename='figure2.jpg')
    return


if __name__ == '__main__':
    main()

[Python][Matplotlib] 等高線プロットcontourfで一部に色を塗らない

結論から言えば、色を塗りたくない場所にnp.nanを代入しておけばよい。

通常のcontourf

from itertools import product
import matplotlib.pyplot as plt
import numpy as np

X = np.linspace(0, 5, 20)
Y = np.linspace(0, 5, 20)
X, Y = np.meshgrid(X, Y)
Z0 = np.cos(1.5 * X) * np.cos(1.5 * Y)
fig = plt.figure()
ax = fig.add_subplot()
cs = ax.contourf(X, Y, Z0)
fig.colorbar(cs, ax=ax)
fig.savefig('test_Z0.jpg', dpi=600)
plt.close(fig)

f:id:cyanatlas:20200707013325j:plain

一部領域で色を塗らないcontourf

例えば右下( y < x)で色を塗らない場合。

from itertools import product
import matplotlib.pyplot as plt
import numpy as np

X = np.linspace(0, 5, 20)
Y = np.linspace(0, 5, 20)
Z0 = np.cos(1.5 * X) * np.cos(1.5 * Y)
Z1 = np.copy(Z0)
for yi, xi in product(range(Z1.shape[0]), range(Z1.shape[0])):
    if Y[yi] < X[xi]:
        Z1[yi][xi] = np.nan
    else:
        Z1[yi][xi] = np.cos(1.5 * X[xi]) * np.cos(1.5 * Y[yi])
fig = plt.figure()
ax = fig.add_subplot()
cs = ax.contourf(X, Y, Z1)
fig.colorbar(cs, ax=ax)
fig.savefig('test_Z1.jpg', dpi=600)
plt.close(fig)

f:id:cyanatlas:20200707013447j:plain

その他

corner_mask変数を使う方法もあるようだ。

matplotlib.org

[Python][Matplotlib] よく使うリンク集

Matplotlibを使うときに自分がよく開くページのリンク集。公式ドキュメントがメインだが他のページも。随時追加。

目次

Matplotlibの公式ドキュメントについて

Matplotlibについて検索すると、バージョンの異なる複数のMatplotlib公式ドキュメントがヒットする。開いている公式ドキュメントが扱っているMatplotlibのバージョンは、URLを見るか、ヘッダー画像を見れば分かる(がやや分かりにくい)。

記事執筆時点での最新バージョンは3.2.2。

matplotlib.org

古いものでは、ver2.0のドキュメントも存在する。少なくともメジャーバージョンが同じドキュメントを参照するように注意した方がよいだろう。

matplotlib.org

最新バージョンは次のページで確認できる。

matplotlib.org

ちなみに自分が使用しているバージョンは__version__変数で次で確認できる。

import matplotlib

print(matplotlib.__version__)

あまり見ることはないが、リリース情報はこのページ。

github.com



色関連

Color

色の指定の仕方とか、Colormapクラスの説明とか

matplotlib.org

名前のついている色(文字列で指定可能な色)の一覧

matplotlib.org

公式ページではないが、色の指定方法について分かりやすくまとめられている。

pythondatascience.plavox.info

Colormap

Colormapの一覧がある。

matplotlib.org

Colorpalette(seaborn)

seabornライブラリを使って色(のリスト)を作成することできる。

qiita.com


等高線プロットContour(f)関連

Axes.contour(f)関数

contourは等高線プロット。contourfは等高線プロットで色を塗ったもの。

matplotlib.org

matplotlib.org

Contourプロットで線上にラベルを付ける方法

matplotlib.org


imshow関連

Axes.imshow関数

本当はcontourfを使って作図したいが、詳細な色設定が面倒なとき、代わりにimshowを使って作図することがある。

matplotlib.org

軸と原点の設定

imshowは通常のプロットとは軸や原点が異なるので注意が必要。軸の貼り直しはextent引数で、原点位置の修正はorigin引数で行う。

matplotlib.org

[Python] CSVファイルの表をはてな記法の表に変換する

2020/07/16追記
オンラインコンバータを作ってみた。
cyanatlas.hatenablog.com

はじめに:はてな記法の表とは

はてなブログなどにははてな記法という独自の言語があり、これを使うことでHTMLファイルを編集せずにWEBページが書けます。

例えば次のように書けば表が表示されます。

|*名前|*色|*個数|
|りんご|赤|1|
|みかん|だいだい|2|
名前 個数
りんご 1
みかん だいだい 2

hatenadiary.g.hatena.ne.jp

問題点:はてな記法で表を書く面倒さ

はてな記法で表を書くのは、HTMLで表を書くより楽だが、それでもやや面倒であると感じる。理由は次の二つ。

  1. 編集中は列(縦のライン)が揃わないので、どこを編集しているのか分かりにくい
  2. WEBページの表をコピペできない

解決策としては次の二つが考えられる。

  1. はてな記法をやめてHTML編集にする(WEBページやExcelの表をコピペできるようになる)。
  2. Excelの表をプログラムではてな記法に変換する。

一つ目の方法については複数のWEBページで紹介されていたので各自検索してほしい。

一つ目の方法で解決できるとは言え、表組みのためだけに、表組以外は簡単に書けるはてな記法をやめたくないという私のような人もいるだろう。そこで、この記事は二つ目の方法を説明する。

解決策:CSVファイルをはてな記法に変換する(Python

  1. Excelなどを使ってCSVファイルを作成する(エンコードUTF8-BOM、コンマ区切りとすれば、後に示すPythonコードをそのまま使えるはず)。
  2. 後に示すPythonコードで、FILENAME変数を変換したいCSVファイルの名前(あるいは相対パス)に変更する。
  3. Pythonコードを実行して、はてな記法を含むtxtファイルを出力する(ファイル名はhatena_FILENAME.txt)。

コードを示す。

import os
import csv


FILENAME = 'test.csv'


def main():
    res = ''
    with open(FILENAME, mode='r', encoding='utf-8-sig') as f:
        csvreader = csv.reader(f, delimiter=',')
        head = True
        for row in csvreader:
            for cell in row:
                if head:
                    res += '|' + '*' + cell
                else:
                    res += '|' + cell
            res += '|\n'
            head = False

    outputfilename = 'hatena_' + os.path.splitext(os.path.basename(FILENAME))[0] + '.txt'
    with open(outputfilename, mode='w', encoding='utf8') as f:
        f.write(res)
    return


if __name__ == '__main__':
    main()

オンラインコンバータ

作ってみた。

cyanatlas.hatenablog.com

[Python] タプルには代入できないがタプル内リストには代入できるという話

環境:Windows10 + Python 3.7.6

リストとタプルの違い

Pythonの組み込み型には、配列を表現できるリストタプルという二つの型が用意されている。この二つには、値の変更を許すかどうかに違いがある。

リスト:要素の変更を許す
タプル:要素の変更を許さない

リストの要素を変更することはできるが、タプルの要素を変更することはできない。もし無理に変更しようとすれば、エラーとなる。

コード 1

x = [1, 2, 3]
print(x)
x[0] = 9
print(x)

出力 1

[1, 2, 3]
[9, 2, 3]


コード 2

x = (1, 2, 3)
print(x)
x[0] = 9
print(x)

出力 2

(1, 2, 3)
Traceback (most recent call last):
  File "XXX.py", line 3, in <module>
    x[0] = 9
TypeError: 'tuple' object does not support item assignment

何度も変更する予定の場合はリストを使い、変更する予定がない場合はバグを防ぐためにタプルを使う。というのが基本的な方針である。

では、タプルの中身は変更されないと考えてよいだろうか?

実はそうとは限らない。以下に紹介するタプル内リストの要素を変更する場合、エラーが出ることなくタプルの中身を変更することができてしまう。

タプル内リストの落とし穴

次のコードではタプルの第一要素であるリストを変更しようとしているが、もちろんこれはエラーが出る。

コード 3

x = ([1, 1], [2, 2], [3, 3])
print(x)
x[0] = [9, 9]
print(x)

出力 3

([1, 1], [2, 2], [3, 3])
Traceback (most recent call last):
  File "XXX.py", line 3, in <module>
    x[0] = [9, 9]
TypeError: 'tuple' object does not support item assignment

しかし、次のコードが示すように、タプル内リストの要素を変更することはできる。なぜなら、タプル側が参照しているリスト自体は変わっていないためである。タプルからすると、同じリストを参照していることは保証しているが、リストの中身が同じであることまでは保証してくれないのである。

コード 4

x = ([1, 1], [2, 2], [3, 3])
print(x)
x[0][0] = 9
print(x)

出力 4

([1, 1], [2, 2], [3, 3])
([9, 1], [2, 2], [3, 3])

解決策:タプル内リストを避ける

上で紹介したような奇妙な挙動は、バグを引き起こす原因となりやすい。そのため、プログラムの中ではタプル内リストを避けるのが良い。状況に応じてタプル内タプルかリスト内リストを使えば良いだろう。

変更予定のある変数 --> リスト内リストを使う
変更予定のない変数 --> タプル内タプルを使う