diff --git a/examples/DimensionReduction/Project.toml b/examples/DimensionReduction/Project.toml new file mode 100644 index 00000000..bfe820e3 --- /dev/null +++ b/examples/DimensionReduction/Project.toml @@ -0,0 +1,7 @@ +[deps] +Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" +EnsembleKalmanProcesses = "aa8a2aa5-91d8-4396-bcef-d4f2ec43552d" +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" diff --git a/examples/DimensionReduction/build_and_compare_diagnostic_matrices.jl b/examples/DimensionReduction/build_and_compare_diagnostic_matrices.jl new file mode 100644 index 00000000..2d9f5257 --- /dev/null +++ b/examples/DimensionReduction/build_and_compare_diagnostic_matrices.jl @@ -0,0 +1,363 @@ +using LinearAlgebra +using EnsembleKalmanProcesses +using EnsembleKalmanProcesses.ParameterDistributions +using Statistics +using Distributions +using Plots +using JLD2 +#Utilities +function cossim(x::VV1, y::VV2) where {VV1 <: AbstractVector, VV2 <: AbstractVector} + return dot(x, y) / (norm(x) * norm(y)) +end +function cossim_pos(x::VV1, y::VV2) where {VV1 <: AbstractVector, VV2 <: AbstractVector} + return abs(cossim(x, y)) +end +function cossim_cols(X::AM1, Y::AM2) where {AM1 <: AbstractMatrix, AM2 <: AbstractMatrix} + return [cossim_pos(c1, c2) for (c1, c2) in zip(eachcol(X), eachcol(Y))] +end + +n_samples = 2000 # paper uses 5e5 +n_trials = 20 # get from generate_inverse_problem_data + +if !isfile("ekp_1.jld2") + include("generate_inverse_problem_data.jl") # will run n trials +else + include("forward_maps.jl") +end + + +Hu_evals = [] +Hg_evals = [] +Hu_mean_evals = [] +Hg_mean_evals = [] +Hu_ekp_prior_evals = [] +Hg_ekp_prior_evals = [] +Hu_ekp_final_evals = [] +Hg_ekp_final_evals = [] + +sim_Hu_means = [] +sim_Hg_means = [] +sim_G_samples = [] +sim_U_samples = [] +sim_Hu_ekp_prior = [] +sim_Hg_ekp_prior = [] +sim_Hu_ekp_final = [] +sim_Hg_ekp_final = [] +sim_Huy_ekp_final = [] + +for trial in 1:n_trials + + # Load the EKP iterations + loaded = load("ekp_$(trial).jld2") + ekp = loaded["ekp"] + prior = loaded["prior"] + obs_noise_cov = loaded["obs_noise_cov"] + y = loaded["y"] + model = loaded["model"] + input_dim = size(get_u(ekp, 1), 1) + output_dim = size(get_g(ekp, 1), 1) + + prior_cov = cov(prior) + prior_invrt = sqrt(inv(prior_cov)) + prior_rt = sqrt(prior_cov) + obs_invrt = sqrt(inv(obs_noise_cov)) + obs_inv = inv(obs_noise_cov) + + # random samples + prior_samples = sample(prior, n_samples) + + # [1a] Large-sample diagnostic matrices with perfect grad(Baptista et al 2022) + @info "Construct good matrix ($(n_samples) samples of prior, perfect grad)" + gradG_samples = jac_forward_map(prior_samples, model) + Hu = zeros(input_dim, input_dim) + Hg = zeros(output_dim, output_dim) + + for j in 1:n_samples + Hu .+= 1 / n_samples * prior_rt * gradG_samples[j]' * obs_inv * gradG_samples[j] * prior_rt + Hg .+= 1 / n_samples * obs_invrt * gradG_samples[j] * prior_cov * gradG_samples[j]' * obs_invrt + end + + # [1b] One-point approximation at mean value, with perfect grad + @info "Construct with mean value (1 sample), perfect grad" + prior_mean_appr = mean(prior) # approximate mean + gradG_at_mean = jac_forward_map(prior_mean_appr, model)[1] + # NB the logpdf of the prior at the ~mean is 1805 so pdf here is ~Inf + Hu_mean = prior_rt * gradG_at_mean' * obs_inv * gradG_at_mean * prior_rt + Hg_mean = obs_invrt * gradG_at_mean * prior_cov * gradG_at_mean' * obs_invrt + + # [2a] One-point approximation at mean value with SL grad + @info "Construct with mean value prior (1 sample), SL grad" + g = get_g(ekp, 1) + u = get_u(ekp, 1) + N_ens = get_N_ens(ekp) + C_at_prior = cov([u; g], dims = 2) # basic cross-cov + Cuu = C_at_prior[1:input_dim, 1:input_dim] + svdCuu = svd(Cuu) + nz = min(N_ens - 1, input_dim) # nonzero sv's + pinvCuu = svdCuu.U[:, 1:nz] * Diagonal(1 ./ svdCuu.S[1:nz]) * svdCuu.Vt[1:nz, :] # can replace with localized covariance + Cuu_invrt = svdCuu.U * Diagonal(1 ./ sqrt.(svdCuu.S)) * svdCuu.Vt + Cug = C_at_prior[(input_dim + 1):end, 1:input_dim] + # SL_gradG = (pinvCuu * Cug')' # approximates ∇G with ensemble. + # Hu_ekp_prior = prior_rt * SL_gradG' * obs_inv * SL_gradG * prior_rt + # Hg_ekp_prior = obs_invrt * SL_gradG * prior_cov * SL_gradG' * obs_invrt + Hu_ekp_prior = Cuu_invrt * Cug' * obs_inv * Cug * Cuu_invrt + Hg_ekp_prior = obs_invrt * Cug * pinvCuu * Cug' * obs_invrt + + # [2b] One-point approximation at mean value with SL grad + @info "Construct with mean value final (1 sample), SL grad" + final_it = length(get_g(ekp)) + g = get_g(ekp, final_it) + u = get_u(ekp, final_it) + C_at_final = cov([u; g], dims = 2) # basic cross-cov + Cuu = C_at_final[1:input_dim, 1:input_dim] + svdCuu = svd(Cuu) + nz = min(N_ens - 1, input_dim) # nonzero sv's + pinvCuu = svdCuu.U[:, 1:nz] * Diagonal(1 ./ svdCuu.S[1:nz]) * svdCuu.Vt[1:nz, :] # can replace with localized covariance + Cuu_invrt = svdCuu.U * Diagonal(1 ./ sqrt.(svdCuu.S)) * svdCuu.Vt + Cug = C_at_final[(input_dim + 1):end, 1:input_dim] # TODO: Isn't this Cgu? + # SL_gradG = (pinvCuu * Cug')' # approximates ∇G with ensemble. + # Hu_ekp_final = prior_rt * SL_gradG' * obs_inv * SL_gradG * prior_rt # here still using prior roots not Cuu + # Hg_ekp_final = obs_invrt * SL_gradG * prior_cov * SL_gradG' * obs_invrt + Hu_ekp_final = Cuu_invrt * Cug' * obs_inv * Cug * Cuu_invrt + Hg_ekp_final = obs_invrt * Cug * pinvCuu * Cug' * obs_invrt + + myCug = Cug' + Huy_ekp_final = N_ens \ Cuu_invrt * myCug*obs_inv'*sum( + (y - gg) * (y - gg)' for gg in eachcol(g) + )*obs_inv*myCug' * Cuu_invrt + + # cosine similarity of evector directions + svdHu = svd(Hu) + svdHg = svd(Hg) + svdHu_mean = svd(Hu_mean) + svdHg_mean = svd(Hg_mean) + svdHu_ekp_prior = svd(Hu_ekp_prior) + svdHg_ekp_prior = svd(Hg_ekp_prior) + svdHu_ekp_final = svd(Hu_ekp_final) + svdHg_ekp_final = svd(Hg_ekp_final) + svdHuy_ekp_final = svd(Huy_ekp_final) + @info """ + + samples -> mean + $(cossim_cols(svdHu.V, svdHu_mean.V)[1:3]) + $(cossim_cols(svdHg.V, svdHg_mean.V)[1:3]) + + samples + deriv -> mean + (no deriv) prior + $(cossim_cols(svdHu.V, svdHu_ekp_prior.V)[1:3]) + $(cossim_cols(svdHg.V, svdHg_ekp_prior.V)[1:3]) + + samples + deriv -> mean + (no deriv) final + $(cossim_cols(svdHu.V, svdHu_ekp_final.V)[1:3]) + $(cossim_cols(svdHg.V, svdHg_ekp_final.V)[1:3]) + + mean+(no deriv): prior -> final + $(cossim_cols(svdHu_ekp_prior.V, svdHu_ekp_final.V)[1:3]) + $(cossim_cols(svdHg_ekp_prior.V, svdHg_ekp_final.V)[1:3]) + + y-aware -> samples + $(cossim_cols(svdHu.V, svdHuy_ekp_final.V)[1:3]) + """ + push!(sim_Hu_means, cossim_cols(svdHu.V, svdHu_mean.V)) + push!(sim_Hg_means, cossim_cols(svdHg.V, svdHg_mean.V)) + push!(Hu_evals, svdHu.S) + push!(Hg_evals, svdHg.S) + push!(Hu_mean_evals, svdHu_mean.S) + push!(Hg_mean_evals, svdHg_mean.S) + push!(Hu_ekp_prior_evals, svdHu_ekp_prior.S) + push!(Hg_ekp_prior_evals, svdHg_ekp_prior.S) + push!(Hu_ekp_final_evals, svdHu_ekp_final.S) + push!(Hg_ekp_final_evals, svdHg_ekp_final.S) + push!(sim_Hu_ekp_prior, cossim_cols(svdHu.V, svdHu_ekp_prior.V)) + push!(sim_Hg_ekp_prior, cossim_cols(svdHg.V, svdHg_ekp_prior.V)) + push!(sim_Hu_ekp_final, cossim_cols(svdHu.V, svdHu_ekp_final.V)) + push!(sim_Hg_ekp_final, cossim_cols(svdHg.V, svdHg_ekp_final.V)) + push!(sim_Huy_ekp_final, cossim_cols(svdHu.V, svdHuy_ekp_final.V)) + + # cosine similarity to output svd from samples + G_samples = forward_map(prior_samples, model)' + svdG = svd(G_samples) # nonsquare, so permuted so evectors are V + svdU = svd(prior_samples') + + push!(sim_G_samples, cossim_cols(svdHg.V, svdG.V)) + push!(sim_U_samples, cossim_cols(svdHu.V, svdU.V)) + + save( + "diagnostic_matrices_$(trial).jld2", + "Hu", + Hu, + "Hg", + Hg, + "Hu_mean", + Hu_mean, + "Hg_mean", + Hg_mean, + "Hu_ekp_prior", + Hu_ekp_prior, + "Hg_ekp_prior", + Hg_ekp_prior, + "Hu_ekp_final", + Hu_ekp_final, + "Hg_ekp_final", + Hg_ekp_final, + "Huy_ekp_final", + Huy_ekp_final, + "svdU", + svdU, + "svdG", + svdG, + ) +end + +using Plots.Measures +gr(size = (1.6 * 1200, 600), legend = true, bottom_margin = 10mm, left_margin = 10mm) +default(titlefont = 20, legendfontsize = 12, guidefont = 14, tickfont = 14) + +normal_Hg_evals = [ev ./ ev[1] for ev in Hg_evals] +normal_Hg_mean_evals = [ev ./ ev[1] for ev in Hg_mean_evals] +normal_Hg_ekp_prior_evals = [ev ./ ev[1] for ev in Hg_ekp_prior_evals] +normal_Hg_ekp_final_evals = [ev ./ ev[1] for ev in Hg_ekp_final_evals] + +loaded1 = load("ekp_1.jld2") +ekp_tmp = loaded1["ekp"] +input_dim = size(get_u(ekp_tmp, 1), 1) +output_dim = size(get_g(ekp_tmp, 1), 1) + +truncation = 15 +truncation = Int(minimum([truncation, input_dim, output_dim])) +# color names in https://github.com/JuliaGraphics/Colors.jl/blob/master/src/names_data.jl + +pg = plot( + 1:truncation, + mean(sim_Hg_means)[1:truncation], + ribbon = (std(sim_Hg_means) / sqrt(n_trials))[1:truncation], + color = :blue, + label = "sim (samples v mean)", + legend = false, +) + +plot!( + pg, + 1:truncation, + mean(sim_Hg_ekp_prior)[1:truncation], + ribbon = (std(sim_Hg_ekp_prior) / sqrt(n_trials))[1:truncation], + color = :red, + alpha = 0.3, + label = "sim (samples v mean-no-der) prior", +) +plot!( + pg, + 1:truncation, + mean(sim_Hg_ekp_final)[1:truncation], + ribbon = (std(sim_Hg_ekp_final) / sqrt(n_trials))[1:truncation], + color = :gold, + label = "sim (samples v mean-no-der) final", +) + +plot!(pg, 1:truncation, mean(normal_Hg_evals)[1:truncation], color = :black, label = "normalized eval (samples)") +plot!( + pg, + 1:truncation, + mean(normal_Hg_mean_evals)[1:truncation], + color = :black, + alpha = 0.7, + label = "normalized eval (mean)", +) + +plot!( + pg, + 1:truncation, + mean(normal_Hg_ekp_prior_evals)[1:truncation], + color = :black, + alpha = 0.3, + label = "normalized eval (mean-no-der)", +) + +plot!(pg, 1:truncation, mean(normal_Hg_ekp_final_evals)[1:truncation], color = :black, alpha = 0.3) + + +plot!( + pg, + 1:truncation, + mean(sim_G_samples)[1:truncation], + ribbon = (std(sim_G_samples) / sqrt(n_trials))[1:truncation], + color = :green, + label = "similarity (PCA)", +) + +title!(pg, "Similarity of spectrum of output diagnostic") + + +normal_Hu_evals = [ev ./ ev[1] for ev in Hu_evals] +normal_Hu_mean_evals = [ev ./ ev[1] for ev in Hu_mean_evals] +normal_Hu_ekp_prior_evals = [ev ./ ev[1] for ev in Hu_ekp_prior_evals] +normal_Hu_ekp_final_evals = [ev ./ ev[1] for ev in Hu_ekp_final_evals] + + +pu = plot( + 1:truncation, + mean(sim_Hu_means)[1:truncation], + ribbon = (std(sim_Hu_means) / sqrt(n_trials))[1:truncation], + color = :blue, + label = "sim (samples v mean)", +) + +plot!(pu, 1:truncation, mean(normal_Hu_evals)[1:truncation], color = :black, label = "normalized eval (samples)") +plot!( + pu, + 1:truncation, + mean(normal_Hu_mean_evals)[1:truncation], + color = :black, + alpha = 0.7, + label = "normalized eval (mean)", +) +plot!( + pu, + 1:truncation, + mean(normal_Hu_ekp_prior_evals)[1:truncation], + color = :black, + alpha = 0.3, + label = "normalized eval (mean-no-der)", +) +plot!(pu, 1:truncation, mean(normal_Hu_ekp_final_evals)[1:truncation], color = :black, alpha = 0.3) + +plot!( + pu, + 1:truncation, + mean(sim_U_samples)[1:truncation], + ribbon = (std(sim_U_samples) / sqrt(n_trials))[1:truncation], + color = :green, + label = "similarity (PCA)", +) + +plot!( + pu, + 1:truncation, + mean(sim_Hu_ekp_prior)[1:truncation], + ribbon = (std(sim_Hu_ekp_prior) / sqrt(n_trials))[1:truncation], + color = :red, + alpha = 0.3, + label = "sim (samples v mean-no-der) prior", +) +plot!( + pu, + 1:truncation, + mean(sim_Hu_ekp_final)[1:truncation], + ribbon = (std(sim_Hu_ekp_final) / sqrt(n_trials))[1:truncation], + color = :gold, + label = "sim (samples v mean-no-der) final", +) +plot!( + pu, + 1:truncation, + mean(sim_Huy_ekp_final)[1:truncation], + ribbon = (std(sim_Huy_ekp_final) / sqrt(n_trials))[1:truncation], + color = :purple, + label = "sim (samples v y-aware) final", +) + +title!(pu, "Similarity of spectrum of input diagnostic") + +layout = @layout [a b] +p = plot(pu, pg, layout = layout) + +savefig(p, "spectrum_comparison.png") diff --git a/examples/DimensionReduction/common_inverse_problem.jl b/examples/DimensionReduction/common_inverse_problem.jl new file mode 100644 index 00000000..7354a3b3 --- /dev/null +++ b/examples/DimensionReduction/common_inverse_problem.jl @@ -0,0 +1,42 @@ +using LinearAlgebra +using EnsembleKalmanProcesses +using EnsembleKalmanProcesses.ParameterDistributions +using Statistics +using Distributions + +# Inverse problem will be taken from (Cui, Tong, 2021) https://arxiv.org/pdf/2101.02417, example 7.1 +include("forward_maps.jl") + + +function linear_exp_inverse_problem(input_dim, output_dim, rng) + # prior + γ0 = 4.0 + β_γ = -2 + Γ = Diagonal([γ0 * (1.0 * j)^β_γ for j in 1:input_dim]) + prior_dist = MvNormal(zeros(input_dim), Γ) + prior = ParameterDistribution( + Dict( + "distribution" => Parameterized(prior_dist), + "constraint" => repeat([no_constraint()], input_dim), + "name" => "param_$(input_dim)", + ), + ) + + # forward map + # random linear-exp forward map from Stewart 1980: https://www.jstor.org/stable/2156882?seq=2 + U = qr(randn(rng, (output_dim, output_dim))).Q + V = qr(randn(rng, (input_dim, input_dim))).Q + λ0 = 100.0 + β_λ = -1 + Λ = Diagonal([λ0 * (1.0 * j)^β_λ for j in 1:output_dim]) + A = U * Λ * V[1:output_dim, :] # output x input + model = LinearExp(input_dim, output_dim, A) + + # generate data sample + obs_noise_std = 1.0 + obs_noise_cov = (obs_noise_std^2) * I(output_dim) + noise = rand(rng, MvNormal(zeros(output_dim), obs_noise_cov)) + true_parameter = reshape(ones(input_dim), :, 1) + y = vec(forward_map(true_parameter, model) + noise) + return prior, y, obs_noise_cov, model, true_parameter +end diff --git a/examples/DimensionReduction/estimate_posteriors.jl b/examples/DimensionReduction/estimate_posteriors.jl new file mode 100644 index 00000000..6c218db4 --- /dev/null +++ b/examples/DimensionReduction/estimate_posteriors.jl @@ -0,0 +1,235 @@ +# Solve the problem with EKS + + +using Plots +using EnsembleKalmanProcesses +using Random +using JLD2 + +rng_seed = 41 +rng = Random.MersenneTwister(rng_seed) + +input_dim = 500 +output_dim = 50 + +include("common_inverse_problem.jl") + +n_trials = 10 + +r_in = 6 +r_out = 1 + + +if !isfile("ekp_1.jld2") + include("generate_inverse_problem_data.jl") # will run n trials +end +if !isfile("diagnostic_matrices_1.jld2") + include("build_and_compare_diagnostic_matrices.jl") # will run n trials +end + +in_diag = "Hu_ekp_prior" +out_diag = "Hg_ekp_prior" +@info "Diagnostic matrices = ($(in_diag), $(out_diag))" + + + +for trial in 1:n_trials + + # Load the EKP iterations + loaded = load("ekp_$(trial).jld2") + ekp = loaded["ekp"] + prior = loaded["prior"] + obs_noise_cov = loaded["obs_noise_cov"] + y = loaded["y"] + model = loaded["model"] + input_dim = size(get_u(ekp, 1), 1) + output_dim = size(get_g(ekp, 1), 1) + + prior_cov = cov(prior) + prior_invrt = sqrt(inv(prior_cov)) + prior_rt = sqrt(prior_cov) + obs_invrt = sqrt(inv(obs_noise_cov)) + obs_inv = inv(obs_noise_cov) + + # Load diagnostic container + diagnostic_mats = load("diagnostic_matrices_$(trial).jld2") + + + # [1] solve the problem with EKS - directly + prior, y, obs_noise_cov, model, true_parameter = linear_exp_inverse_problem(input_dim, output_dim, rng) + + n_ensemble = 100 + n_iters_max = 50 + + initial_ensemble = construct_initial_ensemble(rng, prior, n_ensemble) + ekp = EnsembleKalmanProcess(initial_ensemble, y, obs_noise_cov, Sampler(prior); rng = rng) + + n_iters = [0] + for i in 1:n_iters_max + params_i = get_ϕ_final(prior, ekp) + G_ens = hcat([forward_map(params_i[:, i], model) for i in 1:n_ensemble]...) + terminate = update_ensemble!(ekp, G_ens) + if !isnothing(terminate) + n_iters[1] = i - 1 + break + end + end + + ekp_u = get_u(ekp) + ekp_g = get_g(ekp) + + # [2] Create emulator in truncated space, and run EKS on this + min_iter = max_iter = 8 + i_pairs = reduce(hcat, get_u(ekp)[min_iter:max_iter]) + o_pairs = reduce(hcat, get_g(ekp)[min_iter:max_iter]) + + # Reduce space diagnostic matrix + Hu = diagnostic_mats[in_diag] + Hg = diagnostic_mats[out_diag] + svdu = svd(Hu) + svdg = svd(Hg) + + #= + # find by tolerance doesn't work well... + tol = 0.999 # <1 + r_in_vec = accumulate(+,svdu.S) ./sum(svdu.S) + r_in = sum(r_in_vec .< tol) + 1 # number evals needed for "tol" amount of information + =# + U_r = svdu.V[:, 1:r_in] + + #= + svdg = svd(Hg) + r_out_vec = accumulate(+,svdg.S) ./sum(svdg.S) + r_out = sum(r_out_vec .< tol) + 1 # number evals needed for "tol" amount of information + =# + V_r = svdg.V[:, 1:r_out] + + X_r = U_r' * prior_invrt * i_pairs + Y_r = V_r' * obs_invrt * o_pairs + + # true + true_r = U_r' * prior_invrt * true_parameter + y_r = V_r' * obs_invrt * y + + # [2a] exp-cubic model for regressor + red_model_ids = ["expcubic1d", "G"] + red_model_id = red_model_ids[2] + if red_model_id == "expcubic1d" + Ylb = min(1, minimum(Y_r) - abs(mean(Y_r))) # some loose lower bound + logY_r = log.(Y_r .- Ylb) + β = logY_r / [ones(size(X_r)); X_r; X_r .^ 2; X_r .^ 3] # = ([1 X_r]' \ Y_r')' + # invert relationship by + # exp(β*X_r) + Ylb + if r_in == 1 & r_out == 1 + sc = scatter(X_r, logY_r) + xmin = minimum(X_r) + xmax = maximum(X_r) + xrange = reshape(range(xmin, xmax, 100), 1, :) + expcubic = (β[4] * xrange .^ 3 + β[3] * xrange .^ 2 .+ β[2] * xrange .+ β[1])' + plot!(sc, xrange, expcubic, legend = false) + hline!(sc, [log.(y_r .- Ylb)]) + savefig(sc, "linreg_learn_scatter.png") + end + end + + # now apply EKS to the new problem + initial_ensemble = construct_initial_ensemble(rng, prior, n_ensemble) + initial_r = U_r' * prior_invrt * initial_ensemble + prior_r = ParameterDistribution( + Samples(U_r' * prior_invrt * sample(rng, prior, 1000)), + repeat([no_constraint()], r_in), + "prior_r", + ) + + obs_noise_cov_r = V_r' * V_r # Vr' * invrt(noise) * noise * invrt(noise) * Vr + ekp_r = EnsembleKalmanProcess(initial_r, y_r, obs_noise_cov_r, Sampler(mean(prior_r)[:], cov(prior_r)); rng = rng) + + n_iters = [0] + for i in 1:n_iters_max + params_i = get_ϕ_final(prior_r, ekp_r) + if red_model_id == "expcubic1d" + G_ens = exp.(β[4] * (params_i) .^ 3 .+ β[3] * (params_i) .^ 2 .+ β[2] * params_i .+ β[1]) .+ Ylb # use linear forward map in reduced space + elseif red_model_id == "G" + sv_in = reduce(hcat, repeat([svdu.S], n_ensemble)) # repeat SVs, then replace first by params + sv_in[1:size(params_i, 1), :] = params_i + # evaluate true G + G_ens_full = reduce(hcat, [forward_map(prior_rt * svdu.V * sv, model) for sv in eachcol(sv_in)]) + # project data back + G_ens = V_r' * obs_invrt * G_ens_full + end + + terminate = update_ensemble!(ekp_r, G_ens) + if !isnothing(terminate) + n_iters[1] = i - 1 + break + end + end + ekp_r_u = get_u(ekp_r) + ekp_r_g = get_g(ekp_r) + + # map to same space: [here in reduced space first] + spinup = 10 * n_ensemble + ekp_r_u = reduce(hcat, ekp_r_u) + ekp_r_g = reduce(hcat, ekp_r_g) + ekp_u = reduce(hcat, ekp_u) + ekp_g = reduce(hcat, ekp_g) + projected_ekp_u = U_r' * prior_invrt * ekp_u + projected_ekp_g = V_r' * obs_invrt * ekp_g + + if r_in == 1 && r_out == 1 + pp1 = histogram( + projected_ekp_u[:, spinup:end]', + color = :gray, + label = "projected G", + title = "projected EKP samples (input)", + legend = true, + ) + histogram!(pp1, ekp_r_u[:, spinup:end]', color = :blue, label = "reduced") + # pp1 = histogram(ekp_rlin_u[:,spinup:end]', color = :blue, label="reduced linear", yscale=:log10) + + pp2 = histogram(projected_ekp_g[:, spinup:end]', color = :gray, title = "projected EKP samples (output)") + histogram!(pp2, ekp_r_g[:, spinup:end]', color = :blue, legend = false) + #pp2 = histogram!(ekp_rlin_g[:,spinup:end]', color = :blue, legend=false, yscale=:log10) + l = @layout [a b] + pp = plot(pp1, pp2, layout = l) + savefig(pp, "projected_histograms.png") + end + + # compare in original space + mean_final = get_u_mean_final(ekp) + mean_final_in_red = U_r' * prior_invrt * mean_final + mean_red_final = get_u_mean_final(ekp_r) + sv_in = svdu.S + sv_in[1:r_in] = mean_red_final + mean_red_final_full = prior_rt * svdu.V * sv_in + + @info """ + + Reduced space dimension(input, output): $((r_in, r_out)) + + Norm of final-mean to true in reduced space: + Using Full space optimization: $((1.0/r_in)*norm(mean_final_in_red - true_r)) + Using Red. space optimization: $((1.0/r_in)*norm(mean_red_final - true_r)) + + Norm of final-mean to true in full space: + Using Full space optimization: $((1.0/input_dim)*norm(mean_final - true_parameter)) + Using Red. space optimization: $((1.0/input_dim)*norm(mean_red_final_full - true_parameter)) + """ + #@info norm(cov(get_u_final(ekp),dims=2) - cov(get_u_final(ekp_r), dims=2)) + #= + # 500 dim marginal plot.. + pp = plot(prior) + + spinup_iters = n_iters_max - 20 + posterior_samples = reduce(hcat,get_ϕ(prior,ekp)[spinup_iters:end]) # flatten over iterations (n_dim x n_particles) + posterior_dist = ParameterDistribution( + Dict( + "distribution" => Samples(posterior_samples), + "name" => "posterior samples", + "constraint" => repeat([no_constraint()], input_dim), + ), + ) + plot!(pp, posterior_dist) + =# + +end diff --git a/examples/DimensionReduction/forward_maps.jl b/examples/DimensionReduction/forward_maps.jl new file mode 100644 index 00000000..ec21013b --- /dev/null +++ b/examples/DimensionReduction/forward_maps.jl @@ -0,0 +1,24 @@ +abstract type ForwardMapType end + +## G*exp(X) +struct LinearExp{AM <: AbstractMatrix} <: ForwardMapType + input_dim::Int + output_dim::Int + G::AM +end + +# columns of X are samples +function forward_map(X::AVorM, model::LE) where {LE <: LinearExp, AVorM <: AbstractVecOrMat} + return model.G * exp.(X) +end + +# columns of X are samples +function jac_forward_map(X::AM, model::LE) where {AM <: AbstractMatrix, LE <: LinearExp} + # dGi / dXj = G_ij exp(x_j) = G.*exp.(mat with repeated x_j rows) + # return [G * exp.(Diagonal(r)) for r in eachrow(X')] # correct but extra multiplies + return [model.G .* exp.(reshape(c, 1, :)) for c in eachcol(X)] +end + +function jac_forward_map(X::AV, model::LE) where {AV <: AbstractVector, LE <: LinearExp} + return jac_forward_map(reshape(X, :, 1), model) +end diff --git a/examples/DimensionReduction/generate_inverse_problem_data.jl b/examples/DimensionReduction/generate_inverse_problem_data.jl new file mode 100644 index 00000000..5c226dab --- /dev/null +++ b/examples/DimensionReduction/generate_inverse_problem_data.jl @@ -0,0 +1,54 @@ +using Plots +using EnsembleKalmanProcesses +using Random +using JLD2 + +rng_seed = 41 +rng = Random.MersenneTwister(rng_seed) + +input_dim = 500 +output_dim = 50 + +include("common_inverse_problem.jl") + +n_trials = 20 +@info "solving $(n_trials) inverse problems with different random forward maps" + +for trial in 1:n_trials + prior, y, obs_noise_cov, model, true_parameter = linear_exp_inverse_problem(input_dim, output_dim, rng) + + n_ensemble = 80 + n_iters_max = 20 + + initial_ensemble = construct_initial_ensemble(rng, prior, n_ensemble) + ekp = EnsembleKalmanProcess(initial_ensemble, y, obs_noise_cov, TransformInversion(); rng = rng) + + n_iters = [0] + for i in 1:n_iters_max + params_i = get_ϕ_final(prior, ekp) + G_ens = hcat([forward_map(params_i[:, i], model) for i in 1:n_ensemble]...) + terminate = update_ensemble!(ekp, G_ens) + if !isnothing(terminate) + n_iters[1] = i - 1 + break + end + end + + @info "Iteration of posterior convergence: $(n_iters[1])" + @info "Loss over iterations:" get_error(ekp) + save( + "ekp_$(trial).jld2", + "ekp", + ekp, + "prior", + prior, + "y", + y, + "obs_noise_cov", + obs_noise_cov, + "model", + model, + "true_parameter", + true_parameter, + ) +end