アプリ版:「スタンプのみでお礼する」機能のリリースについて

次の----以降のように、物理の斜方投射のプログラムを
pythonで記述したのですが、目盛り線や軸線を描くために
canvas.create_line
canvas.create_text
のコードを追加すると、シミュレーション中のボールの動きが
かなり、とても遅くなってしましました。

ボールの動きがスムーズに動いてかつ目盛り線描画を維持するためには
どうしたら良いでしょうか?

-------------------
tkinterとmathをインポート、
def move():から window.after(50, move)までインデント

def move():
global x
global y
global vy
global t
global x1
global y1

canvas.delete('ball')
canvas.create_oval(x-MARGIN, (HEIGHT-y)-MARGIN, x+MARGIN, (HEIGHT-y)+MARGIN, fill='red', tags='ball')
canvas.create_oval(x1-MARGIN, (HEIGHT-y1)-MARGIN, x1+MARGIN, (HEIGHT-y1)+MARGIN, fill='gray', tags='ball')

for i in range(1, 60):
canvas.create_line(0, i * 10,800, i * 10, fill = '#000000' if i==30 else '#d2d2d2')
canvas.create_text(10, 250,text="50",font=("", 10))
canvas.create_text(10, 200,text="100",font=("", 10))
for i in range(1, 80):
canvas.create_line(i * 10, 0, i * 10,600, fill = '#000000' if i==1 else '#d2d2d2')
canvas.create_text(60, 310,text="50",font=("", 10))
canvas.create_text(110, 310,text="100",font=("", 10))
canvas.create_text(160, 310,text="150",font=("", 10))
canvas.create_text(210, 310,text="200",font=("", 10))
canvas.create_text(260, 310,text="250",font=("", 10))
canvas.create_text(310, 310,text="300",font=("", 10))
canvas.create_text(360, 310,text="350",font=("", 10))
canvas.create_text(410, 310,text="400",font=("", 10))
canvas.create_text(460, 310,text="450",font=("", 10))
canvas.create_text(510, 310,text="500",font=("", 10))
canvas.create_text(560, 310,text="550",font=("", 10))
canvas.create_text(610, 310,text="600",font=("", 10))
canvas.create_text(660, 310,text="650",font=("", 10))
canvas.create_text(710, 310,text="700",font=("", 10))
canvas.create_text(760, 310,text="750",font=("", 10))
canvas.create_text(810, 310,text="800",font=("", 10))

#時速30km=30km/h=秒速83.3m/s=秒速30pixel=30pixel/s

if 0 <= x and x <= WIDTH and 0 <= y and y <= HEIGHT:
x = x + vx0 * dt
v1 = vy
v2 = vy - g * dt
y = y + (v1+ v2) / 2.0 * dt
vy = v2
t = t + dt
else:
x = x0
y = y0
vy = vy0
t = t0


window.after(50, move)


#WIDTH, HEIGHT = 1000, 500
WIDTH, HEIGHT = 800, 600
MARGIN = 10

t0 = 0.0
dt = 0.1
g = 9.8

v0 = float(input('初速度を入力してください'))
degrees = float(input('角度を入力してください'))

theta = degrees * math.pi / 180
vx0 = v0 * math.cos(theta)
vy0 = v0 * math.sin(theta)
x0 = 10
y0 = 2 * HEIGHT / 4
x1 = 250
y1 = HEIGHT / 2
x = x0
y = y0
vy = vy0
t = t0

geo_str = str(WIDTH+MARGIN*2) + 'x' + str(HEIGHT+MARGIN*2)
window = tkinter.Tk()
window.geometry(geo_str)
window.title('moving')

canvas = tkinter.Canvas(window, width=WIDTH, height=HEIGHT, bg='white')
canvas.place(x=MARGIN, y=MARGIN)
canvas.create_oval(x-MARGIN, (HEIGHT-y)-MARGIN, x+MARGIN, (HEIGHT-y)+MARGIN, fill='red', tags='ball')

move()
window.mainloop()

質問者からの補足コメント

  • https://dotup.org/uploda/dotup.org2995853.zip.html
    で頂いた、projectile_motion.pyをダブルクリックで実行しましたが、
    コマンドプロンプトが一瞬立ち上がってすぐ落ちてしまいます。
    頂いた全ソースコードの使い方を教えて頂けますでしょうか?

    グローバル変数の無駄を用いて何度も計算するのが無駄なのと、
    グローバル変数を使うのは避けた方が良いのですね。

    中盤から後半は、はじめてきく語(ジェネレータ、無限長のシーケンス)があるため今はよくわかりませんが、
    また精進していくなかでその有難味がわかる日がくるかもしれません。
    有難うございます。

    No.1の回答に寄せられた補足コメントです。 補足日時:2023/06/03 14:57
  • 回答有難うございます
    projectile_motion.pyはダブルクリックで初速度など入力する画面が現れますが、
    一瞬コマンドプロンプトが見えて計算結果の数字が羅列される画面が出た後すぐに落ちてしまします。
    さらにmove.pyやmove_support.pyはダブルクリックしてもすぐ落ちてしまいます。
    もう一歩のところなんですが残念です。

    遅れましたが当方Windows10でC言語経験者です、C++もかじっていました。
    Pythonはよくプログラミングでニュースや話題にもなっているし、AIも関係しているし、
    大学の後輩が(私が現役のときはCしかなかったが今は)PythonメインでやっているのでPythonに興味を持ちました。

    No.3の回答に寄せられた補足コメントです。 補足日時:2023/06/04 08:16
  • 無限長のシーケンスは無限級数というか、微積分の無限大の概念みたいなものなのですね  [1, 2, 3]→[1, 2, 3, ...]

    特殊な関数で、中に一種の無限ループする関数が入ってて「実行が止まってる」。
    「実行しろ」って言うと1つだけ値を吐き出してまた止まる。
    というのがジェネレータなんですね。今はあまり使いどころがわからないですが。
    有難うございます。

      補足日時:2023/06/04 08:16
  • projectile_motion.pyをダブルクリックではなく、
    コマンドプロンプトからpythonで実行すると計算結果がでました。
    数値がちょっと予想と違いましたが、今回は有難うございました。

      補足日時:2023/06/05 15:54

A 回答 (4件)

う〜んと、だな。


取り敢えず全ソースコードをここに置いておこうと思う。

https://dotup.org/uploda/dotup.org2995853.zip.html

んでだ。
元々の(貴方が書いた)プログラムがPythonのようなモダンなプログラミング言語で見ると色々と「マズい」プログラムなんだよ。大昔の(科学技術計算用の)Fortranと言うプログラミング言語で書かれたようなバッチ式のプログラムになってる。
これは恐らく、貴方のせいじゃなくって、コンピュータサイエンス系の授業じゃない、それこそ理学系の「Fortranのようなプログラミング言語を用いたスクリプト記述」を教える授業かなんかのせいだと思う。
ゴメンね、悪く言ってるわけじゃなくって、単に元々の「学習環境」のバックグラウンドのせいでそういう差異が生まれたんじゃないか、って思ってるんだ。多分貴方が生粋の理学系の授業を受けてるか何かなんじゃないか、って予想してたわけだけど・・・。
だからその辺はスルーしとくかな、とか思ってたわけだが・・・・・・。

まず平たく言うと、だ。運動学上の計算を「何度も」行ってる辺りが一番速度を低下させてる原因じゃないか、と思う。目的に従って「何度も」描画するのは悪くないんだけど、一方、一個の初速度と角度を与えるだけならそれらが導く「計算結果」自体は変わらんわけ。物理学とはそういう学問だからな。
で、貴方のプログラムの場合、「大域変数」を用いて何度も再計算してる。これがまずは無駄なんだ。
モダンなプログラミング言語環境だと、「同じ計算を何度も行う」より「結果を保存する」事を狙う。具体的に言うと、リストを用いて「軌跡」の演算結果を保存しちまうわけだな。そして描画の際にそれらを順次「参照」してけばいい、ってわけ。そうすれば「再計算」が防げてプログラムは描画だけに集中出来る。
まずはそういう方針で行ってる。

それで繰り返すけど、モダンなプログラミング言語では大域変数(グローバル変数とも呼ぶ)を使うプログラミングは避けよう。参照対象としては悪くないけど、「書き換え」を狙うのは良くない。
あるいは、初期位置や重力加速度とか「定数」を扱いたい、と言う場合は、まず使用を考えるのは大域変数じゃなくって関数やメソッドの「キーワード引数」だ。

【Python入門】キーワード引数の使い方:
https://python-introduction.com/?p=1299

どうしてもキーワード引数で上手く行かない時「だけ」大域変数を使うようにしよう。

次に斜方投射の計算テクニックに付いて、だ。ここではPythonのジェネレータ式、と言うテクニックを用いてる。

ジェネレータ式:
https://docs.python.org/ja/3/reference/expressio …

まず、物理で良くあるのが時間の初期値t0 = 0があって、それに対して微小時間Δtを順次加算していってtを計算していく、と言う手法。貴方の書いたプログラムでもt0 = 0とし、t = t + dtを計算して「時間経過」を表現してる。
一方、Pythonにはitertoolsと言うライブラリがあり、countと言うジェネレータがある。

itertools.count:
https://docs.python.org/ja/3/library/itertools.h …

これはPythonで有名なrangeと良く似てるんだけど、前提が「無限長のシーケンスを生成する」ってのが違いだ。
itertools.countはオプショナル引数として第1引数に初期値、第2引数に「差分」を与えられる。
つまり、

itertools.count(0, 0.1)

と言う計算では0から始まって0.1ずつ増える「無限長のシーケンス」が入手出来るわけだ。と言う事は、実際問題、これを「時間」として扱って構わない、と言う事になる。
なんで「無限長なのか」と言うと、単に「どのくらいの長さの時間列が必要なのか分からないから」だ。
題意から言って、貴方のプログラムでもそうだけど、「描画可能範囲」、つまり800x600か?その範囲外に「ボールが飛び出したら」その辺を計算するのは無駄になる。
一方、斜方投射で「何秒後」に描画範囲外に出るのか、ってのは「分からない」。
いや、分かるよ(笑)?物理学だもん。初速いくらで角度いくら、で投げた場合描画範囲外に到達するのが「何秒後になる」かは、「予言の学問」物理学なら計算で分かる。分かるんだけど、そのために手計算する、とかプログラムを組むってメンド臭いでしょう(笑)。
こういう場合、Pythonのようなモダンなプログラミング言語だと、「無限長の時間列」を採用した方がスッキリするんだ。それで運動学に従って計算する。そして「範囲内にある」計算結果を「切り取って」くるわけ。そしてその「切り取り手段」も用意されている。

itertools.takewhile:
https://docs.python.org/ja/3/library/itertools.h …

さて、「無限長の時間列」が入手出来る以上「無限長の運動学の計算結果」が入手出来る。初期位置が(x0, y0)、初期速度が(vx0, vy0)、重力加速度がg、時間がtだとすれば、運動学の方程式によって、基本的には次のジェネレータ式が「無限長の」ボールの位置情報になる。

((x0 + vx0 * t, y0 - vy0 * t + 1/2 * g * t ** 2) for t in count(0, 0.1))

これは斜方投射したボールが結果「どこまでも進みながら落ち続ける」と言う事柄を表す。そして生成されるタプルの第一要素はx0 + vx0 * tでボールのx軸上での位置、第二要素はy0 - vy0 * t + 1/2 * g * t ** 2でボールのy軸上での位置を表してる。この辺は簡単だよな。
実際はPythonインタプリタ上でこれを実行すると、

<generator object <genexpr> at 0x7fa2bc8f6180>

とか表示されるだろうけど、そのインスタンス内には「どこまでも進んで落ちていくボールの位置情報」が詰まってる。あとは、そこからitertools.takewhileで「ある条件を満たす」データを取ってきて「実体化」させるだけ、だ。
貴方のプログラムを見ると、「斜方投射の模様」を何度も表示させたいみたいだけど、そういう場合はitertools.takewhileで得たデータをまたもや「無限長の長さの」データに変換すればいい。要はデータのアタマをケツに貼り合わせてまたデータのケツに・・・とやってけばいいわけだよな。
そういう無限長データを作成する為のツールもPythonは備えている。itertools.cycleだ。

itertools.cycle:
https://docs.python.org/ja/3/library/itertools.h …

要は、単純にはデータのアタマからグラフィカルにボールを表示して、データを手繰りながら表示してくんだけど、その「データ側」が無限長でなおかつ「繰り返し」してるならいつまで経っても終わらんわけだよ(笑)。
そういうトリックを使って、貴方が書いたプログラムと同等の効果を得つつ、また計算量を減らしてるわけだ。

まぁ、コードを全部説明するのは字数的に無理なんで、まずはソースコードを読んで欲しいし、疑問を持ったらまた質問を投げてもらって構わないんだけど、あとはかいつまんでポイントを書いていく。

・PythonのTkinter用のコードを直接書くのはメンド臭いんで、GUI BuilderであるPAGEを使った。

PAGEは比較的新しくちとバギーなんだけど、GUIの「ガワ」を直接プログラムするよかラクなんで頼った。

PAGE - A Python GUI Generator:
https://page.sourceforge.net/

ボールを作ったり、格子を描いたり、ってのは自分でコードを書かなきゃいけないんだけど、一方、大枠を作るだけ、ならGUI Builderに頼ってもいいと思う。
PAGEはそこそこ有名だとは思うんで、Webを漁れば日本語で書かれたチュートリアルなんかもあると思う。例えば以下のような。

【Python tkinter】GUI作成ソフト「PAGE」使ってみた
https://note.com/kochatece/n/nf23ddf76d30e
この回答への補足あり
    • good
    • 0

> projectile_motion.pyはダブルクリックで初速度など入力する画面が現れますが、


> 一瞬コマンドプロンプトが見えて計算結果の数字が羅列される画面が出た後すぐに落ちてしまします。
> さらにmove.pyやmove_support.pyはダブルクリックしてもすぐ落ちてしまいます。

どういう環境でプログラミングしてるか分からないんだけど、一旦、Python備え付けのIDLEから呼び出して実行した方が早いかも(写真: IDLE)。
あるいはDOS窓で該当フォルダにcdして

python -m move

と打って動かす、とかね。

> 遅れましたが当方Windows10でC言語経験者です

ああなるほど。
多分MS-DOS時代にCを学んだ、のかな・・・?
昔のPC環境だと、メモリの制限が大きかったんで、大域変数使いまくりのプログラミング指南が多かったんですよ。「小さいメモリ」だったら大域変数使いまくってそれを「書き換え」するプログラムの方がメモリ効率が良かったんで・・・・・・。
ただ、Windows 10時代で、メモリがギガバイトレベルになると「メモリ占有率をケチって」プログラムを書くより「贅沢に」メモリを使用した方がいい、んです。その方が安全だし。
元々C言語自体がメモリが(往年のパソコンに比べると)豊潤なミニコンで生まれてるし、UNIXでのC言語の「使用法」ってのは実際はモダンな言語に近いんですが、昔のPCは貧弱な環境だったんで、「UNIXで行われるようなC言語プログラミング」が出来なかったんです。
まぁ、そんな理由で、往年のC言語のPC上での「プログラミング指南」は必然的にFortranとかBASICみたいなプログラミングスタイル教授、が多かったんですよね。

> 無限長のシーケンスは無限級数というか、微積分の無限大の概念みたいなものなのですね 

その通り、です。
そしてPythonのようなモダンなプログラミング言語はかつてのそれらとは違って「無限大の概念」を扱えるようになっている。

> 今はあまり使いどころがわからない

うん、フツーはそうでしょう。C言語もそういう機能が「無い」し。
しかし繰り返しますが、「物理シミュレーション」なんかだと使いまくれる。
itertools.countで

itertools.count(0, 0.1)

が生成するのは

[0, 0.1, 0.2, 0.3, 0.4, ...]

と言う「長さ無限大」のシーケンスだ。これは貴方がt = t + dtと言う計算で欲しかった「時間」の列だ。
物理学だと原則、「物体の運動」を「時間で追う」わけで、そうするとt = t + dtを一々計算するより「無限長の時間列」が使えるならそれを使った方が早い、と言う事です。
同様に、この「時間列」に運動学の方程式を適用すれば

[(x0, y0), (x0 + vx0 * t1, y0 - vy0 * t1 + 1/2 * g * t1 ** 2), (x0 + vx0 * t2, y0 - vy0 * t2 + 1/2 * g * t2 ** 2), ...]

と言う「無限長の」ボールの位置情報が得られる。具体的には例えば初速45m/s、角度45度で初期位置が(x0, y0) = (10, 300)なら

[(10, 300.0), (13.181980515339465, 296.8670194846605), (16.36396103067893, 293.8320389693211), ...]

と言う無限長のタプルのシーケンスが得られる。明らかに無限長でもこれが「貴方の欲しかった計算結果」で、tkinterのCanvasが参照したい「データ」となるわけ。
いずれにせよ、元データ(この場合は時間列)を利用して、それを加工/変形させた(ように見える)結果を得る事に注力する、ってのがモダンなプログラミング言語の使い方のポイントで、ジェネレータ式はその補助として、特に物理シミュレーションでは威力を発揮します。どっちにせよ、物理学だと「時間列」を相手に計算しなきゃなんないのでね。ジェネレータの使い方も「ワンパターン」なんで慣れざるを得ません。
「ボールの動きがスムーズに動いてかつ目盛り」の回答画像4
    • good
    • 0

> projectile_motion.pyをダブルクリックで実行しましたが、


> コマンドプロンプトが一瞬立ち上がってすぐ落ちてしまいます。
> 頂いた全ソースコードの使い方を教えて頂けますでしょうか?

projectile_motion.pyはプログラム本体(と言うか斜方投射の計算を行う心臓部)ですが、GUIを立ち上げるならmove.pyかmove_support.pyをダブルクリックすべきかな。
環境がWindowsならそれでいいかも。
仮にMac OS XみたいなUNIX系OSを使ってるならファイルの実行権限を変更しなくてはならなくて、端末(ターミナル)から

chmod +x ファイル名

とまずは打たなきゃならないかも。

一応、Windows前提だと、ダブルクリックで上手く行かない場合、

Pythonのスクリプト.pyファイルをダブルクリックで起動実行する方法 :
https://boukenki.info/python-py-file-doubleclick …

あるいは、

ダブルクリックでpython(.py)を実行【Windows】【Python】:
https://it.tabitoblog.net/doubleclick_py_exec/

を参考にして設定を。
どうしてもメンド臭い、って場合はIDLE辺りでファイルを開いて、F5キーを叩けば実行出来ます。

> グローバル変数の無駄を用いて何度も計算するのが無駄なのと、
> グローバル変数を使うのは避けた方が良いのですね。

そうです。
モダンなプログラミング言語環境だと大体「グローバル変数を使わない」事が推奨されます。
基本的に「あるプログラミング言語を使う」と言う場合、その言語の設計思想に従う必要があります。
つまり「設計者が意図したように使ってくれ」って事だね。

例えば、Pythonってのは割にスコープがいい加減な言語なんだけど、グローバル変数を使おうとした時にglobalと宣言せなアカン、って事は「記述コストが増える」事を意味してる。言い換えると「メンド臭い」。
何か書く際に「ユーザーに面倒をかける」ような約束事が導入されてる一番大きな理由は「その機能を使わないでくれ」と言ったプログラミング言語設計者の暗黙の主張があるわけですよ。
つまり、Pythonに於いては設計者が「グローバル変数を直接参照したりするプログラムを書かないでくれ」って思ってる、って事です。
貴方がやったような大域変数をバラバラにグローバル宣言する、よりもPython設計者は「どーせならリストで纏めてくれ」と思ってるとか、あるいは一般的にはクラスを使ってまとめる、みたいな事をします。

> 中盤から後半は、はじめてきく語(ジェネレータ、無限長のシーケンス)があるため今はよくわかりませんが、
> また精進していくなかでその有難味がわかる日がくるかもしれません。

ジェネレータ式は早めに慣れておくべきです。後回しにすればするほど「意味が分からなくなる」。
一般に、このジェネレータ式、とかはプロのプログラマでも「フツーのプログラミングでは使いどころが分からない」と言ったような反応になるブツなんだけど、貴方がやってるような「数値計算」の類だと導入が簡単で、かつ多大な効果があります。
むしろ数値計算を行う以上徹底活用すべきで、それは貴方の「技術力」を他の人より高みに登らせます。
まずは例示を自分で打ち込んでみて「動作」を確認しよう。

9.10. ジェネレータ式(Python Tutorial):
https://docs.python.org/ja/3/tutorial/classes.ht …

次、「シーケンス」と言う単語だけど、他の言語だと「コンテナ」とか呼ばれる事もある合成データの事です。Pythonだとリスト、タプル、文字列なんかがシーケンスです。全部数値とか文字とかの「単純なデータ」を纏めている。
んで、例えばリストだと

[1, 2, 3]

とか書くでしょ?これは長さ3のリストとなる。
一方「無限長のシーケンス」と言うのは、その名の通り、概念的には

[1, 2, 3, ...]

と言う「終端がない」、例えばリストなんかを表す。長さは「無限大」だ。
実際には「無限長に見せかけてる」だけで、まあ、あんまりプログラミング言語自体の「中身」には興味ないかもしれないけど(そういう人の方が当然多い・笑)、一応説明すると、中に一種の無限ループする関数が入ってて「実行が止まってる」のね。「実行しろ」って言うと1つだけ値を吐き出してまた止まる。
言わば糞詰りの便秘女みたいな状態になっていて(笑)、そういう特殊な関数を「ジェネレータ」って呼ぶわけだ(他の言語だとサラ金みてぇな「プロミス」って呼んだりする・笑)。そしてこの仕組みで「無限長のシーケンスに見せかける」わけ。
まぁ、背景さえある程度納得すれば使う事自体は難しくない。と言うか下手にプログラミングに慣れれば慣れる程、こういう「機能」に畏れを抱くようになります。よって、数値計算なんかでガンガン使って最初に慣れちまった方が得だ、って事です。
モダンな言語ではこのテの機能を(Pythonでは厳密にはちと違うが)「遅延評価」と呼び実装してたりするんで、今後益々ポピュラーになっていく「イケてる」機能です。そして慣れると「遅延評価が無い」プログラミング言語なんかかったるくって使えなくなる。
繰り返しますが、早いうちに「無限長のシーケンス」と言う概念とジェネレータ式に慣れてください。

遅延評価:
https://ja.wikipedia.org/wiki/%E9%81%85%E5%BB%B6 …
この回答への補足あり
    • good
    • 0

・GUIアプリにする際MVC(Model-View-Controller)と言う形式でプログラムを作っている。



MVCはここで全部説明するのは厄介な概念なんで、取り敢えずWikipediaでも覗いて欲しい。

Model View Controller:
https://ja.wikipedia.org/wiki/Model_View_Control …

CLI(コマンドライン)でのスクリプトが書ければ比較的ラクにGUI化出来る「考え方」だ。今後もGUIをやっていきたい、のなら押さえておけば良い考え方、となる。
なお、それにあたってPythonのPyPubSubと言うライブラリを使用している。

PyPubSub:
https://pypubsub.readthedocs.io/

pipを使えばインストール出来る。コマンドは例えば以下の通り。

pip install pypubsub

・ボールは単独で作れるメソッドを用意した方がいい

貴方が書いたプログラムだと、create_ovalの使い方がメンド臭い為、ちょっとかなり面倒臭い「計算」を経由してるようだ。
しかし、この問題の場合、(x, y)と言う座標とMARGINを利用してボールを描くのはかなり厄介だ。
そんなわけで、最初からMARGINだけを利用してボールを作り、それをCanvasのmoveメソッドで動かした方がラクだ。
そしてmoveメソッドを使ったあと、Windowそのものに付いてるupdateメソッドで「更新」した方が恐らくスムースだろう。
そういう方針で提示したコードを書いている。

まぁ、取り敢えずはこんなトコ、かな。
まずはコードを読んでみて、不明なトコがあったら質問してください。
そしてこのテの・・・物理関係、特に「時間」が関わってる場合、フツーに関数を使うよりもジェネレータを使った方がラクになる場合がある、ってのは知っておいて良いテクニックだと思います。
「ボールの動きがスムーズに動いてかつ目盛り」の回答画像2
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!