MATLAB - przekazywanie funkcji jako parametru
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))')
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
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.
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
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
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])
Matlab dostarcza dwóch mechanizmów,
pozwalających na wygodną obsługę wszystkich wymienionych wyżej przypadków.
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 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.
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)