Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/exp_baseexp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ function exponential!(
LAPACK.gesv!(temp, X)
else
s = log2(nA / 5.4) # power of 2 later reversed by squaring
si = 0 # always defined so the s > 0 squaring loop is type-stable
if s > 0
si = ceil(Int, s)
A ./= convert(T, 2^si)
Expand Down
7 changes: 6 additions & 1 deletion src/kiops.jl
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,15 @@ function kiops(
omega = NaN
orderold = true
kestold = true
# `order`/`kest` carry their previous value across iterations (the `orderold`/
# `kestold` flags select "reuse"); seed them with the first-iteration defaults so
# they are always defined before use rather than only conditionally assigned.
order = 0.0
kest = 2

l = 1

local beta, kest
local beta
while tau_now < tau_end
oldj = Ks.m
arnoldi!(
Expand Down
8 changes: 8 additions & 0 deletions src/krylov_phiv.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ function expv!(
) where {Tw, T, U}
m, beta, V, H = Ks.m, Ks.beta, getV(Ks), getH(Ks)
@assert length(w) == size(V, 1) "Dimension mismatch"
if iszero(beta)
# Zero input: the Krylov basis V was never initialized (firststep! skips
# the fill when beta == 0), so `beta * V * expHe` would be `0 * garbage`,
# which is NaN whenever V holds uninitialized memory. The result is exactly
# zero, matching the complex `expv!` method's guard below.
w .= false
return w
end
if isnothing(cache)
cache = Matrix{U}(undef, m, m)
elseif isa(cache, ExpvCache)
Expand Down
35 changes: 28 additions & 7 deletions test/qa/qa.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,60 @@ using ExponentialUtilities, Aqua, JET, Test
Aqua.test_undefined_exports(ExponentialUtilities)
end

# Analyze only ExponentialUtilities' own code. Without this, JET on Julia 1.12 traces
# into LinearAlgebra/Base internals (e.g. `norm(::Vector)` -> `norm_recursive_check`,
# and the broadcast `unalias`/`copyto_unaliased!` path over `Adjoint{T, Union{}}`) and
# reports abstract-interpretation artifacts there that are not under this package's
# control. Scoping to `ExponentialUtilities` keeps full coverage of this package's code
# (it still flags real `may be undefined` findings here) without asserting that all of
# the stdlib is JET-clean.
const JET_TARGET = (ExponentialUtilities,)

@testset "JET static analysis" begin
@testset "expv" begin
rep = JET.report_call(expv, (Float64, Matrix{Float64}, Vector{Float64}))
rep = JET.report_call(
expv, (Float64, Matrix{Float64}, Vector{Float64}); target_modules = JET_TARGET
)
@test length(JET.get_reports(rep)) == 0
end

@testset "arnoldi" begin
rep = JET.report_call(arnoldi, (Matrix{Float64}, Vector{Float64}))
rep = JET.report_call(
arnoldi, (Matrix{Float64}, Vector{Float64}); target_modules = JET_TARGET
)
@test length(JET.get_reports(rep)) == 0
end

@testset "phi" begin
rep = JET.report_call(phi, (Matrix{Float64}, Int))
rep = JET.report_call(phi, (Matrix{Float64}, Int); target_modules = JET_TARGET)
@test length(JET.get_reports(rep)) == 0
end

@testset "exponential!" begin
rep = JET.report_call(ExponentialUtilities.exponential!, (Matrix{Float64},))
rep = JET.report_call(
ExponentialUtilities.exponential!, (Matrix{Float64},); target_modules = JET_TARGET
)
@test length(JET.get_reports(rep)) == 0
end

@testset "phiv" begin
rep = JET.report_call(phiv, (Float64, Matrix{Float64}, Vector{Float64}, Int))
rep = JET.report_call(
phiv, (Float64, Matrix{Float64}, Vector{Float64}, Int); target_modules = JET_TARGET
)
@test length(JET.get_reports(rep)) == 0
end

@testset "kiops" begin
rep = JET.report_call(kiops, (Float64, Matrix{Float64}, Vector{Float64}))
rep = JET.report_call(
kiops, (Float64, Matrix{Float64}, Vector{Float64}); target_modules = JET_TARGET
)
@test length(JET.get_reports(rep)) == 0
end

@testset "expv_timestep" begin
rep = JET.report_call(expv_timestep, (Float64, Matrix{Float64}, Vector{Float64}))
rep = JET.report_call(
expv_timestep, (Float64, Matrix{Float64}, Vector{Float64}); target_modules = JET_TARGET
)
@test length(JET.get_reports(rep)) == 0
end
end
6 changes: 5 additions & 1 deletion test/test_groups.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
# QA runs the metadata/static-analysis checks (Aqua + JET) in the isolated
# test/qa environment.
# GPU runs the CUDA tests in the isolated test/gpu environment on a self-hosted
# GPU runner (matching the pre-conversion GPU.yml workflow).
# GPU runner (matching the pre-conversion GPU.yml workflow). It is `in_all = false`
# so it only ever runs under an explicit GROUP=GPU on the CUDA-equipped runner and
# is never pulled into the "All" aggregate (which a non-GPU job can fall back to),
# where `using CUDA` errors with "CUDA driver not functional".

[Core]
versions = ["lts", "1", "pre"]
Expand All @@ -19,3 +22,4 @@ versions = ["lts", "1"]
versions = ["1"]
runner = ["self-hosted", "Linux", "X64", "gpu"]
timeout = 60
in_all = false
Loading