e-Gadget - プログラム関数電卓 Casio Pythonへの移植:モンテカルロ法(1)

Casio Pythonへの移植:モンテカルロ法(1)

Python Casio Python
 Casioグラフ関数電卓の Python を使ってみる
     - Casio Python への移植:モンテカルロ法(1) 
目次


初版:2020/06/24
表現の修正:2020/06/27
追記:2020/10/20
追記・修正:2020/11/03
修正:2022/08/17
修正:2022/10/28

前の記事 - 3. Casio Python の入出力 |  次の記事 - 5. 関数の作成と活用


4. Casio Python への移植:モンテカルロ法(1)<fx-CG50 OS3.40以降>

Casio Basic プログラムを Casio Python へ移植しながら、ざっくりと Python の理解を進めてゆきます。

Casio Python では、入力がシェル画面でしか行えません。そこで、入力はシェル画面で行い、出力はグラフィックス画面で行う方針でスクリプト全体の構造を考え、あとは細かく作ってゆきます。

以下の動作をするスクリプトを作ってゆきます。


このスクリプトは、モンテカルロ法により円周率を求めるシミュレーションです。最初にシミュレーション回数を入力させ、シミュレーション実施の状況をグラフィカルに表示しながら、回数と円周率(pi)の値の変化をリアルタイム表示するものです。


スクリプトの構造

円を描画する関数を定義
 circle() 関数を自作

初期化処理

シェル画面でメニューを表示して、試行回数を入力させる
 print() でメニュー表示
 input() で回数を入力させ整数を取得
 ifによる条件分岐
   テンキー以外を押した時に異常終了しない処理

グラフィックス画面に出力する
 自作関数 circle() で円を描画
 draw_string() 関数で文字列を出力
 whileによる繰り返し(入力した回数分だけ繰り返し)
   random() 関数により正方形内の座標(x, y)を取得
   set_pixel() 関数で正方形内にに点を描画
   点の座標から円周率を計算 (Note 参照)
   draw_string() 関数で回数と円数率を表示
   (ここまでを繰り返す)
 draw_string() 関数でシミュレーション終了の表示


スクリプトの上から下へ制御が流れて行き、if 文while 文 で流れが変化する構造です。

Note:モンテカルロ法による円周率の計算方法
  1. 半径1の円と一辺2の外接正方形を考えます。
  2. x=1-2×[0~1の乱数]、y=1-2×[0~1の乱数] を計算して、-1<x<1-1<y<1 の (x, y)を得ます。
    つまり、(x, y) は一辺2の正方形内のランダムな点の座標となります。
  3. 正方形内にランダムに円を打って行き、全ての点の数を outs とし、そのうち x2+y2<1 になる(円内になる)回数を ins とします。(オリジナルのCasio Basic プログラムでこういう感じなので、単純に移植するために追従します)
  4. [正方形の面積]:[円の面積]outs:ins=4:π なので、π=4×ins/outs となります。

円描画関数の作成
Casio Python には、円を描画する組み込み関数が無いので、自分で作る必要があります。
ここでは、円の中心位置を決める x 座標と y 座標、そして円の半径 r をパラメータに指定して円を描画する関数を作ります。
Python の書式に従って、
 def circle(x, y, r):
  必要な処理

と記述します。詳細は 4.4 circle() 関数の作成 で説明します。

初期化処理
各変数の初期値を設定します。個別の処理と一緒に説明します。

シェル画面でメニュー表示と入力
主に、print() 関数と input() 関数の使いこなしがポイントです。if 文も使います、詳しくは 4.3 メニュー機能の作成 で説明します。

グラフィックス画面に出力
set_pixel() 関数と draw_string() 関数、そして clear_screen() 関数と show_screen() 関数の使いこなしがポイントです。 while 文も使います。詳しくは 4.5 グラフィックス出力 で説明します。 



今回作るスクリプトは、"これだけ知っていれば、簡単なスクリプトは作れる最低限の Casio Python の知識" で作りました。今回のメインテーマは、そのレベルの知識を紹介することです。

今回紹介する内容について、ネットで詳しく調べたり、より Python らしいコードを習得すれば、学習が深まると思います。


4.1 コメントの書き方 <fx-CG50, fx-9750GIII, fx-9860GIII OS3.40以降>

Pythonスクリプトでコメントを書くには、# を使うか トリプルクォート を使います。

Python公式:Comments
PEP 8 - Style Guide ofr Python の Comments の項 に推奨するコメントのスタイルが書かれています。ここでは、# を使ったブロックコメントとシングルラインコメントの推奨スタイル、そして3重ダブルクォーテーションを使った docstrings の推奨スタイルについて書いてあります。

Note:PEP 8
Python 公式文書 では、開発者のためのガイドラインや開発した機能が Python Enhancment Proposals (PEPs) にまとめられています。そして、全ての PEP はインデックス番号で文書管理されていて、コメントに関する推奨スタイルは PEP 8 にまとめられています。

#
# から行末までが、コメントとして扱われ、実行されません。公式推奨スタイルでは、# のあとにスペース1個を置いてからコメントを書くことになっています。また、説明を入れる記述と同じインデントレベルで書くことを推奨しています。
Casio Python は画面が狭いので、管理人は #  の後のスペースを入れないことが多いです。

Key Press:#
エディタ画面(スクリプト編集画面) で、[F3] (SYMBOL) - [F6] (▷) と押すと、[F1] (#) で # を入力できます。


''' (トリプルクォート - 3重シングルクォーテーション)
'''''' の間がコメントとして扱われます。複数行をまとめてコメントにすることができます。

Key Press:'
エディタ画面で、[F3] (SYMBOL) - [F6] (▷) と押すと、[F2] (') で ' を入力できます。


""" (トリプルクォート - 3重ダブルクォーテーション)
"""""" の間がコメントとして扱われます。複数行をまとめてコメントにすることができます。
Python のスタイルガイドでは、公開する関数やメソッドに関する説明は、3重ダブルクォーテーションを使った Documentation Strings を推奨しています。

Keypress:"
エディタ画面で、[F3] (SYMBOL) - [F6] (▷) と押すと、[F3] (") で " を入力できます。

Python公式:Domumentation Strings (Docstrings)
PEP 8 - Style Guide for Python の Documentation Strings の項 に詳しく書かれています。
・def 文の行の下の行を """ で始め、これに続いてその関数の説明をできるだけ簡潔に1行以内で書きます。
・そして、空行を入れます。
・空行の下から、パラメータや戻り値の説明を入れます。

Casio Python では、"""""" の間はピンク色になって分かりやすくなります。度 (degree) で表した角度をラジアン (radian) に変換する関数での Docstrings の一例です(たった2行のスクリプトなので、コメントばかりで大げさになっています);

 def rad(x):
 
"""Returns angle in radian

 Parameters:

 ーーーーーーーーーーーーー
 x: float
  angle in degree

 Returns:

 ーーーーーーーーー
 float
  angle in radian
 """

  return pix/180


4.2 モジュールの呼び出し - import <fx-CG50, fx-9750GIII, fx-9860GIII OS3.40以降>

Python は、最初から組み込まれている builtins モジュールに加えて、後からモジュールを追加して機能を増やせるようになっています。Casio Python は、math モジュールと random モジュールが追加された状態で最初にリリースされ、その後 casioplot モジュールが追加されました。今回は、これら全てを使いますので、モジュールの呼び出しをスクリプトの冒頭に書く必要があります。

例えば、set_pixel() 関数を使いたい場合は、それが含まれている casioplot モジュールを呼び出します。関数を使う前に、モジュールの呼び出しを記述しなければなりません。呼び出し方法は2通りあり、それによって関数の使い方が変わります。


import モジュール名

書 式
import casioplot
casioplot.set_pixel(x, y)

モジュールに含まれる関数を使う際は、関数の前に モジュール名. を追加しなければなりません。
casioplot オブジェクトに帰属する関数は、. で繋いで明示的に帰属関係を示す必要があります。これはオブジェクト指向言語に共通する書式と言えます。


from モジュール名 import *

書 式
from casioplot import
set_pixel(x, y)

はワイルドカードで、casioplot から全ての関数を呼び出すという意味で、帰属関数の関数名だけを書いて使えるようになります。

 これまでをまとめたスクリプト:
"""Sample script

Exercise;
porting from Casio Basic
"Monte Carlo Method"

by Krtyski/e-Gadget
"""

from casioplot import *
from random import *
from math import *



4.3 メニュー機能の作成 <fx-CG50, fx-9750GIII, fx-9860GIII OS3.40以降>

シミュレーション試行回数を変数 temp_try に入れることにします。試行回数 (try数) の仮変数という意味で temp を使って、temp_try という変数名にしました。

そこで、入力処理で最低限必要な記述は、

temp_try = input()

となります。

これを実行するとシェル画面で入力待ちになりますが、何も表示されず入力を待っています。そしてテンキーで数値を入力してから[EXE]キーを押すと、変数 temp_try に数値が格納されます。但し、input() の戻り値は、数値ではなくて文字列になるのが仕様で、これに留意します。

さて、次のようにすれば、入力の際に分かりやすくなります;
temp_try = input('Input number:')


Reference:⇒ input()

Note:Python での文字列 - ' ' と " "
string = '12345abcd'
あるいは
string = "12345abcd"
と書けば 変数 string は文字列型の変数として自動的に定義され、srting に文字列データ格納されますす。
Python では、シングルクォーテーションでもダブルクォーテーションでも、どちらでも使えます。
例えば、文字列の一部に " が入っている時、文字列の定義で ' を使えば " が文字列の一部として使える、といったメリットがあります。
string = 'He said "Wow"'
print(string)
を実行すると、
He said "Wow"
と出力されます。

Python で文字列は、シングルクォーテーション ' あるいは ダブルクォーテーション " で挟みます。Python 公式文書ではシングルクォーテションを使う事例が普通に使われているので、ここでは ' を使うことにします。そこで、文字列をパラメータに指定して input('Input number') と書きます。

これを実行すると、シェル画面では以下のようになります;

Input number:

そして、Input number: の直後でカーソルが点滅しているので、入力が分かりやすくなります。

monteca_input_1 

これで十分ですが、管理人は余計な表示を消したくなります。それだけでなく、このプログラムの説明も表示したくなります。

シェル画面では、[EXE}キーを押して改行すると、表示が上へスクロールされます。シェル画面の表示をクリアする関数が用意されていないので、余計な表示は改行でスクロールするしかありません。

そこで、print() 関数を使って表示を追加してみます。

以下のような表示を目標にします。
monteca_first_screen 

プログラム開始時は、試行回数500回をデフォルトにしておき、[EXE]キーでスグにシミュレーション開始。回数を変えたい場合は入力してから[EXE]キーを押してシミュレーション開始、となる仕様にします。これは、print() 関数と input() 関数だけで実現できます。

print(文字列) とするとシェル画面に文字列が表示され、改行されるのが、デフォルトの仕様です。
文字列なしで、単に print() とすると、文字列はシェル画面に表示されずに、改行だけが行われます。

すると、上の表示を行ってから最後に入力待ちにするには、以下のようにします。
・改行のみ
・Monte Carlo: calc Pi と表示して改行
・改行のみ
・# of try:500 と表示して改行
・[EXE] to start. と表示して改行
・改行のみ
・input() で Change the #? と表示して入力待ちして、変数 n に格納

これをスクリプトで書くと、文字列は ' ' の間に書くので、以下のようになります。
print()
print('Monte Carlo: calc Pi')
print()
print('# of try:500')
print('[EXE] to start.')
print()
temp_try = input('Change the #?')


ところで、エスケープシーケンスの1つである '\n' を文字列に使って print('\n') を実行しても改行されます。\ はバックスラッシュといい、/ (スラッシュ) とは傾きが違っていることに注意してください。

Key Press:\ (バックスラッシュ)
エディタ画面で、[F4] (CHAR) を押して、キャラクタ一覧から \ を選びます

そこで、上のスクリプトを書き換えてみます。

print('\nMonte Carlo: calc Pi')
print('\n# of try:500')
print('[EXE] to start.')
temp_try = input('\nChange the #?')


さて、ここで変数 temp_try に代入されるのは文字列です(input() の仕様)。例えば 1000 とキー入力すると temp_try には 文字列 '1000' が格納されます。ここでは、シミュレーション回数の変数 n_try に数値として 1000 を代入したいところです。
そこで、関数 int() を使います。

Note:int()
int(x) は、浮動小数点あるいは文字列の x を整数に変換します。浮動小数点については、小数点以下を切り捨てて整数にします。文字列については、数字だけの文字列なら整数に変換し、数字以外の文字列が含まれるときはエラーになります。

n_try = int(temp_try) とすると、n_try に整数が格納されます。

temp_try が数字だけの文字列かどうかを判定するには、isdigital() 関数が使えます。これは文字列オブジェクトの帰属関数なので、文字列型オプジェクト名に . を使って繋げて記述します。ここでは、文字列型のオブジェクトである temp_try の帰属関数として使うので、temp_try.isdigit() と書きます。

n_try = 500
if temp_try.isdigit():
 n_try = int(temp_try)


こうすると、デフォルトの試行回数が500回で、正しく数値が入力されたときは、その整数値を n_try に代入し、数値以外の文字が含まれた時は、n_try にはデフォルトの500回が適用された状態で、次の処理に進みます。

Note:if 文
if 文の書式
 if
条件1:
  処理1
  ・・・
 elif 条件2:
  処理2
  ・・・
 else:
  処理3
  ・・・
条件1が True (真)のとき(成り立つ時)あるいは 0 でない時、処理1を実行、
条件2が True (真)のとき(成り立つ時)あるいは 0 でない時、処理2を実行
それ以外のとき、処理3を実行
ifelifelse の行末にはコロン : が必要です。
コロン : の次の行には、インデントを1段深くします。深くなったインデントレベルが同じ行が if の範囲を示します。
elif でインデントレベルが1段戻り、if と同じインデントレベルにします。これにより ifelif が同列だと示します。
elif の行末にもコロン : が必要で、その下の行のインデントレベルで elif の範囲を示します。
else: の範囲も同様です。
このように、インデントレベルはスクリプトの正しい実行のために、極めて重要です。


Note:Python のデータ型
Casio Python で使う頻度の高いデータ型は、ざっくりと 数値型とシーケンス型に分けられます。そして数値型とシーケンス型は、いくつかのデータ型にさらに分けられます。
数値型: 整数型浮動小数点型に分けられます。
シーケンス型文字列型リスト型タプル型辞書型集合型に分けられます。
 シーケンス型は、複数要素を , で区切って並べたデータ型です。
データの種類に応じて、以下のデータ型があります。定義方法も異なります。

数値型変数の定義:   val = 123
浮動小数点型変数の定義:val = 12.34

文字列型
変数の定義:val = '12.34'
リスト型変数の定義:val = [12, 16, 24, 48]  val=['I', 'have', 'a', 'pen', '.']
タプル型変数の定義:val = (255, 255, 0)
辞書型変数の定義: val = {'ketchup':'red', 'mustard':'yellow', 'coffee':'black'}
集合型変数の定義: val = {'sun', 'mercury', 'venus', 'earth', 'mars', 'jupiter'}
   要素を囲む記号とコロン : の有無で、シーケンス型に属する細かいデータ型が決まります。
    Casio Python のオブジェクト一覧 を参照

今回は、整数型、浮動小数点型、文字列型の3つのデータ型を使います。


 これまでをまとめたスクリプト:
"""Sample script

Exercise;
porting from Casio Basic
"Monte Carlo Method"

by Krtyski/e-Gadget
"""

from casioplot import *
from random import *
from math import *

#initializing
n_try 500

#display menu to select try#
print('\nMonte Carlo: calc Pi')
print('\n# of try:'+str(n_try))  #ココの説明がまだでした!
print('[EXE] to start.')
temp_try input('\nChange the #?:')
if temp_try.isdigit():
 n_try int(temp_try)

まだ説明していなかった点を補足します。

上の作りかけのスクリプトでは
 print('\n# of try:') 

 print('\n# of try:'+str(n_try))
に変更しています。

数値変数 n_try を文字列に変換したものを + (文字列結合)を使って、文字列 '\n# of try:' に結合しています。
数値を文字列に変換するために、str() 関数を使っています。

Note:str()
str(x) は、オブジェクト x を文字列に変換して返します。x に整数型や浮動小数点型を指定すれば、文字列型で返します。


これで、以下の表示と動作の目標を達成しました。
monteca_10000_start 


4.4 circle() 関数の作成

これから、円を描画する circle() 関数を作ります。

この関数の書式を以下のようにします。
 circle(x, y, r)
 x: 整数型 (int型)、円中心の x 座標
 y: 整数型 (int型)、円中心の y 座標
 r: 整数型 (int型)、円の半径


関数を定義するには、def 文を使います。

 def circle(x,y,r):
  処理1
  処理2
   :
   :


def 文の行末には、コロン : が必要です。
コロン : の下の行は、インデントレベルを1段深くし、このインデントレベル以下の行が、def の範囲を決めます。

circle() 関数を使う場合は、例えば、circle(100,100,50)circle(px, py, r) のように書きます。3つのパラメータは、第1引数は x 座標値、第2引数は y 座標値、第3引数は 半径、といったように引数の順序、つまりその位置で意味が決まります。これを位置引数 (Positional Argument) と言います。

circle() 関数の定義は、circle() を使う前に記述する必要があります。スクリプトは上から下へ実行されるからです。今回は、スクリプトの冒頭、モジュール呼び出しの下に記述することにします。

 circle_desc 
グラフィックス画面 (描画画面) では、左上の座標が (0, 0) 、右方向へ正の x 軸、下方向へ正の y 軸になります。circle() 関数で、中心座標が (x, y)、半径 r の円を描画するようにします。

円を p 個の点で描画する時、描画する点(上で赤い点)を i を 0 から p-1 まで1づつ変化させながら、角度 i*a (=θ)で描画します(上の図を参照)。ここで、円を描く際の角度の1刻みが 角度 a になります。

以下のような方法で円を描画することにします;

def circle(x, y, r):
 p = (式)
 a = (式)
 for i in range(p):
  px = x + r*cos(i*a)
  py = y + r*sin(i*a)  
  draw_pixel(int(px), int(py))
  show_screen()


==========

この方針に従ってスクリプトを仕上げます。ap をどうやって決める(計算する) のかがポイントです。

Casio Python での三角関数は、角度がラジアンに限定されていますので、スクリプトはラジアンを使って角度を記述する必要があります。そこで、1周360° が 2π ラジアンなので、p 個の点で円を描画するときの角度の1刻み a は、2πp で割った角度になります。
 a = 2*pi/p
これで、a を算出できます。

さて、math モジュールには 円周率を返す関数として pi があります。そこで、math モジュールを呼び出した上で、上のように pi を使います。

すると、上の図から分かるように、i 番目の点の座標 (px, py) は次のように計算できます。
 px = x + r*cos(i*a)
 py = y + r*sin(i*a)

 
座標 (px, py) に点を描画するには、
 set_pixel(px, py)
を使いますが、px と py は整数でないとダメです。液晶パネルのドットは整数の座標で決まります。
しかし、上で計算した px と py は浮動小数点になっています。そこで、int() 関数を使って、浮動小数点の小数を切り捨てて、整数に変換します。

 set_pixel(int(px), int(py))

とすればOK。

そして、set_pixel() 関数でVRAMにデータ転送したものを、show_screen() 関数で画面に転送すれば、点が表示されます。

==========

この点の描画を、i を 0 から p-1 (整数) まで1づつ変化させながら繰り返すには、for 文を使います。

for i in range(p):
 px = x+r*cos(i*a)
 py = y+r*sin(i*a)
 set_pixel(int(px), int(py))

 show_screen()

set_pixel() 関数は、VRAMにデータを書き込むだけで、画面への転送を行いません。そこで、show_screen() 関数で画面にデータ転送を行うことで、描画が完了します。

Note:for 文
for i in range(p):
 処理

は、ip-1 になるまで1つづつ増やしながら、処理を繰り返します。
for 文の行末にはコロン : が必要です。コロン : の下の行はインデントレベルを1段深くします。このインデントレベルが同じ行がfor の範囲、つまり繰り返し処理の対象になります。ここでも、コロン : とインデントレベルは、正常動作のために極めて重要です。

==========

最後に、円を描画するために使う点の数 p の値を決めます。

[2020/11/03 追記修正]
先ず、CGモデル (fx-CG50) 向けに検討し、最後に FXモデルへの対応を検討します。

円の描画は、できる限りきれいで滑らかにしたいので、p は十分に大きくすれば良いのでしょうが、一方、できる限り短い時間で効率よく描画するには、p の値は必要最小限にしたいところです。必要最小限の点の数 p をどうやって決めるのかを考えてみます。適当で良いのかも知れませんが、チョットこだわってみます。

円を描画するとき、点と次の点の間隔が一番大きくなるのは、画面横幅分の 387 ドットの半径の円がx軸と垂直に交わる時です(必要条件)。その時に点と点の間隔の y座標の変化が1ピクセルになるときの点の数を Pmax とします。Pmax は、半径が 383 (画面横幅)の円を描画する時の点の数です。383 よりも小さな半径 r の円を描くための点の数 p は、Pmax よりは少なく

p = Pmax × (r/383)   (式1)

で十分だと分かります (Pmax を半径 383r で比例配分したもの)。

半径 383 の円で y座標値が一番急激に変化するのは、角度 0 の時なので、y座標値が 1 (pixel) だけ変化するときの角度変化を θ とすると、この θ は、滑らかに円を描画するために無駄のない最大の角度刻みとなります。これ以下の角度刻みは無駄だし、これ以上だと歯抜けの円となります。

この角度刻み θ は、383×sin(θ) = 1 となるときの θ です。この式を変形すれば、θ は以下のように計算できます。

θ = sin-1(1/383)

半径 383 で、さらに角度刻み θ で点を描画して円にするとき、1週するのに必要な点の数、つまり Pmax は以下になります。

Pmax = 2π/θ = 2π/sin-1(1/383)  (式2)

(式1) と (式2) から、以下の式で p が決まります。この p は、半径 r の円を描画するときに、滑らかでかつ無駄のない最小の描画点数です。

p = r * 2π/sin-1(1/383) / 383 = r*6.283178168  (CGモデル用)

この式を FXモデル用に適用するためには、383127 に置き換えれば良いことになります。383 はCGモデルでの画面の横幅で、127 はFXモデルでの画面の横幅です。

p = r * 2π/sin-1(1/127) / 127 = r*6.28312038  (FXモデル用)

CGモデル用とFXモデル用を見比べると、r の係数が小数点以下4桁まで同じだと気がつきます。そこで有効数字を考慮して r の係数を 6.283 とすれば、CGモデルとFXモデルの両方で共通して使えると思います。

p = r*6.283  (CGモデルとFXモデル共用)

ようやく p が決まったので、これから a が求められ、あとは上のロジックでスクリプトを書けば、circle() 関数が完成します。

==========
求めた p の妥当性
p を決める計算に登場する r の係数 (= 6.283) は、ほぼ 2π (π = 3.1415926...) になっています。円周の長さ (2πr) の値を に採用しても小数点以下3桁まで同じ計算結果になります。離散的なピクセルの間隔から必要最小限の p の数を求めた結果、FXモデルのように解像度の低い液晶画面でも、連続的な円周の長さの公式  p=2πr を採用しても大きな支障がないことが確認できました。 
==========

以上まとめると、中心座標が (x, y)、半径が r の円を描画する関数は、以下になります。

def circle(x, y, r):
 p = r*6.283
 a = 2*pi/p
 for i in range(p):
  px = x+r*cos(i*a)
  py = y+r*sin(i*a)
  set_pixel(int(px), int(py))
  show_screen()


さて、描画しようとする点 (pxpy) がグラフィックス画面の外側の時は、描画しないて次へ進んだ方が、さらに処理時間が節約できて、より効率化できます。

そこで、px を求める計算の直後で、px<0 または px>383 の時は、set_pixel() をスキップして、次の繰り返しを実行するようにします。それには、以下の赤の2行を追加します。

  px = x+r*cos(i*a)
  if px<0 or px>383:
   continue


同様にして、py を求める計算の直後に、py<0 または py>191 の時は、set_pixel() をスキップして、次の繰り返しを実行するように、以下の赤の2行も追加します。

  py = y+r*sin(i*a)
  if py<0 or py>191:
   continue



Note:continue
繰り返し処理(ループ)を行う for 文 と while 文 で、繰り返し処理の中で continue を使うと、それ以降の処理を行わずに forwhile にジャンプできます。

ここで、CPython に慣れた方は、上の range(p) に違和感を覚えるかも知れません。range() 関数の引数は整数でないとエラーになる筈なのに、Casio Python ではエラーになりません。これは独自仕様と言えそうです。

Reference:⇒ range()


以上で circle() 関数ができました。


 これまでをまとめたスクリプト:
"""Sample script

Exercise;
porting from Casio Basic
"Monte Carlo Method"

by Krtyski/e-Gadget
"""

from casioplot import *
from random import *
from math import *

#draw circle
def circle(xyr):
 r*6.283
 2*pi/p
 for i in range(p):
  px r*cos(i*a)
  if px<0 or px>383:
   continue
  py r*
sin(i*a)
  if py<0 or py>191:
   continue 
  set_pixel(int(px)int(py))
  show_screen()

#initializing
n_try=500

#display menu to select try#
print('\nMonte Carlo: calc Pi')
print('\n# of try:'+str(n_try))
print('[EXE] to start.')
temp_try input('\nChange the #?:')
if temp_try.isdigit():
 n_try int(temp_try)

#display graphics
#これ以降を書いて、プログラムを完成させます


4.5 グラフィックス出力 <fx-CG50 OS3.40以降専用>

Casio Python でグラフィックス画面 (描画画面) へ出力する関数は、点を描画する関数 set_pixel() と 文字列を描画する draw_string() の2つしかないので、これらを使いこなしたいと思います。set_pixel() については既に説明したので、ここでは、draw_string() に焦点を当てます。

これらのグラフィックス出力関数は、VRAMへのデータ転送を行い、画面への出力までは実行しません。従ってフラフィックス画面への出力は、show_screen() 関数を実行する必要があります。

グラフィックス画面を消去する clear_screen() 関数も使います。これは OS3.4 では隠し関数でしたが、OS3.5以降は公式関数となっています。

Note:Casio Python 組み込み関数を調べる
既に紹介しているように、隠し関数を調べるために ObjName.py が使えます。
これで見つかるのはオブジェクト名なので、ネットで詳細を調べる必要があります。


モンテカルロ法シミュレーションプログラムのグラフィックス画面への出力ブロックは、冒頭で書いたように、以下の手順でスクリプトを書いてゆきます。

 =============================================
 4.5.1 自作関数 circle() で円を描画
 4.5.2 draw_string()
関数で文字列を出力
 4.5.3 while
文による繰り返し(入力した回数分だけ繰り返し)
 4.5.4  random()
関数により正方形内の座標(x, y)を取得
 4.5.5  set_pixel()
関数で正方形内に点を描画
 4.5.6  
点の座標から円周率を計算
 4.5.7  draw_string()
関数で回数と円数率を表示
      
(ここまでを繰り返す)
 4.5.8 draw_string()
関数でシミュレーション終了の表示
 =============================================

4.5.1 自作関数 circle() で円を描画

円の中心座標を (290, 96) とし、半径を 90 とするので、以下を記述すればOKです。
そこで、それぞれを以下の変数に代入したものを使います。(数値を直接使っても問題ありませんが、あとで変更する可能性を考えて、初期化処理のブロックで変数宣言して、それを使うことにします。

以下を 初期化処理のブロックに追加します。
 r = 90 #radius
 dx = 290dy = 96 #center


Note:文の句切り文字 - ;
Python では、文の句切りとして改行が使われます。1行に複数の文を記述したいときは、文の句切り文字としてセミコロン ; も使えます。

円を描画するために、コメント #display graphics の下に
 circle(dx, dy, r)
を追記します。


4.5.2 draw_string() 関数で文字列を出力

以下のような出力になるようにスクリプトを書きます。
monteca_graphics_2 

 #initializing
 n_try 500
 r 90 #radius
 dx 290dy 96
#center


 #display graphics
 clear_screen()
 circle(dx,dy,r)
 draw_string(12,0'C=')
 draw_string(0,16'pi=')
 draw_string(0,160'# of try:'+str(n_try))
 draw_string(0,176'AC:Quit')
 show_screen()

今回既に説明した内容で書けると思います。


4.5.3 while 文による繰り返し

Python
には、繰り返し処理 (ループ) を行うために for 文と while 文の2つしかありません。for 文は既に使ってみました。ここでは While 文を使います。

Note:while 文
while 条件:
 処理1
 処理2

これが、while文の書式で、while の行末にはコロン : が必要で、コロンの行の下のインデントレベルを1段深くします。同じインデントレベルの行が、while による繰り返し処理の対象になります。

今回の繰り返し条件については、あとで説明します。


4.5.4 random() 関数により正方形内の座標(x, y)を取得

乱数発生関数 random() を使います。random() は、0 以上 1 未満のランダムの数値を返します。
ここでは、x が -1 ~ +1、y が -1 ~ 1 の間の乱数を取得します。

 x=1-2*random()
 y=1-2*random()



4.5.5 set_pixel() 関数で (x, y) に点を描画

点を描画するのは、円の中心 (dx, dy) と同じ座標を中心として1辺が2の正方形の範囲内です。そこで、点を描画する座標は、
 (dx+r*x, dy+r*y) 
となります。但し、set_pixel() のパラメータは、整数でないとエラーになるので、それぞれ int() で小数点以下を切り捨てて整数にします。

さらに、点を描画するには、set_pixel() を実行後に show_screen() を実行します。

 set_pixel(int(dx+r*x), int(dy+r*y))
 show_screen()



4.5.6 点の座標から円周率を計算

Note:モンテカルロ法による円周率の計算方法 (再掲)
  1. 半径1の円と一辺2の外接正方形を考えます。
  2. x=1-2×[0~1の乱数]、y=1-2×[0~1の乱数] を計算して、-1<x<1-1<y<1 の (x, y)を得ます。
    つまり、(x, y) は一辺2の正方形内のランダムな点の座標となります。
  3. 正方形内にランダムに円を打って行き、全ての点の数を outs とし、そのうち x2+y2<1 になる(円内になる)回数を ins とします。(オリジナルのCasio Basic プログラムでこういう感じなので、単純に移植するために追従します)
  4. [正方形の面積]:[円の面積]outs:ins=4:π なのて、π=4×ins/outs となります。
これをスクリプトで表現します。

 if x**2+y**2<1:
  ins+=1
 outs+=1
 pai = round(4*ins/outs, 11)


さて、While の条件を保留にしていましたが、ここで決めます。点を1つ描画するために 変数 outs が1づつ増えます。そこで、outs が試行回数 n_try よりも小さいこと、つまり outs<n_try を繰り返しの条件とします。

Note:累乗演算子 - **
x2Python では、x**2 と書きます。

Note:累算代入演算 (+=, -=, +=, /=, %=)
ここで、+= という演算があります。C言語系の経験があれば、おなじみだと思いますが、累算代入演算と言います。
ins+=1 は、ins=ins+1 と同じで、これを省略して記述するための演算子です。
a = a+b は、a+=b
a = a-b は、a-=b
a = a*b は、a*=b
a = a/b は、a/=b
a = a%b は、a%=b
と書けます。
一方、C言語系でおなじみに ++ 演算子(インクリメント、1増やす) を使って ins++ とすると、Pythonではエラーになります。

Note:四捨五入 - round()
Casio Python の浮動小数点は、小数点以下の有効桁数は15です。
シェル画面で、
 from math import *
 pi
を実行すると円周率が表示されますが、小数点以下15桁になっています。
今回のプログラムでは、計算結果の円周率は、何もしないと小数点以下15桁で表示されます。この桁数だと点描画の領域にテキストが上書きされます。これを避けるために、round() 関数を使って、
 pai = round(4*ins/outs, 11)
により、小数点以下11桁を指定して四捨五入しています。


4.5.7 draw_string() 関数で回数と円周率を表示

draw_string(36, 0, outs)
draw_string(36, 16, pai)


とすれば良いはずです。


ここまでをまとめます。

 これまでをまとめたスクリプト:
"""Sample script

Exercise;
porting from Casio Basic
"Monte Carlo Method"

by Krtyski/e-Gadget
"""

from casioplot import *
from random import *
from math import *

#draw circle
def circle(x,y,r):
 r*6.283
 2*pi/p
 for i in range(p):
  px x+r*cos(i*a)
  if px<0 or px>383:
   continue
  py y+r*sin(i*a)
  if py<0 or py>191:
   continue
  set_pixel(int(px)int(py))
  show_screen()

#initializing
n_try 500
ins = 0; outs = 0
90 #radius
dx 290dy 96 #center

#display menu to select try#
print('\nMonte Carlo: calc Pi')
print('\n# of try:'+str(n_try))
print('[EXE] to start.')
temp_try input('\nChange the #?:')
if temp_try.isdigit():
 n_try int(temp_try)

#display graphics
clear_screen()
circle(dx, dy, r)
draw_string(12,0'C=')
draw_string(0,16'pi=')
draw_string(0,160'# of try:'+str(n_try))
draw_string(0,176'AC:Quit')
show_screen()

while outs<n_try:
 x 1-2*random()
 y 1-2*random()
 set_pixel(int(dx+r*x), int(dy+r*y))
 if x**2+y**2<1:
  ins+=1
 outs+=1
 pai round(4*ins/outs,11)
 draw_string(36,0str(outs))
 draw_string(36,16, str(pai))
 show_screen()


さて一番下から3行の部分は、この通りに動作するとテキスト表示が次々と上書きされ、結果として真っ黒になってしまいます。
そこで、数字をカウントアップして、次々と変化が見えるようなスクリプトを検討します。


カウントアップのスクリプト

先ず、以下のスクリプトを作って検討します;

from casioplot import *
c = 0
clear_screen()
while c<=1000:
 draw_string(0, 0, str(c
))

 show_screen()
 c+=
1


変数 c を while ループを回るたびに 1 づつカウントアップし、それをグラフィックス画面に表示しています。
下から3行目 は、次々と上書きするので、以下のようになります。
countup_1 

そこで、同じところに空白文字で上書きすることで消去したのち、c の値を出力すれば良いというのは、いつも行うことです。
 draw_string(0, 0, '   ')
 draw_string(0, 0, s)


これでは、解決しません。スペースは空白文字ではないことが分かります。
では、明示的にスペースのエスケープシーケンス \x20 (16進数) あるいは \32 (10進数) を使ってみます。
 draw_string(0, 0, '\x20\x20\x20\x20')
 draw_srting(0, 0, s)


これでもダメです。
ちなみに、スクリプト編集画面で、[F4] (CHAR) を押してキャラクタセットを見てみると、そこには空白文字が見当たりません。スペースあるいは空白文字で上書きする方法は、Python では有効ではなさそうです。ネットで調べてみても、それらしい解決方法が簡単には見つかりません。

draw_string() 関数の第3パラメータ(引数) で、文字色を設定できます。そこで、同じ文字列を背景色の白で描画すれば、消去できるはずです。ループで前回描画したときのカウント cprev_c 変数に代入しておき、次に prev_c を白で描画すれば、目的の動作が可能になりそうです。そこで、次のようにスクリプトを変更してみます。

from casioplot import *
c = 0
clear_screen()
while c<=1000:
 draw_string(0, 0, str(prev_c), (255,255,255)) #追加
 draw_string(0, 0, str(c
))

 show_screen()
 prev_c = c #追加
 c+=
1


文字色の指定は、RGB指定なので、(255,255,255) で白を指定します。
これで目的が達成できました!!

ところで、追加した draw_string(0, 0, prev_s, (255,255,255)) の次に show_screen() も追加してみると、数値表示がチカチカして見づらくなります。show_screen() で画面への転送を行うと、消去した文字列が一旦表示されるのが原因です。そこで、VRAM上で、文字列消去と次の文字列データの書き込みを行ったのち、一気に画面への転送を行うのが良いわけです。


これと同じ方法を、モンテカルロ法のスクリプトに適用します。outs と pai の値を prev_outs と prev_pai の2つの変数に一旦格納し、次のループでの文字列表示の前に、前回の文字列を白色で出力します。

 これまでをまとめたスクリプト:
"""Sample script

Exercise;
porting from Casio Basic
"Monte Carlo Method"

by Krtyski/e-Gadget
"""

from casioplot import *
from random import *
from math import *

#draw circle
def circle(x,y,r):
 r*6.283
 2*pi/p
 for i in range(p):
  px x+r*cos(i*a)
  if px<0 or px>383:
   continue
  py y+r*sin(i*a)
  if py<0 or py>191:
   continue
  set_pixel(int(px)int(py))
  show_screen()

#initializing
n_try 500
ins = 0; outs = 0
prev_outs = 0
prev_pai = 0
90 #radius
dx 290dy 96 #center

#display menu to select try#
print('\nMonte Carlo: calc Pi')
print('\n# of try:'+str(n_try))
print('[EXE] to start.')
temp_try input('\nChange the #?:')
if temp_try.isdigit():
 n_try int(temp_try)

#display graphics
clear_screen()
circle(dx, dy, r)
draw_string(12,0'C=')
draw_string(0,16'pi=')
draw_string(0,160'# of try:'+str(n_try))
draw_string(0,176'AC:Quit')
show_screen()

while outs<n_try:
 x 1-2*random()
 y 1-2*random()
 set_pixel(int(dx+r*x), int(dy+r*y))
 if x**2+y**2<1:
  ins+=1
 outs+=1
 pai round(4*ins/outs,11)
 draw_string(36, 0, str(prev_outs), (255,255,255))
 draw_string(36, 16, str(prev_pai), (255,255,255))
 draw_string(36,0str(outs))
 draw_string(36,16, str(pai))
 show_screen()
 prev_outs = outs
 prev_pai =
pai


4.5.8 draw_string() 関数でシミュレーション終了の表示

シミュレーションが終了したときは、以下のように、左下の表示を Finish=>[EXIT] に変更しようと思います。
monteca_500 

シミュレーション実行中は、左下に AC:Quit と表示されています。そこで、これを白色で出力したのち、Finish=>[EXIT] を黒で出力すれば良いことは既に分かっています。

draw_string(0, 176, 'AC:Quit', (255,255,255))
draw_string(0, 176, 'Finish->[EXIT]')
show_screen()



このスクリプトは、while ループの外で実行するので、インデントレベルは while の中の処理よりも1段浅くしなければなりません。

 完成したスクリプト: monteca1.py のダウンロード
"""Sample script

Exercise;
porting from Casio Basic
"Monte Carlo Method"

by Krtyski/e-Gadget
"""

from casioplot import *
from random import *
from math import *

#draw circle
def circle(x,y,r):
 r*6.283
 2*pi/p
 for i in range(p):
  px x+r*cos(i*a)
  if px<0 or px>383:
   continue
  py y+r*sin(i*a)
  if py<0 or py>191:
   continue
  set_pixel(int(px)int(py))
  show_screen()

#initializing
n_try 500
ins = 0; outs = 0
prev_outs = 0
prev_pai = 0
90 #radius
dx 290dy 96 #center

#display menu to select try#
print('\nMonte Carlo: calc Pi')
print('\n# of try:'+str(n_try))
print('[EXE] to start.')
temp_try input('\nChange the #?:')
if temp_try.isdigit():
 n_try int(temp_try)

#display graphics
clear_screen()
circle(dx, dy, r)
draw_string(12,0'C=')
draw_string(0,16'pi=')
draw_string(0,160'# of try:'+str(n_try))
draw_string(0,176'AC:Quit')
show_screen()

while outs<n_try:
 x 1-2*random()
 y 1-2*random()
 set_pixel(int(dx+r*x), int(dy+r*y))
 if x**2+y**2<1:
  ins+=1
 outs+=1
 pai round(4*ins/outs,11)
 draw_string(36, 0, str(prev_outs), (255,255,255))
 draw_string(36, 16, str(prev_pai), (255,255,255))
 draw_string(36,0str(outs))
 draw_string(36,16, str(pai))
 show_screen()
 prev_outs = outs
 prev_pai =
pai

draw_string(0, 176, 'AC:Quit', (255,255,255))
draw_string(0, 176, 'Finish->[EXIT]')
show_screen
()


4.6 まとめ

Casio Python でスクリプトを書くための最低限の知識をざっくりと説明しました。
入出力に必要な関数、構造制御構文である 条件分岐 (if)、繰り返し処理(for, while) について、今回のスクリプトでおおよその使い方が分かったのではないかと思います。

各関数やメソッドについて詳しく知りたい場合は、ネットで確認してください。なお、Casio Python特有の仕様と思われるものは、別途 Reference として取り上げました。



目 次

前の記事 - 3. Casio Python の入出力

次の記事 - 5. 関数の作成と活用





応援クリックをお願いします。励みになるので...
にほんブログ村 IT技術ブログ 開発言語へ




keywords: fx-CG50Pythonプログラム関数電卓

リンク集 | ブログ内マップ



関連記事

テーマ : プログラム関数電卓
ジャンル : コンピュータ

コメントの投稿

非公開コメント

最新記事
検索フォーム
最新コメント
カテゴリ
C# (3)
Online Counter
現在の閲覧者数:
プロフィール

やす (Krtyski)

Author:やす (Krtyski)
since Oct 30, 2013


プログラム電卓は、プログラムを作って、使ってナンボ!

プログラム電卓を実際に使って気づいたこと、自作プログラム、電卓での Casio Basic, C.Basic そして Casio Python プログラミングについて書いています。

なお管理人はカシオ計算機の関係者ではありません。いつでもどこでもプログラミングができるプログラム電卓が好きな1ユーザーです。


写真: 「4駆で泥んこ遊び@オックスフォード郊外」

リンク
月別アーカイブ
Sitemap

全ての記事を表示する

ブロとも申請フォーム

この人とブロともになる

QRコード
QR