Własne programy/skrypty
Jesteśmy już gotowi zapoznać się z najbardziej użyteczną właściwością programów konsolowych – łatwością rozszerzania ich funkcjonalności za pomocą własnych plików tekstowych (tzw. skryptów). Idea jest genialna w swej prostocie: zamiast pisać polecenia bezpośrednio w programie, można je zapisywać w osobnym pliku, a potem wczytywać do programu tak, jakby pochodziły z klawiatury. Podstawowa zaleta tego rozwiązania polega na trwałości skryptów, które możemy spokojnie rozwijać przez wiele dni, a potem używać przez wiele lat. Skrypty rozwiązują więc podstawowy problem programów pisanych bezpośrednio w konsoli Octave – ich ulotność i jednorazowość.
Utworzenie własnego skryptu jest banalnie proste: jest to dowolny plik tekstowy o rozszerzeniu .m
, np. programik.m
. W pliku tym wpisujemy dowolne komendy Octave. Możemy opisać je za pomocą komentarzy (przypominam, że w Octave komentarzem jest tekst od znaku # do końca wiersza).
Aby wykonać polecenia w tak utworzonym pliku (czyli „uruchomić skrypt”), wystarczy jako komendę wpisać w Octave samą nazwę skryptu bez rozszerzenia *.m
.
Załóżmy, że skrypt nazywa się programik.m
, a jego zawartość wygląda następująco:
# W pliku programik.m znajduje się trywialny program testowy Octave 1/7 2/7 3/7
Jeżeli wydamy polecenie programik
:
> programik
Program odpowie następująco:
ans = 0.14286 ans = 0.28571 ans = 0.42857
co świadczy o wykonaniu wszystkich instrukcji z pliku programik.m
.
Teraz możemy dowolnie modyfikować plik i uruchamiać go w Octave za pomocą klawiszy historii (↑, ↓). Prawda, że to niezwykle prosty i użyteczny mechanizm?
Oczywiście aby program mógł wczytać skrypt, plik skryptu musi znajdować się w katalogu bieżącym, który ustalamy poleceniami pwd
i cd
, czyli odpowiednio “print working directory” oraz “change directory”.
Na koniec mały smaczek. Jeżeli zapytamy się Octave, co wie na temat polecenia programik
:
> help programik
Otrzymamy następującą odpowiedź:
octave:6> help programik programik is the file ./programik.m W pliku programik.m znajduje się trywialny program testowy Octave ...
Widać więc, że w skryptach można w trywialny sposób umieszczać nie tylko kod, ale i jego dokumentację, którą stanowią komentarze umieszczone przed pierwszą instrukcją Octave.
Quiz ze skryptów
- Jak wstawić komentarz w pliku skryptowym?
- Czym różnią się programy tekstowe od programów okienkowych?
- Co to jest historia poleceń? Gdzie jest przechowywana?
- Jakie skróty klawiaturowe ułatwiają pracę z historią poleceń?
- Co to jest autouzupełnianie? Jakie skróty klawiaturowe uaktywniają autouzupełnianie?
- Jak w Octave zorganizowano system pomocy?
- Jak opuścić tryb pomocy lub tryb
info
? - Co to są skrypty?
- Jak uruchamia się skrypty?
- Jakie są zalety skryptów?
- Jak w Octave ustala się nazwę katalogu bieżącego?
Zadania ze skryptów
- W wierszu poleceń programu wprowadź trzy pierwsze litery polecenia
sombrero
, tj.>> som
i przyciśnij tabulator. Zwróć uwagę na to, że program sam uzupełnił zapis polecenia.
- Sprawdź w systemie pomocy programu (
>> help sombrero
), do czego służy funkcjasombrero
i jakie znaczenie ma jej (opcjonalny) argument. - Wywołaj
sombrero
z niestandardowym argumentem, np.30
lub100
. - Przeczytaj w podręczniku Octave (
>> doc sombrero
), jaka jest rola poleceniasombrero
? Jakie inne polecenie służy podobnemu celowi? - Sprawdź, że wykresy generowane przez Octave możesz myszką obracać, przybliżać lub oddalać.
- Za pomocą polecenia
help
sprawdź, w jakim pliku znajduje się definicja funkcjisombrero
.- Przekopiuj ten plik do swojego katalogu roboczego i nadaj mu nazwę
kolec.m
. - W tym nowym pliku zmień nazwę i definicję funkcji, tak by polecenie
kolec
wyświetlało wykres funkcji z(x,y) = 2^{-\sqrt{x^2+ y^2}} dla -4 \le x,y \le 4. - Zmień komentarz w swoim pliku (np. przetłumacz jego część na polski). Sprawdź, że Twój nowy komentarz pojawi się na ekranie po wydaniu polecenia
help kolec
.
- Przekopiuj ten plik do swojego katalogu roboczego i nadaj mu nazwę
Definiowanie funkcji
W poprzedniej części kursu wyjaśniliśmy, 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 sinusik
, 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
sinusik.m
- W pliku tym wpisujemy:
function y = sinusik(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:
>> sinusik (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 sinusik
stała się odwzorowaniem, wystarczy poprzedzić kropką wszystkie występujące w jej definicji operatory potęgowania:
function y = sinusik(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.–
.
- Nie ma większego sensu stosować kropki przy mnożeniu lub dzieleniu przez liczby (ogólnie: skalary). Np.
- 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 sinusik
). Stąd też ostateczna zawartość pliku sinusik.m
może wyglądać następująco:
# Funkcja sinusik 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 = sinusik(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 z funkcji
- 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.+y
? - Skąd wiadomo, kiedy m-plik zawiera skrypt, a kiedy definicję funkcji?
Zadania z definiowania funkcji
- Zdefiniuj funkcję
f(x)
zadaną wzorem: f(x) = x \sqrt {|1 - x|} \sin^2(x) + x\cos(x) .
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
(przy okazji zapamiętaj te funkcje na przyszłe zajęcia). Jakie jest znaczenie używanych przy nich parametrów?