Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Sunday, April 13, 2008

Two Ways to Solve a Problem

這些年下來,我反覆觀察到一個現象:程式員各有一套慣用的方法來克服自己遭遇到的問題,這些解題習慣可區分成兩種,工程師多只專精其一,只有少數能任意在兩者間自在地切換。

在很多情況下,無論程式員採用哪種作法,都可輕易把問題解掉;但是另有一些問題,卻不是這樣隨性而為就解得掉的--這就值得我們好好玩味了……

以 1..n 的正整數相加這個例子來說,我知道程式員應該利用現成的副程式,以爬說語來寫,應該要長成這樣:

n = 100
y = sum(range(1, n+1))

假裝我們沒有現成的,像 sum 這樣的副程式可用。那麼,一種可能的寫法如下:

y = 0
for i in range(1, n+1):
    y += i

這是標準的合成(Synthesis)法。以這個例子來說,如果不考慮時間複雜度要 O(n) ,這個方法其實沒什麼不好,畢竟它非常直覺,寫起來也很簡單。

大部分資訊背景的,甚至其他工程背景的,都傾向以這種「合成」的策略來克服問題。

由於這是個已經爛掉的例子,我們當然知道有個時間複雜度只要 O(1) 的作法:

y = (1+n)*n/2

這是典型的以分析(Analysis)手段來解題的例子。通常數學或物理等理科背景的人,比較慣用這種「分析」的手段來解決問題。

大部分的工程問題都牽連太廣、太複雜了,很難找到分析解;所以工程師們很習慣採用 trial and error 的合成策略,只求找到一個可行的作法。

這種「先兜出一個作法,看著它如何失敗,然後再兜另一個作法試試,不行的話再兜另一個……」的合成策略,陪伴我們度過無數個夜晚,也解決了不少問題,但如果每次「歪打」都沒有任何「正著」甚至「歪著」的跡象,這種策略就完全失靈了。

在合成策略無效或顯得白費功夫時,也許可以學著適應理科背景的慣用手法:「靜心分析問題,用數學精確地描繪出問題,建立模型,擴充內容,增大視界」,據此提供新的想法和嘗試的途徑。

推薦文選:

Tags: [] [] []

Saturday, April 12, 2008

Make a Secure Code Server

來這混吃也七個月有餘了,初到公司時正逢新 IC 開發,我受命寫了工具程式以驗證功能,完成了 Boot Loader 以執行外部程式,也開發了應用產品的 firmware 以提供下游客戶 total solution ~~

接單量產、功能穩定後,準備接手的同事人竟然在新竹--先前架的 code server 一直都只在台北這邊的內網使用,安全無虞,現在既然要跨到外網了,當然得提防封包被監聽……

原先架設的版本控制系統 SVN 及搭配的問題追蹤系統 Trac ,兩者都是透過 HTTP 協定和用戶端連線,現在為了隱密地傳輸資料,最直接的方案就是改走 HTTPS (HTTP over SSL)協定。

要讓我們的網頁伺服器 Apache 支援 HTTPS ,最省事的作法就是安裝 Apache 時就採用整合了 SSL 的安裝包。很不巧的是我之前用的安裝包是 no_ssl 的版本,所以得重新安裝 Apache 。另一方面,這一段日子以來, 無論 Apache 或 SVN 等,都陸續推出了新版,索性就把它們都再安裝一次(記得要先 uninstall 喔),相關步驟整理如下:

  1. SVN 及 Trac 的安裝,可以參閱我先前的 Blog SVN & Trac Installation 備忘
  2. SVN 及 Trac 的設定,可參閱我利用 wikidot 作的整理:
  3. SSL 設定範例,我也 wiki 了:
Tags: [] []

Tuesday, September 25, 2007

The Art of Design

為甚麼好的設計會來自於差的設計呢? ScottWhy Good Design Comes from Bad Design 提到攻讀 CMU Computer Science 博士時選了門介面設計課,第一堂課上他發現一位年輕人素描著隨身聽的各種變異版本,而且圖紙上已經堆積了三、四十種不同考量的版本了。 Scott 於是湊過去問這個小伙子「幹嘛費勁畫那麼多草稿?」,小伙子發楞了好一會才笑著回說:

I don't know what a good idea looks like until I've seen the bad ones.
經過時日洗煉, Scott 後來也體會到當初認為多餘的作法,其背後的精神,他提到:
Each new idea I sketched out was more informed than the last. Each bad idea illustrated some important aspect of the problem that I hadn't thought about before. Out of every five or six ideas, I'd have one or two that might be feasible.
I learned the right way to present ideas–you have to show the other candidates in order to help support the good ones.
When the design student showed me his sketches, he was showing me that he was a designer. All creative, talented people recognize the value of process, and have no concerns about revealing to others that it takes many bad ideas to obtain good ones.
這讓我想到 C++ 的老爸 Bjarne Stroustrup 也曾經提到:
At the start of an ambitious development project, we do not know the best way to structure the system. Often, we don't even know precisely what the system should do because particulars will become clear only through the effort of building, testing, and using the system. How - short of building the complete system - do we get the information necessary to understand what design decisions are significant and to estimate their ramifications?
-- ref. The C++ Programmming Language, p710
這段 William 翻譯如下:
偉大的軟體開發專案開始之初,我們並不知道什麼才是最好的系統組織方式,甚至連應該做出什麼樣的系統都不知道;因為唯有透過打造、測試、使用系統的過程,一切才會明朗。如果尚未打造系統,該如何才能獲得必備資訊以事先瞭解有哪些重要的設計決定?
-- ref. 中譯本, p930
至此,應該不難體會:無論是要創造一個好的設計或成就一項偉大的專案,非常重要的就是要畫出許多「草稿」、進行多項測試及「實驗」。
知道要「作實驗」是個好啟發,卻不夠充分,因為我知道只有能很容易進行實驗的情況下,來談多作實驗才顯得實際。實驗容易進行,人們才有耐性多嚐試,一次又一次、反覆、輕快地測試各個主意,如此,設計才有機會趨於完善。這也是為甚麼大家開發軟體時會找個合用的 framework 來執行 Unit Testing
人們進行設計時,常常將一個大系統拆成一塊一塊,然後一次一小塊,個別考量。每一小塊都琢磨得差不多了,再把它們組一組,最後「啪」一聲,整個系統就完成了 :)
唉,事情要是都那麼順利那就好了。實際上我們會遇到許多困難,例如:怎麼把一團模糊的設計概念拆卸成小塊?每一小塊要如何進行設計,將來才兜得起來?每個小塊怎麼兜成一個整體才會穩固?為了無礙進行實驗、兜出想要的設計,還得想法子讓每個小塊都易於抽換。
一個個小塊,就是我們慣稱的一個個模組,而模組設計的目標就是讓每個模組要
  • 夠獨立,不會互相干擾。
  • 夠彈性,滿足抽換的需求。
例如設計自走車時,我們不將輪子的輪軸跟馬達的傳動軸直接連起來,而是在兩者中間放個聯軸器(a shaft coupling)的裝置,如此抽換馬達或輪子時都可以省許多功夫。
又例如設計電子元件時,要求有高輸入阻抗(Zin),及低輸出阻抗(Zout);前級元件的輸出阻抗(Zout)要遠低於後級元件的輸入阻抗(Zin),兩者最好相差十倍以上(以達成最大電壓傳輸),如此我們可以致力於前後級個別的設計,不用憂心它們訊號彼此干擾。
也許剛好自己較熟這塊,總覺得軟體的模組設計花招更多,以 OO 領域來說,相關準則有:
  • Open-Closed Principle
    • Software entities should be open for extension, but closed for modification
    • Principle of Encapsulation of Variation
  • Liskov Substitution Principle
  • Dependence Inversion Principle
    • Abstractions should not depend upon details. Details should depend upon abstractions.
    • Program to an interface, not an implementation
  • Composit/Aggregate Reuse Principle
  • Law of Demeter -- Least Knowledge Principle
    • Only talk to your immediate friends. Don't talk to strangers
  • Interface Segregation Principle
優秀的程式師總是在思索、尋求「一勞永逸」的作法。學習時不妨從具體案例開始;學成應用時,要改而著重背後的精隨,才不會被細節淹沒。這些年下來,我體會到這個精隨就是「因應變化而設計(Design for Change)」。
要明白 Design for Change ,這裡強烈推薦翻翻 Refactoring 裡提出的壞味。個人認為其中又以下列兩個壞味最為深刻:

Monday, September 17, 2007

SVN & Trac Installation 備忘

上週一(9/10)公司要我把 Subversion 環境架起來。除了很高興公司也打算採行版本控制環境來幫助程式開發外,我還打算一併把 Issue Tracking 系統掛上去。

說起 Issue Tracking System,要跟 Subversion 搭配良好,且一樣是 freeware 的,當然非 Trac 莫屬。細查之下,乖乖, Trac 竟然要裝那麼多相關套件,且各個套件還要挑正確版本,才可運作良好。

為了避免大家(或將來的自己)白走冤枉路,這裡把要安裝的東西及安裝步驟條列於後:

Download

反正就把下列連結清單中的檔案都抓下來,待會再一口氣安裝。

Basic install

  1. Run the installer for the latest TortoiseSVN (e.g. TortoiseSVN-1.4.5.10425-win32-svn-1.4.5.msi)
  2. Run the installer for Apache 2.0.xx (e.g. apache_2.0.59-win32-x86-no_ssl.msi)
    • # 讓 SVN client 可以透過 http protocol 連上 SVN server
  3. Run the installer for the latest 1.4.x subversion server(e.g. svn-1.4.5-setup.exe
    • 順便把 D:\AppServ\Apache2\bin 加入系統變數 PATH 裡,以方便之後的操作

上面安裝順序只要 Apache2 先於 Subversion 即可,因為 Subversion 安裝程式會認得執行中的 Apache2 ,並自動完成一些設定,讓人省很多功夫。

如果只是個人使用,且不打算搭配 Trac ,那裝 TortoiseSVN 即可,而 Apache2 只有下列情形下才需要安裝:

  • 想走 http:// 協定來執行 import, check out, commit, export... 等操作,不想走 Subversion 內定的 svn:// 協定。
  • 想要有 MD5 加密的使用者通行口令認證。
  • 想要 Apache2 代為管理讀取權限。
  • 想要搭配 Trac 使用。

Install SVN Python-packages for Trac

  1. Run the installer for the latest Python 2.4.x (e.g. python-2.4.4.msi)
  2. Run ez_setup.py
    • # 會自動下載 setuptools.exe, 透過此工具以簡化後續的安裝步驟
  3. 開 DOS 窗,切到 Python24\Scripts ,待會的 ezsay_install 都在這執行
  4. Install Python bindings for Subversion
    easy_install -Z http://subversion.tigris.org/downloads/1.4.5-win32/apache-2.0/svn-python-1.4.5.win32-py2.4.exe
    • # 安裝成功會出現 "Finished processing dependencies for svn-python==1.4.2"; 這個步驟需要一段明顯的等待
    • # 讓我們透過 Python 操控 SVN
  5. Install ClearSilver
    easy_install -f http://clearsilver.net/downloads clearsilver==0.9.14
    • # 樣板引擎
  6. Install the latest PySQLite
    easy_install pysqlite
    • # 讓我們透過 Python 操控 Sqlite
  7. Run the installer for SilverCity (SilverCity-0.9.7.win32-py2.4.exe)
    • # 程式碼上色

以上只要 Python24 先安裝即可。

Install Trac

  1. Run the installer for Mod_python (mod_python-3.2.10.win32-py2.4-apache2.0.exe)
    • # An Apache module that embeds the Python interpreter within the server (for Apache/Python Integration)
    • # 用於整合Trac 和 Apache Web Server
  2. Run the installer for Trac 0.10.4 (trac-0.10.4.win32.exe)
    • 把 trac-admin 和 tracd 少掉的副檔名(.py)給加上去:
      cd Python24\Scripts
      ren trac-admin trac-admin.py
      ren tracd tracd.py
      • # 如此,以後在命令列執行時可以直接執行(e.g. tracd.py),不用多打 python (e.g. python tracd)
      • # An enhanced wiki and issue tracking system for software development projects
  3. Install optional Trac plugins
    • Install AccountManagerPlugin (for Trac 0.10.x)
      easy_install http://trac-hacks.swapoff.org/svn/accountmanagerplugin/0.10
      • # 若安裝失敗,則直接對 .egg 執行 easy_install 例如
        easy_install D:\Python24\lib\site-packages\tracaccountmanager-0.1.3dev_r2548-py2.4.egg
      • # 來管理 Trac 專案的成員帳號

WebAdmin, AcdountManager, iniAdmin 這三個 Trac plugins ,可以讓我們直接在 Browser 上操作 Trac 設定,減少於命令列下指令的必要,強烈建議安裝。

如果還有其他需求的,可以到 Trac Hacks 看看有沒有人提供現成的。

此外,有 Trac 中文化需求的,可以到下列網站上逛逛:

Module Loadings of Apache2

首先要先以文字編輯器開啟 Apache2\conf\httpd.conf ,然後搜尋到

#LoadModule dav_module modules/mod_dav.so

將上面的註解 ( # 字號) 去除(如果依照上述的步驟安裝,註解應該被安裝程式拿掉了)。

然後在整個 LoadModule 區段的下方加入以下設定:

# For Subversion
LoadModule dav_svn_module modules/mod_dav_svn.so
LoadModule authz_svn_module modules/mod_authz_svn.so

# For Trac
LoadModule python_module modules/mod_python.so

這樣 Apache 重新啟動時,就會載入 DAV, SVN 和 Python 等模組。

Tags: [] []

Sunday, July 15, 2007

Python 與 CSV

許多資料,像通訊錄或試算表之類的,很適合列表呈現。而 comma-separated values, CSV是微軟牌視窗軟體存放表格資料常用的檔案格式。這種純文字的檔案格式是以逗號(comma)來為每筆(record)資料的欄位(field)作分隔。

舉個實際的例子,不久前我因論文需要,由 Davis 那取得了 1999 年美國千大企業的董事會成員資料。內容包括這些董事(directors)的公司、職稱、年齡等等。

由於我只關心每間公司的董事有哪些,所以就輕快地以 Python 語寫了一個 function ,要電腦讀入這個 CSV 檔後,順便吐出各公司的董事們:

def LoadBoards_v0(fn='direct99.csv'):
    """Loads directors of companies from a CSV file and
    returns a dictionary to lookup directors for a company board (version 0).
    Field Names:
        Company name, Director name, Title, Age, Salary, Boards, HQ city, HQ state
    """
    lines = open(fn).readlines()
    field_names = lines[0].split(',')
    records = [dict(zip(field_names, line.split(',')))  for line in lines[1:]]
    boards = {}
    for record in records:
        boards.setdefault(record['Company name'], []).append(record['Director name'])
    return boards

這段 code 只用到 Python 最標準的開檔讀檔 functions 及內定的資料結構,短短幾行就把事情搞定!什麼?這 code 竟然無法正確執行?哎呀,原來董事的 second name 及 first name 間竟然也出現逗號(至少在這個例子中,我們不想把名字拆成兩個欄位)。微軟應付這件事情的方法是把整個 second name, first name 用引號(")括起來。

還好在咒罵完微軟害人要寫煩人的「引號配對碰」程式後,我想起了 Python 也提供了 CSV 模組,於是將程式改寫如下:

def LoadBoards_v1(fn='direct99.csv'):
    """Loads directors of companies from an Excel CSV file and
    returns a dictionary to lookup directors for a company board (version 1).
    Field Names:
        Company name, Director name, Title, Age, Salary, Boards, HQ city, HQ state
    """
    import csv
    reader = csv.reader(file(fn), dialect="excel")
    reader.next() # cast away the field-name tuple
    boards = {}
    for tuple in reader:
        boards.setdefault(tuple[0], []).append(tuple[1])
    return boards

如果有人覺得還要去算欄位順序是一件很蠢的事,可以改採 CSV 的 DictReader:

def LoadBoards_v2(fn='direct99.csv'):
    """Loads directors of companies from an Excel CSV file and
    returns a dictionary to lookup directors for a company board (version 2).
    Field Names:
        Company name, Director name, Title, Age, Salary, Boards, HQ city, HQ state
    """
    import csv
    boards = {}
    for record in csv.DictReader(file(fn), dialect="excel"):
        boards.setdefault(record['Company name'], []).append(record['Director name'])
    return boards
Tags: [] [] []

Sunday, July 01, 2007

Logo 也 3D

Welcome to StarLogo TNG

前陣子 survey 描述機器動作的程式語言時,看到幾個賣像不錯的 Robot 產品,竟不約而同地,都說 Logo 語

經過一連串的 google 、到處點閱後發現:原來 Logo 早已跳出原先的認知,不再只是給小朋友玩的烏龜繪圖了!

一直以來,我對 Logo 語言並不陌生,因為手邊好些科普書都有提到,例如:

  • 電腦如何思考》p53 ,Hillis 以 Logo 為例子,說明程式語言的威力;
  • 沙地上的圖案》p136 ,〈作為藝術家的烏龜,The turtle as artist〉這節說明了 Papert 的 Logo 及其 Turtle Geometry
  • MIT 媒體實驗室》p181 ,提到了控制 LEGO 積木的 Logo 語言;
  • 遊習世紀》p101 ,提到用 Logo 控制的實體機器烏龜如何幫助小孩子學習;
  • ……

Logo 語言我原先比較有印象的,就是它的烏龜繪圖。其最早的版本是一隻地板上亂逛的同時,還會以隨身攜帶的畫筆留下足跡的實體機器龜;後來這隻機器烏龜離開了地面,爬上了螢幕,變成一隻賣弄光影的傢伙。

現在一些 Logo 版本(例如 StarLogoNetLogo 等),允許同時有成百、上千隻烏龜。這些烏龜還可以依行為不同而有不同族系(例如:有些化身成兔子,有些化身成狐狸等)。這下子可以拿 Logo 來跑有大量 agents 的模擬實驗了,這裡是一些例子

此外,最新的 Logo 方言(例如 StarLogo TNG),已經讓烏龜的生活環境,由原本 2D 的平面,躍升到 3D 立體空間了。這下子光是搞搞模擬就太遜了,乾脆拿來寫些小遊戲,豈不是更酷!

如果現在還有人覺得 Logo 是小孩子玩意,是個跟不上時代的古董。這裡建議一定要去看看 Elica ,它是 Logo 另一個方言,支援 OOP ,不但有優雅的語言內涵,且也用於精緻的 3D 繪圖

如果還覺得意猶未盡的,強烈建議去閱讀閱讀 Brian Harvey 的《Computer Science Logo Style》。這本書共有三卷,且有電子檔可供下載:

  1. Symbolic Computing
  2. Advanced Techniques
  3. Beyond Programming

Saturday, December 16, 2006

Motor-controlling PWMs

一個脈寬調變(Pulse-width Modulation, PWM)訊號可控制一顆 DC motor 轉速,或決定一具 servomotor 的方向、位置或轉速。在複雜的機器人身上,常用上好幾顆馬達,因而能以一顆微控制器(microcontroller, uC)產生多組 PWM 訊號是非常實用的。

前陣子在 RobotFun.net 論壇看到一群機器人愛好者討論自製串列伺服控制器(serial servo controller, SSC)的討論。後來又在 CSZone 的 Robotics 版跟 happosaiMasterChang 討論了「以 uC 產生多組 PWM 訊號」的方法。這次就對這個議題作個整理:

Busy Waiting

我們先來看個最直接的作法:

 1: // List 1. PWM loop busy-waiting version (for servo)
 2: 
 3: int main()
 4: {
 5:     // Here put initial code
 6:     ...
 7: 
 8:     for (;;) {
 9:         for (i=0; i<NUM_OF_PWM; ++i)
10:             Pulse(&PWM[i], DutyCycle[i]);  // precision in us
11: 
12:         // calculates the delay of all pulses above
13:         pulse_delay = 0;
14:         for (i=0; i<NUM_OF_PWM; ++i)
15:             pulse_delay += DutyCycle[i];
16: 
17:         Pause(PERIOD - INNATE_DELAY - pulse_delay);  // precision in ms will do
18: 
19:         // Do other thing here
20:         ...
21:     }
22: } 
  • 上面虛擬碼的作法,限制條件是:
    • PERIOD ≥ INNATE_DELAY + pulse_delay
      • INNATE_DELAY 要視核心頻率(Fcore)及 code 最佳化情形來給定。
      • pulse_delay 則嚴重限制了此法能控制的 PWM 組數。
  • 這個方法可造出非常精準的 pulse width ,但產生的 PWM 不夠一般化:
    1. 它要求每個 PWM 的 period 都要一樣。
      • 這在通常的應用,每俱 servo 都一樣時,問題不大。
    2. 當 duty cycle 太大時,可控制的 PWM 組數減少。
      • 對控制 servo 動作的 PWM 而言,相對於 period ,其 pulse width 都不大,所以還可以接受。
  • 因此,此法較適用於控制 servos 。

Time Slicing

為了改進上述缺點,接下來就看看運用 timer interrupts 將時間切片的作法:

 1: // List 2. PWM Timer time-slicing version
 2: 
 3: void interrupt TimerISR_for_PWM1()
 4: {
 5:     ...
 6: 
 7:     for (i=0; i<NUM_OF_PWM; ++i) {
 8:         if (cycle[i] < DutyCycle[i])
 9:             PWM[i] = HI;
10:         else
11:             PWM[i] = LOW;
12: 
13:         ++cycle[i];
14:         cycle[i] %= Period[i];
15:     }
16: } 
  • 這個方法的限制條件為:
    • TICK_INTERVAL ≥ INNATE_DELAY
      • 這裡的 INNATE_DELAY 指的是 interrupt instruction cycles 佔的時間
      • INNATE_DELAY 也由核心頻率(Fcore)及 code 最佳化情形決定。
      • TICK_INTERVAL 是 timer ticks 的間隔時間;減短這個間隔,可造出更精準的 PWM 。
  • duty cycle 最小可為 0 ,最大可佔滿整個 PWM period;
  • 除了用於 servo 外,也非常適於 LED 亮度控制, DC motor 轉速控制等。
  • 只要設好 tick interval,注意要大於 interrupt instructions 執行的時間即可。
  • 相較於 List 1 中 busy waiting 的作法,此法較方便在不同 uC 間移植。
  • 此外,此法有動作冗餘(redundance),較 robust 。

我們可經由去除動作冗餘來換取較少的 interrupt instruction cycles:

 1: // List 3. Another PWM Timer time-slicing version
 2: 
 3: void interrupt TimerISR_for_PWM2()
 4: {
 5:     ...
 6: 
 7:     for (i=0; i<NUM_OF_PWM; ++i) {
 8:         if (cycle[i]==0  &&  DutyCycle[i]>0)
 9:             PWM[i] = HI;
10:         else if (cycle[i] == DutyCycle[i])
11:             PWM[i] = LOW;
12: 
13:         ++cycle[i];
14:         cycle[i] %= Period[i];
15:     }
16: } 
  • 此法比 List 2 作法佔用更少的 instruction cycles ,所以可控制更多 PWMs 。
  • 把 for loop 展開的話,程式雖變得累贅,但可進一步減少 instruction cycles 。
  • 容許的 instruction cycles 受限於 timer ticks 間隔可執行的 instruction 數。

顯然,有個副程式把 List 2 或 List 3 中的 duty cycles 清為 0 是很方便的:

1: // List 4. Initializes PWMs
2: 
3: void ResetPWM()
4: {
5:     for (i=0; i<NUM_OF_PWM; ++i)
6:         //PWM[i] = LOW;
7:         cycle[i] = 0;
8: } 

The Combination

List 2 或 List 3 的作法雖能產生夠一般化的 duty cycles 了,在核心頻率(Fcore)不太大提昇下,想造出高解析(resolution)的 pulse 時,還是得回到 List 1 那種純粹、直接的 loop busy-waiting。

以下就來看看綜合兩者優點(和缺點)的作法:

 1: // List 5. The combination: time-slicing with busy-wating
 2: 
 3: void interrupt TimerISR_for_PWMPeriod()
 4: {
 5:     ...
 6: 
 7:     for (i=0; i<NUM_OF_PWM; ++i) {
 8:         if (cycle[i] == 0)
 9:             bPulseNow[i] = true;
10: 
11:         ++cycle[i];
12:         cycle[i] %= Period[i];
13:     }
14: }
15: 
16: 
17: int main()
18: {
19:     // Here put initial code
20:     ...
21: 
22:     for (;;) {
23:         // for PWM pulse part
24:         for (i=0; i<NUM_OF_PWM; ++i) {
25:             if (bPulseNow[i]) {
26:                 Pulse(&PWM[i], DutyCycle[i]);  // precision in us
27:                 bPulseNow[i] = false;
28:             }
29:         }
30: 
31:         // Do other thing here
32:         ...
33:     }
34: } 
  • 這個綜合法一方面以 interrupt time-slicing 決定 PWM 週期,另一方面又以 loop busy-waiting 產生高解析的 duty cycles 。
  • 它綜合兩者的優點和缺點,非常適用在 servos 的高解析控制場合。

結論

  • 組數不多的 servos 控制訊號,可用 List 1 的 busy wating 產生。
  • 純粹的 interrupt time-slicing 較適用在 DC motors, LED flashing and brightness 及不需高解析控制 servos 的場合。
  • 在要控制盡量多的 servos 且又要求高解析的 pulses 時,也許可以試一下綜合法(List 5)。

補充說明

  • 為了有精準的 duty cycle ,產生 pulse 期間,不可讓其他中斷插進來。
  • 其他工作,例如 SSC 的 command 可在非 pulse 期間處理。

建議閱讀

Tags: [] [] [] [] []

Friday, December 15, 2006

Analyze the Sunspots

上次以 Python 搭配 matplotlib 改寫張智星老師傅立葉轉換教學例子。後來逛到 Anders Andreasen 的專文,裡面有個分析太陽黑子活動週期的例子,相同的例子竟然也出現在 Mathworks 展示 Matlab FFT 用法的網頁上。既然大家那麼愛用太陽黑子,我也來攪和攪和,再次以 Python 搭配 matplotlib 改寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#!/usr/bin/python
"""
This demonstration uses the FFT function to analyze the variations in
sunspot activity over the last 300 years.
 
Sunspot activity is cyclical, reaching a maximum about every 11 years. Let's
confirm that. Here is a plot of a quantity called the Wolfer number, which
measures both number and size of sunspots. Astronomers have tabulated this
number for almost 300 years.
 
Note:
 
This demonstration is original from both Anders Andreasen's and Mathworks' page.
I translated it into Python with matplotlib.
 
See also:
 
- "Python for scientific use, Part II: Data analysis" by Anders Andreasen
    <http://linuxgazette.net/115/andreasen.html>
- Using FFT in Matlab
    <http://www.mathworks.com/products/demos/shipping/matlab/sunspots.html>
"""
__author__ = "Jiang Yu-Kuan, yukuan.jiang(at)gmail.com"
__date__ = "December 2006"
__revision__ = "1.2"
 
 
import scipy.io.array_import
from pylab import *
 
 
figure(figsize=(13,4.5))
 
subplot(121)
sunspot = scipy.io.array_import.read_array('sunspots.dat')
year = sunspot[:,0]
wolfer = sunspot[:,1]
plot(year, wolfer, "r+-")
xlabel('Year')
ylabel('Wolfer number')
title('Sunspot data')
 
subplot(122)
Y = fft(wolfer)
plot(Y.real, Y.imag, "ro")
xlabel('Real Axis')
ylabel('Imaginary Axis')
title('Fourier Coefficients in the Complex Plane')
xlim(-4000, 2000)
 
 
figure(figsize=(13,4.5))
 
subplot(121)
N = len(Y)
power = abs(Y[:(N/2)])**2
Fs = 1.
nyquist = Fs/2
freq = linspace(0,1,N/2)*nyquist
plot(freq[1:], power[1:])
xlabel('Frequency (Cycles/Year)')
ylabel('Power')
title("Spectrum")
xlim(0, 0.20)
 
subplot(122)
period = 1./freq
plot(period[1:], power[1:])
index = find(power==max(power[1:]))
plot(period[index], power[index], 'ro')
text(float(period[index])+1, float(power[index])*.95,
     'Period='+`float(period[index])`)
xlabel('Period (Years/Cycle)')
ylabel('Power')
title("Periodogram")
xlim(0, 40)
 
show()

程式會把三百年份的太陽黑子資料檔調進來分析。結果截圖如下:

Tags: [] [] [] [] []

Software Anti-interference

大多數工程製品,零件會變髒、磨損,精密度不如目錄所示。數位電腦是當中的異數,它沒有前述問題,至少它的位元或位元組功能,完全符合廣告宣稱的--當數位混入類比,一切似乎又要跌回原形……

以機器人來說,它有感測器(sensor)得知類比世界的訊息,訊息處理後,又要餵控制訊號給類比世界的致動器(actuator)。撇開外界原本就混雜各式干擾不說,所使用的致動器,往往就是馬達,其本身是高負載,開關高負載會嚴重干擾控制電路(如果共用電源的話),馬達運轉時,尤其是電刷部份,也會持續產生雜訊。這些雜訊除了會影響感測電路及控制訊號外,有時 uC 本身也會因為這些干擾而錯亂。

為了維持機器正常運作,除了硬體設計時把雜訊納入考量外,軟體也不可坐以待斃。以下整理一些簡單、低階的軟體抗雜訊手段:

  • 對輸入訊號濾波
  • 資料冗餘與查驗(data redundance and verification)
    • Majority, voting
    • Cyclic Redundancy Checks, CRC
    • Error Correction Codes, ECC
  • 程式自我測試(program self-testing)
    • Applies CRC/ECC to code section
    • Sets and checks the function ID
  • Action Redundance
    • Instruction repeating
    • Prefers pollings to interrupts
  • Watchdog -- where to kick the dog?
    • Super-loop architecture
      • "Watchdog timers are most effective in cyclic systems where the watchdog is tickled each time through the loop."
      • ref. "Solving the Software Safety Paradox"
      • "One approach is to clear a number of flags before each loop is started, as shown in Figure 2 . Each flag is set at a certain point in the loop. At the bottom of the loop the dog is kicked, but first the flags are checked to see that all of the important points in the loop have been visited. ...For a specific failure, it is often a good idea to try to record the cause (possibly in NVRAM), since it may be difficult to establish the cause after the reset."
      • ref. "Watchdog Timers"
    • RTOS architecture
      • "Watchdogs are somewhat more difficult to implement in multitasking systems due to their non-determinism. I've seen some multitasking systems that use an interrupt to tickle the watchdog. ... If all the tasks were blocked and unable to run, the interrupt method would continue to service the watchdog and the reset would never occur."
      • "A better solution is to use a separate monitor task that not only tickles the watchdog, but monitors the other system tasks as well. for example, a task that must run at least once per second could increment a counter. The monitor task reads the counter and “safes” the system if the task counter stops incrementing at the expected rate. if the monitor task was blocked, it would not tickle the watchdog and the system would reset."
      • ref. "Solving the Software Safety Paradox"
      • "This scheme uses a task dedicated to the watchdog. This task wakes up at a regular interval and checks the sanity of all other tasks in the system. If all tasks pass the test, the watchdog is kicked. The watchdog monitor task runs at a higher priority than the tasks it is monitoring."
      • ref. "Watchdog Timers"
    • Time-triggered architecture
      • "Start the watchdog in the scheduler Start function"
      • "Refresh the watchdog in the scheduler Update function" (called in the timer ISR)
      • ref. "Patterns for Time-Triggered Embedded Systems", p274
      • 在 Update function 踹狗的主因是每個 task 每次都要在 tick interval 內執行完畢,才能確保這種 co-operative 架構執行的正確性和可預測性。
      • 如果改成在 Task-Dispaching function 內踹狗,那就退化成 super-loop 架構的作法了。
      • 如果改以獨立的 monitor task ,固定時間踹狗,就類似 RTOS 架構的作法了。

一言蔽之,這些泰半是些「積『非』成『是』」的方法 :)

建議文選:

Tags: [] [] []

Saturday, December 09, 2006

FFT in Python

想知道一段訊號的頻譜,實務上我們會運用數位訊號處理,對這段訊號抽樣,得到一段時間序列;並計算時間序列的離散傅立葉轉換(Discrete Fourier transform, DFT)。然後據以估算出離散時間傅立葉轉換(Discrete-time Fourier transform, DTFT),最後再視需要,將頻域橫軸由離散時間的數位頻率(Ωk = ωkTs = 2π fk/Fs)換算回連續時間的訊號頻率(fk)。
張智星老師的 on-line book《音訊處理與辨識》〈離散傅立葉轉換〉這個章節,有許多運用快速傅立葉轉換(Fast Fourier transform, FFT)的教學, FFT 其實就是 DFT 的快速算法。張老師是以 Matlab 作為程式範例;經實際嘗試,我發現可以很容易轉成 Python code ,以下就看看執行結果截圖:

其程式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#!/usr/bin/python
"""
This example demonstrates the FFT of a simple sine wave and displays its
bilateral spectrum.  Since the frequency of the sine wave is folded by
whole number freqStep, the bilateral spectrum will display two non-zero point.
 
Note:
 
This example is coded original in Matlab from Roger Jang's
Audio Signal Processing page.  I translated it into Python with matplotlib.
 
See Also:
 
- "Discrete Fourier Transform" by Roger Jang
    <http://140.114.76.148/jang/books/audioSignalProcessing/ftDiscrete.asp>
"""
__author__ = "Jiang Yu-Kuan, yukuan.jiang(at)gmail.com"
__date__ = "December 2006"
__revision__ = "1.1"
 
from pylab import *
 
 
def fftshift(X):
    """Shift zero-frequency component to center of spectrum.
 
    Y = fftshift(X) rearranges the outputs of fft
    by moving the zero-frequency component to the center of the array.
    """
    Y = X.copy()
    Y[:N/2], Y[N/2:] = X[N/2:], X[:N/2]
    return Y
 
 
N = 256             # the number of points
Fs = 8000.          # the sampling rate
Ts = 1./Fs          # the sampling period
freqStep = Fs/N     # resolution of the frequency in frequency domain
f = 10*freqStep     # frequency of the sine wave; folded by integer freqStep
t = arange(N)*Ts    # x ticks in time domain, t = n*Ts
y = cos(2*pi*f*t)   # Signal to analyze
Y = fft(y)          # Spectrum
Y = fftshift(Y)     # middles the zero-point's axis
 
figure(figsize=(8,8))
subplots_adjust(hspace=.4)
 
# Plot time data
subplot(3,1,1)
plot(t, y, '.-')
grid("on")
xlabel('Time (seconds)')
ylabel('Amplitude')
title('Sinusoidal signals')
axis('tight')
 
freq = freqStep * arange(-N/2, N/2)  # x ticks in frequency domain
 
# Plot spectral magnitude
subplot(3,1,2)
plot(freq, abs(Y), '.-b')
grid("on")
xlabel('Frequency')
ylabel('Magnitude (Linear)')
 
# Plot phase
subplot(3,1,3)
plot(freq, angle(Y), '.-b')
grid("on")
xlabel('Frequency')
ylabel('Phase (Radian)')
 
show()
程式沒幾行,都是叫用現成的副程式。
要由 DTF 推估 DTFT , Paul Bourke 寫的一篇關於 DFT/FFT 的文章,裡面有很清楚的圖示。估算過程如下:
  1. 將 DFT 橫軸序號由 [0…N-1] 重新編排(rearrange)到 [-N/2…N/2-1] 。程式 line 24~32 的 fftshift(.) 就是作這件事。
  2. 由 DFT 估算 DTFT 的方法得知 Ωk = 2π k/N = 2π fk/Fs ,整理後得到:
    • fk = F[k] = kFs/N = k/(NTs) = k/T
      • F: frequency
      • k: in 0, 1, 2…N-1; index of F
      • Fs: sampling rate
      • Ts: sampling period
      • T: length of the whole signal
  3. 套用上式即可把 DFT 或 DTFT 的數位頻率(Ωk)換算成實際訊號的頻率(fk)。
這段 code 還用了 matplotlib ,matplotlib 底層又用了 NumPy 。這兩個套件要另外安裝。
以我的見解,以 Python 作科學運算, NumPy, SciPy, 及 matplotlib 是必備的。
Tags: [] [] [] [] []

Monday, November 13, 2006

Be Pythonic -- 字正腔圓說 Python

學任一種語言,要說得道地,關鍵是融入語言的風格特色中,體會語言社群的風土,知道他們的慣用法。

既然 Python 寫的 code 有 executable pseudo code 的美稱,想寫出 Pythonic 的 code ,當然要站在一定的高度,讓寫出來的 code 夠 pseudo...

Pythonic 一詞被用來形容合於 Python 慣用法的 code 。Pythonic 的 code 除了讓人們更好理解外,機器執行時,也往往更具效率--語言設計者會絞盡腦汁最佳化語言慣用法的執行效率。這在 Python 這類高階、動態的語言,尤其明顯 :)

幾個 Pythonic 的準則:

  • 讓程式結構盡量扁平(flat)
  • 降低 Loops 的複雜度
    • 少用 counter ,偶爾用一下 iterator
    • 改採 Functional Programming
      • 利用 map, reduce, filter 等 functions
    • 能用 List Comprehensions 更好
  • function 是第一級物件,可以不必定義在 class 裡。
    • 如果只是要為一群公用的 function 分組,應該使用 module
    • 分類複雜時再追加使用 package
    • 需要用到多個 instance 時,才要定義 class
  • 向 getters 及 setters 說「不」
    • 以 keyword parameters 及 default parameters 取代一部分 getters/setters 的需求
    • 或者直接存取 object 的成員變數
    • 必要時,追加使用 property,而不必改到 client code
  • Duck typing 取代不必要的繼承
  • 以 tuple assignment 同時 assign 多個變數
  • 以 tuple assignment 讓 function 傳回多個值
  • Generator Function 達成 Continuation
  • 不要多次叫用 + 或 += 來串接字串
  • eval 來 parsing 字串
  • 只被單一 function 呼叫的 functions ,考慮使用 Closure
  • ……

更詳盡的說明,請參考以下的收集及摘要:

  • Be Pythonic
    • You need counters rarely, and iterators only occasionally
    • You may not need that for loop
    • Tuples are not read-only lists
    • Classes are not for grouping utility functions
    • Say no to getters and setters
    • Functions are objects
  • Python isn't Java without the compile
    • First-class functions
    • Keyword parameters
    • Default parameters
    • Tuples
    • Parallel assignment
    • Efficient multiple return values
    • Continuations
    • User-defined operators
    • Closures
    • Meta-programming
  • Python is Not Java
    • Flat is better than nested
    • Got a switch statement? The Python translation is a hash table, not a bunch of if-then statments.
    • XML is not the answer.
    • Code is easier to write than XML.
    • Getters and setters are evil.
    • Stop Writing So much code

其他相關 links:

Sunday, November 05, 2006

Console I/O Without OS

在〈Debugging Embedded Systems〉中曾建議:把 uC 的 UART 跟 PC 的 serial port 第一時間 link 起來,然後就可以在適當的地方 print out 一些訊息,幫助我們確認程式的執行狀況。

這個廣為採用的作法,在沒有 preemptive multitasking OS 支援下,很容易因 print out 的訊息太多,使系統 delay 過久。這在許多場合是無法接受的。一個常見的例子是同時處理另一個網路連線下,許多 protocol 都嚴格限制裝置回應時間。

解法也很簡單,只需把要 print out 的東西丟到 queue 中,然後再拆成一個個 char ,找時間分批餵給 PC 就好了。這叫「化整為零」:P

Renesas H8 系列 HEW 開發環境底層就有兩個分別被 stdin/stdout function 群共用, 處理單一 char 的 function : charput() 被 printf() 及 puts() 等調用;charget() 被 scanf() 及 gets() 等調用。

註:用於 MSC51 最著名的 Keil C 環境底層則分別調用 putchar() 和 _getkey() 。

我們只要自行定義 charput() 就可以解決前述發生在 print out 的 delay 問題。方法如下圖右半:

修改後的 console output (圖右半)讓我們可以無縫使用 printf() 及 puts() 等 functions ,不用改變對 ANSI C standard library 的使用習慣。

console input 部份(圖左半),雖然我也想盡量維持在 PC 下 C 程式的使用習慣,但還是難逃 Joel 提出的〈抽象滲漏法則〉,不但無法適用於 scanf() ,用於 gets() 時還要自行處理換行符號。

註:抽象滲漏法則--All non-trivial abstractions, to some degree, are leaky.

Tags: [] [] [] [] []

Sunday, September 24, 2006

Dynamic Plot on Python

之前介紹 Python 在複雜網路模擬實驗的應用時,曾提到用 matplotlib 來繪製該實驗的圖表。其模仿 Matlab 的繪圖功能,用起來方便,所繪製的圖也在水準之上。有圖有真相,這就秀秀用 matplotlib 為該實驗繪製的兩張圖表:

學長看了後提議:某些實驗邊跑模擬的同時,也把產生的數據動態繪出,效果會更好。

憑著殘留印象,我把相關用法的說明再次翻出來研讀,寫了下列的 Python code 來測試:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""
Interactive random dot plotting.
 
This module demonstrates the usage of the interactive mode of matplotlib.
It is suggested to run this code under ipython. Details please see
http://matplotlib.sourceforge.net/interactive.html
 
 
Usage
=====
 
You can run this module in three ways:
 
    1. As a stand-alone application with GUI
    2. Run it on command line with a -d parameter to just generate the
       image with a specific format. (without GUI)
        * ref. http://matplotlib.sourceforge.net/backends.html
    3. Run it on ipython to call loop() many times
 
Examples
--------
You can simply run this module as a stand-alone application with GUI
 
    > irandot.py
 
Or just generate a image file with a specific format:
 
    > irandot.py -dAGG     # generate PNG file
    > irandot.py -dPS      # generate PS file
    > irandot.py -dSVG     # generate SVG file
    > irandot.py -dEMF     # generate EMF file
 
Running it with ipython is strongly suggested.
 
    In [1]: cd I:\trial\python  # suppose the module is put in I:\trial\python
    ...
    In [2]: run irandot.py
    ...
    In [3]: loop(50)
    ...
    In [4]: loop(20)
    ...
 
"""
__author__ = "Jiang Yu-Kuan, yukuan.jiang(at)gmail.com"
__date__ = "2006/09/24~2006/9/25"
__revision__ = "1.3"
 
from pylab import *
 
 
def dot_gen(dots=2):
    """A random dot generator.
 
    - dots: number of dots with plotting each loop
    """
    i = 0
    while 1:
        l, = plot(rand(dots), rand(dots), 'go')
        #setp(l, alpha=1,
        #    markerfacecolor='w', markeredgecolor='g', markersize=10)
        i += dots
        yield i
 
 
def loop(n=1):
    """Repeat dot plotting of n times."""
    g= dot_gen()  # gets a dot-plotting generator
    for i in xrange(n):
        print g.next(),
 
 
if __name__ == '__main__':
    if not isinteractive():
        ion()  # turns interactive on
 
    loop(50)
    savefig('irandot')
 
    try:
        __IPYTHON__
    except NameError:
        print "\nNOT on IPython"
        show()
    else:
        print "\nOn IPython"
  • 行 59 是真正畫綠點的指令。
  • 行 75 是關鍵所在,它切換 matplotlib 到互動模式。
  • 行 80~86 是為了在 IPython 下不要執行 show()

這個程式取名為 irandot.py ,可以當作一般的執行檔,點兩下檔名執行。執行時我們可以看到隨機產生的座標,一點一點地在座標圖上冒出。以下是其 screenshot:

此外,強烈建議在 IPython shell 下執行 irandot.py:

  1. 開啟 IPython shell
  2. 在 IPython shell 敲進 "cd I:/trial/python" 來切換到存放 irandot.py 的目錄。
  3. 鍵入 "run irandot" 後就會繪出類似上面那幅 2*50=100 個綠點的隨機圖。
  4. 再鍵入 "loop(100)" ,程式會陸續再冒出 2*100 = 200 個綠點。
  5. 高興的話,可以一直下 loop 、改變參數,讓綠點繼續冒下去。

後記:

  • 之前發現無論在 IDLEPythonWin 下,圖表反覆繪製幾次,程式就會掛點的問題。碰巧藉這次測試,找到解法了。作法很簡單,只要改用 IPython shell 即可。
  • 如果你也是裝 Python Enthought 來用,那 IPython 已經在你電腦了。
  • 目前的經驗,開發過程使用 IDLEPythonWin 順手,測試寫好的程式則用 IPython 較方便
  • 如果嫌上述開發環境太陽春,我推薦 SPE
  • Python Enthought 附的 matplotlib 有些小 bug 。可以到這裡下載新版 matplotlib 安裝程式來更新。其底層還用到 NumPy ,可到這裡下載安裝。

Tuesday, August 29, 2006

Graph-based Modeling on Python

Agent-based modeling 的電腦實驗,最核心的架構不外乎一個大迴圈(super loop)包裹著一群規則。大迴圈每跑一輪,系統就更新一次狀態,就如同時鐘的滴答(tick)聲般。通常系統每次滴答都會收集一次統計資料。這類實驗,有許多現成的 famework 可用,如最經典的 Swarm 及其後進 Repast ,還有我模仿 Repast ,自己搞的一個 ,它們都提供了 start, pause, stop 等流程控制的介面。

模擬複雜網路,也可以套用 Agent-based modeling 架構。不過諸如網路的群聚度(clustering coefficient, C)及網路特徵的路經長度(characteristic path length, L)等統計數據計算需耗費的時間,隨著網路的規模成長很快,所以不適合運作太頻繁。但我們卻得靠這些統計數據來判斷網路是否收斂、試驗是否該終止了,想試驗各個參數排列組合時,更是雪上加霜。

透過 Intermediate File 是很直覺的解法。程式每次執行都餵入一個輸入檔來初始網路結構,並以參數決定 super loop 要跑幾次。統計數據只在程式要結束時計算,然後將網路狀態及統計數據吐給輸出檔。下次要執行,就以這個輸出檔當作新的輸入,然後一樣決定要跑幾圈,最後得到另一個輸出檔。採取接力的方式,不用每次都從頭跑,不會浪費先前程式執行的時間。

Multithread 是另一個可行作法。採用 Agent-based modeling framework 的方式,一個 thread 跑核心的規則。另一個 thread 則控制試驗的流程,讓 user 決定何時該暫停下來,計算統計數據,然後決定是否繼續執行。

Dynamic Programming Language 是這次的正解。利用動態程式語言,如 Python ,可以很方便於執行時期控制模擬試驗的程式流程。我就是採用 Python ,搭配 NetworkX 來處理 Graph ,並以 matplotlib 來繪製圖表。接下來就看看如何架設這個試驗平台:

  1. 下載 Python Enthought Edition 的 installer 。它是 Python 的加強版,除了 Python 基本工具外,還整合了許多有用的工具,例如 matplotlib
  2. 這裡下載 NetworkX 的 installer 。
  3. 依序執行這兩個 installer 。

裝好了後,就以 Emergence of a small world from local interactions: modeling acquaintance networks 裡描述的實驗來操刀,試寫第一個 Python 程式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
"""
Simulation for the verifying of "Emergence of a Small World from Local
Interactions: Modeling Acquaintance Network", Davidsen J. et al. 2002.
 
Public Variables:
    - Iters: the interations of loop, used in loop()
 
Private Variables:
    - _G: graph of the acquaintance network
    - _N: the total of nodes
    - _p: the death-and-birth probability, used in _step2()
    - _run: a generator returned by loop()
 
Usage
=====
 
This module must run on a python shell with interative mode; and call
public functions e.g. runnext().
 
Example
-------
You can simply run this module with default settings.
 
    > python -i acq2002.py
    >>> runnext(10)         # loops 10 times of Iters time-steps.
    ...
 
Setting parameters is allowed.
 
    >>> start(1000, 0.04)   # starts the simulation with new N, and p
    >>> Iters=10000         # sets the iterations of each loop
    >>> runnext(5)          # loops 5 times of 10000 time-steps.
    ...
    >>> Iters=100000        # sets the iterations of each loop
    >>> runnext(2)          # loops another 2 times of 100000 time-steps
    ...
 
After above, you may want to save the result.
 
    >>> savepkfig()
    >>> savenet()
 
"""
__author__ = "Jiang Yu-Kuan, yukuan.jiang(at)gmail.com"
__date__ = "2006/08/22~2006/10/02"
__revision__ = "1.10"
 
import random as rnd
import networkx as nx # Must import this as a name to avoid namespace collision!
 
 
def _create():
    """Create an empty acquaintance network.
 
    Adds _N nodes to _G, and _G contains no any edges.
    """
    global _G
    _G = nx.Graph()
    _G.add_nodes_from(xrange(_N))  # adds n nodes into _G
 
 
def _step1():
    """Friend making of two persons.
 
    One randomly chosen person picks any two his acquaintances and
    introduces them to each another. If they have not met before, a new
    link between them is formed. In case the person chosen has less than
    two acquaintances, he introduces himself to one other random person.
    """
    u, v = rnd.sample(xrange(_N), 2)
    nb = _G.neighbors(u)
    if len(nb) > 1:
        u, v = rnd.sample(nb, 2)
    _G.add_edge(u, v)
 
 
def _step2(p):
    """Death and Birth of a chosen person.
 
    With probability p, one randomly chosen person is removed from the
    network, including all links connected to this node, and replaced by
    a new person with one randomly chosen acquaintance.
    """
    if rnd.random() < p:
        # sol. 1
        v = rnd.randrange(_N)
        _G.delete_node(v)
        _G.add_node(v)
 
        # sol. 2: slower than sol. 1.
        #_G.delete_edges_from(_G.edges(rnd.randrange(_N)))
 
 
def _avgK2():
    """Return the average square of degree of all nodes."""
    kss = [k*k for k in _G.degree(_G.nodes())]
    return sum(kss)/float(_N)
 
 
def _avgSPL():
    """Return the average shortest path length of Graph _G."""
    pathlengths=[]
    for v in _G.nodes():
        # sol. 1
        #spl = nx.single_source_shortest_path_length(_G,v)  # including v to v
        #for pl in spl.values():
        #    pathlengths.append(pl)
 
        # sol. 2
        pathlengths += nx.single_source_shortest_path_length(_G,v).values()
 
    return sum(pathlengths) / float(len(pathlengths)-_N)
 
 
def _stat():
    """Gather statistics for Graph _G."""
    print "N, p = %d, %f" % (_N, _p)
    print "Degree histogram:", nx.degree_histogram(_G)
    print "Avg. degree, <k>:", 2.*_G.number_of_edges()/_N
    print "Avg. square of degree, <k^2>:", _avgK2()
    print "Avg. clustering coefficient, C:", nx.average_clustering(_G)
    print "Avg. shortest path length, L:", _avgSPL()  # spends huge time
 
 
def savepkfig(fn='acq2002pk.png'):
    """Save the p(k) figure.
 
    - fn: file name
    """
    import pylab as mpl
    dh = nx.degree_histogram(_G)
    pk = mpl.array(dh)/float(_N)
 
    mpl.loglog(pk, 'r--')
    mpl.grid(True)
    mpl.gca().xaxis.grid(True, which='minor')
    mpl.xlabel('k')
    mpl.ylabel('p(k)')
    mpl.title('N=%d, p=%d' % (_N, _p) )
    mpl.savefig(fn)
 
 
def savenet(fn='acq2002', fm='GPickle'):
    """Save the acquaintance network.
 
    - fn: file name
    - fm: file format
    """
    save = {'edgelist':nx.write_edgelist,   # edge list
            'adjlist':nx.write_adjlist,     # node adjacency-list
            'yaml':nx.write_yaml,           # YAML text format
            'gpickle':nx.write_gpickle      # Python pickle format.
            }
    fm = fm.lower()
    if fm not in save.keys():
        fm = 'edgelist'
 
    import os
    fn, ext = os.path.splitext(fn)
    ext = ext[1:].lower()
    if ext in save.keys():
        fm = ext
    save[fm](_G, '.'.join([fn, fm]))
 
 
def _loop():
    """Loop _step1 and _step2 of Iters times and gather statistics.
 
    This function uses yield statement and returns the total iterations of loop
    """
    import time
    t_start = time.time()  # records the start time
    t_ttl = 0  # clears the total time spent
    i_cnt = i_ttl = 0  # clears the loop counter and total loops.
 
    _create()  # create an empty network
 
    while 1:
        i_cnt += 1
 
        _step1()
        _step2(_p)
 
        if i_cnt == Iters:
            i_ttl += i_cnt
            i_cnt = 0
 
            _stat()
 
            t_inc = time.time() - t_start
            t_ttl += t_inc
            print "Time spent (increment,total): (%f,%f)" % (t_inc, t_ttl)
            yield i_ttl
            t_start = time.time()
 
 
def runnext(n=1):
    """Call _run.next() of n times."""
    for i in xrange(n):
        print _run.next()
 
 
def start(N=100, p=0.04):
    """Start the module.
 
    - N: the total of nodes of the network
    - p: the death-and-birth probability
    """
    global _N, _p, _run
    _N, _p = N, p
    _run = _loop()  # gets a generator
 
 
Iters = 100000
 
if __name__ == "__main__":
    # Uses Psyco if available
    try:
        import psyco
        psyco.full()
        print "Psyco: OK"
    except ImportError:
        print "Psyco: FAIL"
 
    start(100, .04)
 
    """
    import profile, pstats
    profile.run('runnext()', 'acq2002.prof')
 
    s = pstats.Stats('acq2002.prof')
    s.sort_stats('time','name').print_stats()
    """
  • 行 203~211 的 start() 除了測試這個模組外,也用作主程式。
  • 行 211 讓我們取得 generator 給 _run 。
  • 行 214 設定每次 _run.next() 要跑幾輪 super loop ,每 _run.next() 一次,才統計一次數據。
  • 行 225 執行 start() ,並指定不同的 node 數 N 及死生發生的機率 p 。
  • 這個模組執行後,可以在 Python shell 下 _run.next() ,每 _run.next() 一次,大迴圈就跑 Iters 輪。此外,還可以在 Python shell 中改變 Iters 的次數。
  • 行 197~200 的 runnext() 讓 Python shell 下,調用多次 _run.next() 更方便。
  • 行 217~223 使用 Psyco 來加速。

註:

  • NetworkX 的用法可以參考其 TutorialAPI Reference
  • Python 的 random 模組說明可以參考這裡
  • 要以 matplotlib 繪製圖表,可以參考其 TutorialScreenshots 。其刻意模仿 Matlab 的用法,用來倍感親切。
  • 其他 Python 的說明文件可以參考 Python Tutorials