Skip to content

Commit 13c70cf

Browse files
Merge branch 'SciML:main' into dev
2 parents 0f73fcd + 01cec75 commit 13c70cf

File tree

12 files changed

+253
-132
lines changed

12 files changed

+253
-132
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "LinearSolve"
22
uuid = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
33
authors = ["SciML"]
4-
version = "3.46.1"
4+
version = "3.47.0"
55

66
[deps]
77
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"

ext/LinearSolveRecursiveFactorizationExt.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ function SciMLBase.solve!(cache::LinearSolve.LinearCache, alg::ButterflyFactoriz
141141
end
142142

143143
function LinearSolve.init_cacheval(alg::ButterflyFactorization, A, b, u, Pl, Pr, maxiters::Int,
144-
abstol, reltol, verbose::Bool, assumptions::LinearSolve.OperatorAssumptions)
144+
abstol, reltol, verbose::Union{LinearVerbosity, Bool}, assumptions::LinearSolve.OperatorAssumptions)
145145
ws = RecursiveFactorization.🦋workspace(A, b), RecursiveFactorization.lu!(rand(1, 1), Val(false), alg.thread)
146146
end
147147

src/appleaccelerate.jl

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -263,17 +263,20 @@ function SciMLBase.solve!(cache::LinearCache, alg::AppleAccelerateLUFactorizatio
263263
info_value = res[3]
264264

265265
if info_value != 0
266-
if !isa(verbose.blas_info, SciMLLogging.Silent) || !isa(verbose.blas_errors, SciMLLogging.Silent) ||
267-
!isa(verbose.blas_invalid_args, SciMLLogging.Silent)
268-
op_info = get_blas_operation_info(:dgetrf, A, cache.b, condition = !isa(verbose.condition_number, SciMLLogging.Silent))
266+
if !isa(verbose.blas_info, SciMLLogging.Silent) ||
267+
!isa(verbose.blas_errors, SciMLLogging.Silent) ||
268+
!isa(verbose.blas_invalid_args, SciMLLogging.Silent)
269+
op_info = get_blas_operation_info(:dgetrf, A, cache.b,
270+
condition = !isa(verbose.condition_number, SciMLLogging.Silent))
269271
@SciMLMessage(cache.verbose, :condition_number) do
270-
if op_info[:condition_number] === nothing
272+
if isinf(op_info.condition_number)
271273
return "Matrix condition number calculation failed."
272274
else
273-
return "Matrix condition number: $(round(op_info[:condition_number], sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf"
275+
return "Matrix condition number: $(round(op_info.condition_number, sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf"
274276
end
275277
end
276-
verb_option, message = blas_info_msg(
278+
verb_option,
279+
message = blas_info_msg(
277280
:dgetrf, info_value; extra_context = op_info)
278281
@SciMLMessage(message, verbose, verb_option)
279282
end
@@ -282,13 +285,13 @@ function SciMLBase.solve!(cache::LinearCache, alg::AppleAccelerateLUFactorizatio
282285
op_info = get_blas_operation_info(:dgetrf, A, cache.b,
283286
condition = !isa(verbose.condition_number, SciMLLogging.Silent))
284287
@SciMLMessage(cache.verbose, :condition_number) do
285-
if op_info[:condition_number] === nothing
288+
if isinf(op_info.condition_number)
286289
return "Matrix condition number calculation failed."
287290
else
288-
return "Matrix condition number: $(round(op_info[:condition_number], sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf"
291+
return "Matrix condition number: $(round(op_info.condition_number, sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf"
289292
end
290293
end
291-
return "BLAS LU factorization (dgetrf) completed successfully for $(op_info[:matrix_size]) matrix"
294+
return "BLAS LU factorization (dgetrf) completed successfully for $(op_info.matrix_size) matrix"
292295
end
293296
end
294297

@@ -326,7 +329,8 @@ const PREALLOCATED_APPLE32_LU = begin
326329
LU(luinst.factors, similar(A, Cint, 0), luinst.info), Ref{Cint}()
327330
end
328331

329-
function LinearSolve.init_cacheval(alg::AppleAccelerate32MixedLUFactorization, A, b, u, Pl, Pr,
332+
function LinearSolve.init_cacheval(
333+
alg::AppleAccelerate32MixedLUFactorization, A, b, u, Pl, Pr,
330334
maxiters::Int, abstol, reltol, verbose::Union{LinearVerbosity, Bool},
331335
assumptions::OperatorAssumptions)
332336
# Pre-allocate appropriate 32-bit arrays based on input type
@@ -349,7 +353,8 @@ function SciMLBase.solve!(cache::LinearCache, alg::AppleAccelerate32MixedLUFacto
349353

350354
if cache.isfresh
351355
# Get pre-allocated arrays from cacheval
352-
luinst, info, A_32, b_32, u_32 = @get_cacheval(cache, :AppleAccelerate32MixedLUFactorization)
356+
luinst, info, A_32,
357+
b_32, u_32 = @get_cacheval(cache, :AppleAccelerate32MixedLUFactorization)
353358
# Compute 32-bit type on demand and copy A
354359
T32 = eltype(A) <: Complex ? ComplexF32 : Float32
355360
A_32 .= T32.(A)
@@ -365,14 +370,15 @@ function SciMLBase.solve!(cache::LinearCache, alg::AppleAccelerate32MixedLUFacto
365370
cache.isfresh = false
366371
end
367372

368-
A_lu, info, A_32, b_32, u_32 = @get_cacheval(cache, :AppleAccelerate32MixedLUFactorization)
373+
A_lu, info, A_32, b_32,
374+
u_32 = @get_cacheval(cache, :AppleAccelerate32MixedLUFactorization)
369375
require_one_based_indexing(cache.u, cache.b)
370376
m, n = size(A_lu, 1), size(A_lu, 2)
371377

372378
# Compute types on demand for conversions
373379
T32 = eltype(A) <: Complex ? ComplexF32 : Float32
374380
Torig = eltype(cache.u)
375-
381+
376382
# Copy b to pre-allocated 32-bit array
377383
b_32 .= T32.(cache.b)
378384

src/blas_logging.jl

Lines changed: 90 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
"""
2+
Type-stable container for BLAS operation information.
3+
4+
Uses sentinel values for optional fields to maintain type stability:
5+
6+
- condition_number: -Inf means not computed
7+
- rhs_length: 0 means not applicable
8+
- rhs_type: "" means not applicable
9+
"""
10+
struct BlasOperationInfo
11+
matrix_size::Tuple{Int, Int}
12+
matrix_type::String
13+
element_type::String
14+
condition_number::Float64 # -Inf means not computed
15+
rhs_length::Int # 0 means not applicable
16+
rhs_type::String # "" means not applicable
17+
memory_usage_MB::Float64
18+
end
119

220
"""
321
interpret_blas_code(func::Symbol, info::Integer)
@@ -66,15 +84,9 @@ function interpret_positive_info(func::Symbol, info::Integer)
6684

6785
# General eigenvalue problem
6886
elseif occursin("ggev", func_str) || occursin("gges", func_str)
69-
if info <= size
70-
return (:convergence_failure,
71-
"QZ iteration failed",
72-
"The QZ iteration failed to compute all eigenvalues. Elements 1:$(info-1) converged.")
73-
else
74-
return (:unexpected_error,
75-
"Unexpected error in generalized eigenvalue problem",
76-
"Info value $info is unexpected for $func.")
77-
end
87+
return (:convergence_failure,
88+
"Generalized eigenvalue computation failed",
89+
"The algorithm failed to compute eigenvalues (info=$info). This may indicate QZ iteration failure or other numerical issues.")
7890

7991
# LDLT factorization
8092
elseif occursin("ldlt", func_str)
@@ -90,19 +102,50 @@ function interpret_positive_info(func::Symbol, info::Integer)
90102
end
91103
end
92104

105+
"""
106+
Format BlasOperationInfo fields into human-readable strings.
107+
108+
Type-stable implementation using concrete struct fields instead of Dict iteration.
109+
"""
110+
function _format_blas_context(op_info::BlasOperationInfo)
111+
parts = String[]
112+
113+
# Always-present fields
114+
push!(parts, "Matrix size: $(op_info.matrix_size)")
115+
push!(parts, "Matrix type: $(op_info.matrix_type)")
116+
push!(parts, "Element type: $(op_info.element_type)")
117+
push!(parts, "Memory usage: $(op_info.memory_usage_MB) MB")
118+
119+
# Optional fields - check for sentinel values
120+
if !isinf(op_info.condition_number)
121+
push!(parts, "Condition number: $(round(op_info.condition_number, sigdigits=4))")
122+
end
93123

124+
if op_info.rhs_length > 0
125+
push!(parts, "RHS length: $(op_info.rhs_length)")
126+
end
127+
128+
if !isempty(op_info.rhs_type)
129+
push!(parts, "RHS type: $(op_info.rhs_type)")
130+
end
131+
132+
return parts
133+
end
94134

95135
"""
96-
blas_info_msg(func::Symbol, info::Integer, verbose::LinearVerbosity;
97-
extra_context::Dict{Symbol,Any} = Dict())
136+
blas_info_msg(func::Symbol, info::Integer;
137+
extra_context::BlasOperationInfo = BlasOperationInfo(
138+
(0, 0), "", "", -Inf, 0, "", 0.0))
98139
99140
Log BLAS/LAPACK return code information with appropriate verbosity level.
100141
"""
101142
function blas_info_msg(func::Symbol, info::Integer;
102-
extra_context::Dict{Symbol, Any} = Dict())
143+
extra_context::BlasOperationInfo = BlasOperationInfo(
144+
(0, 0), "", "", -Inf, 0, "", 0.0))
103145
category, message, details = interpret_blas_code(func, info)
104146

105-
verbosity_field = if category in [:singular_matrix, :not_positive_definite, :convergence_failure]
147+
verbosity_field = if category in [
148+
:singular_matrix, :not_positive_definite, :convergence_failure]
106149
:blas_errors
107150
elseif category == :invalid_argument
108151
:blas_invalid_args
@@ -116,16 +159,18 @@ function blas_info_msg(func::Symbol, info::Integer;
116159
msg_info = info
117160

118161
# Build complete message with all details
119-
full_msg = if !isempty(extra_context) || msg_details !== nothing
162+
# Check if extra_context has any non-sentinel values
163+
has_extra_context = extra_context.matrix_size != (0, 0)
164+
165+
full_msg = if has_extra_context || msg_details !== nothing
120166
parts = String[msg_main]
121167
if msg_details !== nothing
122168
push!(parts, "Details: $msg_details")
123169
end
124170
push!(parts, "Return code (info): $msg_info")
125-
if !isempty(extra_context)
126-
for (key, value) in extra_context
127-
push!(parts, "$key: $value")
128-
end
171+
if has_extra_context
172+
# Type-stable formatting using struct fields
173+
append!(parts, _format_blas_context(extra_context))
129174
end
130175
join(parts, "\n ")
131176
else
@@ -135,39 +180,38 @@ function blas_info_msg(func::Symbol, info::Integer;
135180
verbosity_field, full_msg
136181
end
137182

138-
139183
function get_blas_operation_info(func::Symbol, A, b; condition = false)
140-
info = Dict{Symbol, Any}()
184+
# Matrix properties (always present)
185+
matrix_size = size(A)
186+
matrix_type = string(typeof(A))
187+
element_type = string(eltype(A))
141188

142-
# Matrix properties
143-
info[:matrix_size] = size(A)
144-
info[:matrix_type] = typeof(A)
145-
info[:element_type] = eltype(A)
189+
# Memory usage estimate (always present)
190+
mem_bytes = prod(matrix_size) * sizeof(eltype(A))
191+
memory_usage_MB = round(mem_bytes / 1024^2, digits = 2)
146192

147-
# Condition number (based on verbosity setting)
148-
if condition && size(A, 1) == size(A, 2)
193+
# Condition number (optional - use -Inf as sentinel)
194+
condition_number = if condition && matrix_size[1] == matrix_size[2]
149195
try
150-
cond_num = cond(A)
151-
info[:condition_number] = cond_num
152-
153-
# Log the condition number if enabled
154-
cond_msg = "Matrix condition number: $(round(cond_num, sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in $func"
155-
196+
cond(A)
156197
catch
157-
# Skip if condition number computation fails
158-
info[:condition_number] = nothing
198+
-Inf
159199
end
200+
else
201+
-Inf
160202
end
161203

162-
# RHS properties if provided
163-
if b !== nothing
164-
info[:rhs_size] = size(b)
165-
info[:rhs_type] = typeof(b)
166-
end
167-
168-
# Memory usage estimate
169-
mem_bytes = prod(size(A)) * sizeof(eltype(A))
170-
info[:memory_usage_MB] = round(mem_bytes / 1024^2, digits = 2)
171-
172-
return info
173-
end
204+
# RHS properties (optional - use 0 and "" as sentinels)
205+
rhs_length = b !== nothing ? length(b) : 0
206+
rhs_type = b !== nothing ? string(typeof(b)) : ""
207+
208+
return BlasOperationInfo(
209+
matrix_size,
210+
matrix_type,
211+
element_type,
212+
condition_number,
213+
rhs_length,
214+
rhs_type,
215+
memory_usage_MB
216+
)
217+
end

src/common.jl

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,21 @@ default_alias_b(::AbstractSparseFactorization, ::Any, ::Any) = true
232232

233233
DEFAULT_PRECS(A, p) = IdentityOperator(size(A)[1]), IdentityOperator(size(A)[2])
234234

235+
# Default verbose setting (const for type stability)
236+
const DEFAULT_VERBOSE = LinearVerbosity()
237+
238+
# Helper functions for processing verbose parameter with multiple dispatch (type-stable)
239+
@inline _process_verbose_param(verbose::LinearVerbosity) = (verbose, verbose)
240+
@inline function _process_verbose_param(verbose::SciMLLogging.AbstractVerbosityPreset)
241+
verbose_spec = LinearVerbosity(verbose)
242+
return (verbose_spec, verbose_spec)
243+
end
244+
@inline function _process_verbose_param(verbose::Bool)
245+
# @warn "Using `true` or `false` for `verbose` is being deprecated."
246+
verbose_spec = verbose ? DEFAULT_VERBOSE : LinearVerbosity(SciMLLogging.None())
247+
return (verbose_spec, verbose)
248+
end
249+
235250
"""
236251
__init_u0_from_Ab(A, b)
237252
@@ -267,7 +282,7 @@ function __init(prob::LinearProblem, alg::SciMLLinearSolveAlgorithm,
267282
abstol = default_tol(real(eltype(prob.b))),
268283
reltol = default_tol(real(eltype(prob.b))),
269284
maxiters::Int = length(prob.b),
270-
verbose = true,
285+
verbose = LinearVerbosity(),
271286
Pl = nothing,
272287
Pr = nothing,
273288
assumptions = OperatorAssumptions(issquare(prob.A)),
@@ -324,22 +339,7 @@ function __init(prob::LinearProblem, alg::SciMLLinearSolveAlgorithm,
324339
copy(A)
325340
end
326341

327-
if verbose isa Bool
328-
# @warn "Using `true` or `false` for `verbose` is being deprecated. Please use a `LinearVerbosity` type to specify verbosity settings.
329-
# For details see the verbosity section of the common solver options documentation page."
330-
init_cache_verb = verbose
331-
if verbose
332-
verbose_spec = LinearVerbosity()
333-
else
334-
verbose_spec = LinearVerbosity(SciMLLogging.None())
335-
end
336-
elseif verbose isa SciMLLogging.AbstractVerbosityPreset
337-
verbose_spec = LinearVerbosity(verbose)
338-
init_cache_verb = verbose_spec
339-
else
340-
verbose_spec = verbose
341-
init_cache_verb = verbose_spec
342-
end
342+
verbose_spec, init_cache_verb = _process_verbose_param(verbose)
343343

344344
b = if issparsematrix(b) && !(A isa Diagonal)
345345
Array(b) # the solution to a linear solve will always be dense!

0 commit comments

Comments
 (0)