You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is your feature request related to a problem? Please describe.
Following from https://discourse.julialang.org/t/handling-divergent-trajectories-in-global-sensitivity-analysis/123186
I am having the following problem in a large parameter space. Let’s say I have two parameters A and B. If I constrain the range of A between 0 and 10 and B between 0 and 5, the sensitivity to A turns out higher. If I do the inverse, sensitivity to B is higher. If I make both of them between 0 and 10, then the solutions to differential equation diverge. I tried putting NaNs to divergent solutions but this impairs the subsequent calculations of sensitivity analysis since the software is not suited for dealing with NaN values (it returns NaNs when there is a NaN in the input).
Describe the solution you’d like
The solution is probably dropping NaNs.
Describe alternatives you’ve considered
So far I was unable to come up with another alternative.
nan_mask = isnan.(all_y)
nonnan_mask = .!nan_mask
nonnan_idx = findall(nonnan_mask)
indices_fA = ((i - 1) * n + 1):(i * n)
if dropnan
indices_fA = intersect(indices_fA, nonnan_idx)
end
fA = all_y[indices_fA]
When I rewrite gsa_sobol_all_y_analysis function with this strategy, the test code in sobol_method.jl passes all tests with no problem. Additionally, I wrote some test code that generates NaNs and it seems the algorithm doesn't return NaNs, instead it returns actual sensitivity values. I copy-pasted the gsa_sobol_all_y_analysis and my little test code below. But I honestly don't know if this makes sense or the results I get are correct results.
Below is a rewrite of gsa_sobol_all_y_analysis function. Note that I added a dropnan = false option.
function gsa_sobol_all_y_analysis(method, all_y::AbstractArray{T}, d, n, Ei_estimator,
y_size, ::Val{multioutput}; dropnan = false) where {T, multioutput}
if dropnan
nan_mask = isnan.(all_y)
nonnan_mask = .!nan_mask
nonnan_idx = findall(nonnan_mask)
if sum(nonnan_mask) == 0
throw(ArgumentError("All values are NaN"))
end
end
nboot = method.nboot
Eys = multioutput ? Matrix{T}[] : T[]
Varys = multioutput ? Matrix{T}[] : T[]
Vᵢs = multioutput ? Matrix{T}[] : Vector{T}[]
Vᵢⱼs = multioutput ? Array{T, 3}[] : Matrix{T}[]
Eᵢs = multioutput ? Matrix{T}[] : Vector{T}[]
step = 2 in method.order ? 2 * d + 2 : d + 2
if !multioutput
for i in 1:step:(step * nboot)
indices = ((i - 1) * n + 1):((i + 1) * n)
if dropnan
indices = intersect(indices, nonnan_idx)
end
push!(Eys, mean(all_y[indices]))
push!(Varys, var(all_y[indices]))
indices_fA = ((i - 1) * n + 1):(i * n)
indices_fB = (i * n + 1):((i + 1) * n)
if dropnan
indices_fA = intersect(indices_fA, nonnan_idx)
indices_fB = intersect(indices_fB, nonnan_idx)
end
fA = all_y[indices_fA]
fB = all_y[indices_fB]
fAⁱ = []
for j in (i + 1):(i + d)
indices_fAⁱ = (j * n + 1):((j + 1) * n)
if dropnan
indices_fAⁱ = intersect(indices_fAⁱ, nonnan_idx)
end
push!(fAⁱ, all_y[indices_fAⁱ])
end
if 2 in method.order
fBⁱ = []
for j in (i + d + 1):(i + 2 * d)
indices_fBⁱ = (j * n + 1):((j + 1) * n)
if dropnan
indices_fBⁱ = intersect(indices_fBⁱ, nonnan_idx)
end
push!(fBⁱ, all_y[indices_fBⁱ])
end
end
push!(Vᵢs, [sum(fB .* (fAⁱ[k] .- fA)) for k in 1:d] ./ n)
if 2 in method.order
M = zeros(T, d, d)
for k in 1:d
for j in (k + 1):d
M[k, j] = sum((fBⁱ[k] .* fAⁱ[j]) .- (fA .* fB)) / n
end
end
push!(Vᵢⱼs, M)
end
if Ei_estimator === :Homma1996
push!(Eᵢs,
[sum((fA .- (sum(fA) ./ n)) .^ 2) ./ (n - 1) .-
sum(fA .* fAⁱ[k]) ./ (n) + (sum(fA) ./ n) .^ 2 for k in 1:d])
elseif Ei_estimator === :Sobol2007
push!(Eᵢs, [sum(fA .* (fA .- fAⁱ[k])) for k in 1:d] ./ (n))
elseif Ei_estimator === :Jansen1999
push!(Eᵢs, [sum(abs2, fA - fAⁱ[k]) for k in 1:d] ./ (2n))
elseif Ei_estimator === :Janon2014
push!(Eᵢs,
[(sum(fA .^ 2 + fAⁱ[k] .^ 2) ./ (2n) .-
(sum(fA + fAⁱ[k]) ./ (2n)) .^ 2) * (1.0 .-
(1 / n .* sum(fA .* fAⁱ[k])
.-
(1 / n .* sum((fA .+ fAⁱ[k]) ./ 2)) .^ 2) ./
(1 / n .* sum((fA .^ 2 .+ fAⁱ[k] .^ 2) ./ 2) -
(1 / n .* sum((fA .+ fAⁱ[k]) ./ 2)) .^ 2))
for k in 1:d])
end
end
else # multioutput case
for i in 1:step:(step * nboot)
indices = ((i - 1) * n + 1):((i + 1) * n)
if dropnan
indices = intersect(indices, nonnan_idx)
end
push!(Eys, mean(all_y[:, indices], dims = 2))
push!(Varys, var(all_y[:, indices], dims = 2))
indices_fA = ((i - 1) * n + 1):(i * n)
indices_fB = (i * n + 1):((i + 1) * n)
if dropnan
indices_fA = intersect(indices_fA, nonnan_idx)
indices_fB = intersect(indices_fB, nonnan_idx)
end
fA = all_y[:, indices_fA]
fB = all_y[:, indices_fB]
fAⁱ = []
for j in (i + 1):(i + d)
indices = (j * n + 1):((j + 1) * n)
if dropnan
indices = intersect(indices, nonnan_idx)
end
push!(fAⁱ, all_y[:, indices])
end
if 2 in method.order
fBⁱ = []
for j in (i + d + 1):(i + 2 * d)
indices = (j * n + 1):((j + 1) * n)
if dropnan
indices = intersect(indices, nonnan_idx)
end
push!(fBⁱ, all_y[:, indices])
end
end
push!(Vᵢs,
reduce(hcat, [sum(fB .* (fAⁱ[k] .- fA), dims = 2) for k in 1:d] ./ n))
if 2 in method.order
M = zeros(T, d, d, length(Eys[1]))
for k in 1:d
for j in (k + 1):d
Vₖⱼ = sum((fBⁱ[k] .* fAⁱ[j]) .- (fA .* fB), dims = 2) / n
for l in 1:length(Eys[1])
M[k, j, l] = Vₖⱼ[l]
end
end
end
push!(Vᵢⱼs, M)
end
if Ei_estimator === :Homma1996
push!(Eᵢs,
reduce(hcat,
[sum((fA .- (sum(fA, dims = 2) ./ n)) .^ 2, dims = 2) ./ (n - 1) .-
sum(fA .* fAⁱ[k], dims = 2) ./ (n) + (sum(fA, dims = 2) ./ n) .^ 2
for k in 1:d]))
elseif Ei_estimator === :Sobol2007
push!(Eᵢs,
reduce(hcat,
[sum(fA .* (fA .- fAⁱ[k]), dims = 2) for k in 1:d] ./ (n)))
elseif Ei_estimator === :Jansen1999
push!(Eᵢs,
reduce(hcat, [sum(abs2, fA - fAⁱ[k], dims = 2) for k in 1:d] ./ (2n)))
elseif Ei_estimator === :Janon2014
push!(Eᵢs,
reduce(hcat,
[(sum(fA .^ 2 + fAⁱ[k] .^ 2, dims = 2) ./ (2n) .-
(sum(fA + fAⁱ[k], dims = 2) ./ (2n)) .^ 2) .* (1.0 .-
(1 / n .* sum(fA .* fAⁱ[k], dims = 2) .-
(1 / n * sum((fA .+ fAⁱ[k]) ./ 2, dims = 2)) .^ 2) ./
(1 / n .* sum((fA .^ 2 .+ fAⁱ[k] .^ 2) ./ 2, dims = 2) .-
(1 / n * sum((fA .+ fAⁱ[k]) ./ 2, dims = 2)) .^ 2)) for k in 1:d]))
end
end
end
if 2 in method.order
Sᵢⱼs = similar(Vᵢⱼs)
for i in 1:nboot
if !multioutput
M = zeros(T, d, d)
for k in 1:d
for j in (k + 1):d
M[k, j] = Vᵢs[i][k] + Vᵢs[i][j]
end
end
Sᵢⱼs[i] = (Vᵢⱼs[i] - M) ./ Varys[i]
else
M = zeros(T, d, d, length(Eys[1]))
for l in 1:length(Eys[1])
for k in 1:d
for j in (k + 1):d
M[k, j, l] = Vᵢs[i][l, k] + Vᵢs[i][l, j]
end
end
end
Sᵢⱼs[i] = cat(
[(Vᵢⱼs[i][:, :, l] - M[:, :, l]) ./ Varys[i][l]
for l in 1:length(Eys[1])]...;
dims = 3)
end
end
end
Sᵢs = [Vᵢs[i] ./ Varys[i] for i in 1:nboot]
Tᵢs = [Eᵢs[i] ./ Varys[i] for i in 1:nboot]
if nboot > 1
size_ = size(Sᵢs[1])
S1 = [[Sᵢ[i] for Sᵢ in Sᵢs] for i in 1:length(Sᵢs[1])]
ST = [[Tᵢ[i] for Tᵢ in Tᵢs] for i in 1:length(Tᵢs[1])]
function calc_ci(x, mean = nothing)
alpha = (1 - method.conf_level)
std(x, mean = mean) / sqrt(length(x))
end
S1_CI = map(calc_ci, S1)
ST_CI = map(calc_ci, ST)
if 2 in method.order
size__ = size(Sᵢⱼs[1])
S2_CI = Array{T}(undef, size__)
Sᵢⱼ = Array{T}(undef, size__)
b = getindex.(Sᵢⱼs, 1)
Sᵢⱼ[1] = b̄ = mean(b)
S2_CI[1] = calc_ci(b, b̄)
for i in 2:length(Sᵢⱼs[1])
b .= getindex.(Sᵢⱼs, i)
Sᵢⱼ[i] = b̄ = mean(b)
S2_CI[i] = calc_ci(b, b̄)
end
end
Sᵢ = reshape(mean.(S1), size_...)
Tᵢ = reshape(mean.(ST), size_...)
else
Sᵢ = Sᵢs[1]
Tᵢ = Tᵢs[1]
if 2 in method.order
Sᵢⱼ = Sᵢⱼs[1]
end
end
if isnothing(y_size)
_Sᵢ = Sᵢ
_Tᵢ = Tᵢ
else
f_shape = let y_size = y_size
x -> [reshape(x[:, i], y_size) for i in 1:size(x, 2)]
end
_Sᵢ = f_shape(Sᵢ)
_Tᵢ = f_shape(Tᵢ)
end
return SobolResult(_Sᵢ,
nboot > 1 ? reshape(S1_CI, size_...) : nothing,
2 in method.order ? Sᵢⱼ : nothing,
nboot > 1 && 2 in method.order ? S2_CI : nothing,
_Tᵢ,
nboot > 1 ? reshape(ST_CI, size_...) : nothing)
end
And the following is a small test code that generates NaNs and runs gsa.
# Tests for nan handling
# Make a differential equation problem that can be divergent.
using Revise, Test, OrdinaryDiffEq, GlobalSensitivity, Plots, QuasiMonteCarlo, Statistics
function f(du, u, p, t)
du .= p[1] * u
return du
end
# Single output case
u0 = [1.0]
p = [50] # diverges
tspan = (0.0, 20.0)
t = collect(range(0, stop = 10, length = 200))
prob = ODEProblem(f, u0, tspan, p)
f1 = let prob = prob, t = t
function (p)
prob1 = remake(prob; p = p)
sol = solve(prob1, Tsit5(); saveat = t)
if sol.retcode == ReturnCode.Success
return mean(Array(sol))
else
return NaN
end
end
end
samples = 100
lb = [20]
ub = [70]
sampler = SobolSample()
A, B = QuasiMonteCarlo.generate_design_matrices(samples, lb, ub, sampler)
m = gsa(f1, Sobol(), A, B; dropnan = true)
@test !any(isnan.(m.S1))
@test !any(isnan.(m.ST))
# Multi output case
f1 = let prob = prob, t = t
function (p)
prob1 = remake(prob; p = p)
sol = solve(prob1, Tsit5(); saveat = t)
if sol.retcode == ReturnCode.Success
return [mean(Array(sol)), std(Array(sol))]
else
return [NaN, NaN]
end
end
end
m = gsa(f1, Sobol(), A, B; dropnan = true)
@test !any(isnan.(m.S1))
@test !any(isnan.(m.ST))
The text was updated successfully, but these errors were encountered:
Is your feature request related to a problem? Please describe.
Following from https://discourse.julialang.org/t/handling-divergent-trajectories-in-global-sensitivity-analysis/123186
I am having the following problem in a large parameter space. Let’s say I have two parameters A and B. If I constrain the range of A between 0 and 10 and B between 0 and 5, the sensitivity to A turns out higher. If I do the inverse, sensitivity to B is higher. If I make both of them between 0 and 10, then the solutions to differential equation diverge. I tried putting NaNs to divergent solutions but this impairs the subsequent calculations of sensitivity analysis since the software is not suited for dealing with NaN values (it returns NaNs when there is a NaN in the input).
Describe the solution you’d like
The solution is probably dropping NaNs.
Describe alternatives you’ve considered
So far I was unable to come up with another alternative.
Additional context
I gave it a try and made some changes in the
sobol_sensitivity.jl
module. The strategy I used is inspired by theconservative
option instatsmodels.tsa.stattools.acf
function in python statsmodels package (https://www.statsmodels.org/dev/_modules/statsmodels/tsa/stattools/_stattools.html#acf). The strategy is the following. Take the codeand rewrite it as
When I rewrite
gsa_sobol_all_y_analysis
function with this strategy, the test code insobol_method.jl
passes all tests with no problem. Additionally, I wrote some test code that generates NaNs and it seems the algorithm doesn't return NaNs, instead it returns actual sensitivity values. I copy-pasted thegsa_sobol_all_y_analysis
and my little test code below. But I honestly don't know if this makes sense or the results I get are correct results.Below is a rewrite of
gsa_sobol_all_y_analysis
function. Note that I added adropnan = false
option.And the following is a small test code that generates NaNs and runs
gsa
.The text was updated successfully, but these errors were encountered: