2013年8月22日 星期四

python 分析程式效率

這是意外發現到的python standard module裡面有個較做profile.py,這東西實在是異常好用阿! 由於上次我用python寫粒子碰撞,我發現當我用到150個particle互相碰撞時,fps就開始降了,於是我開始想著要怎麼提升效率,我在論壇上面發問,結果就有人給我這個分析的方法,實在是很感謝那個人:) python又讓我驚豔了一次。  首先就來看看這張結果圖吧


非常詳細的分析,這個模組的使用方法其實很簡單,官方網站 隨便看都懂,在cmd裡面打cProfile.py -o outputfile.txt youfile.py,這麼一來就會將分析結果存在outputfile.txt中,cProfile.py是用C實現的比起profile.py純python實現,來的有效率。
然後呢要查看這個結果時再用 pstats 這模組

p = pstats.Stats('outputfile.txt')
p.sort_stats('time').print_stats(10)

經過這兩行就會,將結果過濾成以total time來排序並取前10名,如這張圖表示,拖累效率的地方在於collision這function,其實前幾名都把矛頭指向一個地方,那就是我之前寫的vector:(
果然用python來直接寫數學運算是比較沒效率的。

本來的想法是想用numpy來試試看,可是看過官方document後,我覺得numpy是對於數組的運算才有比較大的效率差別,也就是array的運算,因此決定乾脆用C++來寫一個python版的vector :) 不過這得稍微花時間來研究一下了 :)

2013年8月19日 星期一

makefile note 2

關於make的target和prerequisites是用dependency graph來做記錄的,所以像這樣的寫法

a: b.c c.c
a: d.c e.c
其實同等於
a: b.c c.c d.c e.c

他只是持續的修改dependency graph而已,但是如果是用double colon(例:a::b.c c.c)事情就會變得不一樣了,它會分別建立dependency graph,而不會append原來的。


上次說到target可以為檔名也可以不是對吧,不是檔名的target,在術語上名為phony target,其實如同字面上的意思,phony -> fake,假的target,為什麼叫做假的target? 因為這個target並非用來產生檔案的,他只是純粹執行一些shell指令,像是這樣

clean:
    rm -f *.o

如此這類的,但是呢! 當你要用來做這種事情的時候,請記得多加這一行 .phony: clean
會需要多加這行的原因是 make它是無法辨別target是要用來產生檔案或是純粹執行指令,所以要加.phony的宣告,讓make知道它是always out of date,舉個例子吧,如果今天你沒有加這行會發生什麼事呢?

當你同目錄下沒有檔案叫做clean的話那一切就會正常,always out of date,但~~~是~有的話呢!,此時你的target就會被認為是要產生檔案用的,由於clean的rule並沒有任何prerequisites所以always up to date,所以就不會被執行了。

另外make是支援自定義變數的,例如: 你宣告了 compile = g++ , 那麼當你要使用這變數時要用錢字號來取得,$(compile)這樣,make有所謂的 auto variable,詳情請見 這個
目前對這個沒太多研究,我想這個的目的大概是減少code的量,畢竟一直重複打同樣的字是一件很煩的事,像機器人的做的事就交給機器人來做吧 :)

2013年8月17日 星期六

makefile note

我一直以來其實都不怎麼想用makefile來進行編譯的工作 :( 因為實在是有太多好用的IDE了!哈哈,只是我最近常常用sublime text做為我的coding環境,但是只用它來寫python和網頁語言而已。

最近突然想到乾脆比較簡單的C++也在這環境寫好了:) 因此決定來親自碰一下這東西好了,這樣一來我只要自己寫一個subilme build的檔案,就可以按下ctrl+B進行編譯了。


如上所示!OK

 makefile的規則包括三個部份如下
  1. target
  2. prerequisite
  3. commands
target: prerequisite
[tab] commands  # [tab]表示字元\t而已

target:簡單說就是你所要建置的目標,不一定要為檔名
prerequisite:建置目標前,所需要的先決條件
commands:就是shell commands!! 記得開頭得有tab才行

ex:
cc.exe: cc.o
[tab] mingw32-g++ cc.o -o cc.exe 
cc.o: cc.cpp
[tab] mingw32-g++ cc.cpp -std=c++11 -c

然後當我在cmd中輸入mingw32-make,由於我沒給任何argument所以他會以預設得來執行,但是預設的是什麼呢? 答案就是開頭最前面的target。接下來make的分析規則大概是這樣的,先看target和prerequisite去找檔案,如果prerequisite有相關的rule存在那麼就會以他為優先去執行,另外如果prerequisite比target還要新(意思是像是進行修改),那麼就會rebuild該target。

所以,以範例為例一開始碰到cc.exe: cc.o,很好發現了cc.o有相關的rule,那就是cc.o: cc.cpp......那串,所以就會變成先優先執行這條 ,執行完後就會產生cc.o檔,然後就回去執行剛剛那條rule,最後就產生cc.exe這樣。

make是還有更多的用法的,目前這篇僅記錄最基本的法則和用法,不過印象中make不同的版本似乎會有他們獨特的用法就是了,我也不知道會不會用到那麼多 :)

2013年8月16日 星期五

初嘗pyqt

上次用python+PIL來進行將動畫GIF檔給分離出一張一張的圖片,結果不是很讓人滿意,因為PIL對於GIF檔的支援不是很好,因此有部分圖片顏色會有問題,關於當tkinter+PIL碰上gif的時候變的有多suck,看我另外一篇就知道了:(  所以我決定使用pyqt來測試看看。如下面這張animated gif





經過我寫的程式後,分離出來的結果


哈!真是完美,qt就是好用阿,只是目前我還沒有弄熟pyqt,只知道其實它不只是一個gui library,它包括的範疇可真是廣到不行阿,網路、多媒體阿,還有很多很多,目前還沒辦法寫出太多心得,對它還沒特別研究 :) 另外,它官方的文件寫得滿詳細的

img = QImageReader('D:/My Pictures/2.gif')
for i in range(img.imageCount()):
    img.read().save('{}.png'.format(i))
    img.jumpToNextImage()

我僅僅是看官方文件就輕鬆寫出將animated gif分離出來的code了如上面這樣簡單幾行。
對於教學的話,我目前沒有看到哪個教學是不錯的,如果有發現的話就另外補上 :)

2013年8月13日 星期二

python 物理模擬2

上次介紹過vector後,這篇就來講講我的碰撞是怎麼寫的,目前經過測試後我的計算應該是正確的,因為我計算了動能(k),的確有守恆,除了當我用滑鼠點物體的時候,一但點下去會讓物體的速度歸0,並且可以拖曳給予速度,如下影片所示。

話說有什麼軟體是錄製的時候比較不會LAG的嗎? 我用的這個軟體錄起來好像沒說那麼順暢 :(。 回到正題,這邊我是假設兩球碰撞後是沒有旋轉的,因此不考慮進去進入我碰撞程式碼前,先給個概念。
上面要表達的是,一顆移動速度V的球,撞上靜止的球,另外當兩個球體碰撞時,在那個瞬間會有個impulse,且這個imulse方向是平行於連心線的,因此碰撞後會改變得動量也只有平行於連心線的動量,所以在此就把速度向量V拆成平行於連心線(vcos)和垂直於連心線這(vsin)兩種,之後一切就好搞啦 :) 之前說得好像很難,在這邊我要說那時候我腦筋打結阿啊!! 思考不順暢 :( 回到正題,接下來只要把vcos代入,高中教的二維碰撞公式,再將得到的結果與vsin合併,最後這就是答案 :) 這邊我就不教怎麼導出公式了,有興趣的就自己去查囉。
def collision(self, obj):
        '''
            2d collision -- split in two part
            first -- v parallel to the line connected the two circle center

            second -- v vertical to the line connected the two circle center 
            inelastic case, both will has the same velocity....

            v1' = (m1-m2)v1/(m1+m2) + 2 m2v2/(m1+m2) 

            v2' = (2m1)v1/(m1+m2) + (m2-m1)v2/(m1+m2)

        '''
        if self.boundCircle.isCollision(obj.boundCircle): #compare two circle

            o1x, o1y = self.boundCircle.pos.point
            o2x, o2y = obj.boundCircle.pos.point
            p1 = Vector(o2x-o1x, o2y-o1y)

            v1, v2 = self.velocity, obj.velocity
            v1L, v2L = v1.length(), v2.length()

            try:
                rad1 = math.acos( v1.dot(p1)/ (p1.length()*v1L) )
            except:
                rad1 = 0
            try:
                rad2 = math.acos( v2.dot(-p1)/ (p1.length()*v2L) )
            except:
                rad2 = 0
            
                
            v1 = p1.normalize()*v1L*math.cos(rad1)
            v2 = -p1.normalize()*v2L*math.cos(rad2)

            
            m1, m2 = self.mass, obj.mass

            v1f = (m1-m2)*v1/(m1+m2) + 2*m2*v2/(m1+m2) 
            v2f = 2*m1*v1/(m1+m2) + (m2-m1)*v2/(m1+m2)
            

            self.velocity = v1f + (self.velocity-v1)
            obj.velocity = v2f + (obj.velocity-v2)

目前這碰撞只是兩個圓體,我還在想,要是圓體碰撞正方體,要怎麼去計算碰撞後的情形,還要若要考慮旋轉,勢必要改寫這個碰撞公式,必須再花點時間來研究 :) 但是其實如果不想浪費時間在自己寫這些有的沒的,到是可以用別人寫好的物理模擬,目前聽過的有box2d和bullet這兩種,以供參考。

2013年8月9日 星期五

python 物理模擬

最近心血來潮,想要自己實做一些物理方面的模擬,一般來講應該都是從2D方面開始,畢竟3D的世界有很多數學概念,相較起來2D的數學概念比較不吃重,話雖如此,只是果然天算不如人算阿 :(


一開始一路過關斬將,直到我開始寫碰撞的時候,一路上荊棘滿佈,頭一次寫程式寫到有種快要發飆的感覺 :) 我只能說我的數學,大學上學的我都忘了,剩下高中程度,若是一維碰撞那到是簡單的很,公式也能輕易推算出來,但是沒想到只是從1D轉到2D,整件事情就天差地遠阿,到目前為止,我也只是寫出個勉強貼近2D碰撞的模擬。

上面這影片就是我目前寫出來的樣子,獻醜了 :) 這篇就來貼一下,我的2D運算基本工具,基本上我是自己實做了一個vector的class,讓它具有一些特性
  • normalize
  • reflect
等等。
class Vector:

    def __init__(self, x=0.0, y=0.0):
        self.x = x
        self.y = y

    def __neg__(self):
        ''' ex. -Vector(2,2) -> Vector(-2,-2)'''
        return Vector(-self.x, -self.y)

    # def __del__(self):
    #   print('vector {} is delete'.format(self.point))

    def __getitem__(self, value):
        '''return a integer due to the coordinate in pygame is integer
        '''
        return int(self.__dict__[value])

    def __setitem__(self, index, value):
        self.__dict__[index] = value

    def __add__(self, rhs):
        return Vector(self.x+rhs.x, self.y+rhs.y)

    def __truediv__(self, rhs):
        if isinstance(rhs, Vector):
            raise ValueError
        return Vector(self.x/rhs, self.y/rhs)

    def __mul__(self, rhs):
        ''' rhs is a pure num '''
        if isinstance(rhs, Vector):
            raise ValueError
        return Vector(self.x*rhs, self.y*rhs)

    def __rmul__(self, lhs):
        return Vector(self.x*lhs, self.y*lhs)

    def __sub__(self, rhs):
        return Vector(self.x-rhs.x, self.y-rhs.y)

    def __imul__(self, rhs):
        '''
            *= equals to assign and inplace calculation
            ex. a *= b --> a = operator.imul(a, b)
        '''
        if isinstance(rhs, Vector):
            raise ValueError
        self.x *= rhs
        self.y *= rhs
        return self

    def __itruediv__(self, rhs):
        if isinstance(rhs, Vector):
            raise ValueError
        self.x /= rhs
        self.y /= rhs
        return self

    def __iadd__(self, rhs):
        self.x += rhs.x
        self.y += rhs.y
        return self

    def __isub__(self, rhs):
        self.x -= rhs.x
        self.y -= rhs.y
        return self

    def __str__(self):
        return 'vector x ={} y={}'.format(self.x, self.y)

    @property
    def point(self):
        return (int(self.x), int(self.y))

    @point.setter
    def point(self, value):
        self.x = value[0]
        self.y = value[1]

    @property
    def angle(self):
        ''' return radian'''
        return math.atan(self.y/self.x)


    def reflect(self, normal):
        '''
            I <  |---->normal
               \ | /
             ___\|/____

             2*(-I.dot(normal)) -- scalar
        '''
        I = self
        self = (2*(-I.dot(normal))*normal) + I
        return self

    def normalVector(self):
        ''' return the normal vector of self'''
        l = self.length()
        angle = self.angle + math.pi/2
        return Vector(l*math.cos(angle), l*math.sin(angle))

    def normalize(self):
        try:
            length = self.length()
            return Vector(self.x/length, self.y/length)
        except:
            return self


    def rotate(self, radius):
        newx = self.x*math.cos(radius) - self.y*math.sin(radius)
        newy = self.x*math.sin(radius) + self.y*math.cos(radius)
        return Vector(newx, newy)

    def length(self):
        return math.sqrt(self.x**2 + self.y**2)

    def dot(self, v2):
        if isinstance(v2, Vector):
            return self.x*v2.x + self.y*v2.y
        else:
            raise ValueError

關於python的operator我就不說明了,有興趣的就看看這個吧

裡面有些function的意思在這邊說明一下
  1. dot: return 兩向量 內積
  2. rotate: return 旋轉 XX radus後的向量
  3. normalize: 不知道怎翻耶,應該是單位向量這樣
  4. normalVector: return 相對於自己的法向量
  5. reflect: 給予法向量後,計算出反射後的向量
雖然看起來自己另外做一個vector好像有點多餘,只是我覺得這樣對於往後的coding會帶來一些方便性,另外比起用一些 list來代替vector,可讀性也比較高。


等我二維碰撞用出一個更加貼近真實的,我在PO心得,目前到這邊為止 :)

2013年8月5日 星期一

how to write a dll

dynamic link library(dll) 這是在windows上的稱呼,至於linux的稱呼是 shared library,沒記錯的話應該是這樣沒錯。最近想嘗試用python去call自己寫的dll檔,所以就開始學習如何產生dll檔。

 經過我的嘗試後,發現dll的使用方式有兩種
  1. load-time dynamic linking (個人喜歡稱 implicity load way
  2. run-time dynamic linking ( explicit load way

為何會這樣稱呼呢? 我想在稍後或許你就會了解了。
首先寫一個dll檔,它也是有所謂的entry point的,就像是用C++和C寫一個console介面的程式,main就是entry point,win32則是WinMain。
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
    return TRUE;
}
我對這個entry point實際上可以做哪些還不是很清楚,依照我看到的資料是說可以在這邊做一些資料的初始化和釋放,但是這個entry point可有可無並沒有強制規定要存在,所以目前我就沒有多去了解了。

再來就是有個key word要注意了 __declspec() 這個可接受的參數有很多,但是目前我僅用到兩個
  1. dllexport
  2. dllimport
假設今天我寫了一個這樣的函式我想要讓別人用dll可以call它,就要這樣寫
extern "C" __declspec(dllexport) int multipleByTwo(int num=2)
{
    std::cout<<"num will be multipled by 2"<<std::endl;
    return num*2;
}
這樣一來被編譯成dll後,在另外一個檔案要用到這函式呢,就要如下面這樣寫,先做個宣告
extern "C" __declspec(dllimport) int multipleByTwo(int num);
宣告之後呢~嘿嘿還記得前面提到的兩種方式嗎?這個方法是implicity way,因為呢,這樣宣告後必須還要link library檔才有辦法編譯成功,要不然就會跑出錯誤提示找不到該函式的reference,另外dll也必須在執行檔的同個目錄喔! 要不然在run time的時候就會錯誤了。另外像這種宣告都會把它放在header file就是了。

至於 explicity way呢,就是用到了LoadLibrary("xxx.dll")這種函式,明顯的去load dll檔,這種方法就不需要像前面那樣link library和宣告 __declspec(dllimport),只是你相對就要去寫一些LoadLibrary和GetProcAddress之類的東西,不過這種的方式我就沒去嘗試了。

不過因為目前我只是測試如何產生dll檔,所以就沒有特別去講究結構了 :) 之後就是來繼續做深入一點的研究,有心得後再上來PO