Spis treści
Definiowanie funkcji
W poprzedniej części kursu wyjaśniłem, jak łatwo w Octave można utworzyć własne programy (tzw. skrypty). Funkcje użytkownika najłatwiej można zdefiniować właśnie w formie skryptów.
Załóżmy, że naszym celem jest zdefiniowanie funkcji o nazwie sin5
, która dla argumentu x
obliczać będzie wartość wyrażenia x - x3/6 + x5/120
. Aby zdefiniować taką funkcję:
- Tworzymy (w katalogu bieżącym) plik o nazwie
sin5.m
- W pliku tym wpisujemy:
function y = sin5(x) y = x - x**3/6 + x**5/120; endfunction
Uwaga! Powyższa definicja nie jest optymalna i wkrótce ją poprawimy.
Funkcje tę wywołujemy w Octave w naturalny sposób:
>> sin5 (0.1) ans = 0.099833
Ogólnie, podczas definiowania funkcji w pliku obowiązują następujące zasady:
- Plik musi mieć taką samą nazwę jak funkcja i rozszerzenie
m
. - Plik musi być widoczny dla programu (np. znajdować się w katalogu bieżącym programu).
- Definicja funkcji musi zawierać się między poleceniami
function
iendfunction
. - Funkcja może przyjmować wiele argumentów, np.
function y = f(a, b, c)
- Funkcja może zwracać wiele wartości (będących składowymi wektora), np:
function [y, eps, xi] = f(a, b, c)
- Funkcja może składać się z wielu instrukcji, które warto kończyć średnikiem:
y = x - x**3/6; y = y + x**5/120;
Niezakończenie jakiejś instrukcji średnikiem spowoduje wyświetlenie wyniku wykonywanych w niej obliczeń – ta cecha może być przydatna np. podczas szukania błędów w skrypcie, ale rzadko kiedy ma zastosowanie w gotowej, przetestowanej funkcji.
Odwzorowania
W wielu językach programowania, w tym w Octave, istnieje możliwość wywoływania funkcji (np. matematycznych) z argumentami wektorowymi lub macierzowymi. Takie funkcje nazywa się odwzorowaniami (ang. maps lub mapping functions). Jedną z takich funkcji w Octave jest cos
, czyli funkcja wyznaczająca cosinus kąta:
> cos([0, 0.1, 0.2]) ans = 1.00000 0.99500 0.98007
Jak widać, jeżeli argumentem funkcji cos
jest wektor, wynikiem jest wektor utworzony z wartości tej funkcji dla wszystkich elementów wektora [0, 0.1, 0.2]
, czyli
cos([0, 0.1, 0.2]) = [cos(0), cos(0.1), cos(0.2)].
Ogólnie mówimy, że f
jest odwzorowaniem, jeśli dla dowolnej macierzy v
o rozmiarze n na m wyrażenie f(v)
też jest macierzą n na m, przy czym jeżeli podstawimy w = f(v)
, to spełniona jest zależność w(k,l) = f(v(k,l))
. Innymi słowy, w k-tym wierszu i l-tej kolumnie macierzy f(v)
znajduje się wartość funkcji f
dla argumentu v(k,l)
leżącego w k-tym wierszu i l-tej kolumnie macierzy v
.
Informacja o tym, czy dana funkcja Octave jest odwzorowaniem, znajduje się w dokumentacji tej funkcji (help
), w której odwzorowania określane są jako mapper function lub mapping function, zwykle już w pierwszym wierszu opisu.
Odwzorowania są w Octave bardzo ważną klasą funkcji, gdyż znacznie przyspieszają rozwiązywanie wielu podstawowych problemów numerycznych, takich jak znajdowanie pierwiastków równań z wieloma niewiadomymi, rozwiązywanie równań różniczkowych czy obliczanie pól powierzchni (całek), a także tworzenie wykresów funkcji.
Operatory „z kropką”
Jak można się łatwo przekonać, nasza funkcja sin5
nie jest odwzorowaniem i nie można jej wywoływać z argumentami innymi niż skalary, co znacznie ogranicza jej użyteczność. Można temu jednak łatwo zaradzić. Aby funkcja sin5
stała się odwzorowaniem, wystarczy poprzedzić kropką wszystkie występujące w jej definicji operatory potęgowania:
function y = sin5(x) y = x - (x.**3)/6 + (x.**5)/120; endfunction
Ogólnie, kropką można poprzedzić dowolny operator „iloczynowy”, czyli operator mnożenia (.*
, dzielenia (./
lub .\
) i potęgowania (.**
lub .^
). To, kiedy stosować tę kropkę, zależy od następujących czynników:
- Nie ma większego sensu stosować kropki przy mnożeniu lub dzieleniu przez liczby (ogólnie: skalary). Np.
x/2
jest zawsze równoważne wyrażeniux./2
. - Jeżeli nie znasz rachunku macierzowego, to zawsze poprzedzaj operatory mnożenia, dzielenia i potęgowania kropką (z wyjątkiem mnożenie lub dzielenia przez liczbę).
- Jeżeli znasz rachunek macierzowy, to pamiętaj, że mnożenie, dzielenie i potęgowanie „bez kropki” to operacje macierzowe. Jeżeli ani x, ani y nie jest skalarem, to:
- w wyrażeniu
x * y
macierzx
musi mieć tyle kolumn, ile wierszy ma macierzy
. - wyrażenie
x/y
jest równoważne wyrażeniu ((y'-1)x')'
, ale nie wyznacza się wartościy'-1
. - wyrażenie
y\x
ma wartość(y-1)*x
, która jest wyznaczana szybkim algorytmem niewymagającym wyznaczaniay-1
. - w wyrażeniach
x.*y
orazx./y
macierzex
iy
muszą mieć ten sam rozmiar. Operacje z kropką wykonywane są „element po elemencie”. Np.>> m = [1 -1; -1 1] m = 1 -1 -1 1 >> v = [1 2; 3 4] v = 1 2 3 4 >> m .* v # mnożenie element po elemencie ans = 1 -2 -3 4 >> m * v # mnożenie macierzowe ans = -2 -2 2 2
- w wyrażeniu
- Wyrażenie
x**y
(lubx^y
) ma bardzo nieoczekiwaną interpretację, jeżelix
jest skalarem, ay
czymś innym niż skalar (np. macierzą kwadratową). Dopóki nie zrozumiesz, jak to działa, używaj wyłącznie „potęgowania z kropką”:.**
lub.^
. Na przykład, aby wygenerować ciąg dziesięciu kolejnych potęg liczby 2, można użyć następującego wyrażenia:>> 2.^(1:10) ans = 2 4 8 16 32 64 128 256 512 1024
- Nie używaj operatorów
.+
i.–
.
- Pamiętaj, że kropka może też oznaczać część ułamkową liczby. Jeżeli zachodzi konflikt tych znaczeń, Octave przyjmie, że kropka łączy się z operatorem. Np. wyrażenie
>> 2./m
zostanie zinterpretowane następująco:
2 ./ m
Na zakończenie warto dodać, że jeżeli w pliku zawierającym definicję funkcji umieścimy komentarz (najlepiej przed jej definicją), to będzie on wyświetlany przez Octave po wydaniu polecenia help nazwa_funkcji
(np. help sin5
). Stąd też ostateczna zawartość pliku sin5.m
może wyglądać następująco:
# Funkcja sin5 przybliża wartość funkcji sin(x) za pomocą wielomianu stopnia 5. # # Przybliżenie to jest całkiem dobre dla małych wartości x, # np. dla |x| < 1. # function y = sin5(x) y = x - x.**3/6 + x.**5/120; endfunction
Pliki z funkcjami a pliki ze skryptami
Zarówno funkcje, jak i skrypty zapisuje się w m-plikach, czyli plikach o rozszerzeniu .m
. Octave odróżnia jedne od drugich na podstawie zawartości pierwszej instrukcji w pliku. Jeśli jest nią definicja funkcji (ze słowem kluczowym function
), cały plik uznawany jest za definicję funkcji; w przeciwnym wypadku – za skrypt. Skrypty wykonywane są w całości, natomiast po kodzie definiującym funkcje można w pliku umieścić dowolny inny kod, np. definicje funkcji pomocniczych. Ten dodatkowy kod nie będzie widoczny z zewnątrz, ale może być wykorzystywany w kodzie funkcji.
Quiz
- Dlaczego definicje funkcji zwykle umieszcza się w osobnych plikach?
- Jaka jest rola słów kluczowych
function
iendfunction
? - Czy funkcja może zwracać wiele wartości?
- Dlaczego zapis instrukcji funkcji z reguły kończy się średnikiem?
- Co to są odwzorowania?
- Jak sprawdzić, czy dana funkcja Octave jest odwzorowaniem?
- Jak definiuje się własne odwzorowania?
- Jaka jest różnica między
x*y
ix.*y
? - Jaka jest różnica między
x/y
ix./y
? - Jaka jest różnica między
x**2
ix.**2
? - Jaka jest różnica między
x+y
ix.+2
? - Skąd wiadomo, kiedy m-plik zawiera skrypt, a kiedy definicję funkcji?
Zadania
- Zdefiniuj funkcję
f(x)
zadaną wzorem:.
Pierwiastek kwadratowy można w Octave wyznaczyć funkcjąsqrt
, a wartość bezwzględną funkcjąabs
. - Wykonaj wykres
f(x)
dla0 ≤ x ≤ 2.5
. W tym celu możesz użyć następującego ciągu poleceń:>> x = 0 : 0.001 : 2.5; # zakres z krokiem 0.001 >> y = f(x); >> plot (x,y)
Pierwsze z nich definiuje wektor „iksów”, drugie – wektor odpowiadających mu wartości („igreków”), a trzecie powoduje wygenerowanie takiego wykresu jak ten:
- Z wykresu wynika, że równanie
f(x) = 0
ma pierwiastekz
równy w przybliżeniu 2.2. Wyznacz jego wartość za pomocą polecenia fsolve:>> z = fsolve("f", 2.2)
- Oblicz pole powierzchni pomiędzy wykresem funkcji
f(x)
dla0 ≤ x ≤ z
a osią rzędnych (tj.y = 0
) (na poniższym rysunku zostało ono zaznaczone kolorem zielonym):
Możesz posłużyć się następującym poleceniem:>> pole = quad("f", 0, z)
- Zapoznaj się z dokumentacją funkcji
quad
ifsolve
. Jakie jest znaczenie ich parametrów? Jak można oszacować dokładność rozwiązań?