MATLAB - przekazywanie funkcji jako parametru

 

Wstęp

Sformułowanie problemu

Funkcja feval

Ogólne rozwiązanie problemu

Przykład – wykres pochodnej

 

Wstęp

Bardzo często występuje sytuacja, kiedy chcemy napisać funkcję, w której jeden z argumentów sam jest funkcją. MATLAB dostarcza różnych możliwości w tym zakresie, co z jednej strony daje wygodę, z drugiej jest potencjalnym źródłem błędów.

Dla ilustracji popatrzmy na trzy różne wywołania funkcji ezplot, rysującej wykres zadanej w parametrze funkcji.

ezplot('exp')

ezplot('exp(x)')

ezplot('exp(t)')

 

Funkcja ezplot służy do "łatwego" rysowania wykresu zadanej funkcji jednej zmiennej. W najprostszym przypadku ezplot wymaga podania jednego parametru identyfikującego funkcję przeznaczoną do wykreślenia. Parametr ten może być podany w różny sposób, np.:

ezplot('exp')

ezplot('exp(x)')

ezplot('exp(t)')

W pierwszym przypadku funkcja wykładnicza została przekazana do funkji ezplot w postaci łańcucha znakowego 'exp' będącego identyfikatorem funkcji, w drugim przypadku - w postaci łańcucha znakowego zawierającego całe wyrażenie definiujące funkcję wykładniczą podanej zmiennej.

Zauważmy, że w każdym przypadku funkcja ezplot żąda podania parametru w postaci łańcucha znakowego. Wywołanie z samym identyfikatorem spowoduje komunikat o błędzie.

» ezplot(exp)

Error using ==> exp

Incorrect number of inputs.

Natomiast oczywiście możemy wywołać:

» funkcja = 'exp'; ezplot(funkcja)

 

Gdybyśmy napisali funkcję  mojafun jak niżej:

function y = mojafun(x);

y = x.^2 - 2.*x;

 

to każde z następujących wywołań byłoby legalne:

 

» ezplot('mojafun')

» ezplot('mojafun(x)')

» ezplot('mojafun(10*x)')

» ezplot('mojafun(sin(12*x))')

 

 

Sformułowanie problemu napisania funkcji z parametrem funkcyjnym

 

Załóżmy, że chcemy napisać M-funkcję, która dla zadanej funkcji f(x) i zadanej liczby rzeczywistej h  znajduje wartość ilorazu różnicowego

 

 

Najprostsze intuicyjne rozwiązanie mogłoby mieć postać:

 

function y = mojilorazrozn(fun,x,h);

%MOJILORAZROZN oblicza wartość ilorazu różnicowego funkcji 

%           ILORAZROZN(F,X,H) oblicza [F(X+H) - F(X)]/H

y = (fun(x+h) - fun(x))./h;

 

Widać od razu, że taka implementacja nie działa:

 

» mojlorazrozn('exp',1, 0.01)

ans =

     0

» ilorazrozn('exp(x)',1,0.01)

ans =

     0

 

Wynik od razu dowodzi, że funkcja źle działa (pamiętamy, że pochodna funkcji exp(x) w punkcie 1 wynosi e, wobec tego iloraz różnicowy dla h = 0.01 powinien być przynajmniej do tej wartości zbliżony, a nie jest). Powodem takiej sytuacji jest fakt, że wyrażenie przypisywane zmiennej y  w ostatniej linijce funkcji mojilorazrozn jest interpretowane przez MATLAB jak operacja na łańcuchu 'exp' interpretowanym jako tablica znakowa. Co za tym idzie MATLAB rozumie f(x)  jako 'exp'(1), czyli kod ASCII pierwszego znaku. Cała sytuacja powinna być jasna po prześledzeniu następującego dialogu z Matlabem:

 

» fun = 'exp'; x = 1; h = 0.01;

» fun(x+h)

Warning: Subscript indices must be integer values.

ans =

e

» fun(x)

ans =

e

» fun(x+h)-fun(x)

Warning: Subscript indices must be integer values.

ans =

     0

 

Funkcja feval

 

Do obliczenia wartości funkcji przekazywanej jako parametr innej funkcji należy użyć funkcji feval.  W poniższym przykładzie funkcja ilorazrozn „wie”, że ma obliczyć wartość funkcji, a nie wartość znaku w łańcuchu.

 

function y = ilorazrozn(fun,x,h);

%ILORAZROZN oblicza wartość ilorazu różnicowego funkcji w punkcie

%           ILORAZROZN(F,X,H) oblicza [F(X+H) - F(X)]/H

y = (feval(fun,x+h)-feval(fun,x))/h;

 

 

Zbadajmy zachowanie Matlaba przy takim rozwiązaniu.

Przypadek 1. Przekazany parametr: łańcuch znakowy reprezentujący nazwę M-funkcji

 

Załóżmy, że funkcja mojfaun zdefiniowana jest w M-pliku:

 

%fun.m

%przykładowa M-funkcja, która będzie przekazana jako parametr

function y = mojafun(x);

y = exp(2*x);

 

Przekazanie identyfikatora ‘mojafun’ jako parametru funkcji ilorazrozn działa prawidłowo (pochodna funkcji y = exp(2x) w punkcie 0 jest równa 2):

 

» ilorazrozn('mojafun',0,0.001)

 

ans =

    2.0020

 

Przypadek 2. Przekazany parametr: łańcuch znakowy reprezentujący nazwę funkcji wbudowanej

 

Przekazanie identyfikatora “exp” jako parametru funkcji ilorazrozn działa prawidłowo (pochodna funkcji y = exp(x) w punkcie 1 jest równa e):

 

EDU» ilorazrozn('exp',1, 0.001)

 

ans =

    2.7196

 

Przypadek 3. Przekazany parametr: łańcuch znakowy reprezentujący wyrażenie zmiennej x (lub innej)

 

Teraz nasze rozwiązanie nie działa:

 

EDU» ilorazrozn('exp(x)',1,0.001)

??? Cannot find function 'exp(x)'.

 

Error in ==> D:\Mirror of Ulam\public_html\Pakiety2003\Przyklady\ilorazrozn.m

On line 5  ==> y = (feval(fun,x+h)-feval(fun,x))/h;

 

Komunikat o błędzie informuje, że MATLAB usiłował ziinterpretować łańcuch 'exp(x)' jako nazwę funkcji przekazanej do ilorazrozn, a nie jako wyrażenie definiujące funkcję wykładniczą

 

Jak widać, zastosowanie feval jest nieskuteczne, gdy chcemy przekazać jako parametr funkcję w postaci wyrażenia. Przypomnijmy w tym miejscu, że wbudowana funkcja ezplot (i wiele innych) radzi sobie z wszystkimi trzema przypadkami. Każde z poniższych wywołań jest skuteczne:

 

» ezplot('mojafun',[0 3])

» ezplot('exp',[0,3])

» ezplot('exp(x)',[0,3])

 

Ogólne rozwiązanie problemu

 

Matlab dostarcza dwóch mechanizmów, pozwalających na wygodną obsługę wszystkich  wymienionych wyżej przypadków.

 

Funkcja inline

 

inline pozwala na  zdefiniowanie w pamięci operacyjnej (przestrzeni roboczej) Matlaba specjalnego obiektu (inline object), zachowującego się jak funkcja.

 

Wywołanie postaci

 

Przykład.

 

» mojafun = inline('exp(2*x)')

 

mojafun =

     Inline function:

     mojafun(x) = exp(2*x)

 

» ilorazrozn(mojafun,0,0.001)

 

ans =

    2.0020

 

Zauważmy, ze tym razem funkcji ilorazrozn przekazaliśmy parametr funkcyjny nie w postaci łańcucha ‘mojafun’, lecz identyfikatora mojafun. Jest to bardzo wazne rozróżnienie. Obiekt typu inline zachowuje się jak „prawdziwa” funkcja. Główne zastosowanie mechanizmu inline to możliwość zdefiniowana funkcji bezpośrednio w pamięci operacyjnej, bez potrzeby tworzenia osobnego M-pliku.

 

Ostatnie wywołanie funkcji ilorazrozn można uprościć:

 

» ilorazrozn(inline(‘exp(2*x)’, 0, 0.001)

 

ans =

    2.0020

 

Wydawałoby się, że problem przekazywania parametrów funkcyjnych w postaci wyrażeń jest rozwiązany - należy w momencie przekazywania wyrażenia „ubrać je dodatkowo w funkcję inline”, jak to pokazano w ostatnim wywołaniu. Matlab dostarcza jeszcze jednego mechanizmu pozwalającego na całkowicie zunifikowane podejście. Mechanizmem tym jest funkcja fcnchk.

 

Funkcja fcnchk

 

Funkcja fcnchk dokonuje specjalnej kontroli łańcucha znakowego pod względem jego zawartości. W najprostszym przypadku składnia funkcji ma postać

 

FCNCHK(FUN)

 

Jeśli FUN jest łańcuchem nie zawierającym nawiasów ani symboli operacji arytmetycznych, wówczas funkcja fcnchk zachowuje się jak funkcja tożsamościowa, czyli zwraca łańcuch podany jej w parametrze FUN:

 

» waznafunkcja = fcnchk('jakislancuch')

 

waznafunkcja =

jakislancuch

 

» waznafunkcja(1)

 

ans =

j

 

 

Jeśli łańcuch FUN może być zinterpretowany jako wyrażenie definiujące funkcję jednej lub wielu zmiennych, funkcja fcnchk zwraca odpowiedni obiekt typu inline. Prześledźmy poniższe przykłady:

 

» waznafunkcja = fcnchk('x*sin(x)')

 

waznafunkcja =

 

     Inline function:

     waznafunkcja(x) = x*sin(x)

 

» waznafunkcja(pi/2)

 

ans =

 

    1.5708

 

» waznafunkcja = fcnchk('y*sin(x)')

 

waznafunkcja =

 

     Inline function:

     waznafunkcja(x,y) = y*sin(x)

 

Zauważmy, że Matlab porządkuje argumenty alfabetycznie. Aby określić inny porządek, należy podać symbole zmiennych w odpowiedniej kolejności jako kolejne parametry funkcji fcnchk:

 

» waznafunkcja = fcnchk('y*sin(x)', 'y', 'x')

 

waznafunkcja =

 

     Inline function:

     waznafunkcja(y,x) = y*sin(x)

 

» waznafunkcja = fcnchk('a*x+b','x','a','b')

 

waznafunkcja =

 

     Inline function:

     waznafunkcja(x,a,b) = a*x+b

 

 

Dodatkowo funkcji fcnchk można przekazać na ostatniej pozycji parametr ‘vectorized’, który spowoduje, że przed symbolami dodawania, mnożenia, dzielenia i potęgowania w  przekazywanym jako pierwszy parametr wyrażeniu zostaną dodane kropki, a ponadto całe wyrażenie zostanie pomnożone przez macierz jedynek:

 

» waznafunkcja = fcnchk('y*exp(x)','x','y','vectorized')

 

waznafunkcja =

     Inline function:

     waznafunkcja(x,y) = y.*exp(x).*ones(size(x))

 

 

Funkcja fcnchk powinna być użyta wewnątrz funkcji, do której przekazywany jest parametr funkcyjny. Naszą funkcję znajdującą iloraz różnicowy modyfikujemy teraz następująco:

 

function y = ilorazrozn(fun,x,h);

%ILORAZROZN oblicza wartość ilorazu różnicowego funkcji w punkcie

%           ILORAZROZN(F,X,H) oblicza [F(X+H) - F(X)]/H

fun = fcnchk(fun);%w tym momencie następuje ew. modyfikacja parametru fun

y = (feval(fun,x+h)-feval(fun,x))/h;

 

Po tej modyfikacji wszystkie poniższe wywołania będą działały prawidłowo:

 

» ilorazrozn('exp',0,0.01)

 

» ilorazrozn('exp(x)',0,0.01)

 

» ilorazrozn('mojafun',0,0.01)

 

» ilorazrozn('mojafun(x)',0,0.01)

 

(przypomnijmy, że mojafun jest nazwą funkcji zdefiniowanej w M-pliku

 

Cały M-plik zawierający funkcję fcnchk można obejrzeć po wydaniu polecenia edit fcnchk.

 

Przykład – rysowanie funkcji wraz z jej pochodną

 

Następująca M-funkcja ilustruje użycie mechanizmów omówionych wyżej. Funkcja pozwala przekazać w parametrze jakafunkcja łańcuch z nazwą M-funkcji,  łańcuch z nazwą funkcji wbudowanej lub łańcuch zawierający wyrażenie zależne od 1 zmiennej (niekoniecznie „x”!). Odpowiedni M-plik jest do pobrania ze strony przykładów.

 

%RYSUJPOCHODNA.M

%ilustracja FCNCHK oraz FEVAL

%RYSUJPOCHODNA(FUN,A,B,H) rysuje funkcję FUN i jej pochodną w przedziale [a,b]

%pochodna obliczana jest w sposób przybliżony jako iloraz różnicowy odpowiadający przyrostowi H

%parametr jakafunkcja musi reprezentować funkcję, która potrafi operować na wektorach

%przykładowe wywolania:

%rysujpochodna('exp(-x).*sin(x)');

%rysujpochodna('(x.^2-17.*x)./(x.^2+1)',-10,10,0.01)

%rysujpochodna('exp',0,3);

%rysujpochodna('fun'), gdzie fun jest M-funkcją

function rysujpochodna(jakafunkcja,a,b,h);

switch nargin

case 0

 error('Zla liczba parametrow');

case 1

   a = 0;

   b = 2*pi;

case 2

   b = a+1  

 case 3

   h = 0.01;

 end;

f = fcnchk(jakafunkcja);

x = linspace(a,b,200);

y = feval(f,x);

p = (feval(f,x+h)-feval(f,x))/h;

plot(x,y,'b:',x,p,'r-','LineWidth',2);

grid on;

legend('Funkcja','Pochodna');

title(jakafunkcja);

 

Przykład wykonania:

rysujpochodna('(x.^2-17.*x)./(x.^2+1)',-10,10,0.01)