Mi is az a „lineáris algebra"?
Rengeteg technikai definíció létezik róla a [Wikipédián][linalg-wiki], a tankönyvekben és kiváló weboldalakon, például a [3Blue1Brownon][3blue1brown], de a mi céljainkra nyugodtan lehetünk kötetlenebbek.
A lineáris algebra rengeteg érdekes (és nagyon hasznos) dologról szól, amit vektorokkal és mátrixokkal lehet csinálni.
A Julia (és a Python) karbantartói imádják az ilyesmit. Az Exercism vezetősége már kevésbé.
Ez a fogalom egy hatalmas témakör egyszerűbb részeire szorítkozik, de előre szólok: ez elkerülhetetlenül meglehetősen matematikai fogalom.
Ez attól függ, kit kérdezel, és hogyan szeretnél megjeleníteni valami meglehetősen absztrakt dolgot.
Vector{T} csupán a Array{T, 1} álneve, és az eltype T bármi lehet.length és direction értékkel N-dimenziós térben, de rögzített pozíció nélkül.point-ként ábrázolhatjuk N-dimenziós térben, ahol az N elem az origótól mért távolságot jelöli az N tengely mindegyike mentén.A mostani céljainkra hagyd figyelmen kívül a stringekből vagy karakterekből álló vektorokat.
Ebben a fogalomban numerikus típusokkal fogunk dolgozni: Int, Float vagy Complex.
Itt is többféle választ találsz majd.
linear combination-ja, egymás mellé rendezve.transformation, amely alkalmazható egy vektorra, éppúgy, ahogy egy function-t más bemeneti típusokra alkalmazunk.A négyzetes mátrixok néhány típusa elég gyakori ahhoz, hogy külön nevet kapjon.
Átlós mátrix: Minden nem nulla elem a main diagonal-on (bal felsőtől a jobb alsóig) található.
julia> [1 0 0; 0 2 0; 0 0 3]
3×3 Matrix{Int64}:
1 0 0
0 2 0
0 0 3
# a convenient shortcut
julia> diagm(1:3)
3×3 Matrix{Int64}:
1 0 0
0 2 0
0 0 3
Egységmátrix: Olyan átlós mátrix, amelynek az átlójában csak egyesek állnak (olyan okokból, amelyek később világosabbá válnak majd).
Gyakran I-vel rövidítik.
julia> Matrix{Float64}(I, 3, 3)
3×3 Matrix{Float64}:
1.0 0.0 0.0
0.0 1.0 0.0
0.0 0.0 1.0
Felső háromszögmátrix: Nem nulla értékek az átlón és felette, nullák alatta. Az alsó háromszögmátrixot kitalálhatod magad is.
Ha tényleg fel akarod cserélni a sorokat az oszlopokkal, a permutedims() függvény megteszi, és egy új mátrixot ad vissza.
Ez másolással jár, ami nagy mátrixoknál lassú és memóriaigényes.
Lineáris algebrai célokra a transpose() függvény hasznosabb, mert gyorsan egy lazy burkolót hoz létre az eredeti mátrix körül.
Még hasznosabb az adjoint() függvény, amely a komplex számok képzetes részének előjelét is megfordítja.
Ez a művelet elég gyakori ahhoz, hogy egyszerűen egy aposztrófot (') tegyünk a változó neve után, így létrehozva az adjungáltat.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
# new, full copy
julia> permutedims(m)
3×2 Matrix{Int64}:
1 4
2 5
3 6
# lazy version
julia> transpose(m)
3×2 transpose(::Matrix{Int64}) with eltype Int64:
1 4
2 5
3 6
julia> mc = [1+2im 2+3im; 3+2im 1+2im]
2×2 Matrix{Complex{Int64}}:
1+2im 2+3im
3+2im 1+2im
# lazy conjugate transpose
julia> adjoint(mc)
2×2 adjoint(::Matrix{Complex{Int64}}) with eltype Complex{Int64}:
1-2im 3-2im
2-3im 1-2im
# syntactic sugar for adjoint
julia> mc'
2×2 adjoint(::Matrix{Complex{Int64}}) with eltype Complex{Int64}:
1-2im 3-2im
2-3im 1-2im
A dokumentum további része a LinearAlgebra modul funkcióit használja.
Egy using LinearAlgebra sor behozza a névtérbe, de ezt nem fogjuk minden példában megismételni (túl sok vizuális zaj).
Erről a [Vektorműveletek][vector-ops] fogalomban volt szó.
A műveletjelet a .* jelöli, amely páronként működik a bemeneti vektorokon, és akkora, illetve olyan típusú kimenetet ad, mint a bemenetek.
julia> [1, 2] .* [3, 4]
2-element Vector{Int64}:
3
8
Ezt a rendkívül gyakori műveletet a tankönyvek u ⋅ v alakban írják, és „dot” szorzatnak nevezik.
A skaláris szorzat az elemenkénti szorzat összegével egyenlő.
Két vektor a szokásos * operátorral összeszorozható, de csak akkor, ha a bal oldali vektor adjoint: ezt kényelmesen u' * v alakban írjuk.
Ez a későbbi, mátrixszorzásról szóló részben válik majd világosabbá.
A kiemelt (középső) pont elérhető Juliában (\cdot és tab) a dot() függvény szintaktikai cukorkájaként.
Nem kell megadni az adjungáltat, mert ezt a részletet automatikusan kezeli.
julia> using LinearAlgebra
# with element-wise syntax
julia> sum([1, 2] .* [3, 4])
11
# with u' * v syntax
julia> [1, 2]' * [3, 4]
11
# with dot()
julia> dot([1, 2], [3, 4])
11
# with \cdot syntax
julia> [1, 2] ⋅ [3, 4]
11
Komplex értékű vektorok esetén a bal oldali vektornak a konjugáltnak kell lennie (a képzetes rész előjele megfordul).
A dot függvény ezt automatikusan elvégzi, az u' szintaxis kifejezetten megteszi, a sum(u .* v) viszont hibára fut, ha u és v komplex.
Mennyire „nagy” egy vektor?
A norm ezt próbálja megragadni azzal, hogy a vektort egy megfelelő skalárra redukálja.
A normáknak egy egész családját definiálják, de messze a leggyakoribb a 2-es norma, amely √(v ⋅ v).
Ez a négyzetes közép típusú művelet a pitagoraszi távolság az origótól (N-dimenziós térben). Ha a vektort nyílként képzeljük el, amelynek a farka az origóban van, akkor a 2-es norma a nyíl hossza.
Bármely p-norma kiszámítható úgy, hogy a p-t adjuk meg második argumentumként.
Az 1-es norma néha hasznos: egyszerűen az elemek abszolút értékeinek összege (így nagyon gyors és könnyű kiszámítani).
# defaults to the 2-norm
julia> norm([1, 2, 3])
3.7416573867739413
# the 1-norm
julia> norm([1, -2, 3], 1)
6.0
Néha úgy tűnik, mintha a világ minden alkalmazott matematikusa az elmúlt 80 év nagy részét azzal töltötte volna, hogy minden számítást mátrixszorzások sorozatává alakítson.
Ez olyan művelet, amiben a számítógépek nagyon jók:
A részletek meglehetősen egyszerűek, bár első ránézésre nem túl szemléletesek.
Vegyük például, hogy az A mátrix megszoroz egy v vektort, és w kimenetet ad (a lineáris algebra szokása szerint a mátrixokat nagybetűvel, a vektorokat kisbetűvel jelöljük).
Az A legfelső sorát skalárisan megszorozzuk v-vel, hogy megkapjuk a w első elemét, a második sor adja a második elemet, és így tovább lefelé.
julia> A = [1 2; 3 4]
2×2 Matrix{Int64}:
1 2
3 4
julia> v = [5, 6]
2-element Vector{Int64}:
5
6
julia> A * v
2-element Vector{Int64}:
17 # equals [1, 2] ⋅ [5, 6]
39 # equals [3, 4] ⋅ [5, 6]
A mátrix * mátrix szorzás ezt kiterjeszti a jobb oldali mátrix oszlopaira.
A C = A * B esetén úgy tekinthetünk rá, hogy A megszorozza B minden oszlopát, így megkapva C megfelelő oszlopát: mátrix-vektor szorzások sorozatát.
Ezzel egyenértékűen mondhatjuk, hogy az A első sorát B minden oszlopával skalárisan megszorozzuk, így megkapva a C legfelső sorát, a második sor adja a második sort, és így tovább lefelé.
julia> A
2×2 Matrix{Int64}:
1 2
3 4
julia> B = [5 7; 6 8]
2×2 Matrix{Int64}:
5 7
6 8
# left column is the same as A*v previously
julia> A * B
2×2 Matrix{Int64}:
17 23
39 53
julia> B * A
2×2 Matrix{Int64}:
26 38
30 44
Ahogy a fenti példa is mutatja, a mátrixszorzás nem kommutatív: nincs egyszerű kapcsolat A*B és B*A között.
A mátrixszorzást valószínűleg nehéz vizuálisan elképzelni, ha valaki most találkozik vele először, pusztán a szöveget olvasva. A YouTube-on rengeteg videó mutatja be grafikusan, ezért keress rá a „matrix multiplication” kifejezésre, és válassz egyet a neked tetsző stílusban, részletességgel és nyelven.
Két vektor skaláris szorzata azon múlik, hogy egyenlő hosszúak.
Ezt kiterjesztve, mátrixszorzásnál a bal oldali mátrix oszlopainak száma meg kell egyezzen a jobb oldali mátrix sorainak számával.
A méreteket (nrows, ncols) párokként kifejezve, ahogy a size(A) kiadja, (a, b) * (b, c) -> (a, c) adódik.
A „belső” dimenziók, itt b és b, alkalmasak a skaláris szorzat elvégzésére.
A „külső” dimenziók, itt a és c, határozzák meg a kimenet méreteit.
Példa téglalap alakú mátrixokkal:
julia> D = reshape(1:6, 2, 3)
2×3 reshape(::UnitRange{Int64}, 2, 3) with eltype Int64:
1 3 5
2 4 6
julia> E = reshape(1:12, 3, 4)
3×4 reshape(::UnitRange{Int64}, 3, 4) with eltype Int64:
1 4 7 10
2 5 8 11
3 6 9 12
julia> D * E
2×4 Matrix{Int64}:
22 49 76 103
28 64 100 136
# (3, 4) * (2, 3) not possible
julia> E * D
ERROR: DimensionMismatch: matrix A has axes (Base.OneTo(3),Base.OneTo(4)), matrix B has axes (Base.OneTo(2),Base.OneTo(3))
Vektorpárok mátrix módra való szorzásának két lehetősége van.
A szokás szerint az u' * v-t használnánk a skaláris szorzat megfelelőjeként.
A dimenziók (1, 3) * (3, 1), és a Julia a (1, 1) kimenetet skalárra egyszerűsíti (ellentétben például az R-rel).
Alternatívaként használhatjuk az u * v'-t, amelynek dimenziói (3, 1) * (1, 3) -> (3, 3), hogy minden lehetséges elempárt összeszorozzunk, és a vektorokat mátrixszá bővítsük.
julia> u = [1, 2]
2-element Vector{Int64}:
1
2
julia> v = [3, 4]
2-element Vector{Int64}:
3
4
julia> u' * v
11
julia> u * v'
2×2 Matrix{Int64}:
3 4
6 8
Az u' * v-t néha belső szorzatnak nevezik.
A mátrixszorzás egy különösen gyakori fajtája a forgatómátrixokkal való szorzás.
Kétdimenzióban van egy viszonylag egyszerű mátrix, amely θ radiánnal forgatja el a vektort az óramutató járásával ellentétes irányba.
julia> rot2d(θ, vec) = [cos(θ) -sin(θ); sin(θ) cos(θ)] * vec
rot2d (generic function with 1 method)
# unit vector in the x direction
julia> i_hat = [1, 0]
2-element Vector{Int64}:
1
0
# rotate 45 degrees
julia> rot2d(π/4, i_hat)
2-element Vector{Float64}:
0.7071067811865476
0.7071067811865475
# rotate 90 degrees -> unit vector in the y direction, j_hat
julia> rot2d(π/2, i_hat)
2-element Vector{Float64}:
6.123233995736766e-17 # zero, within numerical error
1.0
Egy robotikai startupnál dolgozol, amely egy egyszerű robotot fejleszt koncepcióbizonyítékként. Az a feladatod, hogy olyan funkcionalitást biztosíts, amellyel a robot mozgása vezérelhető.
Hogy nyomon követhessük a robot orientációját és kiterjedését, három jelölője van, amelyek 1 egység távolságra vannak a középpontjától. A pozíció inicializálásához vegyük a három irányvektort, normalizáljuk őket, és tegyük őket egy mátrixba.
Valósítsd meg az orientrobot(vectors) függvényt, amely három vektorból álló vektort kap.
Adj vissza egy 2x3 mátrixot, amelynek oszlopaiban a normalizált vektorok szerepelnek.
julia> orientrobot([[-1,1],[1,0],[-1,-1]])
2×3 Matrix{Float64}:
-0.707107 1.0 -0.707107
0.707107 0.0 -0.707107
Ezután arra van szükségünk, hogy meg tudjuk változtatni a mozgás irányát. Ehhez el kell forgatnunk a robotot, hogy abba az irányba nézzen, amerre menni szeretne.
Valósítsd meg a rotaterobot(orientation, θ) függvényt, amely a robot orientációs mátrixát és egy θ szöget kap, amellyel az óramutató járásával ellentétes irányban elforgatjuk.
Add vissza az új orientációs mátrixot.
julia> orientmatrix = initialize([[-1,1],[1,0],[-1,-1]]);
julia> rotaterobot(orientmatrix, π/2)
2×3 Matrix{Float64}:
-0.707107 6.12323e-17 0.707107
-0.707107 1.0 -0.707107
Ahhoz, hogy a robotot egyik helyről a másikra mozgassuk, először ellenőriznünk kell, hogy megfelelő-e az orientációja, mielőtt elmozdítanánk. Az orientációs mátrix második oszlopa a robot előrefelé néző irányát adja meg.
Valósítsd meg a robotoriented(orientation, direction) függvényt, amely egy orientációs mátrixot és egy relatív pozícióvektort kap.
Add vissza a true értéket, ha a robot ugyanabba az irányba néz, mint a relatív pozícióvektor (a kerekítési hibán belül).
julia> orientmatrix = initialize([[-1,1],[1,0],[-1,-1]]);
julia> robotoriented(orientmatrix, [5, 0])
true
julia> robotoriented(orientmatrix, [0, 5])
false
julia> robotoriented(orientmatrix, [-5, 0])
false
Mivel az orientációs mátrix a robot alakját is nyomon követi, tudnunk kell, hogy ezek a pontok hol helyezkednek el az origóhoz képest, miután a robotot elmozdítottuk. Ez segít a robotnak elkerülni az ütközéseket más objektumokkal, amikor mozog.
Valósítsd meg a bodylocation(orientation, position) függvényt, amely egy orientációs mátrixot és a robot középpontjának aktuális pozícióját kapja.
Add vissza az eltolt orientációs mátrixot.
julia> orientmatrix = initialize([[-1,1],[1,0],[-1,-1]]);
julia> bodylocation(orientmatrix, [5, 3])
2×3 Matrix{Float64}:
4.29289 6.0 4.29289
3.70711 3.0 2.29289
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) Julia nyelvet 35 fogalom128 feladat segítségével, valódi emberi mentorálással, mindez ingyen.