Line | Exclusive | Inclusive | Code |
---|---|---|---|
1 | """ | ||
2 | `OperatorCondition` | ||
3 | |||
4 | Specifies the assumption of matrix conditioning for the default linear solver choices. Condition number | ||
5 | is defined as the ratio of eigenvalues. The numerical stability of many linear solver algorithms | ||
6 | can be dependent on the condition number of the matrix. The condition number can be computed as: | ||
7 | |||
8 | ```julia | ||
9 | using LinearAlgebra | ||
10 | cond(rand(100,100)) | ||
11 | ``` | ||
12 | |||
13 | However, in practice this computation is very expensive and thus not possible for most practical cases. | ||
14 | Therefore, OperatorCondition lets one share to LinearSolve the expected conditioning. The higher the | ||
15 | expected condition number, the safer the algorithm needs to be and thus there is a trade-off between | ||
16 | numerical performance and stability. By default the method assumes the operator may be ill-conditioned | ||
17 | for the standard linear solvers to converge (such as LU-factorization), though more extreme | ||
18 | ill-conditioning or well-conditioning could be the case and specified through this assumption. | ||
19 | """ | ||
20 | EnumX.@enumx OperatorCondition begin | ||
21 | """ | ||
22 | `OperatorCondition.IllConditioned` | ||
23 | |||
24 | The default assumption of LinearSolve. Assumes that the operator can have minor ill-conditioning | ||
25 | and thus needs to use safe algorithms. | ||
26 | """ | ||
27 | IllConditioned | ||
28 | """ | ||
29 | `OperatorCondition.VeryIllConditioned` | ||
30 | |||
31 | Assumes that the operator can have fairly major ill-conditioning and thus the standard linear algebra | ||
32 | algorithms cannot be used. | ||
33 | """ | ||
34 | VeryIllConditioned | ||
35 | """ | ||
36 | `OperatorCondition.SuperIllConditioned` | ||
37 | |||
38 | Assumes that the operator can have fairly extreme ill-conditioning and thus the most stable algorithm | ||
39 | is used. | ||
40 | """ | ||
41 | SuperIllConditioned | ||
42 | """ | ||
43 | `OperatorCondition.WellConditioned` | ||
44 | |||
45 | Assumes that the operator can have fairly contained conditioning and thus the fastest algorithm is | ||
46 | used. | ||
47 | """ | ||
48 | WellConditioned | ||
49 | end | ||
50 | |||
51 | """ | ||
52 | OperatorAssumptions(issquare = nothing; condition::OperatorCondition.T = IllConditioned) | ||
53 | |||
54 | Sets the operator `A` assumptions used as part of the default algorithm | ||
55 | """ | ||
56 | struct OperatorAssumptions{T} | ||
57 | issq::T | ||
58 | condition::OperatorCondition.T | ||
59 | end | ||
60 | |||
61 | function OperatorAssumptions(issquare = nothing; | ||
62 | condition::OperatorCondition.T = OperatorCondition.IllConditioned) | ||
63 | OperatorAssumptions{typeof(issquare)}(issquare, condition) | ||
64 | end | ||
65 | __issquare(assump::OperatorAssumptions) = assump.issq | ||
66 | __conditioning(assump::OperatorAssumptions) = assump.condition | ||
67 | |||
68 | mutable struct LinearCache{TA, Tb, Tu, Tp, Talg, Tc, Tl, Tr, Ttol, issq} | ||
69 | A::TA | ||
70 | b::Tb | ||
71 | u::Tu | ||
72 | p::Tp | ||
73 | alg::Talg | ||
74 | cacheval::Tc # store alg cache here | ||
75 | isfresh::Bool # false => cacheval is set wrt A, true => update cacheval wrt A | ||
76 | Pl::Tl # preconditioners | ||
77 | Pr::Tr | ||
78 | abstol::Ttol | ||
79 | reltol::Ttol | ||
80 | maxiters::Int | ||
81 | verbose::Bool | ||
82 | assumptions::OperatorAssumptions{issq} | ||
83 | end | ||
84 | |||
85 | function Base.setproperty!(cache::LinearCache, name::Symbol, x) | ||
86 | if name === :A | ||
87 | setfield!(cache, :isfresh, true) | ||
88 | elseif name === :b | ||
89 | # In case there is something that needs to be done when b is updated | ||
90 | update_cacheval!(cache, :b, x) | ||
91 | elseif name === :cacheval && cache.alg isa DefaultLinearSolver | ||
92 | @assert cache.cacheval isa DefaultLinearSolverInit | ||
93 | return setfield!(cache.cacheval, Symbol(cache.alg.alg), x) | ||
94 | end | ||
95 | setfield!(cache, name, x) | ||
96 | end | ||
97 | |||
98 | function update_cacheval!(cache::LinearCache, name::Symbol, x) | ||
99 | return update_cacheval!(cache, cache.cacheval, name, x) | ||
100 | end | ||
101 | update_cacheval!(cache, cacheval, name::Symbol, x) = cacheval | ||
102 | |||
103 | init_cacheval(alg::SciMLLinearSolveAlgorithm, args...) = nothing | ||
104 | |||
105 | function SciMLBase.init(prob::LinearProblem, args...; kwargs...) | ||
106 | SciMLBase.init(prob, nothing, args...; kwargs...) | ||
107 | end | ||
108 | |||
109 | default_tol(::Type{T}) where {T} = √(eps(T)) | ||
110 | default_tol(::Type{Complex{T}}) where {T} = √(eps(T)) | ||
111 | default_tol(::Type{<:Rational}) = 0 | ||
112 | default_tol(::Type{<:Integer}) = 0 | ||
113 | default_tol(::Type{Any}) = 0 | ||
114 | |||
115 | default_alias_A(::Any, ::Any, ::Any) = false | ||
116 | default_alias_b(::Any, ::Any, ::Any) = false | ||
117 | |||
118 | # Non-destructive algorithms default to true | ||
119 | default_alias_A(::AbstractKrylovSubspaceMethod, ::Any, ::Any) = true | ||
120 | default_alias_b(::AbstractKrylovSubspaceMethod, ::Any, ::Any) = true | ||
121 | |||
122 | function __init_u0_from_Ab(A, b) | ||
123 | u0 = similar(b, size(A, 2)) | ||
124 | fill!(u0, false) | ||
125 | return u0 | ||
126 | end | ||
127 | __init_u0_from_Ab(::SMatrix{S1, S2}, b) where {S1, S2} = zeros(SVector{S2, eltype(b)}) | ||
128 | |||
129 | 71 (26 %) |
71 (100 %)
samples spent calling
#init#3
function SciMLBase.init(prob::LinearProblem, alg::SciMLLinearSolveAlgorithm,
|
|
130 | args...; | ||
131 | alias_A = default_alias_A(alg, prob.A, prob.b), | ||
132 | alias_b = default_alias_b(alg, prob.A, prob.b), | ||
133 | abstol = default_tol(real(eltype(prob.b))), | ||
134 | reltol = default_tol(real(eltype(prob.b))), | ||
135 | maxiters::Int = length(prob.b), | ||
136 | verbose::Bool = false, | ||
137 | Pl = IdentityOperator(size(prob.A)[1]), | ||
138 | Pr = IdentityOperator(size(prob.A)[2]), | ||
139 | assumptions = OperatorAssumptions(issquare(prob.A)), | ||
140 | kwargs...) | ||
141 | @unpack A, b, u0, p = prob | ||
142 | |||
143 | A = if alias_A || A isa SMatrix | ||
144 | A | ||
145 | elseif A isa Array || A isa SparseMatrixCSC | ||
146 | copy(A) | ||
147 | else | ||
148 | deepcopy(A) | ||
149 | end | ||
150 | |||
151 | b = if b isa SparseArrays.AbstractSparseArray && !(A isa Diagonal) | ||
152 | Array(b) # the solution to a linear solve will always be dense! | ||
153 | elseif alias_b || b isa SVector | ||
154 | b | ||
155 | elseif b isa Array || b isa SparseMatrixCSC | ||
156 | copy(b) | ||
157 | else | ||
158 | deepcopy(b) | ||
159 | end | ||
160 | |||
161 | u0_ = u0 !== nothing ? u0 : __init_u0_from_Ab(A, b) | ||
162 | |||
163 | # Guard against type mismatch for user-specified reltol/abstol | ||
164 | reltol = real(eltype(prob.b))(reltol) | ||
165 | abstol = real(eltype(prob.b))(abstol) | ||
166 | |||
167 | 71 (26 %) |
71 (100 %)
samples spent calling
init_cacheval
cacheval = init_cacheval(alg, A, b, u0_, Pl, Pr, maxiters, abstol, reltol, verbose,
|
|
168 | assumptions) | ||
169 | isfresh = true | ||
170 | Tc = typeof(cacheval) | ||
171 | |||
172 | cache = LinearCache{typeof(A), typeof(b), typeof(u0_), typeof(p), typeof(alg), Tc, | ||
173 | typeof(Pl), typeof(Pr), typeof(reltol), typeof(assumptions.issq)}(A, b, u0_, | ||
174 | p, alg, cacheval, isfresh, Pl, Pr, abstol, reltol, maxiters, verbose, assumptions) | ||
175 | return cache | ||
176 | end | ||
177 | |||
178 | function SciMLBase.solve(prob::LinearProblem, args...; kwargs...) | ||
179 | solve!(init(prob, nothing, args...; kwargs...)) | ||
180 | end | ||
181 | |||
182 | function SciMLBase.solve(prob::LinearProblem, | ||
183 | alg::Union{SciMLLinearSolveAlgorithm, Nothing}, | ||
184 | args...; kwargs...) | ||
185 | solve!(init(prob, alg, args...; kwargs...)) | ||
186 | end | ||
187 | |||
188 | 81 (30 %) |
81 (100 %)
samples spent calling
#solve!#6
function SciMLBase.solve!(cache::LinearCache, args...; kwargs...)
|
|
189 | 81 (30 %) |
81 (100 %)
samples spent calling
solve!
solve!(cache, cache.alg, args...; kwargs...)
|
|
190 | end | ||
191 | |||
192 | # Special Case for StaticArrays | ||
193 | const StaticLinearProblem = LinearProblem{uType, iip, <:SMatrix, | ||
194 | <:Union{<:SMatrix, <:SVector}} where {uType, iip} | ||
195 | |||
196 | function SciMLBase.solve(prob::StaticLinearProblem, args...; kwargs...) | ||
197 | return SciMLBase.solve(prob, nothing, args...; kwargs...) | ||
198 | end | ||
199 | |||
200 | function SciMLBase.solve(prob::StaticLinearProblem, | ||
201 | alg::Union{Nothing, SciMLLinearSolveAlgorithm}, args...; kwargs...) | ||
202 | if alg === nothing || alg isa DirectLdiv! | ||
203 | u = prob.A \ prob.b | ||
204 | elseif alg isa LUFactorization | ||
205 | u = lu(prob.A) \ prob.b | ||
206 | elseif alg isa QRFactorization | ||
207 | u = qr(prob.A) \ prob.b | ||
208 | elseif alg isa CholeskyFactorization | ||
209 | u = cholesky(prob.A) \ prob.b | ||
210 | elseif alg isa NormalCholeskyFactorization | ||
211 | u = cholesky(Symmetric(prob.A' * prob.A)) \ (prob.A' * prob.b) | ||
212 | elseif alg isa SVDFactorization | ||
213 | u = svd(prob.A) \ prob.b | ||
214 | else | ||
215 | # Slower Path but handles all cases | ||
216 | cache = init(prob, alg, args...; kwargs...) | ||
217 | return solve!(cache) | ||
218 | end | ||
219 | return SciMLBase.build_linear_solution(alg, u, nothing, prob) | ||
220 | end |