Skip to content

Commit fc8b9bc

Browse files
Add multiple outputs for the evaluation of Sobol indices (#98)
Co-authored-by: andrea perin <andrea.perin@irz.uni-hannover.de> Co-authored-by: Jasper Behrensdorf <jasper@behrensdorf.de>
1 parent 02068bd commit fc8b9bc

File tree

3 files changed

+124
-61
lines changed

3 files changed

+124
-61
lines changed

demo/sensitivity/sobolindices.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ B = Model(
1414

1515
mc = MonteCarlo(10000)
1616

17-
s_mc = sobolindices([B], [X; ω], :B, mc)
17+
s_mc = sobolindices(B, [X; ω], :B, mc)
1818

1919
# Compare with analytical solution
2020
VB = sum(σx .^ 2 .* σω .^ 2)

src/sensitivity/sobolindices.jl

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
const _sobol_table_types = [Symbol[], Float64[], Float64[], Float64[], Float64[]]
2+
const _sobol_table_header = [
3+
"Variables", "FirstOrder", "FirstOrderStdError", "TotalEffect", "TotalEffectStdError"
4+
]
5+
16
function sobolindices(
27
models::Vector{<:UQModel},
38
inputs::Vector{<:UQInput},
4-
output::Symbol,
9+
outputs::Vector{Symbol},
510
sim::AbstractMonteCarlo,
611
)
712
sim_double_samples = @set sim.n = 2 * sim.n
@@ -12,20 +17,19 @@ function sobolindices(
1217
random_names = names(filter(i -> isa(i, RandomUQInput), inputs))
1318

1419
evaluate!(models, samples)
20+
indices = Dict([
21+
(name, DataFrame(_sobol_table_types, _sobol_table_header)) for name in outputs
22+
])
1523

1624
A = samples[1:(sim.n), :]
1725
B = samples[(sim.n + 1):end, :]
1826

19-
fA = A[:, output]
20-
fB = B[:, output]
21-
22-
fA .-= mean(fA)
23-
fB .-= mean(fB)
24-
25-
VY = var([fA; fB])
27+
fA = Matrix(A[:, outputs])
28+
fB = Matrix(B[:, outputs])
29+
fA = fA .- mean(fA; dims=1)
30+
fB = fB .- mean(fB; dims=1)
2631

27-
Si = zeros(length(random_names), 2)
28-
STi = zeros(length(random_names), 2)
32+
VY = var([fA; fB]; dims=1)
2933

3034
for (i, name) in enumerate(random_names)
3135
ABi = select(A, Not(name))
@@ -35,30 +39,25 @@ function sobolindices(
3539
evaluate!(m, ABi)
3640
end
3741

38-
ABi[:, output] .-= mean(ABi[:, output])
42+
for (j, qty) in enumerate(outputs)
43+
ABi[:, qty] .-= mean(ABi[:, qty])
3944

40-
first_order = x -> mean(fB .* (x .- fA)) / VY # Saltelli 2009
41-
total_effect = x -> (1 / (2 * sim.n)) * sum((fA .- x) .^ 2) / VY # Saltelli 2009
45+
# First order effects
46+
first_order = x -> mean(fB[:, j] .* (x .- fA[:, j])) / VY[j] # Saltelli 2009
47+
bs = bootstrap(first_order, ABi[:, qty], BasicSampling(1000))
48+
Sᵢ = first_order(ABi[:, qty])
49+
σSᵢ = stderror(bs)[1]
4250

43-
# First order effects
44-
Si[i, 1] = first_order(ABi[:, output])
45-
bs = bootstrap(first_order, ABi[:, output], BasicSampling(1000))
46-
Si[i, 2] = stderror(bs)[1]
51+
# Total effects
52+
total_effect = x -> (1 / (2 * sim.n)) * sum((fA[:, j] .- x) .^ 2) / VY[j] # Saltelli 2009
53+
bs = bootstrap(total_effect, ABi[:, qty], BasicSampling(1000))
54+
Sₜ = total_effect(ABi[:, qty])
55+
σSₜ = stderror(bs)[1]
4756

48-
# Total effects
49-
STi[i, 1] = total_effect(ABi[:, output])
50-
bs = bootstrap(total_effect, ABi[:, output], BasicSampling(1000))
51-
STi[i, 2] = stderror(bs)[1]
57+
push!(indices[qty], [name, Sᵢ, σSᵢ, Sₜ, σSₜ])
58+
end
5259
end
53-
54-
indices = DataFrame()
55-
indices.Variables = random_names
56-
indices.FirstOrder = Si[:, 1]
57-
indices.FirstOrderStdError = Si[:, 2]
58-
indices.TotalEffect = STi[:, 1]
59-
indices.TotalEffectStdError = STi[:, 2]
60-
61-
return indices
60+
return length(outputs) > 1 ? indices : indices[outputs[1]]
6261
end
6362

6463
function sobolindices(pce::PolynomialChaosExpansion)
@@ -78,3 +77,48 @@ function sobolindices(pce::PolynomialChaosExpansion)
7877

7978
return indices
8079
end
80+
81+
function sobolindices(
82+
models::Vector{<:UQModel},
83+
inputs::I where {I<:UQInput},
84+
outputs::Vector{Symbol},
85+
sim::AbstractMonteCarlo,
86+
)
87+
return sobolindices(models, [inputs], outputs, sim)
88+
end
89+
90+
function sobolindices(
91+
models::Vector{<:UQModel},
92+
inputs::Vector{<:UQInput},
93+
outputs::Symbol,
94+
sim::AbstractMonteCarlo,
95+
)
96+
return sobolindices(models, inputs, [outputs], sim)
97+
end
98+
99+
function sobolindices(
100+
models::M where {M<:UQModel},
101+
inputs::Vector{<:UQInput},
102+
outputs::Symbol,
103+
sim::AbstractMonteCarlo,
104+
)
105+
return sobolindices([models], inputs, [outputs], sim)
106+
end
107+
108+
function sobolindices(
109+
models::Vector{<:UQModel},
110+
inputs::I where {I<:UQInput},
111+
outputs::Symbol,
112+
sim::AbstractMonteCarlo,
113+
)
114+
return sobolindices(models, [inputs], [outputs], sim)
115+
end
116+
117+
function sobolindices(
118+
models::M where {M<:UQModel},
119+
inputs::I where {I<:UQInput},
120+
outputs::Symbol,
121+
sim::AbstractMonteCarlo,
122+
)
123+
return sobolindices([models], [inputs], [outputs], sim)
124+
end

test/sensitivity/sobolindices.jl

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,88 @@
11
@testset "sobolindices" begin
22
x = RandomVariable.(Uniform(-π, π), [:x1, :x2, :x3])
33

4-
ishigami = Model(
5-
df -> sin.(df.x1) + 7 .* sin.(df.x2) .^ 2 + 0.1 .* df.x3 .^ 4 .* sin.(df.x1), :f
4+
ishigami1 = Model(
5+
df -> sin.(df.x1) + 7 .* sin.(df.x2) .^ 2 + 0.1 .* df.x3 .^ 4 .* sin.(df.x1), :f1
66
)
7+
ishigami2 = Model(
8+
df -> sin.(df.x1) + 7 .* sin.(df.x2) .^ 2 + 0.05 .* df.x3 .^ 4 .* sin.(df.x1), :f2
9+
)
10+
11+
n_mc = 4000
12+
n_qmc = 2300
713

8-
n = 1000
14+
firstorder_analytical1 = [0.3138, 0.4424, 0.00]
15+
totaleffect_analytical1 = [0.5574, 0.4424, 0.2436]
916

10-
firstorder_analytical = [0.3138, 0.4424, 0.00]
11-
totaleffect_analytical = [0.5574, 0.4424, 0.2436]
17+
firstorder_analytical2 = [0.219, 0.687, 0.00]
18+
totaleffect_analytical2 = [0.3136, 0.687, 0.0946]
1219

1320
@testset "Monte Carlo" begin
1421
Random.seed!(8128)
1522

16-
si = sobolindices([ishigami], x, :f, MonteCarlo(n))
23+
si = sobolindices(ishigami1, x, :f1, MonteCarlo(n_mc))
1724

1825
Random.seed!()
1926

20-
@test all(isapprox(si.FirstOrder, firstorder_analytical; rtol=0.1))
21-
@test all(isapprox(si.TotalEffect, totaleffect_analytical; rtol=0.1))
27+
@test all(isapprox(si.FirstOrder, firstorder_analytical1; rtol=0.1))
28+
@test all(isapprox(si.TotalEffect, totaleffect_analytical1; rtol=0.1))
2229
end
2330

2431
@testset "Sobol" begin
25-
Random.seed!(8128)
32+
si = sobolindices(ishigami1, x, :f1, SobolSampling(n_qmc))
2633

27-
si = sobolindices([ishigami], x, :f, SobolSampling(n))
34+
@test all(isapprox(si.FirstOrder, firstorder_analytical1; rtol=0.1))
35+
@test all(isapprox(si.TotalEffect, totaleffect_analytical1; rtol=0.1))
36+
end
2837

29-
Random.seed!()
38+
@testset "Sobol Nultiple Outputs" begin
39+
si = sobolindices([ishigami1, ishigami2], x, [:f1, :f2], SobolSampling(n_qmc))
3040

31-
@test all(isapprox(si.FirstOrder, firstorder_analytical; rtol=0.1))
32-
@test all(isapprox(si.TotalEffect, totaleffect_analytical; rtol=0.1))
41+
@test all(isapprox(si[:f1].FirstOrder, firstorder_analytical1; rtol=0.1))
42+
@test all(isapprox(si[:f1].TotalEffect, totaleffect_analytical1; rtol=0.1))
43+
@test all(isapprox(si[:f2].FirstOrder, firstorder_analytical2; rtol=0.1))
44+
@test all(isapprox(si[:f2].TotalEffect, totaleffect_analytical2; rtol=0.1))
3345
end
3446

3547
@testset "Halton" begin
36-
Random.seed!(8128)
37-
38-
si = sobolindices([ishigami], x, :f, MonteCarlo(n))
48+
si = sobolindices(ishigami1, x, :f1, HaltonSampling(n_qmc))
3949

40-
Random.seed!()
41-
42-
@test all(isapprox(si.FirstOrder, firstorder_analytical; rtol=0.1))
43-
@test all(isapprox(si.TotalEffect, totaleffect_analytical; rtol=0.1))
50+
@test all(isapprox(si.FirstOrder, firstorder_analytical1; rtol=0.1))
51+
@test all(isapprox(si.TotalEffect, totaleffect_analytical1; rtol=0.1))
4452
end
4553

4654
@testset "Latin Hypercube" begin
47-
Random.seed!(8128)
55+
si = sobolindices(ishigami1, x, :f1, LatinHypercubeSampling(n_qmc))
4856

49-
si = sobolindices([ishigami], x, :f, MonteCarlo(n))
50-
51-
Random.seed!()
57+
@test all(isapprox(si.FirstOrder, firstorder_analytical1; rtol=0.1))
58+
@test all(isapprox(si.TotalEffect, totaleffect_analytical1; rtol=0.1))
59+
end
5260

53-
@test all(isapprox(si.FirstOrder, firstorder_analytical; rtol=0.1))
54-
@test all(isapprox(si.TotalEffect, totaleffect_analytical; rtol=0.1))
61+
@testset "Convenience Functions" begin
62+
x1 = RandomVariable(Uniform(-π, +π), :x1)
63+
x2 = RandomVariable(Uniform(0, +π), :x2)
64+
t1 = Model(df -> sin.(df.x1), :t1)
65+
t2 = Model(df -> cos.(df.x1), :t2)
66+
67+
@test isa(sobolindices([t1; t2], x1, :t1, SobolSampling(2)), DataFrame)
68+
@test isa(sobolindices(t1, [x1; x2], :t1, SobolSampling(2)), DataFrame)
69+
@test isa(sobolindices([t1; t2], [x1; x2], :t1, SobolSampling(2)), DataFrame)
70+
@test isa(
71+
sobolindices([t1; t2], x1, [:t1, :t2], SobolSampling(2)), Dict{Symbol,DataFrame}
72+
)
73+
@test isa(sobolindices(t1, x1, :t1, SobolSampling(2)), DataFrame)
5574
end
5675

5776
@testset "Polynomial Chaos Expansion" begin
5877
p = 6
5978
Ψ = PolynomialChaosBasis(LegendreBasis.([p, p, p]), p)
6079

6180
gq = GaussQuadrature()
62-
pce, _ = polynomialchaos(x, ishigami, Ψ, :f, gq)
81+
pce, _ = polynomialchaos(x, ishigami1, Ψ, :f1, gq)
6382

6483
si = sobolindices(pce)
6584

66-
@test all(isapprox(si.FirstOrder, firstorder_analytical; rtol=0.1))
67-
@test all(isapprox(si.TotalEffect, totaleffect_analytical; rtol=0.1))
85+
@test all(isapprox(si.FirstOrder, firstorder_analytical1; rtol=0.1))
86+
@test all(isapprox(si.TotalEffect, totaleffect_analytical1; rtol=0.1))
6887
end
69-
end
88+
end

0 commit comments

Comments
 (0)