2025-10-06 Low Rank#
Last time#
Solving least squares problems
Geometry of the SVD
Today#
Reflection on algorithm choices
Low-rank structure
Primer on interpolation
using LinearAlgebra
using Plots
default(linewidth=4, legendfontsize=12)
function vander(x, k=nothing)
if isnothing(k)
k = length(x)
end
m = length(x)
V = ones(m, k)
for j in 2:k
V[:, j] = V[:, j-1] .* x
end
V
end
function gram_schmidt_classical(A)
m, n = size(A)
Q = zeros(m, n)
R = zeros(n, n)
for j in 1:n
v = A[:,j]
R[1:j-1,j] = Q[:,1:j-1]' * v
v -= Q[:,1:j-1] * R[1:j-1,j]
R[j,j] = norm(v)
Q[:,j] = v / R[j,j]
end
Q, R
end
function qr_householder(A)
m, n = size(A)
R = copy(A)
V = [] # list of reflectors
for j in 1:n
v = copy(R[j:end, j])
v[1] += sign(v[1]) * norm(v) # <---
v = normalize(v)
R[j:end,j:end] -= 2 * v * v' * R[j:end,j:end]
push!(V, v)
end
V, R
end
function qr_chol(A)
R = cholesky(A' * A).U
Q = A / R
Q, R
end
function qr_chol2(A)
Q, R = qr_chol(A)
Q, R1 = qr_chol(Q)
Q, R1 * R
end
function peanut()
theta = LinRange(0, 2*pi, 50)
r = 1 .+ .4*sin.(3*theta) + .6*sin.(2*theta)
r' .* [cos.(theta) sin.(theta)]'
end
function circle()
theta = LinRange(0, 2*pi, 50)
[cos.(theta) sin.(theta)]'
end
function Aplot(A)
"Plot a transformation from X to Y"
X = peanut()
Y = A * X
p = scatter(X[1,:], X[2,:], label="in")
scatter!(p, Y[1,:], Y[2,:], label="out")
X = circle()
Y = A * X
q = scatter(X[1,:], X[2,:], label="in")
scatter!(q, Y[1,:], Y[2,:], label="out")
plot(p, q, layout=2, aspect_ratio=:equal)
end
Aplot (generic function with 1 method)
Condition number via SVD#
A = randn(2,2) ## nonsymmetric
#A = A + A'
2×2 Matrix{Float64}:
-0.7476 1.08969
0.487512 1.0334
@show svdvals(A)
U, S, V = svd(A)
@show norm(U - U')
Aplot(A)
svdvals(A) = [1.5227644790588872, 0.8562092695757885]
norm(U - U') = 1.5700924586837752e-16
Example: autonomous vehicles#
Need to solve least squares problems in real time
Weight/cost/size increase with compute
What algorithm to choose?
What precision to use?
Factors#
How many objects?
Speed (of robot and objects)
Aerial, wheeled, walking
Fog, light – longer memory?
Tolerences (how accurate does the solution need to be?)
Consequences of being wrong, who bears those consequences?
A = rand(5000, 500)
A_32 = Float32.(A)
@show cond(A)
@time qr(A); # Householder; backward stable
@time qr_chol(A); # Unstable
@time qr(A_32);
cond(A) = 56.40146543951136
0.202298 seconds (42.99 k allocations: 21.522 MiB, 56.51% gc time, 13.47% compilation time)
0.126691 seconds (271.75 k allocations: 37.191 MiB, 8.83% gc time, 64.42% compilation time)
0.103200 seconds (110.47 k allocations: 15.324 MiB, 7.64% gc time, 42.06% compilation time)
V = vander(LinRange(-1, 1, 20))
@show cond(V)
Q, R = qr(Float32.(V))
@show norm(Q' * Q - I)
Q, R = qr_chol(V)
@show norm(Q' * Q - I)
cond(V) = 2.7224082312417406e8
norm(Q' * Q - I) = 1.6641898f-6
norm(Q' * Q - I) = 0.1749736012761826
0.1749736012761826
Best low rank approximation#
The SVD can be truncated to yield the best rank-\(k\) approximation of a matrix.
n, k = 2, 1
A = randn(n, n)
Aplot(A)
U, S, V = svd(A)
@show S[1:k+1]
Uhat = U[:, 1:k]
Shat = S[1:k]
Vhat = V[:, 1:k]
Ahat = Uhat * diagm(Shat) * Vhat'
@show norm(Ahat)
Aplot(A - Ahat)
S[1:k + 1] = [2.230299652752217, 0.7704811470050307]
norm(Ahat) = 2.230299652752217
Example: Galaxies#
Suppose we have two galaxies of size \(n_1 = 100\) and \(n_2 = 200\), each randomly distributed around their respective centers.
galaxy(center, sigma, n) = reshape(center, 1, 3) .+ sigma*randn(n, 3)
g1 = galaxy([0 0 0], 1, 100)
g2 = galaxy([10 0 0], 1, 100)
scatter(g1[:,1], g1[:,2], aspect_ratio=:equal)
scatter!(g2[:,1], g2[:,2])
Forces between stars#
Consider the gravitational force from a star at position \(x_2\) acting on a star at position \(x_1\),
function gravity(g1, g2)
m = size(g1, 1)
n = size(g2, 1)
F = zeros(3*m, n)
for i in 0:m-1
for j in 1:n
r = g2[j,:] - g1[1+i,:]
F[1+3*i:3*(i+1),j] = r / norm(r)^3
end
end
F
end
gravity(g1, g2)
300×100 Matrix{Float64}:
0.0106383 0.00699245 0.00740756 … 0.00727092 0.00776894
0.00146269 -2.39241e-5 -0.000206345 -0.00188306 -0.000283591
-0.000963822 0.000284672 -0.000547106 -0.000633304 -0.000292133
0.0153603 0.00956796 0.010052 0.00954714 0.0106886
0.00237108 -0.000137105 -0.000436873 -0.00303367 -0.000577769
-0.00247369 8.82342e-5 -0.00127273 … -0.00138278 -0.00091052
0.011718 0.00801123 0.00872117 0.00942179 0.00917859
0.00386666 0.00107166 0.000976801 -0.00127195 0.000971326
-0.000639344 0.000643732 -0.000377622 -0.000531795 -2.68599e-5
0.0101141 0.0075187 0.00801389 0.00906729 0.00848553
0.00414464 0.00150439 0.00145124 … -0.000522076 0.00149879
-0.00132426 0.000134957 -0.00084421 -0.00109643 -0.000570612
0.0205216 0.0113415 0.0124251 0.0113496 0.0131232
⋮ ⋱
0.0154037 0.0103487 0.0106232 0.0105237 0.0114475
0.00385677 0.000519887 0.000237531 -0.00275643 0.000148895
-0.00403553 -0.000610274 -0.00216537 … -0.00238739 -0.00186007
0.0165091 0.0103901 0.011059 0.0109207 0.0117973
0.00393368 0.000429022 0.000144592 -0.00296454 4.10898e-5
-0.00245481 0.000269469 -0.00127624 -0.0014626 -0.000846407
0.0105469 0.00781179 0.00793094 0.00828917 0.0084915
0.00303115 0.000808781 0.00064279 … -0.00135293 0.000630549
-0.00296305 -0.000769551 -0.00179631 -0.00205143 -0.00162192
0.0122637 0.00851357 0.00929646 0.0102936 0.00981414
0.0046912 0.00141933 0.00135089 -0.00113591 0.001371
-0.000785178 0.000665875 -0.000468575 -0.000663133 -8.41088e-5
Spectrum#
g1 = galaxy([0 0 0], 1, 500)
g2 = galaxy([3 0 0], 1, 500)
F = gravity(g1, g2)
U, S, V = svd(F)
scatter(S, yscale=:log10, ylims=(1e-10, 10), xlims=(0, 200))
k = 200
Uhat = U[:,1:k]
Shat = S[1:k]
Vhat = V[:,1:k]
Fhat = Uhat * diagm(Shat) * Vhat'
@show norm(F)
@show norm(F - Fhat)
size(F)
norm(F) = 259.6305281734927
norm(F - Fhat) = 0.06569130964932607
(1500, 500)