using InstantiateFromURL # optionally add arguments to force installation: instantiate = true, precompile = true github_project("QuantEcon/quantecon-notebooks-julia", version = "0.8.0") using LinearAlgebra, Statistics, BenchmarkTools, Random Random.seed!(42); # seed random numbers for reproducibility A = I(2) cond(A) ϵ = 1E-6 A = [1.0 0.0 1.0 ϵ] cond(A) inv(A) det(A) @show det(1000 * A) @show cond(1000 * A); @show cond(A) @show cond(inv(A)); lauchli(N, ϵ) = [ones(N)'; ϵ * I(N)]' ϵ = 1E-8 L = lauchli(3, ϵ) |> Matrix @show cond(L) @show cond(L' * L); sort(sqrt.(Complex.(eigen(L*L').values)), lt = (x,y) -> abs(x) < abs(y)) sqrt.([3 + ϵ^2, ϵ^2, ϵ^2]) |> sort svd(L).S |> sort N = 3 A = lauchli(N, 1E-7)' |> Matrix b = rand(N+1) x_sol_1 = A \ b # using a least-squares solver x_sol_2 = (A' * A) \ (A' * b) # forming the normal equation ourselves norm(x_sol_1 - x_sol_2) N = 5 f(x) = exp(x) x = range(0.0, 10.0, length = N+1) y = f.(x) # generate some data to interpolate A = [x_i^n for x_i in x, n in 0:N] A_inv = inv(A) c = A_inv * y norm(A * c - f.(x), Inf) cond(A) N = 10 f(x) = exp(x) x = range(0.0, 10.0, length = N+1) y = f.(x) # generate some data to interpolate A = [x_i^n for x_i in x, n in 0:N] A_inv = inv(A) c = A_inv * y norm(A * c - f.(x), Inf) N = 20 f(x) = exp(x) x = range(0.0, 10.0, length = N+1) y = f.(x) # generate some data to interpolate A = [x_i^n for x_i in x, n in 0:N] A_inv = inv(A) c = A_inv * y norm(A * c - f.(x), Inf) cond(A) c = A \ y norm(A * c - f.(x), Inf) using Plots N_display = 100 g(x) = 1/(1 + 25x^2) x_display = range(-1, 1, length = N_display) y_display = g.(x_display) # interpolation N = 5 x = range(-1.0, 1.0, length = N+1) y = g.(x) A_5 = [x_i^n for x_i in x, n in 0:N] c_5 = A_5 \ y # use the coefficients to evaluate on x_display grid B_5 = [x_i^n for x_i in x_display, n in 0:N] # calculate monomials for display grid y_5 = B_5 * c_5 # calculates for each in x_display_grid plot(x_display, y_5, label = "P_5(x)") plot!(x_display, y_display, w = 3, label = "g(x)") N = 9 x = range(-1.0, 1.0, length = N+1) y = g.(x) A_9 = [x_i^n for x_i in x, n in 0:N] c_9 = A_9 \ y # use the coefficients to evaluate on x_display grid B_9 = [x_i^n for x_i in x_display, n in 0:N] # calculate monomials for display grid y_9 = B_9 * c_9 # calculates for each in x_display_grid plot(x_display, y_9, label = "P_9(x)") plot!(x_display, y_display, w = 3, label = "g(x)") using ApproxFun N = 10000 S = Chebyshev(-1.0..1.0) # form Chebyshev basis x = points(S, N) # chooses Chebyshev nodes y = g.(x) g_approx = Fun(S,ApproxFun.transform(S,y)) # transform fits the polynomial @show norm(g_approx.(x) - g.(x), Inf) plot(x_display, g_approx.(x_display), label = "P_10000(x)") plot!(x_display, g.(x_display), w = 3, label = "g(x)") using LinearAlgebra, IterativeSolvers, Statistics α = 0.1 N = 100 Q = Tridiagonal(fill(α, N-1), [-α; fill(-2α, N-2); -α], fill(α, N-1)) r = range(0.0, 10.0, length=N) ρ = 0.05 A = ρ * I - Q v_direct = A \ r mean(v_direct) using IterativeSolvers, LinearAlgebra, SparseArrays v = zeros(N) jacobi!(v, A, r, maxiter = 40) @show norm(v - v_direct, Inf) v = zeros(N) gauss_seidel!(v, A, r, maxiter = 40) @show norm(v - v_direct, Inf); v = zeros(N) sor!(v, A, r, 1.1, maxiter = 40) @show norm(v - v_direct, Inf); N = 100 A = sprand(100, 100, 0.1) # 10 percent non-zeros A = A * A' # easy way to generate a symmetric positive-definite matrix @show isposdef(A) b = rand(N) x_direct = A \ b # sparse direct solver more appropriate here cond(Matrix(A * A')) x = zeros(N) sol = cg!(x, A, b, log=true, maxiter = 1000) sol[end] AP = A * inv(Diagonal(A)) @show cond(Matrix(A)) @show cond(Matrix(AP)); using Preconditioners x = zeros(N) P = DiagonalPreconditioner(A) sol = cg!(x, A, b, log=true, maxiter = 1000) sol[end] using IncompleteLU x = zeros(N) P = ilu(A, τ = 0.1) sol = cg!(x, A, b, Pl = P, log=true, maxiter = 1000) sol[end] x = zeros(N) P = AMGPreconditioner{RugeStuben}(A) sol = cg!(x, A, b, Pl = P, log=true, maxiter = 1000) sol[end] using IterativeSolvers N = 10 f(x) = exp(x) x = range(0.0, 10.0, length = N+1) y = f.(x) # generate some data to interpolate A = sparse([x_i^n for x_i in x, n in 0:N]) c = zeros(N+1) # initial guess required for iterative solutions results = gmres!(c, A, y, log=true, maxiter = 1000) println("cond(A) = $(cond(Matrix(A))), $(results[end]) Norm error $(norm(A*c - y, Inf))") N = 10 f(x) = exp(x) x = range(0.0, 10.0, length = N+1) y = f.(x) # generate some data to interpolate A = [x_i^n for x_i in x, n in 0:N] P = ilu(sparse(A), τ = 0.1) c = zeros(N+1) # initial guess required for iterative solutions results = gmres!(c, A, y, Pl = P,log=true, maxiter = 1000) println("$(results[end]) Norm error $(norm(A*c - y, Inf))") α = 0.1 N = 100 Q = Tridiagonal(fill(α, N-1), [-α; fill(-2α, N-2); -α], fill(α, N-1)) r = range(0.0, 10.0, length=N) ρ = 0.05 A = ρ * I - Q v = zeros(N) results = gmres!(v, A, r, log=true) v_sol = results[1] println("$(results[end])") A_mul(x) = [ (ρ + α) * x[1] - α * x[2]; [-α * x[i-1] + (ρ + 2*α) * x[i] - α * x[i+1] for i in 2:N-1]; # comprehension - α * x[end-1] + (ρ + α) * x[end]] x = rand(N) @show norm(A * x - A_mul(x)) # compare to matrix; using LinearMaps A_map = LinearMap(A_mul, N) # map uses the A_mul function x = rand(N) @show norm(A_map * x - A * x) y = similar(x) mul!(y, A_map, x) # in-place multiplication @show norm(y - A * x) @show size(A_map) @show norm(Matrix(A_map) - A) @show nnz(sparse(A_map)); typeof(A_map) <: AbstractArray results = gmres(A_map, r, log = true) # Krylov method using the matrix-free type println("$(results[end])") function A_mul!(y, x) # in-place version y[1] = (ρ + α) * x[1] - α * x[2] for i in 2:N-1 y[i] = -α * x[i-1] + (ρ + 2α) * x[i] -α * x[i+1] end y[end] = - α * x[end-1] + (ρ + α) * x[end] return y end A_map_2 = LinearMap(A_mul!, N, ismutating = true) # ismutating == in-place v = zeros(N) @show norm(A_map_2 * v - A * v) # can still call with * and have it allocate results = gmres!(v, A_map_2, r, log=true) # in-place gmres println("$(results[end])") B = 2.0 * A_map + I # composite linear operator B * rand(N) # left-multiply works with the composition typeof(B) Q_mul(x) = [ -α * x[1] + α * x[2]; [α * x[i-1] - 2*α * x[i] + α*x[i+1] for i in 2:N-1]; # comprehension α * x[end-1] - α * x[end];] Q_map = LinearMap(Q_mul, N) A_composed = ρ * I - Q_map # map composition, performs no calculations @show norm(A - sparse(A_composed)) # test produces the same matrix gmres(A_composed, r, log=true)[2] M = 1000 N = 10000 σ = 0.1 β = rand(M) # simulate data X = sprand(N, M, 0.1) y = X * β + σ * randn(N) β_direct = X \ y results = lsmr(X, y, log = true) β_lsmr = results[1] @show norm(β_direct - β_lsmr) println("$(results[end])") # Could implement as matrix-free functions. X_func(u) = X * u # matrix-vector product X_T_func(v) = X' * v # i.e., adjoint-vector product X_map = LinearMap(X_func, X_T_func, N, M) results = lsmr(X_map, y, log = true) println("$(results[end])") using Arpack, LinearAlgebra N = 1000 A = Tridiagonal([fill(0.1, N-2); 0.2], fill(0.8, N), [0.2; fill(0.1, N-2);]) A_adjoint = A' λ, ϕ = eigs(A_adjoint, nev=1, which=:LM, maxiter=1000) # Find 1 of the largest magnitude eigenvalue ϕ = real(ϕ) ./ sum(real(ϕ)) @show λ @show mean(ϕ); θ = 0.1 ζ = 0.05 N = 5 P = Tridiagonal(fill(ζ, N-1), [1-θ; fill(1-θ-ζ, N-2); 1-ζ], fill(θ, N-1)) P' P_adj_mul(x) = [ (1-θ) * x[1] + ζ * x[2]; [θ * x[i-1] + (1-θ-ζ) * x[i] + ζ * x[i+1] for i in 2:N-1]; # comprehension θ * x[end-1] + (1-ζ) * x[end];] P_adj_map = LinearMap(P_adj_mul, N) @show norm(P' - sparse(P_adj_map)) λ, ϕ = eigs(P_adj_map, nev=1, which=:LM, maxiter=1000) ϕ = real(ϕ) ./ sum(real(ϕ)) @show λ @show ϕ N = 2 M = 3 shape = Tuple(fill(N, M)) v = rand(shape...) @show typeof(v) for ind in CartesianIndices(v) println("v$(ind.I) = $(v[ind])") # .I gets the tuple to display end e_m = [CartesianIndex((1:M .== i)*1...) for i in 1:M] ind = CartesianIndex(1, 2, 2) # example counts coming from CartesianIndices @show ind + e_m[1] # increment the first index @show ind - e_m[3]; # decrement the third index @show ind @show count(ind.I .> 1) @show count(ind.I .< N); using Parameters, BenchmarkTools default_params = @with_kw (θ = 0.1, ζ = 0.05, ρ = 0.03, N = 10, M = 6, shape = Tuple(fill(N, M)), # for reshaping vector to M-d array e_m = ([CartesianIndex((1:M .== i)*1...) for i in 1:M])) function Q_mul!(dv, v, p) @unpack θ, ζ, N, M, shape, e_m = p v = reshape(v, shape) # now can access v, dv as M-dim arrays dv = reshape(dv, shape) @inbounds for ind in CartesianIndices(v) dv[ind] = 0.0 for m in 1:M n_m = ind[m] if(n_m < N) dv[ind] += θ * v[ind + e_m[m]] end if(n_m > 1) dv[ind] += ζ *v[ind - e_m[m]] end end dv[ind] -= (θ * count(ind.I .< N) + ζ * count(ind.I .> 1)) * v[ind] end end p = default_params() v = zeros(p.shape) dv = similar(v) @btime Q_mul!($dv, $v, $p) function r_vec(p) z = (1:p.M).^2 # payoffs per type m r = [0.5 * dot(ind.I, z) for ind in CartesianIndices(p.shape)] return reshape(r, p.N^p.M) # return as a vector end @show typeof(r_vec(p)) r_vec(p) |> mean p = default_params(N=10, M=4) Q = LinearMap((df, f) -> Q_mul!(df, f, p), p.N^p.M, ismutating = true) A = p.ρ * I - Q A_sparse = sparse(A) # expensive: use only in tests r = r_vec(p) v_direct = A_sparse \ r iv = zero(r) @btime $A_sparse \ $r # direct @show norm(gmres(A, r) - v_direct) @btime gmres!(iv, $A, $r) setup = (iv = zero(r)) @show norm(bicgstabl(A, r) - v_direct) @btime bicgstabl!(iv, $A, $r) setup = (iv = zero(r)) @show norm(idrs(A, r) - v_direct) @btime idrs($A, $r); function solve_bellman(p; iv = zeros(p.N^p.M)) @unpack ρ, N, M = p Q = LinearMap((df, f) -> Q_mul!(df, f, p), N^M, ismutating = true) A = ρ * I - Q r = r_vec(p) sol = gmres!(iv, A, r, log = false) # iterative solver, matrix-free return sol end p = default_params(N=10, M=6) @btime solve_bellman($p); function Q_T_mul!(dψ, ψ, p) @unpack θ, ζ, N, M, shape, e_m = p ψ = reshape(ψ, shape) dψ = reshape(dψ, shape) @inbounds for ind in CartesianIndices(ψ) dψ[ind] = 0.0 for m in 1:M n_m = ind[m] if(n_m > 1) dψ[ind] += θ * ψ[ind - e_m[m]] end if(n_m < N) dψ[ind] += ζ *ψ[ind + e_m[m]] end end dψ[ind] -= (θ * count(ind.I .< N) + ζ * count(ind.I .> 1)) * ψ[ind] end end p = default_params(N=5, M=4) # sparse is too slow for the full matrix Q = LinearMap((df, f) -> Q_mul!(df, f, p), p.N^p.M, ismutating = true) Q_T = LinearMap((dψ, ψ) -> Q_T_mul!(dψ, ψ, p), p.N^p.M, ismutating = true) @show norm(sparse(Q)' - sparse(Q_T)); # reminder: use sparse only for testing! p = default_params(N=5, M=4) eig_Q_T = eigen(Matrix(Q_T)) vec = real(eig_Q_T.vectors[:,end]) direct_ψ = vec ./ sum(vec) @show eig_Q_T.values[end]; p = default_params(N=5, M=4) # sparse is too slow for the full matrix Q_T = LinearMap((dψ, ψ) -> Q_T_mul!(dψ, ψ, p), p.N^p.M, ismutating = true) ψ = fill(1/(p.N^p.M), p.N^p.M) # can't use 0 as initial guess sol = gmres!(ψ, Q_T, zeros(p.N^p.M)) # i.e., solve Ax = 0 iteratively ψ = ψ / sum(ψ) @show norm(ψ - direct_ψ); p = default_params(N=4, M=4) # Dense and sparse matrices are too slow for the full dataset. Q_T = LinearMap((dψ, ψ) -> Q_T_mul!(dψ, ψ, p), p.N^p.M, ismutating = true) Q_T_dense = Matrix(Q_T) Q_T_sparse = sparse(Q_T) b = zeros(p.N^p.M) @btime eigen($Q_T_dense) @btime eigs($Q_T_sparse, nev=1, which=:SM, v0 = iv) setup = (iv = fill(1/(p.N^p.M), p.N^p.M)) @btime gmres!(iv, $Q_T, $b) setup = (iv = fill(1/(p.N^p.M), p.N^p.M)); function stationary_ψ(p) Q_T = LinearMap((dψ, ψ) -> Q_T_mul!(dψ, ψ, p), p.N^p.M, ismutating = true) ψ = fill(1/(p.N^p.M), p.N^p.M) # can't use 0 as initial guess sol = gmres!(ψ, Q_T, zeros(p.N^p.M)) # i.e., solve Ax = 0 iteratively return ψ / sum(ψ) end p = default_params(N=10, M=5) @btime stationary_ψ($p); using OrdinaryDiffEq, DiffEqOperators function solve_transition_dynamics(p, t) @unpack N, M = p ψ_0 = [1.0; fill(0.0, N^M - 1)] O! = MatrixFreeOperator((dψ, ψ, p, t) -> Q_T_mul!(dψ, ψ, p), (p, 0.0), size=(N^M,N^M), opnorm=(p)->1.25) # define the corresponding ODE problem prob = ODEProblem(O!,ψ_0,(0.0,t[end]), p) return solve(prob, LinearExponential(krylov=:simple), tstops = t) end t = 0.0:5.0:100.0 p = default_params(N=10, M=6) sol = solve_transition_dynamics(p, t) v = solve_bellman(p) plot(t, [dot(sol(tval), v) for tval in t], xlabel = "t", label = ["E_t(v)"])