12/31/2006

第十三章 圖形介面

(編譯及圖示摘自mathworks.com)

什麼是圖形介面 GUI?

圖形介面(GUI)是藉圖形展示裝置、指令元件或程式執行結果,使用者因此可以在交談的模式下執行特定的軟體。因此,利用 GUI介面,可以不經由敘述檔或輸入指令群,只要利用滑鼠選擇參數及執行指令即可。在這種情況下,使用者不必知道程式的內容或其執行的方式,可立即獲得所要的答案。

圖形介面GUI元件包括選單、工具欄、按鈕、點圈鈕、列單盒、滑尺等等。在MATLAB中,亦可利用 GUI 顯示表格式的資料或圖形,亦可設為群組件使用。

下面為一個簡單之圖形介面之示範:



在此圖形介面中,包括下列組件:
  • 一組圖形座標元件。
  • 一組下拉式選單,例如對應於三個函數peaks, membrane及 sinc之選擇。
  • 固定文字元件以標明跳出式選單。
  • 三個按鈕可以提供不同繪圖功能,包括:表面圖 surface、表面網線圖mesh及等高線圖 contour等。
這個程式儲存於simple_gui.m檔案中,每次執行時即可出現上述之圖面。再利用滑鼠進行點選即可得到不同的結果。但是,如何做才能達到這等境界呢?

GUI如何工作?

在GUI介面中,每一元件均與一或數個使用者自定的函數或程式相關,此相關之程序稱為回叫程式(callbacks)。每一個回叫程式可以經由按鈕觸動、滑鼠點入、選單項目選定或游標滑過特定元件等動作後產生的事件下執行。這些回叫程式都要經過 GUI之程式設計者加以事先規劃。

前節所述之GUI中,利用跳出式選單可以選定資料組合,然後按鍵進入執行繪圖型式。在按下特定按鈕時即會依其內容執行相對應於該按鈕之回叫程式,執行後即會繪出所期盼的圖形。

這種程式設計的過程通常屬事件驅型。亦即當某特定事件發生時(例如按下按鈕、更換數據等等),則該事件立即形成。利用這事件之發生,再觸發對應程式之執行。這些事件通常都是使用者藉GUI介面以交談的形式產生。

在程式執行過程中,回叫程式並無法控制事件發生之順序,亦無法確定何者會被執行。有時甚至另一個回叫在執行期間,另一個回叫程式又被啟動,且同時進行,其情況變得更為複雜。

要從何處起頭呢?


當然首先必須設計一個屬於自己的 GUI介面。但是事先必須決定要做什麼事,而且要使用者怎樣操作。在這些界面之應用元件中,到底要使用那些元件或幾個元件,設計者必須胸有成竹才能順利完成,有時必須先列出相關參考資料,以為備用。

其次,必須決定使用何種技巧去產生新的 GUI介面。還好,MATLAB讓初學者有機會使用GUIDE這個輔助指令進行圖形設計。它的名稱因為前三個字母是 GUI,配合起來稱為GUIDE雖是巧合,但也包含了引導的意義。利用GUIDE可以直接建造自己的GUI介面。其中並提供一些簡化標準對話盒的函數,可以直接引用。其應用存乎一心,但可依經驗、喜好及對GUI功能上之要求而有不同的進境。下面的表中列出幾個可用的元件:







GUITechnique
對話盒MATLAB提供一些標準對話盒之選項,可以產生簡單之函數呼叫,其連結可參考相關輔助。
僅含有數個元件之GUI介面若僅包含一些元件之程式設計較為簡單,每一個元件可以利用單一函數進行呼叫。
略為複雜性之GUI介面可以利用GUIDE指令簡化設計之過程
包括甚多元件之複雜GUI介面,有些甚至必須與其他 GUI介面發生交談者直接撰寫程式以便完全控制所有元件置放之位置,以增加其可複製性。


GUIDE之使用法

GUIDE是MATLAB圖形介面之發展環璄,以簡化GUI介面之製作。利用這些工具可設定圖形位置、大小、屬性及畫面之布置與規劃。

GUI介面之畫面編輯器(Layout Editor)

GUIDE指令中含有畫面編輯器,使用者利用滑鼠選項可以移動預設之 GUI元件,諸如按鈕、文字區、滑標、座標軸等等,也可以置入選單及選單中之文字項目。

編輯器中同時提供GUI介面所需之大小,並修改其外觀及畫面內容,諸如元件之對齊、鍵入順序、階層型選單內容及相關參數之設定等。

GUI圖面經規劃完成後,GUIDE 會自動產生兩個檔案,一為般之 M-檔案,另一為以.fig為附屬之檔案,後者僅能執行,不直接開啟,必須經由guide才能檢視。兩個之名稱相同,可以控制GUI介面之運作。每次執行此檔案時,會將原設計之介面初始化,並令其與對應之 GUI 回叫程式相連,因此只要利用滑鼠選項,產生特定事件,即可執行該程式。同時,此檔案亦可利用M-檔案編輯器進行編輯,並增加或更改回叫程式之內容。

設計 GUI應用例

以前面介紹之simple_qui程式為例,其內之元件可分為下面幾個大類:



其中包括三個按鈕(Surf, Mesh, Contour)、一串靜態文字(Select Data)、一組下拉選單(Peaks, ...)及一個圖軸。開始時,先下guide之指令:

>> guide



待上圖出現後,可以依左邊視窗內之項目選擇,其中之第一項空白GUI為預設值,其餘三項均為示範例。本節將採用空白的編輯視窗,以執行自定的範例。若guide 之視窗已經打開,則可選擇開啟新視窗,最後之畫面編輯視器亦會出現如下:


畫面編輯器之正中央為畫面之所在,可放置所需之元件,其空間大小可以按右下角進行調整。也可以利用圖上緣之View選單(或按滑鼠之右鍵),選擇其對應之屬性檢視表(Property Inspector),以設定此GUI之屬性。屬性檢視表分別儲存許多有關此選單或元件之特性參數,其內容包括視窗之正確之尺寸及顏色等。輸入尺寸時,必須先選擇適當單位。亦即先至Units項下,選擇公分或英寸。然後到Position處更改原點位置(x,y)與窗格之寬度與高度值。記得選定後,必須將單位換為文字(character),否則其他元件對應時不易對齊。


每一元件有一對應之屬性檢視表,只要指向不同元件,表中即會出現屬於該元件之對應值;利用此表可以更改元件之顏色、大小、位置、回叫程式及參數值。在後來增添之元件都有這種對應表之設計,只要指向該元件然後按滑鼠右鍵即可找到對應之屬性檢視表或Property Inspector。

畫面編輯器之左方為諸元件之選項,若要同時出現元件之對應名稱,則可在檔案項下按Preferences,在其元件名稱顯示選項下打勾即可。

增添元件(Add Elements)


要增加一個元件,只要由左邊之元件標示上選取即可。在本例中,要先選擇三個Push Button,可利用滑鼠分別拉到想要置放的大概位置,此時即使不對齊也無所謂。其大小若相同,則可採用拷貝的方式。



其次再依次增加一個靜態文字元件、一組跳出式選單及一組圖形座標等。將其相關位置安排如下圖。此時各元件都事先給一個名稱,但這個名稱可以事後在屬性檢視表中加以更改。


對齊(Align)


三個按鈕位置並不對齊,若要令其對齊在一垂直線上,用手調的也可以,也可使用對齊指令。首先可以將要對齊之三個按鈕選取,再至工具選項中,選擇物件對齊(Align Objects)之指令,即會出現此相關指令之選單如下:



選單中可以選擇元件之間距(20畫素)以及對齊之方式(上下或左右對齊),然後按OK即可。最後之結果會變成非常整齊、美觀如下圖:


屬性檢視表(Property Inspector)

待各元件之相關位置底定後,即可以開始更改各元件之標示名稱及其內所需之參數。前文曾提及,每一元件均有對應之屬性檢視表(Property Inspector),因此先將屬性檢視器叫出,然後用滑鼠指定要更改標示之按鈕,即可顯示其對應之屬性。呼叫屬性檢視表也可由View中或按滑鼠右鍵出現之輔助單中進行選擇,但通常只要呼叫第一次即可,其他元件只要指向改變,屬性表之內容也會隨之改變:


按鈕(Push Bottom)

在GUI畫面中先選定其中一個按鈕,此時若屬性檢視表已出現,其內容即會出現對應於該按鈕之屬性參數。在諸多參數中,先找到string這個屬性,這是元件的外在稱呼,在本例中可先將其內容更改為Surf。此時只要將滑鼠點在檢視器之其他地方,即可觀察其改變。其他按鈕也可依同樣的方式改變其標示名稱,其餘兩按鈕名稱可改為"Mesh"、"Contour"。

跳出式選單(Pop-up Menu)


其次再由GUI畫面先選定Pop-up Menu這個按鈕。同樣在對應之屬性檢視器中,按下String,此時會出現一個String之輸入視窗。此時可將原內容刪去,改輸入Peaks、 Membrane、 Sinc等三項,按下OK結束,此時選單內容即會隨之改變。

靜態文字(Static Texts)


在諸元件中,有時必須在圖版上置放一些文字說明,或在選擇前作提示。這些文字僅能供使用者觀察,不能供其修改或選擇,此元件因此稱為靜態文字元件。這種文字元件在設計過程中仍然可以修改,而且其大小、顏色也可以設定。因此,要更改其內容時,同樣可在GUI畫面中選取該文字元件,在其對應之屬性檢視器中,可直接更改String項下之內容。本例請將其改為"Select Data",作為其下選單之說明。選完後,只要按下OK即可改變內容。

最後之結果如下圖:



儲存檔案

如前所言,將上述畫面存檔時,Guide會產生兩個檔,一為Fig檔,一為M檔。前者屬二位元檔,為內容畫面之說明。後者為附屬名為.m為一般程式檔案,可以利用編輯器查看。此兩檔案必須同時放在一個目錄之下,才能執行。本例以simple_gui為檔名存檔,因此目錄中會有simple_gui.fig及simple_gui.m兩檔案出現。

執行該程式時只要在指令行打入該名稱即可。即:

>> simple_gui

結果如下:


只是目前之內容空洞,點選按鈕時也沒有反應。若要使它具有生命,仍需要繼續後段的程式加入才行。

Handles結構矩陣扮演的角色

利用guide產生的M檔案,主要在搭配介面作為一種橋樑之功能。在處理過程中,GUI利用此程式中一個以handles為名之結構陣列作為傳遞參數之媒介,因此處理諸項參數時不可不知。由前述之屬性表中可知,各元件中均有一個'Tag'的名稱。只要利用handles.Tag的名稱型式即可指出所要處理的對象與參數值,而且可將此值直接傳遞到其對應之回叫函數中應用。此handles內資料之儲存,常需與guidata配合,詳細用法參考後面guidata之應用一節。

資料之分享


例如要將存於X中之資料傳至函數中時,可以藉handles結構將其設定於其下之一欄位參數,然後利用guidata傳遞:

handles.current_data= X;
guidata(hObject, handles)

只要在起始函數中作上述動作,則未來在其他回叫函數中,均可利用handles結構取出該項值:

X = handles.current_data;

若在其他回叫函數中,更改到handles內之任何項數值,則必須將對應該參數值改變後,再次執行guidata(hObject,handles)一次,新值才能為其他函數使用。

例如:若有一組滑尺與編輯輸入元件,當編輯輸入值改變時,其對應之滑尺位置亦會改變,反之亦然。換言之,當滑尺更動位置時,編輯口有產生對應新值:


set(handles.edit1,'String', num2str(get(handles.slider1,'Value')))


程式中之edit1與slider1分別為編輯口及滑尺之標籤(Tag)名稱。其中get指令是先取得滑尺之位置值,並利用num2str()指令轉換為文字串,然後用set指令將該文字串設定給編輯口之String,以供顯示。

其次是由編輯口輸入值再轉換為滑尺置。由於滑尺之範圍在0與1之間,故輸入值若在範圍之外,必須顯示錯誤訊息,比較週到的話計算輸入錯誤之次數,以茲警惕。下面為有關於滑尺之回叫函叫函數內容:

滑尺位置之設定


編輯口輸入值之後可由下面程式進行檢查。若所得之值在其最大值與最小值之間,則更改滑尺之數值及對應位置。


val = str2double(get(handles.edit1,'String'));
% Determine whether val is a number between 0 and 1
if isnumeric(val) & ...
val >= get(handles.slider1,'Min') & ...
val <= get(handles.slider1,'Max')
set(handles.slider1,'Value',val);
else
% Increment the error count, and display it
handles.number_errors = handles.number_errors+1;
guidata(hObject,handles); % store the changes
set(handles.edit1,'String',...
['You have entered an invalid entry ',...
num2str(handles.number_errors),' times.']);
end


在處理錯誤輸入之功能上,則必須自handles.number_errors取出舊值,加上本次後,再存入guidata,以更新舊資料;同時在輸入口上印出此次輸錯之次數。

取得GUI資料


同樣,利用handles結構亦可應用於選單上,取得所需之相關資料。例如某一選單有巧克力、草苺及香草等項。此時其元件屬性中之'String'應為此三項的內容,而對應之'Value'則存有所選定的項值。例如若已選定草苺,則其對應之'Value'應等於2。設此選單之'Tag'為'my_menu',則下列程式可以得到對應之選單名稱current_choice:

all_choices = get(handles.my_menu, 'String')
current_choice = all_choices{get(handles.my_menu, 'Value')};

執行此回叫函數後,其current_choice值應為'草苺'。

這種變數甚至可以包括整個圖介面。設其屬性標籤為figure1,則下面的指令可以將此介面刪除,或等於結束本程式之執行:

delete(handles.figure1)

參考資料:

函數的再認識

輸出函數


輸出函數是將執行後之狀態輸出至指令行。也可以利用這項功能將一變數值再傳遞給另一個gui指令。

經過guide自動產生之輸出函數很簡單,只有這樣一行:

function varargout = my_gui_OutputFcn(hObject, eventdata, handles)
varargout{1} = handles.output;


因此,若gui本身未被遮斷,其輸出內容應為gui之握把,其內容置於handles.output內。若要獲得不同於上述輸出項時,則可以利用下列方式取得:

  • 在啟動函數中增加uiwait指令,令M檔案執行時能暫停,直到使用者啟動gui中之元件,例如按鈕。
  • 在gui中之期待回應之每一元件中,令其回叫函數更新handles.output之值,並執行uiresume之指令。
例如:若gui中之按鈕元件之string值為'Yes',則可在其回叫函數中增加下列指令:


handles.output = 'Yes';
guidata(hObject, handles);
uiresume;


此時,當gui程式被呼叫時,其執行型式如下:

>>OUT = my_gui


輸出參數varargout也是細胞矩陣,因此可以包括任何型式及數目的輸出項。guide之輸出預設備為handles.output。若要產生第二種輸出型式,則可作如下之添加:

varargout{2} = handles.second_output;

上述值之設定可以在任何回叫函數中為之,然後用guidata指令儲存。上述名稱並非一成不變,你可以選擇自己喜歡的參數名稱。

回叫函數


回叫函數在gui中是最重要的一環,因為元件之作動與呼叫程式藉此相互連繫。其函數名稱之設定係以元件之標籤為參數,例如一按鈕之Tag等於print_button時,其回叫函數之型式為:


function print_button_Callback(hObject, eventdata, handles)


輸出入參數


  • 呼叫gui之檔名有幾種方式,其功用自有不同,有時可以利用,設程式名稱為demo_gui:
  • 直接呼叫demo_gui:沒有引數,直接打開demo_gui程式。
  • 呼叫h=demo_gui:打開demo_gui,得其握把h。
  • 呼叫demo_gui('Property',Value,...):指令中為圖屬性,打開demo_gui使用輸入之屬性對,屬性對可以使用一對以上。
  • 呼叫demo_gui('demo_function',hObject, evendata, handles,...):呼叫次函數demo_function。
  • 呼叫demo_gui('Velocity',300):打開demo_gui,但傳遞['Velocity',300]引數向量至啟動函數。

產生繪圖資料

(編譯及圖示摘自mathworks.com)

上面的圖無法順利執行,主要是回叫程式及相關程式未能配套演出。這時必須利用改變 M檔案之內容。當此程式執行時,必須由程式產生所需之資料。這些程式由許多函數組成,其中包括:

  1. 啟動函數(Opening function):在GUI介面出現前先行執行之程式。有些必須事先設定之初值及各項資料可列於此啟動函數之內。此時也可以將handles結構資料儲存於guidata之內。
  2. 輸出函數(Output function):必要時部份運作資料若必須輸出至指令行。
  3. 回叫函數(Callbacks):當使用者作動介面上之元件時,對應須執行之函數。
上述各函數都是函數的型式,且各有函數名稱。其輸入參數則與handles結構有關,其中包括:

  • hObject:回叫元件或介面圖之握把。
  • handles:以handles為名之結構矩陣。

若執行當中,handles結構矩陣中之項目內容變更,則必須用guidata再度儲存,以維持最新資料:

guidata(hObject, handles)


當Guide執行完畢後,會自動在此程式檔案中產生所需要配合的函數名稱及內容。其中不少是相同的說明文件,但它只是制式的格式,內容仍需要配合實際的需要。

啟動函數



在這些函數當中,有一個函數稱為啟動函數(opening function),亦即執行該程式之後,此啟用函數先執行需要步驟,然後等待使用者輸入資料或選擇所要之動作按鈕。啟用函數也是一種函數的型式,由GUIDE自動產生。通常,程式設計者可以在這裡作修改或添加所需要之資料。由於啟動函數執行前,所有gui元件均已形成,所以幾乎所有的工作,諸如產生資料、畫圖或影像,或由uiwait指令隔離的gui等均可在此執行。

啟動函數之典型格式如下:


function my_gui_OpeningFcn(hObject, eventdata, handles, varargin)


其中my_gui為此介面之檔名。其中hObject與handles兩參數前面已有討論,其他輸入參數則有eventdata與 varargin。前者為matlab公司自行保留使用之變數,後者則為輸入指令行(若有的話)之參數。這些參數可以在指令行下該檔指令時使用。例如:


>>my_gui('Position', [70 45 75 20])

這個位置參數會被引入,然後改變其位置屬性。


以本例為例,由於使用者必須由peaks、membrane及sinc三個函數中取得資料,但在程式執行之初,使用者仍然來不及選擇那一種函數。因此必須事先預設一個函數(例如peaks),使程式執行之初,立即選擇此項資料並加以顯示。

本例中,為達到其原有之目的,必須在啟用函數內增加三組產生peaks、membrane及sinc之程式碼。茲分別說明如下。

  1. 首先,利用程式編輯器並將前節所儲存之simple_gui.m程式打開。在編輯器之指令行內有一個letter f on the toolbar 之符號,按下後會出現選單,就其中選擇simple_gui_OpeningFcn這一項,其內容如下:

    function varargout = sinple_gui(varargin)
    % SINPLE_GUI M-file for sinple_gui.fig
    % -----
    % Begin initialization code - DO NOT EDIT
    gui_Singleton = 1;
    gui_State = struct('gui_Name', mfilename, ...
    'gui_Singleton', gui_Singleton, ...
    'gui_OpeningFcn', @sinple_gui_OpeningFcn, ...
    'gui_OutputFcn', @sinple_gui_OutputFcn, ...
    'gui_LayoutFcn', [] , ...
    'gui_Callback', []);
    if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
    end

    if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
    else
    gui_mainfcn(gui_State, varargin{:});
    end
    % End initialization code - DO NOT EDIT


    % --- Executes just before sinple_gui is made visible.
    function sinple_gui_OpeningFcn(hObject, eventdata, handles, varargin)
    handles.peaks=peaks(35);
    handles.membrane=membrane;
    [x,y] = meshgrid(-8:.5:8);
    r = sqrt(x.^2+y.^2) + eps;
    sinc = sin(r)./r;
    handles.sinc = sinc;
    % Set the current data value.
    handles.current_data = handles.peaks;
    surf(handles.current_data)
    % Choose default command line output for sinple_gui
    handles.output = hObject;

    % Update handles structure
    guidata(hObject, handles);


    % --- Outputs from this function are returned to the command line.
    function varargout = sinple_gui_OutputFcn(hObject, eventdata, handles)
    varargout{1} = handles.output;


    % --- Executes on button press in pushbutton1.
    function pushbutton1_Callback(hObject, eventdata, handles)
    surf(handles.current_data);

    % --- Executes on button press in pushbutton2.
    function pushbutton2_Callback(hObject, eventdata, handles)
    mesh(handles.current_data);
    % --- Executes on button press in pushbutton3.
    function pushbutton3_Callback(hObject, eventdata, handles)
    contour(handles.current_data);

    function edit1_Callback(hObject, eventdata, handles)


    % --- Executes during object creation, after setting all properties.
    function edit1_CreateFcn(hObject, eventdata, handles)
    if ispc && isequal(get(hObject,'BackgroundColor'),
    get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
    end


    % --- Executes on selection change in popupmenu1.
    function popupmenu1_Callback(hObject, eventdata, handles)
    str = get(hObject, 'String');
    val = get(hObject,'Value');
    % Set current data to the selected data set.
    switch str{val};
    case 'Peaks' % User selects peaks.
    handles.current_data = handles.peaks;
    case 'Membrane' % User selects membrane.
    handles.current_data = handles.membrane;
    case 'Sinc' % User selects sinc.
    handles.current_data = handles.sinc;
    end
    % Save the handles structure.
    guidata(hObject,handles)


    % --- Executes during object creation, after setting all properties.
    function popupmenu1_CreateFcn(hObject, eventdata, handles)
    if ispc && isequal(get(hObject,'BackgroundColor'),
    get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
    end



    此時游標移至啟用函數中之這一部份:
    • % --- Executes just before simple_gui is made visible.
      function simple_gui_OpeningFcn(hObject, eventdata, handles, varargin)
      % This function has no output args, see OutputFcn.
      % hObject handle to figure
      % eventdata reserved - to be defined in a future version of MATLAB
      % handles structure with handles and user data (see GUIDATA)
      % varargin command line arguments to simple_gui (see VARARGIN)

      % Choose default command line output for simple_gui
      handles.output = hObject;

      % Update handles structure
      guidata(hObject, handles);

      % UIWAIT makes simple_gui wait for user response (see UIRESUME)
      % uiwait(handles.figure1);

  1. 在上面啟用函數之程式碼中,必須在第七行下面(即% varargin...之後)加入下面一段程式碼,以產生適當之資料供繪圖:
    • % Create the data to plot.
      handles.peaks=peaks(35);
      handles.membrane=membrane;
      [x,y] = meshgrid(-8:.5:8);
      r = sqrt(x.^2+y.^2) + eps;
      sinc = sin(r)./r;
      handles.sinc = sinc;
      % Set the current data value.
      handles.current_data = handles.peaks;
      surf(handles.current_data)
開頭之六行執行程式可以利用 MATLAB 原有之函數指令 peaks,membrane, 及sinc產生相關之資料。這些資料必須置放於一個以 handles 為名之結構矩陣中,利用此參數作為傳遞資料給對應之回叫函數之橋樑,亦即成為呼叫函數之輸入值。對應於GUI圖面之各元件之回叫函數將由handles之參數結構中取得所需之資料。

加入程式中之最後兩行之目的則在獲得現存資料,令立即以surf指令先顯示其外觀圖。也是執行此程式時,且在使用者尚未下任何指令前之見面禮。執行後,其圖形之最終界面表示如下:


跳出式選單之程式

(編譯及圖示摘自mathworks.com)
上一節所完成之程式執行後,你會發覺並不能有結果,因為按下的鍵並沒有動靜。不過不要灰心,因為仍然有些情節必須處理。現在談到跳出式選單之程式撰寫,這也是一個有趣的項目。跳出式選單屬於下拉式,但可置於畫面上之任一位置,本例中係利用它提供不同的繪圖資料,當選擇三者之一時,其對應屬性值應分別設定於其字串中,如此才能搭上線。當代表跳出式選單之其屬性值取得後,即可立即顯示之內容,此內容通常會存在handles.current_data這個參數內。

同上節,我們仍然要回到編輯視窗中,在function之標示下可以找到popupmenu1_Callback這個選項。也可以直接到畫面編輯窗中,用滑鼠直接點選跳出式選單之元件,再按右鍵選擇Callback(方式如下圖),然後回到 M-檔案之編輯窗來。



此時對應於本例之 GUI M-檔案內容會出現,而游標會指向跳出式選單之回叫程式,其內容如下:



% --- Executes on selection change in popupmenu1.
function popupmenu1_Callback(hObject, eventdata, handles)
% hObject handle to popupmenu1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)

在此段程式下面可以增加與 popupmenu1_Callback相關 之程式內容,其細節如下所示。在此段附加之程式之功能是先自屬性表中取得與此跳出式選單與有關之兩項參數,分別為:
* String:屬細胞陣列,內有此選單之內容。
* Value :選單指標,向選定之資料集中之選單內容。

添加之程式部份,使用 switch函數設定所選定之資料集,並將其置於立即顯現之組合參數內,以便立即顯示。最後一項則將選定之 handles 結構陣列置於guidata參數中,以便傳遞。


% Determine the selected data set.
str = get(hObject, 'String');
val = get(hObject,'Value');
% Set current data to the selected data set.
switch str{val};
case 'Peaks' % User selects peaks.
handles.current_data = handles.peaks;
case 'Membrane' % User selects membrane.
handles.current_data = handles.membrane;
case 'Sinc' % User selects sinc.
handles.current_data = handles.sinc;
end
% Save the handles structure.
guidata(hObject,handles)

按鈕回叫程式

(編譯及圖示摘自mathworks.com)
本例中有三個按鈕,每一個按鈕會產生不同的圖形;而這些圖形資料必須取自跳出式選單中所設定之資料。按鈕也是依據其對應之回叫程式將儲存於handles結構陣列中之資料取出,然後進行繪圖。

首先選取畫面編輯器中之 Surf 按鈕,然後令其指向M-檔案編輯器中之push button callback程式之位置。方法之一是直接由畫面編輯器中選取該元件,按右鍵後在其內容中選取 Callback之選項:



或者,亦可自檔案編輯器中之f標示下,選取pushbutton1_Callback項,游標即會指向該段之程式內容:


% --- Executes on button press in pushbutton1.
function pushbutton1_Callback(hObject, eventdata, handles)
% hObject handle to pushbutton1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)


在此段程式之下,必須添加下面之回叫程式內容:

% Display surf plot of the currently selected data.
surf(handles.current_data);


其他兩個相同的按鈕Mesh與Contour亦可利用同樣的方式進行修正。屬於Mesh按鈕之回收程式名稱為pushbutton2_Callback,應添加之程式為:

% Display mesh plot of the currently selected data.
mesh(handles.current_data);

於Contour按鈕之回收程式名稱為pushbutton3_Callback,應添加之程式為:

% Display contour plot of the currently selected data.
contour(handles.current_data);


終於完成!執行 GUI!


辛苦了半天,你一定想執行看看,有什麼結果,首先存檔,即可下指令執行:

>> simple_gui



在跳出式選單中,選取Membrane,然後點下Mesh按鈕,這個 GUI程式即會顯示不同的結果:



試一試其他組合,應該有更多的發現。

產生GUI介面之函數

MATLAB 提供一系列之函數可以產生圖形介面。下列為各種函數之總合,可作為撰寫之參考。


函數名稱Function
功能說明
align
介面之元件與座標軸之對齊。
axes
產生座標軸元件。
figure
產生圖元件。一個GUI 介面即為一個圖元件。
movegui
移動 GUI 圖形至螢幕中之特定位置。
uicontrol
產生使用者介面控制元件,如 push buttons, static text, 與 pop-up menus等。

使用在GUI介面之其他MATLAB函數:


函數名稱 Function
功能說明

contour
繪製矩陣之等高線圖。
eps
浮點相對準確度。
get
查詢元件屬性。
membrane
產生 MATLAB 之標誌。
mesh
製作格網圖形。
meshgrid
三維圖中產生以 X與 Y陣列構成之網格點
peaks
產生雙變數函數之例。
set
設定元件之參數值。
sin
正弦函數,以弧度表示。
sqrt
開方根。
surf
產生三維覆蓋圖。

自製GUI介面

前面介紹使用GUIDE函數組合GUI介面,雖然方便,但是必須同時保留兩個檔案(即.fig與.m檔案)才能執行,應用上比較麻煩。撰寫GUI介面程式也可以不經GUIDE函數的環境而獲得同樣的目的。下面就介紹以純程式撰寫之角度完成相同介面之寫作方法。

開始時,先產生一個基本的M-檔案,以代替GUIDE之圖形編輯介面:

在 MATLAB 指令窗下,打入


edit simple_gui2

此時,MATLAB 之編輯器會打開,並產生一個新檔simple_gui2.m。 在編輯器中打入第一行程式,作為函數之開頭。


function simple_gui

其次可以加入一些說明行,以作為函數之開場白,最後加一空白行,使打入help simple_gui2指令後,能出現空白行所以上所列之說明文字:


% SIMPLE_GUI Select a data set from the pop-up menu, then
% click one of the plot-type push buttons. Clicking the button
% plots the selected data in the axes.
(留下至少一空白行)


在程式之最後,應加上一個end,因為在後來之回收函數加入時係以巢狀函數之型式出現,此時之主函數必須加上end之指令,以區別主函數之限界。即程式應為:


function simple_gui2
% SIMPLE_GUI Select a data set from the pop-up menu, then
% click one of the plot-type push buttons. Clicking the button
% plots the selected data in the axes.
(留下至少一空白行)

end


Figure指令之運用

在 MATLAB中,一個 GUI介面本身也是一張圖。因此,首要之步驟是在螢幕的一角落上定位出所需之繪圖位置及其大小。這項工作通常可藉助figure這個指令來達成。前面第九章中曾介紹此一指令之應用,大體上都是用來指示所要繪圖之圖序,以決定在眾圖中指令所要引用或加添的圖號,如figure(2)是。但是,此一指令也有較複雜的用法,諸如設定圖的名稱或其參數,如:


figure('Name','Simulation Plot Window','NumberTitle','off')

這個指令是開啟一個圖版,將其序號隱去,此圖檔之名稱為'Simulation Plot Window',此圖名使用中文亦可,若序號不設成'off',則圖號會與圖名同時出現。這裡所定的圖視窗是以Matlab之預設參數為之,是以仍有相關的指令目錄可以使用。

這個圖指令之另一項功能是用來自行定介面圖視窗之位置與大小。也可以預設該圖是否在元件加入過程是否為可見。若'Visible'設定為'off',表示使用者無法看到元件之加入與初始化之過程。其程式型式如下:


% Initialize and hide the GUI as it is being constructed.
f = figure('Visible','off','Position',[360,500,450,285]);

在呼叫圖視窗時,其所需輸入之參數通常成對輸入,即屬性與屬性值。後者可用字串、陣列或數值等,依其特性而定。在視窗位置以'Position'表示,其對應值為一陣列,其位置與左邊界及底邊界之距離、寬度與高度。例如,[360,500,450,285]表示離左邊界為360單位、離底邊界為500單位,而寬度與高度分別為450與285個單位。其單位可為像數值或字數,必須在屬性表中設定,否則其預設值為像素。


如何加入元件


在本例之 GUI介面中具有六項元件,即三個按鈕、一靜態文字、一跳出式選單及一個圖軸。現在就針對這六項開始寫入元件之程式。前五項可用uicontrol指令建立;圖形則必須利用軸指令 axes 函數產生座標軸。

在figure指令執行後,即可加入三行uicontrol指令,以設定三個按鈕之 GUI ,以函數之呼叫形式存在M-檔案之中:

% Construct the components.
hsurf = uicontrol('Style','pushbutton',...
'String','Surf','Position',[315,220,70,25]);
hmesh = uicontrol('Style','pushbutton',...
'String','Mesh','Position',[315,180,70,25]);
hcontour = uicontrol('Style','pushbutton',...
'String','Countour','Position',[315,135,70,25]);

使用uicontrol指令主要在取代前面guide的功能,可以由程式設計者決定元件之屬性,取代guide的屬性表。利用uicontrol指令設定按鈕之參數時,仍然採用{屬性/數值}之配對輸入方式,列項於uicontrol函數之參數之中。目前比較常用之屬性參數如下表:


屬性
功能說明
Style
本例中, pushbutton 表示按鈕元件;popupmenu表示跳出式選單。
String
內含顯示在按鈕上之標示文字,此例中有三種繪圖標示,即 Surf, Mesh, Contour等。
Position
採用四元素陣列表示每個按鈕之位置與大小,分別[至左邊界之距離 至底邊界之距離,寬度,高度],其預設單位為像素。



每次呼叫uicontrol函數後均會產生一個對應握把,作為分辨元件呼叫之基礎,此處將三個按鈕元件分別設定為hsurf、hmesh、 hcontour。

其次再處理跳出式選單及靜態文字兩元件,此時仍然以同同樣的指令,但設定不同的'Style',其屬性值分別為'popupmenu'與'text',所得的握把分別設為hpopup與htext',注意這兩元件同樣均需設定個別之'Position'參數,其屬性值則會不一樣:

hpopup = uicontrol('Style','popupmenu',...
'String',{'Peaks','Membrane','Sinc'},...
'Position',[300,50,100,25]);
htext = uicontrol('Style','text','String','Select Data',...
'Position',[325,90,60,15]);


在此例中,跳出式選單中之字串屬性String 須使用細胞陣列,分別表示三項目: Peaks, Membrane, Sinc,並且以大括符括起來,表示屬於細胞陣列。本例所用之靜態文字係在說明跳出式選單之內容,設其為Select Data,如此當開始執行時,由String 屬性告訴GUI 介面使用者,由底下之選單中先選擇資料。同樣此處之定位單位仍預設為像素。

圖軸之設定指令不是使用unicontrol 指令而是改用axes指令,其尺寸單位亦應指名相同的單位,以免座標軸顯示之間距產生不對檔的現象。


ha = axes('Units','pixels','Position',[50,60,200,185]);

上述宣告圖軸之握把為ha。除圖軸部份外,將其他元件均居中,則指令敘述可作如下宣告,注意所有要居中之項目之握把可以安排為一個陣列:

align([hsurf,hmesh,hcontour,htext,hpopup],'Center','None');

由於在執行上述變化過程中,不擬讓使用者看到,故曾宣告'Visible''off',現在事情辦妥後可以再宣告為'on',以免什麼都看不到。

set(f,'Visible','on')

最後整個程式內容如下:

function simple_gui2
% SIMPLE_GUI Select a data set from the pop-up menu, then
% click one of the plot-type push buttons. Clicking the button
% plots the selected data in the axes.

% Create and hide the GUI as it is being constructed.
f = figure('Visible','off','Position',[360,500,450,285]);

% Construct the components.
hsurf = uicontrol('Style','pushbutton','String','Surf',...
'Position',[315,220,70,25]);
hmesh = uicontrol('Style','pushbutton','String','Mesh',...
'Position',[315,180,70,25]);
hcontour = uicontrol('Style','pushbutton',...
'String','Countour',...
'Position',[315,135,70,25]);
htext = uicontrol('Style','text','String','Select Data',...
'Position',[325,90,60,15]);
hpopup = uicontrol('Style','popupmenu',...
'String',{'Peaks','Membrane','Sinc'},...
'Position',[300,50,100,25]);
ha = axes('Units','Pixels','Position',[50,60,200,185]);
align([hsurf,hmesh,hcontour,htext,hpopup],'Center','None');

%Make the GUI visible.
set(f,'Visible','on')

end

在指令窗中,可以直接下令執行simple_gui2,其結果如下圖。只是不要高興太早,因為即使你選了項目,且按了鍵,仍然毫無反應。可以想像得到,這是因為我們尚未寫到跳出式視選單之對應程式尚未加入之故。


(編譯及圖示摘自mathworks.com)

如果有空,可以打入 help simple_gui,看看顯示出來之結果如何:

>>help simple_gui
SIMPLE_GUI Select a data set from the pop-up menu, then
click one of the plot-type push buttons. Clicking the button
plots the selected data in the axes.

完成GUI的畫面程式!

要使前述之介面可以執行,必須先將程式起始化,然後才能順利使用,其中必須考慮下述幾項:

所用單位必須正規化,以取得大小一致。這可利用'Units'屬性宣告為 'normalized'之狀態。此時所有元件均會隨GUI圖面之大小呈比例變化。正規化之單位其圖會以左下角為視窗的(0,0)點,而右上角則落在(1.0,1.0)之上。其餘要做的事項則包括:

  • 產生繪圖所需之資料。本例中需要三組資料:peaks_data, membrane_data與 sinc_data。每組資料必須與跳出式選單之項目匹配。
  • 在圖軸上顯示起始圖表。
  • 將此介面賦于一名稱,此名稱會在視窗之上方標題格出現。
  • 將介面視窗移至螢幕之中央。
  • 打開 GUI介面顯示開關。

依據上述各點,程式中尚需增加的部份如下:

% Initialize the GUI.
% Change units to normalized so components resize automatically.
set([f,hsurf,hmesh,hcontour,htext,hpopup],'Units','normalized');
% Generate the data to plot.
peaks_data = peaks(35);
membrane_data = membrane;
[x,y] = meshgrid(-8:.5:8);
r = sqrt(x.^2+y.^2) + eps;
sinc_data = sin(r)./r;
% Create a plot in the axes.
current_data = peaks_data;
surf(current_data);
% Assign the GUI a name to appear in the window title.
set(f,'Name','Simple GUI')
% Move the GUI to the center of the screen.
movegui(f,'center')
% Make the GUI visible.
set(f,'Visible','on');

綜合前節之內容,完整個的GUI程式如下,可以存檔後直接執行:

function simple_gui2
% SIMPLE_GUI Select a data set from the pop-up menu, then
% click one of the plot-type push buttons. Clicking the button
% plots the selected data in the axes.

% Create and hide the GUI figure as it is being constructed.
f = figure('Visible','off','Position',[360,500,450,285]);

% Construct the components
hsurf = uicontrol('Style','pushbutton','String','Surf',...
'Position',[315,220,70,25]);
hmesh = uicontrol('Style','pushbutton','String','Mesh',...
'Position',[315,180,70,25]);
hcontour = uicontrol('Style','pushbutton',...
'String','Countour',...
'Position',[315,135,70,25]);
htext = uicontrol('Style','text','String','Select Data',...
'Position',[325,90,60,15]);
hpopup = uicontrol('Style','popupmenu',...
'String',{'Peaks','Membrane','Sinc'},...
'Position',[300,50,100,25]);
ha = axes('Units','Pixels','Position',[50,60,200,185]);
align([hsurf,hmesh,hcontour,htext,hpopup],'Center','None');

% Create the data to plot
peaks_data = peaks(35);
membrane_data = membrane;
[x,y] = meshgrid(-8:.5:8);
r = sqrt(x.^2+y.^2) + eps;
sinc_data = sin(r)./r;

% Initialize the GUI.
% Change units to normalized so components resize
% automatically.
set([f,hsurf,hmesh,hcontour,htext,hpopup],...
'Units','normalized');
%Create a plot in the axes.
current_data = peaks_data;
surf(current_data);
% Assign the GUI a name to appear in the window title.
set(f,'Name','Simple GUI')
% Move the GUI to the center of the screen.
movegui(f,'center')
% Make the GUI visible.
set(f,'Visible','on');

end

執行結果如下圖:

(編譯及圖示摘自mathworks.com)

不過,執行看看,按鍵時並沒有動靜,是不是錯了呢?

程式二部曲--回叫函數

前面所述的細節,在寫程式時均未提到回叫函數(Callbacks)的部份,顯然沒有考慮回叫函數與各元件之對應是無法將結果與按鍵相連繫在一起的。前節僅是建好GUI的門面而已,但已經花了五牛二虎之力了。雖然如此,也不能半途而廢,不是嗎?

跳出式選單的回叫函數


跳出式選單目的是讓使用者選擇所要繪製之圖檔,故當使用者選定其中之一時,程式必須讀出其String之對應值Value,以決定那一項目的對應圖要顯示。其法是將該圖之資料送至current_data參數之中,以後只要按下任何鍵,依回叫函數之current_data執行即可。

因此,上述的前部份程式中,必須增添回叫函數之功能,並以巢狀程式納入:

% Pop-up menu callback. Read the pop-up menu Value property
% to determine which item is currently displayed and make it
% the current data. This callback automatically has access to
% current_data because this function is nested at a lower level.

function popup_menu_Callback(source,eventdata)
% Determine the selected data set.
str = get(source, 'String');
val = get(source,'Value');
% Set current data to the selected data set.
switch str{val};
case 'Peaks' % User selects Peaks.
current_data = peaks_data;
case 'Membrane' % User selects Membrane.
current_data = membrane_data;
case 'Sinc' % User selects Sinc.
current_data = sinc_data;
end
end


按鈕之回叫函數


三個按鈕各別產生不同的繪圖型式,圖之資料則得自跳出式選單中之項目,其資料置於current_data參數中,可以直接作為輸入值。

下面為此三按鈕個別回叫函數,可以將其加入於前節之程式後面,但必須在主程式之end之前:


% Push button callbacks. Each callback plots current_data in the
% specified plot type.

function surfbutton_Callback(source,eventdata)
% Display surf plot of the currently selected data.
surf(current_data);
end

function meshbutton_Callback(source,eventdata)
% Display mesh plot of the currently selected data.
mesh(current_data);
end

function contourbutton_Callback(source,eventdata)
% Display contour plot of the currently selected data.
contour(current_data);
end


整合回叫函數


上述之回叫函數分別列於程式之中,但如何呼叫變成重要的環節。為此必須從原程式中之uicontrol函數指令內之參數著手。換言之,三個按鈕相對應之uicontrol指令中,必須加入一組回收函數之參數,其屬性為'Callback',對應值為回收函數之名稱,但必須採用隱函數之呼叫,如:{@surfbutton_Callback}是,故三者之uicontrol中之參數均應修改,以Surf按鈕為例:

hsurf = uicontrol('Style','pushbutton','String','Surf',...
'Position',[315,220,70,25],...
'Callback',{@surfbutton_Callback});

其他二組及跳出式選單亦需將對應之值作如下之修正:

hmesh = uicontrol('Style','pushbutton','String','Mesh',...
'Position',[315,220,70,25],...
'Callback',{@meshbutton_Callback});
hcontour = uicontrol('Style','pushbutton','String','Contour',...
'Position',[315,220,70,25],...
'Callback',{@contourbutton_Callback});
hcontour = uicontrol('Style','pushbutton','String','Contour',...
'Position',[315,220,70,25],...
'Callback',{@contourbutton_Callback});
hpopup = uicontrol('Style','popupmenu',...
'String',{'Peaks','Membrane','Sinc'},...
'Position',[300,50,100,25],'Callback',{@popup_menu_Callback});

程式三部曲--絕地大反攻!

利用上兩節所敘述之增添與修正,最後的程式應為如下之型式與內容:


function simple_gui
% SIMPLE_GUI Select a data set from the pop-up menu, then
% click one of the plot-type push buttons. Clicking the button
% plots the selected data in the axes.

% Create and then hide the GUI as it is being constructed.
f = figure('Visible','off','Position',[360,500,450,285]);

% Construct the components.
hsurf = uicontrol('Style','pushbutton','String','Surf',...
'Position',[315,220,70,25],...
'Callback',{@surfbutton_Callback});
hmesh = uicontrol('Style','pushbutton','String','Mesh',...
'Position',[315,180,70,25],...
'Callback',{@meshbutton_Callback});
hcontour = uicontrol('Style','pushbutton',...
'String','Countour',...
'Position',[315,135,70,25],...
'Callback',{@contourbutton_Callback});
htext = uicontrol('Style','text','String','Select Data',...
'Position',[325,90,60,15]);
hpopup = uicontrol('Style','popupmenu',...
'String',{'Peaks','Membrane','Sinc'},...
'Position',[300,50,100,25],...
'Callback',{@popup_menu_Callback});
ha = axes('Units','Pixels','Position',[50,60,200,185]);
align([hsurf,hmesh,hcontour,htext,hpopup],'Center','None');

% Create the data to plot.
peaks_data = peaks(35);
membrane_data = membrane;
[x,y] = meshgrid(-8:.5:8);
r = sqrt(x.^2+y.^2) + eps;
sinc_data = sin(r)./r;

% Initialize the GUI.
% Change units to normalized so components resize
% automatically.
set([f,ha,hsurf,hmesh,hcontour,htext,hpopup],'Units','normalized');
%Create a first plot in the axes.
current_data = peaks_data;
surf(current_data);
% Assign the GUI a name to appear in the window title.
set(f,'Name','Simple GUI')
% Move the GUI to the center of the screen.
movegui(f,'center')
% Make the GUI visible.
set(f,'Visible','on');

% Callbacks for simple_gui. These callbacks automatically
% have access to component handles and initialized data
% because they are nested at a lower level.

% Pop-up menu callback. Read the pop-up menu Value property
% to determine which item is currently displayed and make it
% the current data.
function popup_menu_Callback(source,eventdata)
% Determine the selected data set.
str = get(source, 'String');
val = get(source,'Value');
% Set current data to the selected data set.
switch str{val};
case 'Peaks' % User selects Peaks.
current_data = peaks_data;
case 'Membrane' % User selects Membrane.
current_data = membrane_data;
case 'Sinc' % User selects Sinc.
current_data = sinc_data;
end
end

% Push button callbacks. Each callback plots current_data in
% the specified plot type.

function surfbutton_Callback(source,eventdata)
% Display surf plot of the currently selected data.
surf(current_data);
end

function meshbutton_Callback(source,eventdata)
% Display mesh plot of the currently selected data.
mesh(current_data);
end

function contourbutton_Callback(source,eventdata)
% Display contour plot of the currently selected data.
contour(current_data);
end

end


其執行之結果應與前面使用Guide所做的相同,讀者不妨一試,並比較其差異性。到底兩種方法之優劣如何呢?你能設法像本方法一樣,拷貝前一方法所討論的程式而加以執行嗎?

GUI其他元件之應用

(Sources: Matlab GUI Tips. RS Schestowitz, )

圓鈕按鈕(Radio Buttons)


圓鈕按鈕通常以群組方式出現,但僅能從中選其一。為此,每一次按鈕之回叫函數中,必須伴隨下面之設定指令,將選擇之按鈕值設定為1,其餘皆設定為0:


set(handles.radio1, 'Value', 1);
set(handles.radio2, 'Value', 0);
set(handles.radio3, 'Value', 0);


上式為radio1之回叫函數內容,為此,其他之按鈕值須設定為0。在實際之狀態測試時,則可用下式進行。通常圓鈕之表示法有按下與釋放兩狀態,按下時圓鈕內會有一點綠。按下狀態其內定值為1,或等於Max值,否則為零。其測試方式如下:

if (get(hObject,'Value') == get(hObject,'Max'))
% 此處執行圓鈕被按下之程式
else
% 此處執行圓鈕釋放狀態之程式
end

若圓鈕成為群組形式時,除採用上述方法設定外,亦可利用群組回叫函數SelectionChangeFcn處理,不必採用個別設定的方式。其功能後述。

開関鈕(Toggle Buttons)


開關鈕之功能與圓鈕相同,只是開關鈕為長方形,外觀與按鈕相同。只是它存在按下與釋放兩狀態,前者呈外凸而後者呈內陷。其狀態值按下時等於Max(預設值為1),釋放時等於Min(預設值為0)。利用下面回叫函數可以偵測其狀態:

function togglebutton1_Callback(hObject, eventdata, handles)
button_state = get(hObject,'Value');
if button_state == get(hObject,'Max')
% 執行開關鈕按下狀態之程式
elseif button_state == get(hObject,'Min')
% 執行開關鈕釋放狀態之程式
end

與圓鈕同樣,開關鈕亦可以利用群組的形式處理,其回叫函數為SelectionChangeFcn。

若想將開關鈕(或圓鈕)加上影像,則可利用CData這個屬性,設其色值為mxnx3之RGB值,屬實色圖像。若以亂數產生16x128圖像,並作如下設定:

a(:,:,1) = rand(16,128);
a(:,:,2) = rand(16,128);
a(:,:,3) = rand(16,128);
set(hObject,'CData',a)


勾選盒(Check Boxes)


勾選盒旨在確定某項元件所代表意義之確認。已勾選狀態之值等於Max(預設值為1),無勾選狀態之值則為Min(預設值為0),可以利用下式進行測試:

if (get(handles.state,'Value') == 0),
set(handles.checkbox, 'Value', 0);
set(handles.state, 'String', '0');
else
set(handles.checkbox, 'Value', 1);
set(handles.state, 'String', '1');
end


下拉選單(Drop-down Menus)


下拉選單與跳出式選單(Pop-Up Menus)相同。其內可選擇之項目至少兩項以上,但實際應用僅能其中一項有效。通常選單存在'String'之內,被選定的項目序則置放'Value'內。因此,為確定被選定之名稱,必須使用如下指令:


function popupmenu1_Callback(hObject, eventdata, handles)
val = get(hObject,'Value');
string_list = get(hObject,'String');
selected_string = string_list{val}; % convert from cell array
% to string
% proceed with callback...

if (strcmp(selected_string,'MenuEntry1')),
set(handles.data, 'String','Data1');
elseif (selected_string,'MenuEntry2')),
set(handles.data, 'String', 'Data2');
else
msgbox('Error with menu callback. Parameter passed is not recognized.');
end

若選擇之項目多時,亦可使用switch case之型式:

function popupmenu1_Callback(hObject, eventdata, handles)
val = get(hObject,'Value');
switch val
case 1
% 選擇第一項時之程式
case 2
% 選擇第二項時之程式
case 3
% 選擇第三項時之程式
...
end


選單勾記選項


在下拉式清單中,已選的項目加打勾記號時,必須針對同一選單中之項目加以標記:

set(handles.menuitem1, 'Checked', 'off');
set(handles.menuitem2, 'Checked', 'off');
set(handles.menuitem3, 'Checked', 'off');
set(handles.menuitem4, 'Checked', 'on');
set(handles.menu_selection, 'String', 'item4');


滑尺(Sliders)


利用滑尺之位置算出實際之滑尺值,再滙入實際程式,其程序如下:

function slider1_Callback(hObject, eventdata, handles)
slider_value = get(hObject,'Value');
% proceed with callback...

亦可使用下列指令設定其值:

set(handles.slider_value,'String', num2str(ceil(get(handles.slider))));

若要將滑尺值與編輯窗內之值連動,亦可採用下面回叫函數。當使用者拉動滑尺,其對應值可以在編輯窗中顯示:

set(handles.edit1,'String',num2str(get(handles.slider1,'Value')));

上述指令中,應用到三項指令:

  • 使用get指令取得滑尺之現值。
  • 利用num2str函數將數值轉換為字串。
  • 利用set指令重設編輯窗之'String' 文字屬性。

編輯窗(Edit Text)


使用者亦可經由編輯窗輸入文字,然後由其'String'屬性取得內容。其正式回叫函數如下:

function edittext1_Callback(hObject, eventdata, handles)
user_string = get(hObject,'string');
% proceed with callback...

若要取得的是數值,則必須將文字串轉換為數值,此可以使用str2double轉換函數為之。因此,若取得的內容屬非數值文字,則經過此函數轉換之結果會得到NaN,如此可以使用isnan()進行測試,然後利用errordlg發出錯誤信息。其型式如下:

function edittext1_Callback(hObject, eventdata, handles)
user_entry = str2double(get(hObject,'string'));
if isnan(user_entry)
errordlg('你必須輸入數值','Bad Input','modal')
end
% 由此繼續其他程式指令...


列示窗(List Boxes)


列示窗主要在顯示一系列項目,供使用者選擇,其選項可為單選或多選。常應用於檔案或目錄顯示。在guide中設定時,可循下列方式進行:

  • 在屬性表中,就'String'項下輸入要顯示的項目。
  • 選 OK確定,清單中之頭一項即會顯示在窗口中。
  • 下面參數可供設定,以獲得不同的功能:其中包括ListBoxTop、Value、Max及Min 等項。
     * 若窗口不足以顯示所有項目時,可以用'ListBoxTop'屬性選定那些項目置於最頂端。
    * 使用者預設僅能一次單選。若要多選,可使用屬性表中之'Max' 與'Min'參數設定,
    使其 Max - Min > 1。例如: Max = 2, Min = 0。
    * 在最初顯示時可以設定'Value',作為最初預設為被選之項目。
    * 不設初值選定時,可以設定 Max與 Min值,以得多重選擇;或設為空集合即可。


列示窗也可以利用回叫函數進行程式設定,回叫函數常與滑鼠按鈕釋放時或特定鍵按下時之動作連動。而利用箭號鍵則可改變數值屬性,並觸動回叫函數之執行及設定圖之 SelectionType 屬性至normal狀態。

使用Enter鍵或空白鍵並不改變數值屬性,但可以觸發回叫函數,並設定 SelectionType 屬性至open狀態。若使用者雙按滑鼠,則每一單擊會令回叫函數執行。第一個單擊時,MATLAB設定圖之SelectionType屬性至normal,第二個單擊則會讓其再度打開(open)。

下面程式為一個典型列示窗之回叫函數內容。設listbox1 為其標籤值,先取得其選定值序,再取得其名單,再求得對應值序之名稱,其過程與一般下拉式選單類似。

function listbox1_Callback(hObject, eventdata, handles)
index_selected = get(hObject,'Value');
list = get(hObject,'String');
item_selected = list{index_selected}; % Convert from cell array
% to string



群組功能(Panel, Button Group)



版面與按鈕群組功能可以將一群類似功能之元件歸納在一起,互相之間具有連動之關係。這種群組也具有名稱,可以代表整組的呼叫。這個名稱可以置於群組外圍之位置。

群組功能可利用uipanel與uibuttongroup函數產生各項組合元件。其語法如下:

ph = uipanel(fh,'PropertyName',PropertyValue,...)


其中 ph 為組合之握把,而第一項參數 fh 則為其母系之圖握把,或者其直屬之面版或按鈕組合握把。其語法如下:


bgh = uibuttongroup('PropertyName',PropertyValue,...)


其中 bgh 為按鈕群組之握把。其母系屬性則用來說明其下各元件之屬性。若兩者均不表明其母系之關係,則預設母系應為現圖。

在guide應用群組指令則更簡單,其特性屬性也較明確。其應用過程可以簡述如下:

1. 在屬性表中,先選標題'Title',將其名稱更改其他有意義的名稱。
2. 只要在週圍按鍵,該標題即刻更換。
3. 若要變換標題之置放位置,則可選擇標題位置'TitlePosition',並從其跳出窗中選擇適當位置,其預設值為左上角(lefttop)。

常用的參數如下表所示:


屬性Property
數值
Values

說明
Description

Parent
Handle
元件之母圖、母版或按鈕組合之握把。
Position
4-元素向量: [離左邊矩, 離底邊矩,寬度,高度]. 預設值=s [0, 0, 1, 1].
元件對應母系座標之位置及大小。
Title
String
元件字串名稱,不得使用 remove, defaultfactory 等相同字眼。否則前面要加倒斜線。
TitlePosition
lefttop, centertop, righttop, leftbottom, centerbottom, rightbottom. Default is lefttop.
標題在組合邊之位置。
Units
pixels, normalized, inches, centimeters, points, characters. Default is normalized.
量測位置向量之單位。




面版群組功能(Panel Group)


下面之指令可以建立面版群組之握把pgh。指向此握把,可以建立其內群組之元件。

pgh = uipanel('Parent',fh,'Title','Demo Panel',...
'Position',[.25 .1 .5 .8]);


其中fh為此組合之母系圖握把。利用這個握把亦可建立按鈕功能組合。 設此群組之標題名稱為'Demo Panel',並以lefttop為其預設位置。單位預設值為'normalized',表示其大小可以自動依圖變更。其位置則設為圖寬度之50%、圖高度之80%。離左緣25%,離底緣10%。此外,尚另加二個按鈕(即A、B按鈕),其相關位置也以相對位置表示:


pbh1 = uicontrol(pgh,'Style','pushbutton','String','A按鈕',...
'Units','normalized','Position',[.1 .55 .8 .3]);
pbh2 = uicontrol(pgh,'Style','pushbutton','String','B按鈕',...
'Units','normalized','Position',[.1 .15 .8 .3]);


按鈕群組功能(Button Group)


下面程式可建立一個按鈕群組,其名稱為'按鈕組合',握把為bgh。母系圖握把為fh,標題位置採預設值lefttop。

bgh = uibuttongroup('Parent',fh,'Title','按鈕組合',...
'Position',[.1 .2 .8 .6]);


圓鈕群組功能(Radio Group)


同樣,亦可建立圓鈕群組,其程式如下:

rbh1 = uicontrol(bgh,'Style','radiobutton','String','紅色',...
'Units','normalized','Position',[.1 .6 .3 .2]);
rbh2 = uicontrol(bgh,'Style','radiobutton','String','藍色',...
'Units','normalized','Position',[.1 .2 .3 .2]);

開始時,MATLAB會自動以第一個按鈕為預選值,若要第一個以外為預選值則可事先設定其'Value'參數值。

範例一:執行各繪圖

利用GUI介面執行程式,最簡單的方式是使用GUIDE之輔助畫面,首先執行GUIDE指令如下,然後選擇空白畫面:

>>guide



將上面圖形準備妥當,並將跳出式選單中之string參數加入下列選項:


'plot(x)', 'pie(x)', 'hist(y)', 'ezplot3(f)',...
'polar(theta,r)','area(x,y)'

由圖面先執行,設其檔案為demo3.m,並令其自動產生該檔案,如下述:
其中黃色部份為另外添者。

function varargout = demo3(varargin)
% DEMO3 M-file for demo3.fig
% DEMO3, by itself, creates a new DEMO3 or raises the existing
% singleton*.
%
% H = DEMO3 returns the handle to a new DEMO3 or the handle to
% the existing singleton*.
%
% DEMO3('CALLBACK',hObject,eventData,handles,...) calls the local
% function named CALLBACK in DEMO3.M with the given input arguments.
%
% DEMO3('Property','Value',...) creates a new DEMO3 or raises the
% existing singleton*. Starting from the left, property value pairs are
% applied to the GUI before demo3_OpeningFunction gets called. An
% unrecognized property name or invalid value makes property application
% stop. All inputs are passed to demo3_OpeningFcn via varargin.
%
% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one
% instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES

% Edit the above text to modify the response to help demo3

% Last Modified by GUIDE v2.5 04-Jan-2007 10:02:38

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name', mfilename, ...
'gui_Singleton', gui_Singleton, ...
'gui_OpeningFcn', @demo3_OpeningFcn, ...
'gui_OutputFcn', @demo3_OutputFcn, ...
'gui_LayoutFcn', [] , ...
'gui_Callback', []);
if nargin && ischar(varargin{1})
gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
[varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT


% --- Executes just before demo3 is made visible.
function demo3_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% varargin command line arguments to demo3 (see VARARGIN)

% Choose default command line output for demo3
handles.output = hObject;

% Update handles structure
guidata(hObject, handles);

% UIWAIT makes demo3 wait for user response (see UIRESUME)
% uiwait(handles.figure1);
if strcmp(get(hObject,'Visible'),'off')
plot(rand(5));
end

% --- Outputs from this function are returned to the command line.
function varargout = demo3_OutputFcn(hObject, eventdata, handles)
% varargout cell array for returning output args (see VARARGOUT);
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure
varargout{1} = handles.output;


% --- Executes on selection change in popupmenu1.
function popupmenu1_Callback(hObject, eventdata, handles)
% hObject handle to popupmenu1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)

% Hints: contents = get(hObject,'String') returns popupmenu1 contents as cell array
% contents{get(hObject,'Value')} returns selected item from popupmenu1


% --- Executes during object creation, after setting all properties.
function popupmenu1_CreateFcn(hObject, eventdata, handles)
% hObject handle to popupmenu1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles empty - handles not created until after all CreateFcns called

% Hint: popupmenu controls usually have a white background on Windows.
% See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'),
get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor','white');
end

set(hObject, 'String', {'plot(x)', 'pie(x)', 'hist(y)', 'ezplot3(f)',...
'polar(theta,r)','area(x,y)'});

% --- Executes on button press in pushbutton1.
function pushbutton1_Callback(hObject, eventdata, handles)
% hObject handle to pushbutton1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
axes(handles.axes1);
cla;

popup_sel_index = get(handles.popupmenu1, 'Value');
switch popup_sel_index
case 1
plot(rand(5));
case 2
n=round(10*rand)+10;
pie(2:4:n);
case 3
hist(randn(1000,1));
case 4
ezplot3('t*sin(t)', 'cos(t)', 't', [0,6*pi])
case 5
theta = linspace(0, 2*pi);
polar(theta, 3+2*rand*cos(4*theta));
case 6
y=[1 1.2 1.5 2*rand;4 4.5 6.6 7*rand;5 6.5 8 15*rand]';
area([1980 1990 2000 2008],y);
grid on;colormap cool;

end


% --------------------------------------------------------------------
function OpenMenuItem_Callback(hObject, eventdata, handles)
% hObject handle to OpenMenuItem (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
file = uigetfile('*.fig');
if ~isequal(file, 0)
open(file);
end

% --------------------------------------------------------------------
function PrintMenuItem_Callback(hObject, eventdata, handles)
% hObject handle to PrintMenuItem (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
printdlg(handles.figure1)


% --------------------------------------------------------------------
function CloseMenuItem_Callback(hObject, eventdata, handles)
% hObject handle to CloseMenuItem (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
selection = questdlg(['Close ' get(handles.figure1,'Name') '?'],...
['Close ' get(handles.figure1,'Name') '...'],...
'Yes','No','Yes');
if strcmp(selection,'No')
return;
end

delete(handles.figure1)

% --------------------------------------------------------------------
function FileMenu_Callback(hObject, eventdata, handles)
% hObject handle to FileMenu (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)



不用Guide輔助之程式寫法



function simple_guix
% SIMPLE_GUI Select a data set from the pop-up menu, then
% click one of the plot-type push buttons. Clicking the button
% plots the selected data in the axes.

% Create and then hide the GUI as it is being constructed.
f = figure('Visible','off','Position',[360,500,450,285]);

% Construct the components.
h1 = uicontrol('Style','pushbutton','String','開始',...
'Position',[315,220,70,25],...
'Callback',{@startbutton_Callback});
htext = uicontrol('Style','text','String','請選擇繪圖指令:',...
'Position',[325,90,60,15]);
hpopup = uicontrol('Style','popupmenu',...
'String',{'plot(x)', 'pie(x)', 'hist(y)', 'ezplot3(f)',...
'polar(theta,r)','area(x,y)'},...
'Position',[300,50,100,25],...
'Callback',{@popup_menu_Callback});
ha = axes('Units','Pixels','Position',[50,60,200,185]);

% Initialize the GUI.
% Change units to normalized so components resize
% automatically.
set([f,ha,h1,htext,hpopup],'Units','normalized');
%Create a first plot in the axes.
plot(rand(5));
currentfig='plot(x)';
% Assign the GUI a name to appear in the window title.
set(f,'Name','Simple GUI')
% Move the GUI to the center of the screen.
movegui(f,'center')
% Make the GUI visible.
set(f,'Visible','on');

% Callbacks for simple_gui.
% the current data.
function popup_menu_Callback(source,eventdata)
% Determine the selected data set.
str = get(source, 'String');
val = get(source,'Value');
currentfig=cell2mat(str(val));
end

% Push button callbacks. Each callback plots current_data in
% the specified plot type.

function startbutton_Callback(source,eventdata)
% Display surf plot of the currently selected data.
% Set current data to the selected data set.
switch currentfig;
case 'plot(x)' % User selects plot.
plot(rand(5));
case 'pie(x)' % User selects pie.
n=round(10*rand)+10;
pie(2:4:n);
case 'hist(y)' % User selects hist
hist(randn(1000,1));
case 'ezplot3(f)' % User selects ezplot
ezplot3('t*sin(t)', 'cos(t)', 't', [0,6*pi])
case 'polar(theta,r)' % User selects polar
theta = linspace(0, 2*pi);
polar(theta, 3+2*rand*cos(4*theta));
case 'area(x,y)' % User selects area
y=[1 1.2 1.5 2*rand;4 4.5 6.6 7*rand;5 6.5 8 15*rand]';
area([1980 1990 2000 2008],y);
grid on;colormap cool;
end

end


end

如何在同一畫面中指定繪製兩圖

圖軸axes之應用


使用guide進行介面製作時,比較常見的困擾是當同一個畫面使用兩個以上圖時,常常繪製之圖內容會一直繪製在同一圖框上。為避免這種情形發生,可以應用handles這個結構矩陣。這是在guide中專屬的資料參數。

話說在同一畫面中,若有許多圖軸時,通常它會以axes1,axes2,...表示。故當對應某特定按鈕之回叫函數呼叫時,其內容需加上此類似指令,以呼叫第一圖軸(axes1)為例:

axes(handles.axes1)
plot(...)


若不在第一行設定圖軸,在執行時亦可用滑鼠指向要顯示的圖軸,然後再按指定之按鈕即可。這種方式的應用可依實際需求而定。

第一行亦可寫成如下之形式,但其表現之外觀則有不同,若想做子母圖之讀者不妨自行一試:

axes('Tag','axes1')


雙圖應用例


下面為matlab介紹的一個雙圖之例子。先利用guide製造圖形介面,第一個圖軸之標籤為frequency_axes,第二圖軸為 time_axes。介面之右邊有三個edit輸入口,分別為f1,f2及t值,最後一個按鈕為plot。在此edit輸入口並沒有回叫函數,故僅能就現有之值取出計算。其初值通常在屬性表中設定。



(圖摘自mathworks.com)

其對應之程式在.m檔中,大致上與其他檔案相同。比較大差異在於plot之回叫函數部份。其程式內容如下:


function varargout = plot_button_Callback(h, eventdata, handles, varargin)
% hObject handle to plot_button (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)

% Get user input from GUI
f1 = str2double(get(handles.f1_input,'String'));
f2 = str2double(get(handles.f2_input,'String'));
t = eval(get(handles.t_input,'String'));

% Calculate data
x = sin(2*pi*f1*t) + sin(2*pi*f2*t);
y = fft(x,512);
m = y.*conj(y)/512;
f = 1000*(0:256)/512;;

% Create frequency plot
axes(handles.frequency_axes)
plot(f,m(1:257))
set(handles.frequency_axes,'XMinorTick','on')
grid on

% Create time plot
axes(handles.time_axes)
plot(t,x)
set(handles.time_axes,'XMinorTick','on')
grid on


在plot_bottom_Callback回叫函數中,開頭三行為處理輸入之值,前兩項屬f1及f2值之轉換,後一項則為t值之範圍,由於是一個字串輸入,故必須使用eval函數就其內容執行後置於t變數之中。

其後將計算之頻率資料繪製於frequency_axes之圖軸上,並將對應於時間t之x資料繪於time_axes軸上。

Guidata 函數之應用

guidata是介面程式特有函數指令,可供暫存介面資料的地方,因此在函數與函數之間,可以很方便地作為一個中介。當你從一個函數程式中存入後,可以到另一個函數程式中取出,繼續使用。它與其他輸入函數之功能略同,但其最大好處是不必經由函數之輸入參數位置進出。對於某些圖面所含介面參數甚多的情況,此函數特別有用。其語法如下:


guidata(h,data)
data = guidata(h)


此時h為某特定物件之握把。所以第一行指令之型式是將資料data存入與h握把指向之圖中,若h是某物件之握把時,則其指向的是包含該物件之圖。

此處之data可為MATLAB之變數,但通常均採用結構變數,因此可以逐層往下堆疊,隨時依需要增加欄位。

GUIDATA指令一次僅能處理一個變數,故連續呼叫guidata(h,data),只會將新值墊去舊值。在利用guide產生之檔案中,其函數間若使用guidata傳遞GUI資料時則僅能使用handles這個變數結構,若需要儲存與handles不同的變數,唯一的辦法是在其下建立一個新欄位,如handles.demo是。

另一指令data=guidata(h)則是自先前存放有關h握把之GUI資料取出。若前面沒有儲存則取出的為空矩陣。

若要更改先前存入之資料,則可先用data=guidata(h)取出,將data之內容更改之後,再用guidata(h,data)存入。

舉例:有一圖介面之握把設為f,此介面含有滑件、文字輸入等,其對應之uicontrol中之標籤(或變數)分別為'valueSlider'、'valueEdit'。下面舉的例子為在應用M檔案使用guiddata指令取得一個名為mygui.fig圖介面之結構資料,此資料可用guihandles指令取得,再加上程式需要之額外資料後,在回叫函數及初始化函數間傳遞之情形:

...
f = openfig('mygui.fig');
data = guihandles(f); % initialize it to contain handles

data.errorString = 'Total number of mistakes: ';
data.numberOfErrors = 0;

guidata(f, data); % store the structure

上述黃色部份為原資料data取出之後,再加入errorString、numberOfErrors等兩欄變數值,然後又置回guidata存放。

另一方面在回叫函數中,則加入下列指令:

data = guidata(gcbo); % need handles, may need error info
val = str2double(get(data.valueEdit,'String'));
if isnumeric(val) & length(val)==1 & ...
val >= get(data.valueSlider, 'Min') & ...
val <= get(data.valueSlider, 'Max')
set(data.valueSlider, 'Value', val);
else
% increment the error count, and display it
data.numberOfErrors = data.numberOfErrors + 1;
set(handles.valueEdit, 'String',...
[ data.errorString, num2str(data.numberOfErrors) ]);
guidata(gcbo, data); % store the changes...
end

上面在回叫函數中,若輸入值超出範圍或輸入不是數值時,會顯示錯誤,而且記錄錯誤之次數。完畢後再回傳guiddata。式中之gcbo(Get Current CallBack Object)則是取得現行之回叫物件握把的意思,以確認存取的位置正確。

12/22/2006

第十二章 數值法與微積分

12.1 前言


函數之微分為求函數對自變數之導數,或為其斜率;利用數值方法則可以解出其他相關之問題,其應用部份已在前章討論。數值微分有兩種應用,其一是在資料收集完備後,分析其變化速度;其二為即時估計或量測速率。後者需要快速演算法才能有立即反應。

計算斜率,依其定義即為dy/dx,在數值分析上必須轉化為可量測之變化量,亦即lim(Δy/Δx)。量取Δy或Δx則必須借助前後之數值點,例如Δx=x2-x1=x3-x2等關係。但這樣總是會因Δx值之大小而有差異。此時之Δx值也不可能如理論之無限小值。在實際之計算上,取Δx之前、後或中就有向前差分、向後差分或中心差分之區別。

12.2 差分函數diff

12.2 差分函數diff


對於一個向量,差分函數在於求取該數列元素間之差異及其導數,相關指令格式如下:

Y = diff(X)
Y = diff(X,n)
Y = diff(X,n,dim)

若X為向量,則上述指令在於計算其相鄰元素間之差,亦即:

[X(2)-X(1) X(3)-X(2) ... X(n)-X(n-1)]

若X為矩陣,則結果將計算其相鄰列間之差,如:

[X(2:n,:) - X(1:n-1,:)].

參數n為階數,n=1時,其結果與Y = diff(X)相同,n=2時,其結果為diff(diff(X)),依此類推。因此每相差1其項數將減少一項。參數dim則是控制行向或列向差分,dim=1行向(預設值),dim=2為列向。

例:


>> x = 2:2:10
x =
2 4 6 8 10

>> y=diff(x)
y =

2 2 2 2

>> y=diff(x,2)
y =

0 0 0


若間矩較細,將過差分後,其結果應為原函數之導數。


h = .01;
x = 0:h:pi;
d=diff(sin(x.^2))/h;
plot(x,sin(x.^2),x,[0 d],'r:')


上例等於計算sin(x.^2)之導數 2*cos(x.^2).*x,注意d值之長度會比x少1。

例:

>>X=(1:10).^2,

X =
1 4 9 16 25 36 49 64 81 100 

 >>x1=diff(X),x2=diff(X,2), x3=diff(y)

x1 =
3 5 7 9 11 13 15 17 19
x2 =
2 2 2 2 2 2 2 2
x3 =
2 2 2 2 2 2 2 2

結果為x1比X少一項,x2比x1又少一項,x2與x3相同,證明y3=diff(diff(X))。
例:

X = [3 7 5 3; 2 0 8 4]
x1=diff(X,1), x2= diff(X,1,2)

X =
3 7 5 3
2 0 8 4
x1 =
-1 -7 3 1
x2 =
4 -2 -2
-2 8 -4

例:正弦函數sin(t)以常態分布繪出[0 pi]間之區線及其第二導數cos(t)。

t=[0:pi/50:pi];nn=length(t);td=cos(t);
y=sin(t)+0.05*(randn(1,nn)-0.5);
dt1=diff(y)./diff(t);%backward(+)
dt2=(y(3:nn)-y(1:nn-2))./(t(3:nn)-t(1:nn-2));
plot(t,sin(t));hold on;plot(t,y,'b.');
plot(t,cos(t),'r:');
plot(t(1:nn-1),dt1,'k+');
plot(t(1:nn-2),dt2,'bo');



由其結果比較,中央差分比向後差分之誤差為小。可由其與實際值之相關係數可以確定。就向後差分者,相關係數為0.4927;而中央差分則為0.7674。

[r1,p1]=corrcoef(t(1:nn-1),dt1)
[r2,p2]=corrcoef(t(1:nn-2),dt2)

r1 =
1.0000 -0.4927
-0.4927 1.0000
p1 =
1.0000 0.0003
0.0003 1.0000
r2 =
1.0000 -0.7674
-0.7674 1.0000
p2 =
1.0000 0.0000
0.0000 1.0000


由於diff之前減特質,所以其功能亦可用來決定某些向量之特性,例如屬增加序列或減少序列,或所謂之單向(monotonic)系列,或屬等距分佈之系列等。下面的例子可作為一般之應用:
  • diff(x)==0 測試重複性之元素
  • all(diff(x)>0) 測試其單向性
  • all(diff(diff(x))==0) 測試各元素是否等距分佈

12.3 數值梯度gradient

12.3數值梯度gradient

一具有二自變數之函數F(x,y,z),其梯度之定義為:

▽F = (dF/dx)i +(dF/dy)j +(dF/dz)k

其計算之指令格式如下:

FX = gradient(F)
[FX,FY] = gradient(F)
[Fx,Fy,Fz,...] = gradient(F)
[...] = gradient(F,h)
[...] = gradient(F,h1,h2,...)

其中F為函數向量,切分後之間隔預設值為1,亦即h=1;若函數之變數增多,則h之值可因變數之不同另外設值。例如,h1可能屬於第一變數之區間;h2為第二變數之區間,而左邊之輸出值則依x,y,z之分量。


例一:



v=-2:0.2:2;
[x,y]=meshgrid(v);
z=x.*exp(-x.^2-y.^2);
[px,py]=gradient(z,.2,.2);
contour(z),hold on, quiver(px,py), hold off




例二:


設有一向量F由魔術矩陣及巴斯上矩陣組成,其內容為:

>> F(:,:,1) = magic(3); F(:,:,2) = pascal(3);
>> F

F(:,:,1) =

8 1 6
3 5 7
4 9 2


F(:,:,2) =

1 1 1
1 2 3
1 3 6

取其梯度值,即▽F,設dx=1,dy=1,dz=1。則


>> gradient(F)

ans(:,:,1) =

-7 -1 5
2 2 2
5 -1 -7


ans(:,:,2) =

0 0 0
1.0000 1.0000 1.0000
2.0000 2.5000 3.0000


若間矩不同,即設dx=0.1,dy=0.1,dz=0.1。則▽F為


>> [PX,PY,PZ] = gradient(F,0.1,0.2,0.3)

PX(:,:,1) =

-70.0000 -10.0000 50.0000
20.0000 20.0000 20.0000
50.0000 -10.0000 -70.0000


PX(:,:,2) =

0 0 0
10.0000 10.0000 10.0000
20.0000 25.0000 30.0000


PY(:,:,1) =

-25.0000 20.0000 5.0000
-10.0000 20.0000 -10.0000
5.0000 20.0000 -25.0000


PY(:,:,2) =

0 5.0000 10.0000
0 5.0000 12.5000
0 5.0000 15.0000


PZ(:,:,1) =

-23.3333 0 -16.6667
-6.6667 -10.0000 -13.3333
-10.0000 -20.0000 13.3333


PZ(:,:,2) =

-23.3333 0 -16.6667
-6.6667 -10.0000 -13.3333
-10.0000 -20.0000 13.3333

12.4 Del2--Laplacian

Del2指令--非連續Laplacian


若U矩陣為一個函數u(x,y),其計算值係以該點之方格網絡為基準,則4*del2(U)是拉普拉斯運算函數U之估計值,是以固定差分法估算的方式。亦即令:

L=▽²u/4 = (1/4)[d²u/dx² +d²u/dy²]

其中,

L(ij)= (1/4)[ui+1,j + ui-1,j +ui,j+ui,i+1+ui,j-1-ui-1,j-1

上述L矩陣與U同大小,其值為四個相鄰點平均值。上式若為三度空間,則除數4要改為6。計算上述函數之指令格式如下:

 L = del2(U)
 L = del2(U,h)
 L = del2(U,hx,hy)
 L = del2(U,hx,hy,hz,...)

其中U矩陣之每點之間隔為1,亦即h=1,若h之值不同於1,則另設其值。若U為兩維函數,則其在X與Y方向之間隔可用hx與hy另定,甚至三度空間也可加hz定義。


例:設U=x²+3y²,求其Laplacian向量


[x, y]=meshgrid(-4:4,-3:3);
U=x.*x+2*y.^2
V=4*del2(U)

U =
34 27 22 19 18 19 22 27 34
24 17 12 9 8 9 12 17 24
18 11 6 3 2 3 6 11 18
16 9 4 1 0 1 4 9 16
18 11 6 3 2 3 6 11 18
24 17 12 9 8 9 12 17 24
34 27 22 19 18 19 22 27 34
V =
6 6 6 6 6 6 6 6 6
6 6 6 6 6 6 6 6 6
6 6 6 6 6 6 6 6 6
6 6 6 6 6 6 6 6 6
6 6 6 6 6 6 6 6 6
6 6 6 6 6 6 6 6 6
6 6 6 6 6 6 6 6 6


12.5 多項式微分

多項式微分


多項式函數及多項數商等之導數可用polyder指令計算。其指令型式如下:

k = polyder(p)
k = polyder(a,b)
[q,d] = polyder(b,a)

polyder指令之輸入參數中,p,a,b等均為多項式係數之向量,以降幂排列。若僅有一個參數p,則k值為其導數。若是a,b兩輸入參數項,則表示為求a與b兩多項式之積之導數。若左邊有二個輸出參數(即[q,d])時,其計算方式是求多項數相除,即b/a之結果之導數,結果之分子置於q;分母置於d。

例如:有兩個多項式a=3x²+6x+9及b=2x+1,則其相乘積之導數可用下面之方式求得:

a=[3 6 9];b=[2 1];k=polyder(a,b)
k =
18 30 24

其導數之多項式為:18x²+30x+24

[k2,d]=polyder(a,b)

k2 =
6 6 -12
d =
4 4 1

此項之導數之多項式為: [6x²+6x-12]/ [4x²+4x+1]

12.6 一階微分方程之解法

12.6一階微分方程之解法


連續性函數之微分可以利用多種指令為之,而基本之細段微分法則是最早使用的概念,亦可利用MATLAB運算指令直接求解。

尤拉法(Euler method)


尤拉法為數值積分法最基本之一種。它是利用時間細段微分,利用初值及一階微分方程進行求解。以下列方程式為例:

y'=dy/dt = f(t,y)


式中t為時間,y為因變數。y', dy/dt 均為其一階微分的表示式。m為t之函數。根據微分之定義,可以改寫如下:

y'=dy/dt = limΔ-0 [y(t+Δt)-y(t)]/dt = f(t, y)

展開,可以求得經過Δt之時間後,y(t+Δt)之結果為:

y(t+Δt)=y(t)+Δtf(t,y)

若設Δt為一均勻之區段時間,或稱為步進值,則可以給予y(t)一個初值,然後利用增加步進值之個數逐步累積至最終的區間,最後得到y之答案值。其求解之型式如下:

y(tk+1) = y(tk) + Δtf(tk,y(tk))

故只要給定時間範圍及f(t0,y0)等初值,即可利用迴圈的方式得到最終答案。其中,Δt設定值之大小則會影響答案之準確度,Δt值愈大,準確度會差,Δt值愈小則運算時間加長。

例:設f(t,y)=-10y,初值t=0, y0=2。且y'=-10y 之真實解為y(t)=2e-10t。設Δt=0.02,以此利用MATLAB求解。

function y=euler(y0,time,delta)
% Using Euler method to solve differential eqs.
% Inputs:
% y0:initial value
% time:time limit
% delta:step size
% Example: y=euler(2,0.5,0.02)
r=-10;k=0;
t=0:delta:time;y=zeros(size(t));
y(1)=y0;
for i=2:length(t),y(i)=y(i-1)+r*y(i-1)*delta;end
y_true=2*exp(-10*t);
plot(t,y,'o',t,y_true),xlabel('t'),ylabel('y');

執行結果:



>> y=euler(2,0.5,0.02);




同樣的問題亦可使用ode45指令計算,則先設定其函數為fun:

fun=@(t,y)(-10*y);
[t,y]=ode45(fun,[0 0.5],2);
plot(t,y,'ro',t,2*exp(-10*t)),xlabel('t'),ylabel('y');



利用MATLAB之ODE45之指令所得之結果比前面所用的方法準確度高。以下我們將介紹數種微分方程之解法及相關指令。

12.7初值型微分方程

12.7初值型微分方程


常微分方程式(Ordinary differential equations, ODE)為一般工數中求解的問題,包含有自變數與應變數,可以應用在多工程多方面動態之解析。若自變數增多,則成偏微分方程式,簡稱PDE(Partial differential equation)。目前MATLAB提供之微分方程解法器有數種,其性能及應用範圍略有不同。其中較常用的有ode23及ode45,這兩個均是利用Runge-Kutta法進行求解。其餘如ode113、ode15s、ode15i、ode23s、ode23t、ode23tb等等,其功能與範圍在運用上均有些差異,有些是根據不同方法得到的求解過程,常應用在控制方面。

常微分初值型解法器之相關函數均是利用數值積分方法作為求解過程。最初由設定之起始時間與條件,依設定之時間驅間逐步計算其解。若結果能滿足解法器設定之容許標準,就算成功;若無法達到,則必須再降低區間重新嚐試。常微分解法器之輸出入格式如下:

 [t,Y] = solver(odefun,tspan,y0)
 [t,Y] = solver(odefun,tspan,y0,options)
 [t,Y,TE,YE,IE] = solver(odefun,tspan,y0,options)
 sol = solver(odefun,[t0 tf],y0...)

上述指令中之solver,所指的就是ode45, ode23, ode113, ode15s, ode23s, ode23t, or ode23tb等等。其他相關參數介紹如下:

odefun:


用以計算微分方右邊之函數。所有解法器處理之系統方程式均是y'=dydt=f(t,y)之型式,有些是包括質量矩之問題,如M(t,y)y'=f(t,y)之函數。這些都與時間t有關,t為常數,而dydt及y均為行向量。ode45s解法器僅能處理質量為常數之問題;而ode15s與ode23t則能處理具奇數型質量矩陣之方程式,或稱為Differential-algebraic equations(DAEs)。

tspan 積分區間之向量[t0, tf]。解法器設定初值在tspan(1),然後由tspan(1)積分至tspan(end)。為得到特定時間(升冪或降冪)之對應解,可以使用tspan=[t0,t1, . . .,tf]。若tspan=[t0, tf],則會回應每個積分之步驟值。若tspan多於二個元素,則會回應時間對應值。時間必須依順序安排,或升冪或降冪。由於內部運算之安排,使用tspan向量之大小並不影響運算時間,但會佔據較多的記憶體。

y0:初值向量。

options:改變積分特性之相關參數。這些參數也可用odeset函數取得或設定。

輸出值包括t,Y,前者為時間點之行向量;後者為解答陣列,每一行之解y均與時間t相對應。每組(t,Y)之產生稱為事件函數。每次均會檢查是否函數等於零。並決定是否在零時終止運算。這可以在函數中之特性上設定。例如以events 或@events產生一函數。

[value, isterminal,direction]=events(t,y)

其中,value(i)為函數之值,isterminal(i)=1時運算在等於零時停止,=0時繼續;direction(i)=0時所有零時均需計算(預設值), +1在事件函數增加時等於零, -1在事件函數減少時等於零等狀況。

此外,TE, YE, IE則分別為事件發生之時間,事件發生時之答案及事件函數消失時之指標i。

有關初值型常微分方程之指令及其功能說明如下:


指令名稱 、說明及算法:
  • ode45 非剛性中階解法器 Runge-Kutta
  • ode23 非剛性低階解法器 Runge-Kutta
  • ode113 非剛性變階解法器 Adams
  • ode15s 剛性變階解法器及 DAE NDFs (BDFs)
  • ode23s 剛性低階解法器及 DAE Rosenbrock
  • ode23t 中剛性梯形法則解法獸器及DAE Trapezoidal rule
  • ode23tb 剛性低階解法器 TR-BDF2
茲就這些指令名稱說明如下:

ode45


此指令以 Runge-Kutta (4,5) 演算法為基礎,運算上是屬單步驟計算y(tn),故僅需時間點前之解y(tn-1)。 ode45函數指令可作為第一階段評估的數據,然後再思考是否採取下一步驟。

ode23


與ode45函數同以 Runge-Kutta (2,3)方程式為基礎,並以Bogacki 與Shampine配對。在溫和剛性的情況,容許度較粗略時可能比ode45 更有效率。此指令也是單步驟解題。

ode15s


以數值微分方程(NDFs).為基礎之可變順序指令。可選擇後向微分方程BDFs, (或稱為Gear 法)。ode15s 是一個多步驟指令。若執行前認為問題特性屬於剛性,亦或使用過ode45 指令但無法達成目的時,可以試用f ode15s指令。

ode23s


以Rosenbrock修正方程式二階為基礎。. 由於它是單步驟解題指令,故在粗略容許範圍下可能比ode15s 更有效率。它可能解一些ode15s效率低或無法解的剛性問題。

ode23t


採用自由間極的梯形法。在問題屬於中等剛性或不需涵括數值阻尼的特性時可以使用此指令解題。

ode23tb


執行 TR-BDF2—初期利用梯形法涵蓋Runge-Kutta 方程式而第二階段採用後向微分法二階。 如同ode23s一樣,在粗略容許度下此法比ode15s有效率。

ODE 處理


積分指令之特性、參數可以藉ODE函數之處理而改變指令之內涵,並因而可以影響解題的結果:

函數名稱及說明
  • odeset 設定或改變ODE指令之輸入選項
  • odeget 取得odeset產生的選項特性

ODE 指令之輸出函數:


若輸出函數己經設定,則解題指令會在完成積分步驟後,呼叫這些特定函數。此時你可以使用odeset指令指定其中之一個樣本函數,作為OutputFcn 的特性,或你也可以加以修正,使其成為自已擁有的函數。

  函數名稱及說明
  • odeplot   時序圖
  • odephas2  二維平面圖
  • odephas3  三維相面圖
  • odeprint  印出至指令窗

例:設一微分方程式為y'=sin(t),其初值y(0)=0,區間為 ,設其步進為週期(2π)之1/13,或者Δt=2π/13。其實際積分值為1-cos(t)。以尤拉法寫出之程式如下:

function y=euler2(y0,time,n)
% Using Euler method to solve differential eqs.
% y'=A*sin(t)
% Inputs:
% y0:initial value
% time:time limit,in the form of [t1 t2]*pi
% n:in the form of [1/n]*t2*pi, for step size
% Example: y=euler2(0,[0 2],13)
k=0;delta=time(2)/n;tt=time*pi;
t=tt(1):delta:tt(2);y=zeros(size(t));
y(1)=y0;
for i=2:length(t),
y(i)=y(i-1)+sin(t(i-1))*delta;
end
y_true=1-cos(t);
plot(t,y,'o',t,y_true),xlabel('t'),ylabel('y');


執行結果:

>>y=euler2(0,[0 4],13);



若改用ode45指令,其程式如下:

fun=@(t,y) sin(t);
[t,y]=ode45(fun,[0 4*pi],0);
plot(t,y,'ro',t,1-cos(t)),xlabel('t'),ylabel('y');




顯然利用ode45解法器比利用迴圈法為佳,當然迴圈法之Δt設定越小值,其準確度會趨近於後者之狀況。在上式之fun函數數,注意即使沒有y變數參與其中,也要將其列入,否則無法執行。
例:試解下面之微分方程式


 10y' + y = te-2t y(0)=2; 0≦t≦2


並以其結果 y(t)=[732e-0.1t-19e-2t-10e-2t]/361比較之。
解:

fun=@(t,y) 0.1*(t*exp(-2*t)-y);
[t,y]=ode23(fun,[0 2],2);
y2=(732*exp(-0.1*t)-19*t.*exp(-2*t)-10*exp(-2*t))/361;
plot(t,y,'ro',t,y2),xlabel('t'),ylabel('y');



電路分析


例:一電路由一電阻器R與一電容器C串聯接於一電流v上,試求通過電容器兩端之端電壓y。
解:根據克希荷夫電壓定律總電壓為電流端電壓之和,故可建立如下之微分式:

RC[dy/dt]+y=v(t)
y(0)=2V RC=0.1s


由於開始時並無電源,故v=0。設時間常數RC=0.1s,經整理,其結果如次:

dy/dt =[v(t)-y]/0.1 = -10y


執行時,使用ode23解法器,一樣可獲得正確的結果。

fun=@(t,y) -10*y;
[t,y]=ode23(fun,[0, 0.4],2);
plot(t,y,'ro',t,2*exp(-10*t)),xlabel('t'),ylabel('y');



就所用點數而言,ode23解法器所用的點數較少。

由以上之範例可知,線性微分方程式均可使用數值法進行解析,雖然也有通用解可用,但大部份仍以數值法較為簡便。但若階數大於二時,使用通式會相當複雜,只有數值法可以解決,而且得到的結果可以立即用圖去表示,因而可以作比較。

若上例中之外在電壓有值,其型式為:

v(t) = 10et/Isin(2πt/p)

同樣,設 RC=0.1s,則前例仍可維持如下之型式:

dy/dt = 10[v(t)-y]


解法:

function y=euler3(y0,tf,tau,P)
% Using Euler method to solve differential eqs.
% RC(dy/dt)+y=v(t)
% Inputs:
% y0:initial value
% tf:time interval[t1 t2]
% tau, P:constants
% Example: y=euler3(0,[0 2],0.3,2)
vv=@(t) 10*exp(-t/tau).*sin(2*pi*t/P);
fun=@(t,y) 10*(vv(t)-y);
[t,y]=ode23(fun,tf,y0);
subplot(2,1,1)
plot(t,vv(t),'k-');xlabel('t'),ylabel('y');
subplot(2,1,2)
plot(t,y,'r-',t,y,'.');xlabel('t'),ylabel('y');

執行結果:

>>y=euler3(0,[0 2],0.3,2); %tau=0.3s, P=2s




>>y=euler3(0,[0 0.2],0.05,0.03); %tau=0.05s, P=0.03s



>>y=euler3(0,[0 0.8],0.3,0.03); %tau=0.3s, P=0.03s

12.8 非線性方程式解

12.8非線性方程式解


當常微分方程屬非線性時,就很難得到解析解,必須使用數值分析解。但這也常會發生一些垃圾答案的問題,必須就常識判斷其解之正當性。

例:有一大漏斗倉裝滿水,其外觀呈倒角錐形,半徑R高度為H,其體積為:

 V=πR²H/3

倉體之上方,有一進水口,其流入速率為Qin;其底部則另有孔口流出。截面積為A,其流量Q與倉中之水位高度h有下列關係:

Q = Cd A (2gh)½

方程式中,Cd為孔口係數,其值為0.6。根據流體質量不滅的定理,倉中體積的變化應等於流體進出之流量,即:

  dV/dt = Qin-Q

而由於r = (Rh/H),故:

 dV/dt=d/dt[πr²dh/3]=πR²h²/(3H²]dh/dt , 故

 [πR²h²/(3H²]dh/dt=Qin-Cd*A*(2gh)½  整理之,

dh/dt=[Qin-Cd*A*(2gh)½]/[πR²h²/(3H²]

整理上面之公式寫成程式,並加以執行。


function [h,t]=tri_tank(tf,h0,R,H,cd,A,Qin)
% Using Euler method to solve differential eqs.
% Water tank
% Inputs:
% h0:initial water level
% tf:time interval[t1 t2]
% R,H:radius & height of tank, m
% cd:coefficient of orifice
% A: Crosssection of orifice, m^2
% Qin: Inlet flow, cms
% Example: h=tri_tank([0 10],9,10,9,0.6,1,10)
aa=3*(H/R)^2/pi;g=9.8;
vv=@(t,h) aa*(Qin-cd*A*sqrt(2*g*h));
[t,h]=ode23(vv,tf,h0);
plot(t,h,'r-',t,h,'.');xlabel('t'),ylabel('y');

執行結果:

>>hold on; for i=[5 7 10 15],h=tri_tank([0 10],9,10,9,0.6,1,i);end


圖中所示,由上而下分別為Qin=15, 10, 7, 5 cms。前兩者之水位高度會累積比初設值高,後二者因進水量較低,故會消耗一部份水量,最後在4米高時達到平衡。

高階微分方程式


解二階以上之微分方程式必須採用特定的方法。例如考慮二階微方如下:

  5y"+7y'+4y = f(t)

先將其改寫為如下之型式以配合原來之一階微方:

  y"=(1/5)f(t)-(4/5)y-(7/5)y'


此時因為右邊仍有一階微分項,為令其符合一階之格式,可以定義另外新變數x1及x2:

x1=y
x2=y'


根據此兩定義項,可以再進行微分一次,得到下面之等式:


x1'=y'
x2'=y"


將此代入上項微方,得聯立方程式:


x1'=x2
x2'=y"=(1/5)f(t)-(4/5)x1-(7/5)x2


此型式稱為柯西式(Cauchy form) 。設 f(t) = sin(t),其區間為0<t<6 ,並設起始條件y(0)=3,y'(0)=9,輸入初值因此為[3, 9]。則可以利用ode23解法器求得相關值。由於變數x1及x2可以置於同一矩陣中,其函數因此可以呼叫如下:

fun=@(t,x) [x(2);(1/5)*sin(t)-4*x(1)-7*x(2)];
[t,x]=ode23(fun,[0 6],[3 9]);
plot(t,x(:,1),'r-',t,x(:,2),'bo',t,x(:,2))



12.7 實例-剛性與非剛性問題

(編譯及圖示摘自mathworks.com)

簡單非剛性問題

MATLAB提供一個名為rigidode之程式是處理一剛性體體在無外在受力之情況下,所產生之速度與位移變化。這是一個由柯洛氏(Krogh)針對非剛性解法器提出的標準測試方法。其解析之答案屬於 Jacobian楕圓函數,可以由MATLAB中得到。其所用的區間約為1.5週期。其求解之聯立方程式如下:



為解此聯立微分方程,必須先建立包含這些方程式之函數,設為rigid,其中y為代表聯立方程式右邊之函數關係,為含有三元素之行向量,其內容如下:

function dydt = rigid(t,y)
dydt =[y(2) * y(3);-y(1) * y(3);-0.51 * y(1) * y(2)];


上述函數亦可利用隱函數表示,如下:

rigid=@(t,y) [y(2) * y(3);-y(1) * y(3);-0.51 * y(1) * y(2)];


解法器可用ode45,其呼叫格式為:ode45(rigid,,),其中y0為初值,即為[0 1 1];tspan經歷時間,此處設為[0 12]。為方便起見,整個問題之定義及解題以M-檔存放。其微分方程則用次函數f表示。由於程式呼叫ode45 指令函數,沒有輸出項,故其輸出屬內定輸出函數odeplot,利用該函數直接將結果繪圖。


function rigidodex(tspan,y0)
% Solution of Euler's rigid body Equation
% revised from rigidode.m
% tspan: Interval, [t1, t2]
% y0: Initial value
%Example: rigidodex([0 12],[0 1 1])
y0 =y0(:);
rigid=@(t,y) [y(2)*y(3);-y(1)*y(3);-0.51*y(1)*y(2)];
options = odeset('RelTol',1e-4,'AbsTol',[1e-4 1e-4 1e-5]);
[T,Y]=ode45(rigid,tspan,y0); % solve the problem using ODE45
plot(T,Y(:,1),'-',T,Y(:,2),'-.',T,Y(:,3),'.')


執行上述程式之結果如下圖:



剛性問題


凡德波(van der Pol)方程式屬一般剛性問題,其剛性用µ表示(本例之預設值設為µ=1000)。其微分聯立方程式如下:



在第二式中,當值增加,問題的本質會更顯出其剛性,此時擺動週期也隨之增大。當其值為1000時,擺動進入遲滯狀態,其剛性更強。在其限制的週期內有一部份解變化趨緩,然後反向迅速變化,呈現交替狀態。初期值則接近於變化緩慢之區域,以測試步距之大小。

為解上述聯立方程式,可先建立對應之函數vdp1000:

function dy = vdp1000(t,y)
dy = zeros(2,1); % a column vector
dy(1) = y(2);
dy(2) = 1000*(1 - y(1)^2)*y(2) - y(1);


對於剛性問題所用之ODE解法器係以採用近似Jacobian矩陣為主。為此通常可用odeset('Jacobian',@J)進行設定。其中J為次函數J(t,y,mu),屬於上項係數之偏微分矩陣,可求得點(t,y)附近具µ值之Jacobian 矩陣df/dx。

本例中,初值為[2 0],時間區間為[0 3000],使用ode15s解法器求解。

[T,Y] = ode15s(@vdp1000,[0 3000],[2 0]);

完整的程式如下:

function vdpodex(MU,y0)
% Solution of van der Pol Equation
% Example: vdpodex(500,[2 0])
if nargin==0,MU=1000;y0=[2 0];end
if nargin==1,;y0=[2 0];end
y0 =y0(:);tspan=[0; max(20,3*MU)];
J=@(t,y,mu) [0 1;-2*mu*y(1)*y(2)-1 mu*(1-y(1)^2) ];
f=@(t,y,mu) [y(2) ; mu*(1-y(1)^2)*y(2)-y(1) ];
options = odeset('Jacobian',J);
figure;
[t,y]=ode15s(f,tspan,y0,options,MU);
plot(t,y(:,1),'-o');


執行結果如下圖:



µ=1,並將繪圖指令改為:

plot(t,y(:,1),'-',t,y(:,2),'-.')

則上述程式執行後情況略有不同,其結果如下:

>>vdpodex(1,[2 1]) %設mu=1



具時間變數之微分方程


本例旨在解具有時間變數項之微分方程式。以下面之 ODE為例,其時間因變參數僅經由一系列之資料以兩向量型式決定:

y'(t) + f(t)y(t) = g(t)

式中之初值條件為 y(0) = 0,其中函數 f(t)定義為 n-x-1 向量 tf 與 f;而函數 g(t)則經過 m-x-1 向量 tg 與 g定義。

首先,與時間變化有關之參數 f(t)與 g(t)可以表示如下:

ft = linspace(0,5,25); % Generate t for f
f = ft.^2 - ft - 3; % Generate f(t)
gt = linspace(1,6,25); % Generate t for g
g = 3*sin(gt-0.25); % Generate g(t)

寫出 M-檔案函數程式就上述之指定值進行內插,以求得特定時間點之時變對應值。

function dydt = myode(t,y,ft,f,gt,g)
f = interp1(ft,f,t); % Interpolate the data set (ft,f) at time t
g = interp1(gt,g,t); % Interpolate the data set (gt,g) at time t
dydt = -f.*y + g; % Evalute ODE at time t

其次,利用ode45解法器中之參數呼叫上述之導數函數 myode.m :

Tspan = [1 5]; % Solve from t=1 to t=5
IC = 1; % y(t=0) = 1
[T Y] = ode45(@(t,y) myode(t,y,ft,f,gt,g),TSPAN,IC); % Solve ODE

繪出 y(t)解與時間之函數關係:

plot(T, Y);
title('Plot of y as a function of time');
xlabel('Time'); ylabel('Y(t)');


12.9矩陣解法

12.9矩陣解法


柯西型是高階微分方程之解法,但也可配合矩陣的型式求解。利用矩陣時,可以先以eig函數求其特徵值(eigen value),以確定其根及時間常數,其時間常數則為特徵根之負倒數。利用時間常數可以估計函數達到穩定狀態所需之時間。

以直流馬達之應用為例,電樞與電感L、電阻器R,共同串聯於一直流電壓之中如下圖,電樞則因扭力、慣性力及阻尼等達到動力平衡。




L(di/dt) = -Ri - Keω+v(t)
I(dω/dt)=KTi-Cdω

其中,L、R、I分別代表馬達的電感、電阻及電樞之轉動慣量,而Kt、Ke、Cd分別為力矩常數、反電動勢及阻尼係數。將上述聯立微分方程式改為柯西式,並設 x1=i, x2=ω,以此代入上式,並改寫矩陣型式:[X']=[A][X]+[1/L; 0]v(t),其中

[X]=
x1
x2

[X']=
x'1
x'2

[A]=
-R/L -Ke/L
KT/I -Cd/I

設直流電壓v為一控制馬達的因子,利用電壓之變化可以將馬達加速到穩定的速度。經過一段時間運轉後,再減電壓,使電樞減速並至停止。

在解法器所需之函數中,其表示法除常用的匿函數外,亦可使用其他型式之函數。匿函數最大的優點可以在主程式中共享變數資源,但其形成必須濃縮在一行之中,有時無法表達所有信息,故仍然必須借助次函數或巢狀函數。巢狀函數是將函數包藏在主函數之中,可供主函數直接呼叫,也可以共用主函數內之變數。茲將上式改寫為程式,其結果如下:

function motor(R,L,Cd,I,Kt,Ke,Tc)
% Calculate the voltage & velocity of motor
% R:resistor, ohms
% L:Inductance, H
% Cd: Coef of Damping of armature
% Kt: Torque constant N.m/A
% Ke: counter emf, V
% I: moment of inertia, kg.m^2
% Tc:control points for voltage=[t1 t2 tf]
% Example:
% motor(0.6,0.002,0,6e-5,0.04,0.04,[0.1 0.4 0.5])
A=[-R/L, -Ke/L;Kt/I, -Cd/I];B=[1/L; 0];
function v=volt(t)
v=zeros(size(t));
a=t<=Tc(3) & t>Tc(2); v(a)=-100*(t(a)-Tc(3));
b=t<=Tc(2) & t>Tc(1); v(b)=10;
v(t<Tc(1))=100*t(t<Tc(1));
end
xdot=@(t,x) A*x+B*volt(t);
[t,x]=ode23(xdot,[0 0.6],[0 0]);
subplot(3,1,1)
plot(t,volt(t));xlabel('t(s)');ylabel('Voltage,V');
subplot(3,1,2)
plot(t,x(:,2),'k-');xlabel('t(s)');ylabel('Ang. Velocity,rads/s');
subplot(3,1,3)
plot(t,x(:,1),'r-');xlabel('t(s)');ylabel('current, A');
end



指令之格式如下:

function motor(R, L, Cd, I, Kt, Ke, Tc)

輸入參數說明如下:

R:串聯電阻器, ohms
L:電感, H
Cd:電樞之阻尼係數
Kt:扭力常數,N.m/A
Ke:反電動勢, V
I:慣性扭矩, kg.m^2
Tc:電壓控制點=[t1 t2 tf],t1升程第一段,t2返程始, tf返程終。

>> motor(0.6,0.002,0,6e-5,0.04,0.04,[0.1 0.4 0.5])

12.10 轉移函數tf

12.10轉移函數tf


上述的例子中,已使用到轉換函數。基本上,轉換函數之指指令型式如下:

sys = tf(num,den)
sys = tf(num,den,Ts)%增加時間函數
sys = tf(M)%增益值
sys = tf(num,den,ltisys)%採用LTI物件設定

sys = tf('s')%設定拉普拉斯變數
sys = tf('z')%設定輸出項變數

tfsys = tf(sys)%將其他物件轉為tf物件
tfsys = tf(sys,'inv') % 空間狀態轉為tf物件

最基本之型式參數為NUM與DEN。此二者分別為連續時間轉換函數 SYS之分子與分母項。輸出 SYS為轉換指令 TF 物標。若加參數TS,則可利用此參數產生一個分隔時間之轉換函數,並以 TS為取樣時間 (若取樣時間未定,則設定 TS=-1 )。

利用tf('s')指令設定變數為拉普拉斯變數's'之型式;利用tf('z')則設定為時間函數。

例如:

s = tf('s'); H = (s+1)/(s^2+3*s+1)

Transfer function:
s + 1
-------------
s^2 + 3 s + 1

當使用tf但無輸入參數,或輸入參數僅有一項如sys=tf、sys=tf(M)時,前者會產生一個空集合;後者則增加M增益值。

g=tf([1 0; 2 1])

Transfer function from input 1 to output...
#1: 1
#2: 2

Transfer function from input 2 to output...
#1: 0
#2: 1

 sys = tf(num,den,'Property1',Value1,...,'PropertyN',ValueN)
 sys = tf(num,den,Ts,'Property1',Value1,...,'PropertyN',ValueN)

上述指令輸入參數中,可作成對之輸入,如: 'PropertyName1', PropertyValue1, ...等與 LTIPROPS 相關之參數。若要模式使用現成的特性,則可使用 REFSYS參數,或

  SYS = TF(NUM,DEN,REFSYS).

12.11 非時變系統(LTI模式)

線性非時變系統(Linear Time-Invariant System)又稱為LTI系統,通常可用來措述一個線性之非時變方程式,經由許多函數轉換或分析而達成。其數學模型包括轉移函數(TF)、空間狀態模式(SS)、脈衝響應及頻率轉移函數(FRD)等。處理這些函數時,可以利用MATLAB指令如tf、ss、zpk等指令計算其間之關係,並使用模式分析函數如bode與step進行分析。FRD可以使用相同的方向,但僅限於頻率領域。

在LTI模式下,其資料可能為具有分數型式或成對之單一輸出入(SISO)轉移函數、四矩陣型之狀態空間模式、多重組合之零值與極值之多重輸出輸入(MIMO)資料或屬FRD模式之迴應向量。這些資料構成各種LTI模式物件,稱為TF、SS、ZPK及FRD物件。透過這四個物件之描述,可以直接操作LTI模式,不必經過許多資料向量與矩陣等繁瑣細節之處理。如何產生LTI物件?要處理TF、SS、ZPK及FRD等型式之物件,可以直接利用tf、ss、zpk或frd等指令產生,下面將舉例加以說明。

12.12 轉移函數

12.12轉移函數

此處先介紹轉移函數tf的功能。此函數指在轉換LTI模式至另一種轉移函數型式,或稱為拉普拉斯轉換,以轉移函數在拉普拉斯領域中表示控制對象。這種型式包括實數或複數值之轉換、狀態空間變數之轉換或零極增益(Zero-pole-gain)模式之變數等。其處理資料方式包括單一輸出入(SISO)及多重輸出輸入(MIMO)等二種。

設以下面之h(s)為例,求其轉換函數、零點、極點及增益等。


h(s)=

s² + 3 s + 2
-----------
s3 + 4 s² + 3 s + 1

上述之分母分子多項式,可以用轉換函數表示如下:

>> sys=tf([1 3 2],[1 4 3 1]) %組立轉移函數

Transfer function:
s^2 + 3 s + 2
---------------------
s^3 + 4 s^2 + 3 s + 1


此即為一般所用之表示式,作為TF之物件。在這裡也可以先設定s變數為TF物件之特殊變數,則公式所用之表示式會與前面之表示式一樣。例如:
  
s=tf('s');
H=(s^2+3*s+2)/(s^3+4*s^2+3*s+1)

Transfer function:
s^2 + 3 s + 2
---------------------
s^3 + 4 s^2 + 3 s + 1


這個結果與sys之變數內涵相同。在這裡s變數只要宣告一次即可,除非在zpk指令中再將s變數轉換。

在多重輸出輸入(MIMO)轉換函數中,可能需要兩維陣列之SISO函數。其處理方式分合併SISO函數模型或使用tf指令,但其參數使用細胞陣列。例如:
 
H(s) =

s -1
------
s + 1

s + 2
------------
s²+4s+5

此時可以使用兩個SISO之函數運作,例如:
 
h11=tf([1 -1],[1 1])
h12=tf([1 2],[1 4 5])

Transfer function:
s - 1
-----
s + 1
Transfer function:
s + 2
-------------
s^2 + 4 s + 5


或者採用設定's'拉普拉斯參數法:

>>s=tf('s');h12=(s-1)/(s+1)
>>h22=(s+2)/(s^2+4*s+5)

Transfer function:
s - 1
-----
s + 1
Transfer function:
s + 2
-------------
s^2 + 4 s + 5


結果與h11與h12相同。最後H(s)可以合併為:

>>H=[h12;h22]


Transfer function from input to output...
s - 1
#1: -----
s + 1
s + 2
#2: -------------
s^2 + 4 s + 5


這種方式比較簡潔易明。另外之方法則利用tf函數指令而採用細胞陣列(設為N與D)代表其分子與分母。亦即:

有關此MIMO轉換矩陣之輸入順多如下:

N={[1 -1];[1 2]}; % N(s)
D={[1 1];[1 4 5]}; % D(s)
H=tf(N,D)


Transfer function from input to output...
s - 1
#1: -----
s + 1
s + 2
#2: -------------
s^2 + 4 s + 5


tfdata指令


指令tf執行結果均以物件表示,若要將其轉換回原分子與分母之型式,則必須採用tfdata指令,等於tf之反向指令。例如:

>>h=tf([1 1],[1 2 5])
Transfer function:
s + 1
-------------
s^2 + 2 s + 5

>>[num, den]=tfdata(h,'v')
num =
0 1 1
den =
1 2 5


其回應值為兩個列向量。若採用多項輸出入(MIMO),將上述物件h改為:

>>H=[h;tf(1,[ 1 1])];
>>[num, den]=tfdata(H);
>>celldisp(num)
>>celldisp(den)
num{1} =
0 1 1
num{2} =
0 1
den{1} =
1 2 5
den{2} =
1 1