Skip to content
Merged
32 changes: 19 additions & 13 deletions src/appleaccelerate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -263,17 +263,20 @@ function SciMLBase.solve!(cache::LinearCache, alg::AppleAccelerateLUFactorizatio
info_value = res[3]

if info_value != 0
if !isa(verbose.blas_info, SciMLLogging.Silent) || !isa(verbose.blas_errors, SciMLLogging.Silent) ||
!isa(verbose.blas_invalid_args, SciMLLogging.Silent)
op_info = get_blas_operation_info(:dgetrf, A, cache.b, condition = !isa(verbose.condition_number, SciMLLogging.Silent))
if !isa(verbose.blas_info, SciMLLogging.Silent) ||
!isa(verbose.blas_errors, SciMLLogging.Silent) ||
!isa(verbose.blas_invalid_args, SciMLLogging.Silent)
op_info = get_blas_operation_info(:dgetrf, A, cache.b,
condition = !isa(verbose.condition_number, SciMLLogging.Silent))
@SciMLMessage(cache.verbose, :condition_number) do
if op_info[:condition_number] === nothing
if isinf(op_info.condition_number)
return "Matrix condition number calculation failed."
else
return "Matrix condition number: $(round(op_info[:condition_number], sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf"
return "Matrix condition number: $(round(op_info.condition_number, sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf"
end
end
verb_option, message = blas_info_msg(
verb_option,
message = blas_info_msg(
:dgetrf, info_value; extra_context = op_info)
@SciMLMessage(message, verbose, verb_option)
end
Expand All @@ -282,13 +285,13 @@ function SciMLBase.solve!(cache::LinearCache, alg::AppleAccelerateLUFactorizatio
op_info = get_blas_operation_info(:dgetrf, A, cache.b,
condition = !isa(verbose.condition_number, SciMLLogging.Silent))
@SciMLMessage(cache.verbose, :condition_number) do
if op_info[:condition_number] === nothing
if isinf(op_info.condition_number)
return "Matrix condition number calculation failed."
else
return "Matrix condition number: $(round(op_info[:condition_number], sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf"
return "Matrix condition number: $(round(op_info.condition_number, sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf"
end
end
return "BLAS LU factorization (dgetrf) completed successfully for $(op_info[:matrix_size]) matrix"
return "BLAS LU factorization (dgetrf) completed successfully for $(op_info.matrix_size) matrix"
end
end

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

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

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

A_lu, info, A_32, b_32, u_32 = @get_cacheval(cache, :AppleAccelerate32MixedLUFactorization)
A_lu, info, A_32, b_32,
u_32 = @get_cacheval(cache, :AppleAccelerate32MixedLUFactorization)
require_one_based_indexing(cache.u, cache.b)
m, n = size(A_lu, 1), size(A_lu, 2)

# Compute types on demand for conversions
T32 = eltype(A) <: Complex ? ComplexF32 : Float32
Torig = eltype(cache.u)

# Copy b to pre-allocated 32-bit array
b_32 .= T32.(cache.b)

Expand Down
127 changes: 86 additions & 41 deletions src/blas_logging.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
"""
Type-stable container for BLAS operation information.

Uses sentinel values for optional fields to maintain type stability:

- condition_number: -Inf means not computed
- rhs_length: 0 means not applicable
- rhs_type: "" means not applicable
"""
struct BlasOperationInfo
matrix_size::Tuple{Int, Int}
matrix_type::String
element_type::String
condition_number::Float64 # -Inf means not computed
rhs_length::Int # 0 means not applicable
rhs_type::String # "" means not applicable
memory_usage_MB::Float64
end

"""
interpret_blas_code(func::Symbol, info::Integer)
Expand Down Expand Up @@ -84,23 +102,50 @@ function interpret_positive_info(func::Symbol, info::Integer)
end
end

"""
Format BlasOperationInfo fields into human-readable strings.

Type-stable implementation using concrete struct fields instead of Dict iteration.
"""
function _format_blas_context(op_info::BlasOperationInfo)
parts = String[]

# Always-present fields
push!(parts, "Matrix size: $(op_info.matrix_size)")
push!(parts, "Matrix type: $(op_info.matrix_type)")
push!(parts, "Element type: $(op_info.element_type)")
push!(parts, "Memory usage: $(op_info.memory_usage_MB) MB")

# Optional fields - check for sentinel values
if !isinf(op_info.condition_number)
push!(parts, "Condition number: $(round(op_info.condition_number, sigdigits=4))")
end

if op_info.rhs_length > 0
push!(parts, "RHS length: $(op_info.rhs_length)")
end

if !isempty(op_info.rhs_type)
push!(parts, "RHS type: $(op_info.rhs_type)")
end

# Type barrier for string interpolation with Any-typed values
# The ::String return type annotation prevents JET from seeing runtime dispatch propagate
@noinline _format_context_pair(key::Symbol, value)::String = string(key, ": ", value)
return parts
end

"""
blas_info_msg(func::Symbol, info::Integer, verbose::LinearVerbosity;
extra_context::Dict{Symbol,Any} = Dict())
blas_info_msg(func::Symbol, info::Integer;
extra_context::BlasOperationInfo = BlasOperationInfo(
(0, 0), "", "", -Inf, 0, "", 0.0))

Log BLAS/LAPACK return code information with appropriate verbosity level.
"""
function blas_info_msg(func::Symbol, info::Integer;
extra_context::Dict{Symbol, Any} = Dict())
extra_context::BlasOperationInfo = BlasOperationInfo(
(0, 0), "", "", -Inf, 0, "", 0.0))
category, message, details = interpret_blas_code(func, info)

verbosity_field = if category in [:singular_matrix, :not_positive_definite, :convergence_failure]
verbosity_field = if category in [
:singular_matrix, :not_positive_definite, :convergence_failure]
:blas_errors
elseif category == :invalid_argument
:blas_invalid_args
Expand All @@ -114,17 +159,18 @@ function blas_info_msg(func::Symbol, info::Integer;
msg_info = info

# Build complete message with all details
full_msg = if !isempty(extra_context) || msg_details !== nothing
# Check if extra_context has any non-sentinel values
has_extra_context = extra_context.matrix_size != (0, 0)

full_msg = if has_extra_context || msg_details !== nothing
parts = String[msg_main]
if msg_details !== nothing
push!(parts, "Details: $msg_details")
end
push!(parts, "Return code (info): $msg_info")
if !isempty(extra_context)
for (key, value) in extra_context
# Use type barrier to prevent runtime dispatch from propagating
push!(parts, _format_context_pair(key, value))
end
if has_extra_context
# Type-stable formatting using struct fields
append!(parts, _format_blas_context(extra_context))
end
join(parts, "\n ")
else
Expand All @@ -134,39 +180,38 @@ function blas_info_msg(func::Symbol, info::Integer;
verbosity_field, full_msg
end


function get_blas_operation_info(func::Symbol, A, b; condition = false)
info = Dict{Symbol, Any}()
# Matrix properties (always present)
matrix_size = size(A)
matrix_type = string(typeof(A))
element_type = string(eltype(A))

# Matrix properties
info[:matrix_size] = size(A)
info[:matrix_type] = typeof(A)
info[:element_type] = eltype(A)
# Memory usage estimate (always present)
mem_bytes = prod(matrix_size) * sizeof(eltype(A))
memory_usage_MB = round(mem_bytes / 1024^2, digits = 2)

# Condition number (based on verbosity setting)
if condition && size(A, 1) == size(A, 2)
# Condition number (optional - use -Inf as sentinel)
condition_number = if condition && matrix_size[1] == matrix_size[2]
try
cond_num = cond(A)
info[:condition_number] = cond_num

# Log the condition number if enabled
cond_msg = "Matrix condition number: $(round(cond_num, sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in $func"

cond(A)
catch
# Skip if condition number computation fails
info[:condition_number] = nothing
-Inf
end
else
-Inf
end

# RHS properties if provided
if b !== nothing
info[:rhs_size] = size(b)
info[:rhs_type] = typeof(b)
end

# Memory usage estimate
mem_bytes = prod(size(A)) * sizeof(eltype(A))
info[:memory_usage_MB] = round(mem_bytes / 1024^2, digits = 2)

return info
end
# RHS properties (optional - use 0 and "" as sentinels)
rhs_length = b !== nothing ? length(b) : 0
rhs_type = b !== nothing ? string(typeof(b)) : ""

return BlasOperationInfo(
matrix_size,
matrix_type,
element_type,
condition_number,
rhs_length,
rhs_type,
memory_usage_MB
)
end
37 changes: 23 additions & 14 deletions src/mkl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ struct MKLLUFactorization <: AbstractFactorization end
# MKL_jll < 2022.2 doesn't support the mixed LP64 and ILP64 interfaces that we make use of in LinearSolve
# In particular, the `_64` APIs do not exist
# https://www.intel.com/content/www/us/en/developer/articles/release-notes/onemkl-release-notes-2022.html
@static if !@isdefined(MKL_jll) || !MKL_jll.is_available() || pkgversion(MKL_jll) < v"2022.2"
@static if !@isdefined(MKL_jll) || !MKL_jll.is_available() ||
pkgversion(MKL_jll) < v"2022.2"
__mkl_isavailable() = false
else
__mkl_isavailable() = true
Expand Down Expand Up @@ -252,32 +253,40 @@ function SciMLBase.solve!(cache::LinearCache, alg::MKLLUFactorization;
info_value = res[3]

if info_value != 0
if !isa(verbose.blas_info, SciMLLogging.Silent) || !isa(verbose.blas_errors, SciMLLogging.Silent) ||
!isa(verbose.blas_invalid_args, SciMLLogging.Silent)
op_info = get_blas_operation_info(:dgetrf, A, cache.b, condition = !isa(verbose.condition_number, SciMLLogging.Silent))
if !isa(verbose.blas_info, SciMLLogging.Silent) ||
!isa(verbose.blas_errors, SciMLLogging.Silent) ||
!isa(verbose.blas_invalid_args, SciMLLogging.Silent)
op_info = get_blas_operation_info(:dgetrf, A, cache.b,
condition = !isa(verbose.condition_number, SciMLLogging.Silent))
if cache.verbose.condition_number != Silent()
if op_info[:condition_number] === nothing
@SciMLMessage("Matrix condition number calculation failed.", cache.verbose, :condition_number)
if isinf(op_info.condition_number)
@SciMLMessage("Matrix condition number calculation failed.",
cache.verbose, :condition_number)
else
@SciMLMessage("Matrix condition number: $(round(op_info[:condition_number], sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf", cache.verbose, :condition_number)
@SciMLMessage("Matrix condition number: $(round(op_info.condition_number, sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf",
cache.verbose, :condition_number)
end
end
verb_option, message = blas_info_msg(
verb_option,
message = blas_info_msg(
:dgetrf, info_value; extra_context = op_info)
@SciMLMessage(message, verbose, verb_option)
end
else
if cache.verbose.blas_success != Silent()
op_info = get_blas_operation_info(:dgetrf, A, cache.b, condition = !isa(verbose.condition_number, SciMLLogging.Silent))
op_info = get_blas_operation_info(:dgetrf, A, cache.b,
condition = !isa(verbose.condition_number, SciMLLogging.Silent))
if cache.verbose.condition_number != Silent()
if op_info[:condition_number] === nothing
@SciMLMessage("Matrix condition number calculation failed.", cache.verbose, :condition_number)
if isinf(op_info.condition_number)
@SciMLMessage("Matrix condition number calculation failed.",
cache.verbose, :condition_number)
else
@SciMLMessage("Matrix condition number: $(round(op_info[:condition_number], sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf",
@SciMLMessage("Matrix condition number: $(round(op_info.condition_number, sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf",
cache.verbose, :condition_number)
end
end
@SciMLMessage("BLAS LU factorization (dgetrf) completed successfully for $(op_info[:matrix_size]) matrix", cache.verbose, :blas_success)
@SciMLMessage("BLAS LU factorization (dgetrf) completed successfully for $(op_info.matrix_size) matrix",
cache.verbose, :blas_success)
end
end

Expand Down Expand Up @@ -360,7 +369,7 @@ function SciMLBase.solve!(cache::LinearCache, alg::MKL32MixedLUFactorization;
# Compute types on demand for conversions
T32 = eltype(A) <: Complex ? ComplexF32 : Float32
Torig = eltype(cache.u)

# Copy b to pre-allocated 32-bit array
b_32 .= T32.(cache.b)

Expand Down
26 changes: 15 additions & 11 deletions src/openblas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -274,17 +274,20 @@ function SciMLBase.solve!(cache::LinearCache, alg::OpenBLASLUFactorization;
info_value = res[3]

if info_value != 0
if !isa(verbose.blas_info, SciMLLogging.Silent) || !isa(verbose.blas_errors, SciMLLogging.Silent) ||
!isa(verbose.blas_invalid_args, SciMLLogging.Silent)
op_info = get_blas_operation_info(:dgetrf, A, cache.b, condition = !isa(verbose.condition_number, SciMLLogging.Silent))
if !isa(verbose.blas_info, SciMLLogging.Silent) ||
!isa(verbose.blas_errors, SciMLLogging.Silent) ||
!isa(verbose.blas_invalid_args, SciMLLogging.Silent)
op_info = get_blas_operation_info(:dgetrf, A, cache.b,
condition = !isa(verbose.condition_number, SciMLLogging.Silent))
@SciMLMessage(cache.verbose, :condition_number) do
if op_info[:condition_number] === nothing
if isinf(op_info.condition_number)
return "Matrix condition number calculation failed."
else
return "Matrix condition number: $(round(op_info[:condition_number], sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf"
return "Matrix condition number: $(round(op_info.condition_number, sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf"
end
end
verb_option, message = blas_info_msg(
verb_option,
message = blas_info_msg(
:dgetrf, info_value; extra_context = op_info)
@SciMLMessage(message, verbose, verb_option)
end
Expand All @@ -293,13 +296,13 @@ function SciMLBase.solve!(cache::LinearCache, alg::OpenBLASLUFactorization;
op_info = get_blas_operation_info(:dgetrf, A, cache.b,
condition = !isa(verbose.condition_number, SciMLLogging.Silent))
@SciMLMessage(cache.verbose, :condition_number) do
if op_info[:condition_number] === nothing
if isinf(op_info.condition_number)
return "Matrix condition number calculation failed."
else
return "Matrix condition number: $(round(op_info[:condition_number], sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf"
return "Matrix condition number: $(round(op_info.condition_number, sigdigits=4)) for $(size(A, 1))×$(size(A, 2)) matrix in dgetrf"
end
end
return "BLAS LU factorization (dgetrf) completed successfully for $(op_info[:matrix_size]) matrix"
return "BLAS LU factorization (dgetrf) completed successfully for $(op_info.matrix_size) matrix"
end
end

Expand Down Expand Up @@ -359,7 +362,8 @@ function SciMLBase.solve!(cache::LinearCache, alg::OpenBLAS32MixedLUFactorizatio

if cache.isfresh
# Get pre-allocated arrays from cacheval
luinst, info, A_32, b_32, u_32 = @get_cacheval(cache, :OpenBLAS32MixedLUFactorization)
luinst, info, A_32, b_32,
u_32 = @get_cacheval(cache, :OpenBLAS32MixedLUFactorization)
# Compute 32-bit type on demand and copy A
T32 = eltype(A) <: Complex ? ComplexF32 : Float32
A_32 .= T32.(A)
Expand All @@ -382,7 +386,7 @@ function SciMLBase.solve!(cache::LinearCache, alg::OpenBLAS32MixedLUFactorizatio
# Compute types on demand for conversions
T32 = eltype(A) <: Complex ? ComplexF32 : Float32
Torig = eltype(cache.u)

# Copy b to pre-allocated 32-bit array
b_32 .= T32.(cache.b)

Expand Down
Loading
Loading