"रैखिक बीजगणित" क्या है?
इसकी बहुत सारी तकनीकी परिभाषाएँ हैं: [Wikipedia][linalg-wiki] पर, पाठ्यपुस्तकों में, और [3Blue1Brown][3blue1brown] जैसी बेहतरीन वेबसाइटों पर। लेकिन यहाँ हमारे लिए इसे थोड़ा सरल रखना ठीक रहेगा।
रैखिक बीजगणित उन ढेरों रोचक (और बहुत उपयोगी) चीज़ों के बारे में है जो आप सदिशों और आव्यूहों के साथ कर सकते हैं।
Julia (और Python) के मेंटेनरों को ऐसी बातें बहुत पसंद हैं। Exercism के प्रबंधन को उतनी नहीं।
यह कॉन्सेप्ट इतने बड़े विषय के सिर्फ आसान पहलुओं तक सीमित रहेगा, लेकिन एक बात याद रखिए: यह अनिवार्य रूप से काफी गणितीय कॉन्सेप्ट है।
यह इस पर निर्भर करता है कि आप किससे पूछते हैं और किसी अमूर्त चीज़ की आप कैसी कल्पना करना चाहते हैं।
Vector{T} तो बस Array{T, 1} का दूसरा नाम है, और eltype T कुछ भी हो सकता है।length और direction तय होते हैं, पर जिसकी कोई तय जगह नहीं होती।point के रूप में दिखा सकते हैं, जहाँ N एलिमेंट बताते हैं कि मूल बिंदु से हर एक N अक्ष के साथ कितनी दूरी है।अभी के लिए स्ट्रिंग या अक्षरों के सदिशों को छोड़ दीजिए।
इस कॉन्सेप्ट में हम संख्यात्मक टाइप के साथ काम करेंगे: Int, Float, या Complex.
यहाँ भी आपको तरह-तरह के जवाब मिलेंगे।
linear combination, जो बगल-बगल रखे हों।transformation जिसे किसी सदिश पर लागू किया जा सकता है, ठीक वैसे ही जैसे किसी function को दूसरे इनपुट टाइप पर लागू किया जाता है।कुछ वर्ग आव्यूह इतने आम हैं कि उनके खास नाम हैं।
विकर्ण आव्यूह: इसकी सभी गैर-शून्य प्रविष्टियाँ main diagonal पर होती हैं (ऊपर-बाएँ से नीचे-दाएँ)।
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
तत्समक आव्यूह: एक विकर्ण आव्यूह जिसके विकर्ण पर सिर्फ 1 होते हैं (इसका कारण आगे और साफ होगा)।
इसे अक्सर I से लिखा जाता है।
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
ऊपरी त्रिभुजाकार आव्यूह: विकर्ण पर और उसके ऊपर गैर-शून्य वैल्यू रहती हैं, नीचे शून्य। निचला त्रिभुजाकार आव्यूह का अंदाज़ा आप खुद लगा सकते हैं।
अगर आप सचमुच पंक्तियों को कॉलमों से बदलना चाहते हैं, तो permutedims() फंक्शन यही करेगा और आपको एक नया आव्यूह देगा।
इसमें कॉपी करना पड़ता है, जो बड़े आव्यूहों के साथ काम करते समय धीमा भी होता है और बहुत मेमोरी भी खाता है।
रैखिक बीजगणित के लिहाज़ से transpose() फंक्शन ज़्यादा काम का है, क्योंकि यह मूल आव्यूह के चारों ओर जल्दी से एक लेज़ी रैपर बना देता है।
और भी ज़्यादा काम का है adjoint() फंक्शन, जो किसी भी कॉम्प्लेक्स संख्या के काल्पनिक भाग का चिह्न भी पलट देता है।
यह ऑपरेशन इतना आम है कि संयुग्मी बनाने के लिए हमें वेरिएबल के नाम के आगे सिर्फ एक अपॉस्ट्रॉफी ' लगानी पड़ती है।
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
इस दस्तावेज़ के बाकी हिस्से में LinearAlgebra मॉड्यूल की सुविधाएँ शामिल हैं।
एक using LinearAlgebra लाइन इसे नेमस्पेस में ले आती है, पर हम उदाहरणों में इसे बार-बार नहीं दोहराएँगे (देखने में बहुत ज़्यादा भीड़ हो जाएगी)।
इस पर [सदिश संक्रियाएँ][vector-ops] कॉन्सेप्ट में चर्चा हो चुकी है।
ऑपरेटर .* है, जो इनपुट सदिशों पर जोड़ी-जोड़ी में काम करता है और ऐसा आउटपुट देता है जिसका साइज़ और टाइप इनपुट जैसा ही होता है।
julia> [1, 2] .* [3, 4]
2-element Vector{Int64}:
3
8
यह बेहद आम ऑपरेशन पाठ्यपुस्तकों में u ⋅ v लिखा जाता है और इसे "डॉट" गुणनफल कहा जाता है।
डॉट गुणनफल, एलिमेंट-वाइज़ गुणनफल के जोड़ के बराबर होता है।
दो सदिशों को आम * ऑपरेटर से गुणा किया जा सकता है, पर तभी जब बाईं ओर वाला सदिश एक adjoint हो: इसे सुविधा के लिए u' * v लिखा जाता है।
यह आव्यूह गुणन वाले आगे के हिस्से में और साफ हो जाना चाहिए।
ऊपर उठा हुआ (बीच वाला) डॉट Julia में उपलब्ध है (\cdot टैब से टाइप किया जाता है), जो dot() फंक्शन के लिए सिंटैक्टिक शुगर है।
संयुग्मी बताने की यहाँ ज़रूरत नहीं है, क्योंकि यह बात अपने आप संभाल ली जाती है।
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
कॉम्प्लेक्स वैल्यू वाले सदिशों के लिए बाईं ओर वाले सदिश को संयुग्मी होना चाहिए (काल्पनिक भाग का चिह्न पलटा हुआ)।
dot फंक्शन यह अपने आप कर देता है, u' सिंटैक्स इसे साफ-साफ करता है, पर अगर u और v कॉम्प्लेक्स हैं तो sum(u .* v) फेल हो जाएगा।
कोई सदिश कितना "बड़ा" होता है?
norm इसी को पकड़ने की कोशिश है, जो सदिश को एक उपयुक्त अदिश में बदल देती है।
नॉर्म का एक पूरा परिवार परिभाषित किया जाता है, पर सबसे आम है 2-नॉर्म, जो √(v ⋅ v) होता है।
यह रूट-मीन-स्क्वेयर ऑपरेशन मूल बिंदु से पाइथागोरस दूरी है (N-आयामी अंतरिक्ष में)। अगर हम सदिश की कल्पना एक तीर के रूप में करें, जिसकी पूँछ मूल बिंदु पर हो, तो 2-नॉर्म उस तीर की लंबाई होती है।
कोई भी p-नॉर्म निकाला जा सकता है, बस p को दूसरा आर्गुमेंट देकर।
1-नॉर्म कभी-कभी काम आता है: यह प्रविष्टियों के निरपेक्ष मानों का जोड़ ही होता है (इसलिए निकालना बहुत तेज़ और आसान है)।
# defaults to the 2-norm
julia> norm([1, 2, 3])
3.7416573867739413
# the 1-norm
julia> norm([1, -2, 3], 1)
6.0
कभी-कभी ऐसा लगता है जैसे दुनिया के हर अनुप्रयुक्त गणितज्ञ ने पिछले 80 सालों का बड़ा हिस्सा हर गणना को आव्यूह गुणन की एक श्रृंखला में बदलने में लगा दिया है।
यह एक ऐसा ऑपरेशन है जिसमें कंप्यूटर बहुत अच्छे हैं:
इसका विवरण काफी सरल है, हालाँकि पहली नज़र में यह बहुत सहज नहीं लगता।
मान लीजिए एक आव्यूह A, एक सदिश v से गुणा होकर आउटपुट w देता है (रैखिक बीजगणित की परिपाटी के अनुसार, आव्यूहों के लिए हम बड़े अक्षर और सदिशों के लिए छोटे अक्षर इस्तेमाल करेंगे)।
A की सबसे ऊपर वाली पंक्ति का v के साथ डॉट गुणनफल लेने पर w का पहला एलिमेंट मिलता है, दूसरी पंक्ति से दूसरा एलिमेंट, और इसी तरह नीचे तक।
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]
आव्यूह * आव्यूह गुणन इसे दाईं ओर वाले आव्यूह के सभी कॉलमों पर लागू करता है।
C = A * B के लिए हम मान सकते हैं कि A, B के हर कॉलम को गुणा करके C का उसके जैसा कॉलम देता है: यानी आव्यूह-सदिश गुणन की एक श्रृंखला।
इसी तरह यह भी कह सकते हैं कि A की पहली पंक्ति का B के हर कॉलम के साथ डॉट गुणनफल लेने पर C की सबसे ऊपर वाली पंक्ति मिलती है, दूसरी पंक्ति से दूसरी पंक्ति, और इसी तरह नीचे तक।
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
जैसा ऊपर के उदाहरण में दिखाया गया है, आव्यूह गुणन क्रम-विनिमेय नहीं होता: A*B और B*A के बीच कोई सीधा संबंध नहीं होता।
जो लोग इसे पहली बार समझ रहे हैं, उनके लिए सिर्फ शब्द पढ़कर आव्यूह गुणन की कल्पना करना शायद मुश्किल होगा। YouTube पर इसे ग्राफ़िक रूप से समझाने वाले ढेरों वीडियो हैं, तो "matrix multiplication" खोजिए और अपनी पसंद की शैली, विस्तार और भाषा वाला वीडियो चुन लीजिए।
दो सदिशों का डॉट गुणनफल इस पर निर्भर करता है कि उनकी लंबाई बराबर हो।
इसी तरह, आव्यूह गुणन के लिए बाएँ आव्यूह के कॉलमों की संख्या दाएँ आव्यूह की पंक्तियों की संख्या से मेल खानी चाहिए।
साइज़ को (nrows, ncols) टपल के रूप में लिखें, जैसे size(A) देता है, तो हमें (a, b) * (b, c) -> (a, c) मिलता है।
यहाँ "भीतरी" आयाम, यानी b और b, डॉट गुणनफल लेने के लिए मेल खाते हैं।
"बाहरी" आयाम, यानी a और c, आउटपुट के आयाम तय करते हैं।
आयताकार आव्यूहों वाला एक उदाहरण:
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))
सदिशों की जोड़ियों को आव्यूह की तरह गुणा करने के दो तरीके हैं।
परिपाटी के अनुसार, हम u' * v को डॉट गुणनफल के बराबर इस्तेमाल करेंगे।
इसके आयाम (1, 3) * (3, 1) होते हैं, और Julia (1, 1) आउटपुट को सरल करके एक अदिश बना देता है (उदाहरण के लिए R की तरह नहीं)।
इसके बजाय हम u * v' इस्तेमाल कर सकते हैं, जिसके आयाम (3, 1) * (1, 3) -> (3, 3) होते हैं, जिससे एलिमेंटों की सभी संभव जोड़ियाँ गुणा होती हैं और सदिश एक आव्यूह में फैल जाते हैं।
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
u' * v को कभी-कभी आंतरिक गुणनफल कहा जाता है।
आव्यूह गुणन का एक खास तौर पर आम प्रकार घूर्णन आव्यूहों से जुड़ा है।
2D में एक सदिश को θ रेडियन वामावर्त घुमाने के लिए तुलनात्मक रूप से सरल आव्यूह होता है।
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
आप एक रोबोटिक्स स्टार्टअप के लिए काम कर रहे हैं, जो प्रूफ-ऑफ-कॉन्सेप्ट के तौर पर एक सरल रोबोट बना रहा है। आपको रोबोट की गति को नियंत्रित करने के लिए कुछ कार्यक्षमता देने का काम सौंपा गया है।
रोबोट के ओरिएंटेशन और विस्तार का हिसाब रखने के लिए उस पर तीन मार्कर लगे हैं, जो उसके केंद्र से 1 इकाई की दूरी पर हैं। उसकी स्थिति को इनिशियलाइज़ करने के लिए हमें तीनों दिशा वेक्टर लेकर उन्हें नॉर्मलाइज़ करना है और एक मैट्रिक्स में रखना है।
orientrobot(vectors) फंक्शन बनाइए, जो तीन वेक्टरों वाला एक वेक्टर लेता है।
नॉर्मलाइज़ किए गए वेक्टरों को कॉलम के रूप में रखने वाला एक 2x3 मैट्रिक्स लौटाइए।
julia> orientrobot([[-1,1],[1,0],[-1,-1]])
2×3 Matrix{Float64}:
-0.707107 1.0 -0.707107
0.707107 0.0 -0.707107
इसके बाद, हमें गति की दिशा बदलने की कार्यक्षमता चाहिए। इसके लिए हमें रोबोट को उस दिशा की ओर घुमाना है जिधर उसे जाना है।
rotaterobot(orientation, θ) फंक्शन बनाइए, जो रोबोट का ओरिएंटेशन मैट्रिक्स और एक कोण θ लेता है, जिससे रोबोट को वामावर्त दिशा में घुमाया जाता है।
नया ओरिएंटेशन मैट्रिक्स लौटाइए।
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
रोबोट को एक जगह से दूसरी जगह ले जाने के लिए पहले यह जाँचना होता है कि उसे हिलाने से पहले उसका ओरिएंटेशन सही है या नहीं। ओरिएंटेशन मैट्रिक्स का दूसरा कॉलम उसकी सामने की दिशा बताता है।
robotoriented(orientation, direction) फंक्शन बनाइए, जो एक ओरिएंटेशन मैट्रिक्स और एक सापेक्ष स्थिति वेक्टर लेता है।
अगर रोबोट उसी दिशा में है जिस दिशा में सापेक्ष स्थिति वेक्टर है (राउंडिंग एरर की सीमा के भीतर), तो true लौटाइए।
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
चूँकि ओरिएंटेशन मैट्रिक्स रोबोट के आकार का भी हिसाब रखता है, इसलिए रोबोट को हिलाने के बाद हमें यह जानना होता है कि ये बिंदु मूल बिंदु के सापेक्ष कहाँ हैं। इससे रोबोट को इधर-उधर चलते समय दूसरी वस्तुओं से टकराने से बचने में मदद मिलेगी।
bodylocation(orientation, position) फंक्शन बनाइए, जो एक ओरिएंटेशन मैट्रिक्स और रोबोट के केंद्र की वर्तमान स्थिति लेता है।
स्थानांतरित ओरिएंटेशन मैट्रिक्स लौटाइए।
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
Exercism पर साइन अप कीजिए और Julia को 35 कॉन्सेप्ट128 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।