Jest to krótki, ale powinien być to wystarczający, wstęp do języka programowania Julia na potrzeby tego laboratorium. Celem tego dodatku jest przedstawienie minimalnego podzbioru składni języka, aby można było jak najszybciej być w stanie sprawnie implementować programy, które będą realizować proste obliczenia numeryczne. Natomiast chciałbym też podkreślić, że nie jest to wstęp do programowania jako takiego, to już powinieneś umieć. Oficjalny podręcznik do języka Julia, który w miarę całościowo wprowadza do języka, można znaleźć tutaj, natomiast zawiera on zdecydowanie nadmiar informacji, niż jest to potrzebne.
Dlaczego julia? Czy lepsza? Jakie są plusy, jakie minusy?
Jako lekturę dodatkową, przed przejściem dalej, można sobie pobieżnie przeglądnąć:
W skrócie powinieneś mieć pojęcie o
podstawowe typy numeryczne,
instrukcje warunkowe
wielowymiarowe tablica, krotka, słownik
pętle for i while
Wykonuj kod i sprawdzaj jak działa.
Jak wyskakuje błąd, to przeanalizuj komunikat zwracany przez kompilator.
Dlaczego Julia?
We want a language that’s open source, with a liberal license. We want the speed of C with the dynamism of Ruby. We want a language that’s homoiconic, with true macros like Lisp, but with obvious, familiar mathematical notation like Matlab. We want something as usable for general programming as Python, as easy for statistics as R, as natural for string processing as Perl, as powerful for linear algebra as Matlab, as good at gluing programs together as the shell. Something that is dirt simple to learn, yet keeps the most serious hackers happy. We want it interactive and we want it compiled.
— Jeff Bezanson, Stefan Karpinski, Viral B. Shah, Alan Edelman, Why We Created Julia, 14 Luty 2012
Zalety
Wady
Julia jest relatywnie nowym językiem programowania, który powstał w roku 2012. Natomiast pierwsza wersja Python została opublikowana 20 lutego 1991. Matlab powstał w roku 1984. (Nawet ja jeszcze się nie urodziłem :D).
https://juliateachingctu.github.io/Julia-for-Optimization-and-Learning/stable/why/ https://blakeaw.github.io/2019-09-20-numba-vs-julia/
REPL
REPL to akronim powstały od angielskich słów "Read-Eval-Print-Loop".
Zmienne
Zmienna, czyli nazwa (ciąg znaków), do której przyporządkowana jest jakaś wartość, można tworzyć poprzez operator przypisania =
(znak równa się).
a = 1
b = 1.0
c = 'a'
d = "abc"
Reguły dotyczące nazw zmiennych są bardzo podobne jak w innych popularnych językach programowania, więc nie będziemy się tutaj zbytnio w to zagłębiać. Przykładowo nazwa zmiennej nie może zaczynać się od cyfry, ale może zaczynać się od znaku podkreślenia.
4zmienna = 1 # Błąd skladni
_zmienna = 1 # Brak błędu składni
W nazwie zmiennej mogą występować znaki Unicode, jak na przykład znaki alfabetu greckiego.
_α = 1.0
d_β = 2.0
Γ_1 = 3.0
W REPL (Read-Eval-Print-Loop) i edytorach tekstu, które wspierają programowanie w języku Julia, znaki takie można szybko wprowadzać poprzez wpisanie \kod_znaku
i naciśnięcie klawisza tabulacji.
\pi<TAB>
\beta<TAB>
\Gamma<TAB>
Lista | słów | zarezerwowanych | przez | język | Julia |
---|---|---|---|---|---|
baremodule | begin | break | catch | const | continue |
do | else | elseif | end | export | false |
finally | for | function | global | if | import |
let | local | macro | module | quote | return |
struct | true | try | using | while |
Spis wszystkich takich znaków można znaleźć w rozdziale dokumentacji Julia [Unicode input].(https://docs.julialang.org/en/v1/manual/unicode-input/) Niektóre symbole, jak na przykład , mogą mieć specjalnie znacznie. Pamiętaj, że każda zmienna ma jakiś typ. Żeby sprawdzić typ zmiennej, można skorzystać z funkcji typeof
, co może przydać się czasem podczas analizy napisanego kodu.
typeof(a1) # Int
typeof(b2) # Float64
typeof(c3) # Char
typeof(d4) # String
Przydane rozdziały z oficjalnego podręcznka języka programowania Julia:
Liczby
W zasadzie powinno się wiedzieć dwie rzeczy o liczbach: typy i operacje. Najbardziej istotnymi dla nas typami numerycznymi są:
Typ
Int
(a właściweInt64
) reprezentujący liczbę całkowitą:1
,2
,10000
,10_000
.Typ
Float64
reprezentujący liczbę zmiennoprzecinkową:1.0
,2.0
,1e-3
,1e+6
.Typ
ComplexF64
reprezentujący zespoloną liczba zmiennoprzecinkową:1.0 + im*2.0
.
W bibliotece standardowej istnieją oczywiście też inne typy numeryczne (patrz tutaj), natomiast nie są one raczej w centrum naszego zainteresowania, przynajmniej, póki co.
Co można robić z liczbami? Wykonywać na nich działania arytmetyczne.
Wyrażenie | Operacja | Opis |
---|---|---|
x + y | dodawanie | dodanie x do y |
x - y | odejmowanie | odjęcie y od x |
x * y | mnożenie | przemonżenie x przez y |
x / y | dzielenie | dzielenie x przez y |
x ÷ y | dzielenie bez reszty | równoważne z funckja div(x,y) (\div<TAB> -> ÷) |
x ^ y | potęgowanie | podnieś x do potęgi y |
x % y | reszta z dzielenia | równoważne z funckją rem(x,y) |
W bibliotece standardowej są zdefiniowane pewne istotne stałe numeryczne, takie jak,
Liczba urojona , kryje pod zmienną
im
.Liczba , kryje się pod zmienną
pi
orazπ
(\pi<TAB>
->π
).
Jakiego typu w Julii jest stała ?
Przydatne funkcje dla liczb zespolonych:
abs(z)
- moduł liczby zespolonejz
.angle(z)
- argument (faza) liczby zespolonejz
.real(z)
- część rzeczywista liczby zespolonejz
.imag(z)
- część urojona liczby zespolonejz
.
Przydane rozdziały z oficjalnego podręcznka języka programowania Julia:
Wartości logiczne
Typ Bool
to typ reprezentujący binarną wartość logiczną, która może przyjąć wartość prawda (true
), bądź fałsz (false
). Głównie ma on zastosowanie przy instrukcjach warunkowych, o których będzie za chwile. Podstawowe operacje na wartościach logicznych, które można wykorzystać do budowania wyrażeń logicznych to:
Wyrażenie | Operacja |
---|---|
!x | negacja |
x && y | koniunkcja |
x || y | alternatywa |
Przykład wyrażenia logicznego:
!(true && false) || true
Wyrażenia logicznie najczęściej buduje się z wykorzystaniem operacji porównywania dwóch wartości.
Wyrażenie | Opis |
---|---|
x == y | prawda jeżeli x jest równe y . |
x != y | prawda jeżeli x jest różne od y . |
x < y | prawda jeżeli x jest mniejsze niż y . |
x <= y | prawda jeżeli x jest mniejsze niż lub równe y . |
x > y | prawda jeżeli x jest większe niż y . |
x >= y | prawda jeżeli x jest większe niż lub równe y . |
a = 1
b = 2.0
c = 1.0
!(a == c) && (a > b) || !(a >= b) || (a < b) && !((a <= b) && !(a <= c))
Kolejność wykonywania działań logicznych: negacja, koniunkcja, alternatywa. Zawsze można też skorzystać z nawiasów, aby wymusić pożądaną kolejność.
Sterowanie przepływem
Sterowanie przepływem, na potrzeby tego wstępu, to zbiór instrukcji, które pozwalają na warunkowe wykonywanie lub powtarzanie bloków instrukcji. Interesują nas tutaj w zasadzie dwie rzeczy: instrukcje warunkowe oraz pętle. Omówimy sobie dwie instrukcje warunkowe: if
i ?:
(ternary operator), oraz dwa pętle: while
i for
.
Operator warunkowy ?:
ma następującą składnię:
warunek ? "zwróc wartość jeżeli prawda" : "zwróc wartość jeżeli fałsz"
Parę przykładów wykorzystania tego operatora:
1+1 == 2 ? "prawda" : "fałsz" # Zwraca "prawda"
1+1 == 3 ? "prawda" : "fałsz" # Zwraca "fałsz"
x = 0.5
x = x > 1.0 ? x^2 : 2x
Druga instrukcja warunkowa, to instrukcja if
, która daję odrobinę więcej możliwości. Instrukcji if
mogą akompaniować, o ile jest taka potrzeba, również instrukcje elseif
i else
. Składnia jest standardowa, nie ma co tutaj za bardzo się rozwodzić, więc przejdźmy od razu do trzech przykładów, które pokazują trzy warianty wykorzystania tych instrukcji.
a = 1
if _WARUNEK # Jeżeli _WARUNEK jest prawdziwy to wykonaj ten blok kodu
a += 1
end
@show a
a = 1
if _WARUNEK # Jeżeli _WARUNEK jest prawdziwy to wykonaj ten blok kodu
a += 1
else # Jeżeli _WARUNEK jest fałszwy to wykonaj ten blok kodu
a -= 1
end
@show a
a = 1
if _WARUNEK # Jeżeli _WARUNEK jest prawdziwy to wykonaj ten blok kodu
a += 1
elseif _WARUNEK2 # Jeżeli poprzednie warunki są fałszye,
a *= 10 # ale ten nie, to wykonaj ten blok kodu
else # Jeżeli wszystkie warunki są fałszwy to wykonaj ten blok kodu
a -= 1
end
@show a
Jeżeli jest taka potrzeba, to oczywiście można umieścić więcej niż jedną instrukcję elseif
w ramach takiego wyrażenia.
Pętla while
służy do warunkowego powtarzania bloku instrukcji. Instrukcje są powtarzane, dopóki warunek jest spełniony. Warunek jest sprawdzany przed każdym wykonaniem bloku instrukcji. Przykład:
i = 0
while i < 10
@show i
i += 1
end
Pętla for
służy głównie do iterowania po iterowalnych strukturach i wykonania bloku instrukcji dla aktualnie wygenerowanego elementu przez iterator. Dla zobrazowania przykładową strukturą, po której możliwa jest iteracja, będzie tablica [3, 1, 4]
, natomiast do omówienia tablic przejdziemy za niedługo. Przykładowe wykorzystane pętli for
poniżej.
a = 1
for i in 1:10
a += i
@show i, a
end
Skupmy się teraz na wyrażeniu 1:10
, które to tworzy iterowalną strukturę UnitRange{Int64}
, która zwraca kolejne liczby od 1 do 10 gdy się po niej iteruje. W ogólności wyrażenie start:krok:stop
generuje iterator, który będzie zwracał wartości wypisane przez poniższy kod:
i = start
while i <= stop
@show i
i += krok
end
Jeżeli w wyrażeniu start:krok:stop
nie podamy wartości krok
, czyli wywołamy wyrażenie start:stop
, to wartość domyślna krok = 1
. Do generowania takich iteratorów istnieje także funkcja range
, która może być czasem wygodniejsza niż wyrażenie start:krok:stop
. Zapoznaj się z dokumentacją tej funkcji poprzez wpisanie w REPL ?range
, i sam oceń.
Oczywiście dostępne są także dwie specjalne instrukcje break
oraz continue
, które to można umieścić w pętli. Dla przypomnienia wywołanie instrukcji break
przerwie wykonywanie pętli, natomiast wywołanie instrukcji continue
wywołuje przejście do następnej iteracji pętli.
Inne przydane funkcje dla pętli for
, z którymi polecam się zapoznać to enumerate
oraz zip
.
Przydane rozdziały z oficjalnego podręcznka języka programowania Julia:
Funkcje
W języku programowania Julia, funkcje możemy definiować na dwa sposoby. Jako że każdy z tych sposób może być przydatny, to omówmy sobie oba. Zanim przejdziemy sobie do omówienia składni, to nadmienie, że argumenty przekazywane do funkcji są zawsze przekazywane przez wartość. Zacznijmy od omówienia sobie najbardziej ogólnego przypadku na poniższym przykładzie.
function foo(a, b, c=10.0; klucz1, klucz2=1)
return klucz1 > klucz2 ? a*b*c : a+b+c
end
Definicje funkcji zaczynamy od słowa kluczowego function
. Następnie powinna pojawić się nazwa funkcji, w przykładzie jest to foo
. W nawiasach podajemy argumenty, które funkcja przyjmuje. W przykładzie mamy trzy argumenty pozycyjne: a
, b
, c
oraz dwa argumenty przez słowo kluczowe: klucz1
, klucz2
. Argumenty przez słowo kluczowe są oddzielone od argumentów pozycyjnych znakiem ;
. Różnica między tymi dwoma rodzajami argumentów jest podczas wywołania funkcji. Argumenty przez słowo kluczowe trzeba nazwać podczas wywołania funkcji i kolejność ich podawania nie ma znaczenia. W przypadku argumentów pozycyjnych nie podajemy ich nazw i kolejność ma znaczenie. Dodatkowo każdy argument może przyjąć wartość domyślną poprzez operator przypisania wartości. Co przekłada się na to że jeżeli nie podamy danego argumentu podczas wywołania, to przyjmuje on w ten czas domyślną wartość (o ile jest podana). Wartość zwracana przez funkcje jest wskazana przez instrukcję return
. Przeanalizuj poniższe przykłady wywołania funkcji foo
.
foo(1, 2, 3; klucz1=10, klucz2=20)
foo(1, 2; klucz1=10)
Akurat przykładowa funkcja jest dość prosta i można wykorzystać tutaj drugi (jedno linijkowy) sposób definiowania funkcji, który jest następujący:
bar(a, b, c=10.0; klucz1, klucz2=1) = klucz1 > klucz2 ? a + b * c : a * b + c
Istnieje możliwość jawnej specyfikacji typów dla argumentów oraz wartości zwracanej przez funkcje. Składnia jest jak w przykładzie poniżej, natomiast nie trzeba tego robić.
suma_int(a::Int, b::Int)::Int = a + b
Funkcje wyższego rzędu
hehe
Przydane rozdziały z oficjalnego podręcznka języka programowania Julia:
Tablice
Tablica bądź lista, to taka struktura danych, która reprezentuje skończony i uporządkowanego zbioru elementów, który można modyfikować (ang. mutable). Elementami tablicy w zasadzie może być dowolna wartość, natomiast z punktu widzenia obliczeń numerycznych, interesują nas głównie tablice, które zawierają w sobie liczby. Aby stworzyć tablicę, trzeba zbudować wyrażenie z wykorzystaniem nawiasów kwadratowych, przykładowo:
julia> a = [1, 2, 3]
> 3-element Vector{Int64}:
1
2
3
W rezultacie otrzymaliśmy coś, co jest typu Vector{Int64}
. I tak typ Vector
to jest właśnie jednowymiarowa tablica, a w zasadzie alias na parametryczny typ Array
.
julia> Vector
> Vector (alias for Array{T, 1} where T)
Typ Int64
, który pojawił się w nawiasach {}
mówi jakiego typu elementy przechowuje ten Vector
. Nazwa Vector
nie jest przypadkowa i wrócimy do omówienia tego później. Natomiast na początku skupmy się na jedno wymiarowych tablicach.
Jeżeli w wyrażeniu znajdą się literały o różnych typach, to interpreter spróbuje rzutować je do wspólnego typu (o ile jest to możliwe).
julia> b = [1.0, 2, 3]
> 3-element Vector{Float64}:
1.0
2.0
3.0
Jeżeli nie jest to możliwe, to powstanie tablica przechowująca elementy typu Any
, który oznacza co do zasady dowolny typ.
julia> b = [1.0, 2, 3, "tekst"]
> 4-element Vector{Any}:
1.0
2
3
"tekst"
Jak stworzyć pustą jednowymiarową tablicę? Przykładowo w taki sposób:
a = [] # Pusta tablica przechowująca elementy o dowolnym typie `Any`
b = Any[] # To samo co `a = []`, tylko bardziej jawnie
c = Int[] # Pusta tablica przechowująca elementy o typie `Int`
d = Float64[] # Pusta tablica przechowująca elementy o typie `Float64`
e = ComplexF64[] # Pusta tablica przechowująca elementy o typie `ComplexF64`
Podstawowe operacje na jednowymiarowych tablicach:
push!(a, 3) # Dodaj na koniec tablcy wartość `3`
last = pop!(a) # Usuń ostatni element tablicy i zwróc go
append!(a, [4, 5]) # Do tablicy `a` doklej tablice `[4, 5]`
length(a) # Zwróć ilość elementów w tablicy (nie tylko jednowymiarowej)
W języku Julia istnieje konwencja w której to funkcje których nazwa kończy się znakiem !
, modyfikują argument funkcji. Przeważnie modyfikowany jest w ten czas pierwszy argument pozycyjny.
Aby dostać się bezpośrednio to i-tego elementu tablicy, można wykorzystać nawiasy kwadratowe.
a = [1, 2, 3, 4]
a[2] # Zwraca drugi element tablicy a
a[2] = 10 # Przypisanie do drugiego elementu nowej wartości
W języku programowania Julia pierwszy element tablicy (czy w ogólności uporządkowanych kolekcji) ma indeks 1. Więc specyfikujemy pozycję w tablicy, a nie przesunięcie względem pierwszego elementu jak np. w języku C. Jeżeli potrzebujesz operować na przesunięciach, to zawsze możesz napisać wyrażenie w następujący sposób a[1+i]
, gdzie i to przesunięcie.
Jeżeli chcemy wybrać podzbiór elementów z tablicy, to można skorzystać z iteratora start:krok:stop
albo wyspecyfikować indeksy przez tablicę zawierającą dodatnie liczby całkowite. Zapoznaj się z poniższymi przykładami.
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
a[1:2:9] # Zwróci elementy o nieparzystych indeksach
a[begin:2:end]
a[3:7] # Zwróć wycinek tablicy
a[9:-1:1] # Zwróci tablice w odwróconej kolejności
a[end:-1:begin] # Tak też zadziała
a[[3, 8]] # Zwrócy 3 i 8 element tablicy
No i tyle o tablicach jednowymiarowych. Przejdźmy do tablic wielowymiarowych.
Tablice dwuwymiarowe można stworzyć następującą składnią:
A = [1 2; 3 4]
W rezultacie, jak może się domyślić, powstał macierz, czyli typ Matrix
, będący także aliasem na parametryczny typ Array
.
julia> Matrix
> Matrix (alias for Array{T, 2} where T)
Dla tablic co najmniej trzy wymiarowych nie ma już specjalnych aliasów i tworzenie ich w taki sposób jest już wysoce niepraktyczne. Przeważnie korzysta się w ten czas z funkcji do alokacji tablicy o danym rozmiarze jak zeros
. Natomiast omówmy sobie składnie tego wyrażenia. Elementy w wierszach oddzielamy odstępem (spacja), natomiast kolejne wiersze oddzielamy średnikiem (;). Możemy w tym stylu zbudować też macierz z wektorów. Przeanalizuj, co generuje poniższy kod.
a = [3, 2, 1]
A = [a a]
b = [a; a]
C = [A; b b]
Zasady dostępu do elementów z tablic wielowymiarowych są analogiczne jak w przypadku tablic jednowymiarowych. Jedyna różnica jest podanie indeksu (bądź iteratora) dla kolejnych wymiarów.
A = [1 2 3; 4 5 6; 7 8 9]
A[1,3] # Który element zostanie zwróci?
A[1:2, 2:3] # Jaka macierz zostanie zwrócona?
A[[1,3], [2, 3]] # A tutaj czego się spodziewasz?
No dobra, to, w jakim celu jednowymiarowa tablica jest typu Vector
, a nie po prostu Array
? Powód jest prosty, zmienne typu Vector
traktowane są jako wektory, a oznacza to, że co do zasady można na nich wykonywać podstawowe działania arytmetyczne znane z algebry liniowej.
a = [3, 2, 1]
b = [1, 2, 3]
c = adjoint(a) # Sprzężenie hermitowskie (transpzycja i sprzężenie zespolone)
d = 3*a + 2*b # Liniowa kombinajca wektorów
e = a' * b # Iloczny skalarny dwóch wektorów
f = a * b' # Iloczny zewnętrzny dwóch wektorów
Podobnie jest w przypadku tablic dwuwymiarowych, które są traktowane jako macierze.
A = [1 2 3; 4 5 6; 7 8 9]
b = [1, 2, 3]
c = A*b # Przekształcenie liniowe
d = b'*A*b # Forma kwadratowa
e = A' * A
Natomiast błąd zwróci takie wyrażenie jak [1, 2, 3] + 3
, bo nie da się dodać wartości skalarnej i wektora, tak jak nie da się dodać macierzy i wektora. Można to rozwiązać przez mechanizm rozgłaszania, o którym będzie krótko w następnej sekcji. Zapoznaj się, co robią następujące funkcje, bo pewnie się przydadzą:
zeros(4)
zeros(ComplexF64, 6, 4)
zeros(ComplexF64, (5, 10))
size(zeros(4, 8))
length(zeros(4, 8))
reshape(zeros(ComplexF64, 4, 8), 2, 16)
eltype(zeros(4, 8))
eltype(zeros(ComplexF64, 4, 8))
ones(4)
ones(ComplexF64, 4, 4)
ones(ComplexF64, (4, 4))
rand(4)
rand(ComplexF64, 4, 4)
rand(ComplexF64, (4, 4))
randn(4)
randn(ComplexF64, 4, 4)
randn(ComplexF64, (4, 4))
Przydane rozdziały z oficjalnego podręcznka języka programowania Julia:
Rozgłaszanie
Rozgłaszanie (ang. broadcasting) to prosty mechanizm, który ma za zadanie rozwiązać następujący problem. Jeżeli mam funkcję abs(z)
, która oblicza moduł liczby zespolonej z
, a chciałbym obliczyć moduł dla każdej liczby zespolonej w tablicy zt = randn(ComplexF64, 100)
, to jak to mogę zrobić? Na przykład pętlą for
, ale wygodniejszym rozwiązaniem jest rozgłoszenie funkcji abs
na elementy tablicy zt
w następujący sposób abs.(zt)
. Symbol .
(kropka) jest operatorem rozgłaszania. Więc jeżeli chcemy dodać skalara do wektora, to musimy rozgłosić skalar na elementy wektora.
a = [1, 2, 3]
b = a .+ 1
Dwóch wektorów kolumnowych nie da się przez siebie przemnożyć, natomiast możemy przemnożyć przez siebie odpowiednie elementy.
a = [1, 2, 3]
b = a * a # Bład
b = a .* a # Sukces
Sam mechanizm rozgłaszania jest trochę bardziej skomplikowany, niż zostało to tutaj przedstawione, natomiast takie zastosowanie rozgłaszania powinno wystarczyć na potrzeby implementacji prostych obliczeń.
Krotka
Krotka (ang. tuple), to taka jednowymiarowa lista, tyle że jak się ją stworzy, to nie można zmieniać wartość jej elementów (ang. immutable type). Aby stworzyć krotkę, trzeba wykorzystać nawiasy okręgłe. Przykład
krotka = (1, 2, 3) # Stwórz krotkę i przypisz ją do zmiennej
krotka[1] # Dobierz się do pierwszego elemetu
a, b, c = krotka # Można łatwo i szybko rozpakować sobie krotkę
@show typeof(krotka) # Zauważ że typ krotki jest bardzo konkretny
krotka[2] = 55 # Nie można tego zrobić
Krotki występują przy opazji zwracania wielu wartości z funkcji.
function foobar(a, b)
c = a*b
d = a^b
return c, d # Tutaj zwracana jest krotka, mimo że nie ma nawiasów
end
prod, power = foobar(10, 4) # A tutaj krotka jest od razu rozpakowana
Czy też podczas iterowania pętlą for
z wykorzystaniem funkcji enumerate
for (i, x) in enumerate([11, 22, 33, 44])
@show i
@show x
end
Przydate funkcje z biblioteki standardowej
N, R = 10, 3
a, b, c, d = 1.9, 2.7, 3.5, 4.3
z = 1.0 + im*1.0
T = [a, b, c, d]
# Operacja na liczbach całkowitych
div(N, R) # Dzielenie całkowite
rem(N, R) # Reszta z dzielenia
divrem(N, R) # Dzielenie i reszta za jednym razem
# Podstawowe funkcje matematyczne
sqrt(d) # Pierwiastek kwadratowy liczby x
exp(d) # Eksponenta liczy x
cos(d) # Cosinus kąta x (rad)
sin(d) # Sinus kąta x (rad)
sinc(d) # Funkcja sinc z liczby x
log(d) # Logarytm naturalny z liczby x
log2(d) # Logarytm binarny z liczby x
log10(d) # Logarytm dziesiętny z liczby x
abs(z) # Moduł liczby zespolone
angle(z) # Argument liczby zespolonej
real(z) # Część rzeczywista
imag(z) # Część urojona
floor(Int, d) # Podłoga z liczby x (zrzutowany na typ Int)
ceil(Int, d) # Sufit z liczby x (zrzutowany na typ Int)
max(a, b, c, d) # Wartość maksymalna argumentów
min(a, b, c, d) # Wartość minimalna argumentów
# Funkcje dla tablic
length(T) # Ilość elementów w tablicy
size(T) # Wymiar tablicy
sum(T) # Suma wszystkich elementów
maximum(T) # Maksymalna wartość w tablicy
minimum(T) # Minimalna wartość w tablicy
argmax(T) # Indeks maksymalne wartość w tablicy
argmin(T) # Indeks minimalne wartość w tablicy
# Alokowanie tablicy o zadanym wymiarze
# Wymiarów można dodawać w opór. Wymiar można alternatywnie podać w postaci krotki.
# Domyślnie typ Float64, można też podać inny jako pierwszy argument.
zeros(N) # Wektor o długości N o typie Float64
zeros(N, N) # Macierz o wymiarze N x N o typie Float64
zeros(N, N, N) # Tablica o wymiarze N x N x N o typie Float64
zeros(ComplexF64, N) # Wektor o długości N o typie ComplexF64
zeros(ComplexF64, N, N) # Macierz o wymiarze N x N o typie ComplexF64
zeros(ComplexF64, N, N, N) # Tablica o wymiarze N x N x N o typie ComplexF64
# Iterator
start, krok, stop = 10, 0.1, 100
start:stop
start:krok:stop
range(start, stop; step=krok)
range(start, stop; length=100)
Ciągi znakowe
Do reprezentacji ciągów znakowych służy niezmienny typ String
.
# Stwórz pusty ciąg znakowy
a = ""
@show a
# Przyłącz do ciągu `a` drugi ciąg znaków
a = a * "Już " * "nie taki pusty"
@show a
# Wypisz podciąg
@show a[3:10]
Wypisywanie na standardowe wyjście
a = "to jest"
b = "przykład"
x = randn(4, 4)
# Wypisz podane wartości
print(a, b, x)
# Wypisz podane wartości ze znakiem nowej linii na końcu
println(a, b, x)
# Wyświelt ładnie wartość (tylko jedne argument)
display(x)
# Wypisywanie wartości w ustrukturyzowany sposób
@show a b x
Wykresy
Istnieje co najmniej parę sensowych bibliotek do generowania wykresów w ekosystemie Julii, natomiast skupimy się wyłącznie na #link("https://makie.org")[Makie]. Makie jest wysokopoziomową biblioteką, która co do zasady definiuje ujednolicony interfejs programistyczny między różnymi silnikami renderującymi. My zainteresujemy się silnikiem Cairo (grafika wektorowa) i OpenGL. Moduł Makie dla silnik renderującego Cairo to CaiorMakie.jl
, natomiast dla silnika renderującego OpenGL to GLMakie.jl
. Aby zainstalować paczki wpisz w REPL:
] add CairoMakie GLMakie
i jak się skonczy proces instalacji (kompilacji), przyciśnij klawisz backspace, aby wyjść z menadzera paczek. Aby załadować paczkę CairoMakie.jl
do użytku, należy wywołać instrukcję using CairoMakie
. CairoMakie.jl
jest wspierane przez rozszerzenie julialang
w VSCode i będzie automatycznie wyświetlało wygenerowane obrazki w okienku, w VSCode. Podstawy Makie, które będą wystarczające na potrzeby labów, omówione są w #link("https://docs.makie.org/stable/tutorials/basic-tutorial/")[Makie.org - Basic Tutorial]. Proszę się zapoznać z tym materiałem. Poniżej parę przepisów jak rysować wykresy w Makie.
using CairoMakie
fs = 100
dt = 1/fs
t_1, t_2 = 0.0, 1.0
t = range(t_1, t_2; step=dt)
x = sin.(2*pi*10*t)
lines(t, x)
using CairoMakie
fs = 256
dt = 1/fs
t_1, t_2 = 0.0, 1.0
t = range(t_1, t_2; step=dt)
x = sin.(2*pi*10*t)
scatter(t, x)
using CairoMakie
fs = 256
dt = 1/fs
t_1, t_2 = 0.0, 1.0
t = range(t_1, t_2; step=dt)
x = sin.(2*pi*10*t)
lines(t, x)
scatter!(t, x)
xlims!(0.2, 0.3)
ylims!(-2.2, 2.2)
current_figure()
using CairoMakie
fs = 256
dt = 1/fs
t_1, t_2 = 0.0, 1.0
t = range(t_1, t_2; step=dt)
x = sin.(2*pi*10*t)
fig = Figure(size=(800, 400))
ax1 = Axis(fig[1,1], aspect=1)
ax2 = Axis(fig[1,2], aspect=1)
lines!(ax1, t, x)
scatter!(ax2, t, x)
current_figure()
using CairoMakie
fs = 256
dt = 1/fs
t_1, t_2 = 0.0, 1.0
t = range(t_1, t_2; step=dt)
x = sin.(2*pi*10*t)
fig = Figure(size=(800, 400))
ax1 = Axis(fig[1,1], aspect=1)
ax2 = Axis(fig[1,2], aspect=1)
lines!(ax1, t, x; label="sinus kreski")
scatter!(ax2, t, x; label="sinus kropki")
axislegend(ax1)
axislegend(ax2)
xlims!(ax1, 0.2, 0.3)
ylims!(ax1, -2.2, 2.2)
xlims!(ax2, 0.2, 0.3)
ylims!(ax2, -2.2, 2.2)
current_figure()
using CairoMakie
x = 0:10
y = 10:40
fig = Figure(size=(800, 400))
ax = Axis(fig[1,1], aspect=1)
heatmap!(ax, x, y, rand(length(x),length(y)))
current_figure()