Skip to content
Snippets Groups Projects
Commit 355539ef authored by Luca Lenz's avatar Luca Lenz
Browse files

changed src structure; added ensemble simulation

parent 35c87e6a
No related branches found
No related tags found
No related merge requests found
Showing
with 1107 additions and 35 deletions
module MultilevelChainSampler
using AbstractMCMC: AbstractMCMC, AbstractModel
using BangBang
using Distributions
using Random, StatsBase
using Primes, HaltonSequences
using Graphs
using DataFrames
import Base: getindex, length, size, eltype, convert, rand, push!
import Distributions: logpdf
import StatsBase: sample, autocov
export logdensity module MultilevelChainSampler
export LogDensity, SampledLogDensity using Reexport
export MultilevelLogDensity, MultilevelSampledLogDensity
export propose
export RandomWalk, CyclicWalk
export ErdosRenyi
export sample
export MetropolisHastings, ChristenFox, LykkegaardScheichl
export nchains, info
export levels, is_multilevel
export states
export autocov, logpart, empirical_cdf
export RejectionChains
include("sampling/Samplers.jl")
@reexport using .Samplers
include("chains/all.jl") include("powergrid/PowerGrids.jl")
include("models/all.jl") @reexport using .PowerGrids
include("proposals/all.jl")
include("algos/all.jl")
end end
......
module PowerGrids
using Statistics
using Graphs
using OrdinaryDiffEq
using NonlinearSolve
using Primes, HaltonSequences
include("dynamics.jl")
include("stability.jl")
export swing_dynamics!
export synchronous_state
export stability_problem
end
\ No newline at end of file
# differential equation
function swing_dynamics!(dₜx, x, p, t)
# unpack
G,P,K,D = p.grid, p.power, p.coupling, p.damping
N = nv(G); ϕ = x[1:N]; ω = x[N+1:2N]
# second-order, frequency is derivative of phase
dₜx[1:N] .= ω;
# swing equation
L = laplacian_matrix(G)
s = sin.( L * ϕ )
dₜx[N+1:2N] .= @. P - D * ω + K * s
end
function sync_rhs(x,p)
N = length(x)÷2
# time derivative at t = 0
dₜx = similar(x)
swing_dynamics!(dₜx, x, p, 0.0)
# ignore constant angle shift
dₜx[1:N] .-= mean(dₜx[1:N])
return dₜx[2:2N]
end
# steady state
function synchronous_state(p)
nlp = NonlinearProblem(sync_rhs, zeros(2*nv(p.grid)), p)
sync = solve(nlp, NewtonRaphson()).u
return sync
end
"""
`freqamp` frequency perturbation amplitude
`n` number of perturbations
Example
≡≡≡≡≡≡≡≡
julia>
J = 1 # only node 1
J = [1,2,3] # set of nodes
"""
function stability_problem(p, J; freqamp = 1.0, t_final=120.0, tolerance = 0.1)
# default ode problem
u_sync = synchronous_state(p)
ode_prob = ODEProblem(swing_dynamics!, u_sync, (0, t_final), p)
# low discrepancy sequence for unit inverval
N = nv(p.grid)
m = length(J)
P = primes(4*m^2+1)[1:2m]
H = Halton.(P)
# perturb intial condition
function _bstab_init(prob, i, repeat)
u0 = u_sync[:]
Z = getindex.(H, i) .* 2 .- 1
u0[J] .= Z[1:m] .* pi
u0[J .+ N] .= Z[1+m:2m] .* freqamp
prob.u0 .= u0
prob
end
# check convergence to sync
function _bstab_out(sol, i)
u = sol.u[end];
Δu = maximum( abs.( u[1+N:end] .- u_sync[1+N:end] ) )
S = (Δu < tolerance)
return (S, false)
end
return prob = EnsembleProblem(ode_prob;
output_func = _bstab_out,
prob_func = _bstab_init
)
end
module Samplers
using AbstractMCMC: AbstractMCMC, AbstractModel
using BangBang
using Distributions
using Random, StatsBase
using Primes, HaltonSequences
using Graphs
using DataFrames
import Base: getindex, length, size, eltype, convert, rand, push!
import Distributions: logpdf
import StatsBase: sample, autocov
export logdensity
export LogDensity, SampledLogDensity
export MultilevelLogDensity, MultilevelSampledLogDensity
export propose
export RandomWalk, CyclicWalk
export ErdosRenyi
export sample
export MetropolisHastings, ChristenFox, LykkegaardScheichl
export nchains, info
export levels, is_multilevel
export states
export autocov, logpart, empirical_cdf
export RejectionChains
include("chains/all.jl")
include("models/all.jl")
include("proposals/all.jl")
include("algos/all.jl")
end
abstract type RejectionBasedSampler <: AbstractMCMC.AbstractSampler end
# Ignore chain length by default
function AbstractMCMC.save!!(samples, sample, iterations::Integer, model::AbstractModel, sampler::RejectionBasedSampler, ::Integer; kwargs...)
AbstractMCMC.save!!(samples, sample, iterations, model, sampler; kwargs...)
end
# Size hint sample container chain length
function AbstractMCMC.samples(sample, model::AbstractModel, sampler::RejectionBasedSampler, N::Integer; kwargs...)
samples = AbstractMCMC.samples(sample, model, sampler; kwargs...)
T = typeof(samples)
for (k,t) in zip(T.parameters[1], T.types)
if t <: AbstractVector
sizehint!(getfield(samples, k), N)
end
end
return samples
end
function AbstractMCMC.bundle_samples(samples, m::AbstractModel, s::RejectionBasedSampler, state, chain_type::Type; kwargs... )
AbstractMCMC.bundle_samples(samples, m, s, state, RejectionChains; kwargs... )
end
function _chain_info(samples, model::AbstractModel, sampler::RejectionBasedSampler)
return Dict{Symbol, Vector}()
end
function AbstractMCMC.bundle_samples(samples, m::AbstractModel, s::RejectionBasedSampler, state, chain_type::Type{<:AbstractRejectionChains}; kwargs... )
x = AbstractMCMC.bundle_samples(samples, m, s, state, NamedTuple; kwargs...)
c = RejectionChains( x.states, x.logprobs, x.rejections )
data = _chain_info(samples, m, s)
for k in keys(data)
c.info[k] = [data[k]]
end
return c
end
include("metropolis_hastings.jl")
include("christen_fox.jl")
include("lykkegard_scheichl.jl")
"""
Delayed Acceptance algorithm
If `saveproxies == true` save log-density lower levels.
This is particularly useful for Multilevel integration.
References
* Christen, J.A. and Fox, C. (2005). "Markov chain Monte Carlo Using an Approximation"
Journal of Computational and Graphical Statistics
"""
struct ChristenFox{saveproxies, P <: AbstractProposal} <: RejectionBasedSampler
proposal :: P
end
ChristenFox(proposal::AbstractProposal, saveproxies::Bool=false) = ChristenFox{saveproxies, typeof(proposal)}(proposal)
## Save samples only for highest level
function AbstractMCMC.samples(sample, model::AbstractMultilevelModel, ::ChristenFox{false, P}; kwargs...) where {P}
(level, x, f_x) = sample
return (;
states = typeof(x)[],
logprobs = typeof(f_x)[],
rejections = Vector{Int}[],
)
end
function AbstractMCMC.save!!(samples, sample, ::Integer, model::AbstractMultilevelModel, ::ChristenFox{false, P}; kwargs... ) where {P}
(level, x, f_x) = sample
# accept (at hightest level)
if level == length(model)
push!(samples.states, x)
push!(samples.logprobs, f_x)
push!(samples.rejections, zeros(Int, length(model)))
# reject
else samples.rejections[end][level+1] += 1 end
return samples
end
## Save each level
function AbstractMCMC.samples(sample, model::AbstractMultilevelModel, ::ChristenFox{true, P}; kwargs...) where {P}
(level, x, f_x) = sample
return (;
states = typeof(x)[],
logprobs = typeof(f_x)[],
rejections = Vector{Int}[],
current = Ref{Int}(0) # reference to current top level state
)
end
function AbstractMCMC.save!!(samples, sample, ::Integer, model::AbstractMultilevelModel, ::ChristenFox{true, P}; kwargs... ) where {P}
(level, x, f_x) = sample
# accept
if level == length(model)
push!(samples.states, x)
push!(samples.logprobs, f_x)
push!(samples.rejections, zeros(Int, length(model)))
samples.current[] = length(samples.states) # reset current to accepted
else
samples.rejections[samples.current[]][level+1] += 1 # update rejection counter
# store promoted
if level > 0
push!(samples.states, x)
push!(samples.logprobs, f_x)
push!(samples.rejections, zeros(Int, length(model)))
end
end
return samples
end
## General chain stepping
function AbstractMCMC.step(rng::AbstractRNG, model::AbstractModel, sampler::ChristenFox; x0=nothing, f0=nothing, kwargs...)
# Initialize states
_resample = isnothing(x0)
x = _resample ? rand(rng, sampler.proposal) : x0
# Initialize logprobs
_recompute = _resample || isnothing(f0)
f_x = _recompute ? [ logdensity(model, x; level=l) for l=1:length(model) ] : f0
return (length(model), x, f_x), (x, f_x)
end
function AbstractMCMC.step(rng::AbstractRNG, model::AbstractMultilevelModel, sampler::ChristenFox, state; kwargs...)
# Load old state
x, f_x = state
# Propose
y = propose(rng, sampler.proposal, x)
f_y = [ logdensity(model, y; level=1) ]
q = logpratio(sampler.proposal, x, y)
# Promotion probability
A_1 = min( f_y[1] - f_x[1] + q, 0)
accept = log(rand(rng)) < A_1
if !accept return (0, x, f_x), (x, f_x) end # completly reject, never promoted
# Promotion loop
for l = 2:length(model)
push!(f_y, logdensity(model, y; level=l) )
# Next promotion
A_l = f_y[l] - f_x[l] + f_y[l-1] - f_x[l-1]
accept = log(rand(rng)) < A_l
if !accept return (l-1, y, f_y), (x, f_x) end # rejected at level l, promoted to l-1
end
# Accept at highest level
return (length(model), y, f_y), (y, f_y)
end
## Chain stepping specialized for Sampled-Based LogDensities, uses cached evaluation
function AbstractMCMC.step(rng::AbstractRNG, model::MultilevelSampledLogDensity, sampler::ChristenFox; x0=nothing, f0=nothing, kwargs...)
# Initialize states
_resample = isnothing(x0)
x = _resample ? rand(rng, sampler.proposal) : x0
# Initialize logprobs
_recompute = _resample || isnothing(f0)
if _recompute
# Recursively cached
f_x = [ logdensity(model, x; level=1) ]
sizehint!(f_x, length(model))
for l=2:length(model)
push!(f_x, logdensity(model, x; level=l, cache=f_x[end]))
end
else f_x = f0 end
return (length(model), x, f_x), (x, f_x)
end
function AbstractMCMC.step(rng::AbstractRNG, model::MultilevelSampledLogDensity, sampler::ChristenFox, state; kwargs...)
# Load old state
x, f_x = state
# Propose
y = propose(rng, sampler.proposal, x)
f_y = [ logdensity(model, y; level=1) ]
q = logpratio(sampler.proposal, x, y)
# Promotion probability
A_1 = min( f_y[1] - f_x[1] + q, 0)
accept = log(rand(rng)) < A_1
if !accept return (0, x, f_x), (x, f_x) end # completly reject, never promoted
# Promotion loop
for l = 2:length(model)
push!(f_y, logdensity(model, y; level=l, cache=f_y[end]) ) # > Only this line changes!
# Next promotion
A_l = f_y[l] - f_x[l] + f_y[l-1] - f_x[l-1]
accept = log(rand(rng)) < A_l
if !accept return (l-1, y, f_y), (x, f_x) end # rejected at level l, promoted to l-1
end
# Accept at highest level
return (length(model), y, f_y), (y, f_y)
end
function AbstractMCMC.bundle_samples(samples, ::AbstractMultilevelModel, ::ChristenFox, state, chain_type::Type{<:NamedTuple}; kwargs...)
return (; states = samples.states, logprobs = samples.logprobs, rejections = samples.rejections)
end
function _chain_info(samples, model::AbstractMultilevelModel, ::ChristenFox)
n_total = sum( 1 .+ sum.(samples.rejections))
n_reject = sum(samples.rejections)
info = Dict(
:chain_length => n_total,
:rejection_rate => n_reject ./ n_total,
)
if model isa MultilevelSampledLogDensity
evals_per_level = n_total .- cumsum([0, n_reject[1:end-1]... ])
costs_per_level = model.nlevels .- [0, model.nlevels[1:end-1]...]
info[:total_costs] = sum( evals_per_level .* costs_per_level )
end
return info
end
"""
Recursive Delayed Acceptance algorithm
References
* Mikkel B. Lykkegaard, T. Dodwell, C. Fox, Grigorios Mingas, Robert Scheichl (2022). "Multilevel Delayed Acceptance MCMC"
SIAM/ASA J. Uncertain. Quantification
"""
struct LykkegaardScheichl{saveproxies, P <: AbstractProposal, N} <: RejectionBasedSampler
proposal :: P
sublen :: N # sub-chain length (may be a distribution)
end
LykkegaardScheichl(p,s=1,saveproxies::Bool=true) = LykkegaardScheichl{saveproxies, typeof(p), typeof(s)}(p,s)
subchainlength(rng::AbstractRNG, s::LykkegaardScheichl{saveproxies,P,<:Distribution}) where {saveproxies,P} = rand(rng, s.sublen)
subchainlength(rng::AbstractRNG, s::LykkegaardScheichl{saveproxies,P,<:Integer}) where {saveproxies,P} = s.sublen
## Only save top level
function AbstractMCMC.samples(sample, mode::AbstractMultilevelModel, sampler::LykkegaardScheichl{false,P,N}; L=length(model), kwargs...) where {P,N}
accept, x, f_x, r_x, Y = sample
return (;
states = typeof(x)[],
logprobs = typeof(f_x)[],
rejections = typeof(r_x)[],
)
end
function AbstractMCMC.save!!(samples, sample, ::Integer, model::AbstractMultilevelModel, sampler::LykkegaardScheichl{false, P,N}) where {P,N}
accept, x, f_x, r_x, Y = sample
if accept # save new entry
push!(samples.states, x)
push!(samples.logprobs, f_x)
push!(samples.rejections, r_x )
else # update rejection counter
samples.rejections[end][L] += 1
end
return samples
end
## Save proxies
function AbstractMCMC.samples(sample, model::AbstractMultilevelModel, sampler::LykkegaardScheichl{true,P,N}; L=length(model), kwargs...) where {P,N}
accept, x, f_x, r_x, Y = sample
return (;
states = typeof(x)[],
logprobs = typeof(f_x)[],
rejections = typeof(r_x)[],
current=Ref{Int}(0) # reference to current top level state
)
end
function AbstractMCMC.save!!(samples, sample, iter::Integer, model::AbstractMultilevelModel, sampler::LykkegaardScheichl{true, P,N}; L=length(model), kwargs... ) where {P,N}
accept, x, f_x, r_x, Y = sample
# Save proxies
append!(samples.states, Y.states)
append!(samples.logprobs, Y.logprobs)
append!(samples.rejections, Y.rejections)
if accept
push!(samples.states, x)
push!(samples.logprobs, f_x)
push!(samples.rejections, r_x)
samples.current[] = length(samples.states) # update reference
else
samples.rejections[samples.current[]][L] += 1 # reference,
end
return samples
end
# Initialize chain
function AbstractMCMC.step(rng::AbstractRNG, model::AbstractMultilevelModel, sampler::LykkegaardScheichl; x0=nothing, f0=nothing, L=length(model), kwargs...)
x = !isnothing(x0) ? x0 : rand(rng, sampler.proposal)
if !isnothing(x0) && !isnothing(f0)
if (f0 isa Number) && (L == 1)
f_x = [f0]
elseif (f0 isa Vector)
f_x = f0[1:L]
end
else
f_x = [ logdensity(model, x; level=l) for l=1:L ]
end
Y = (; states=typeof(x)[], logprobs=typeof(f_x)[], rejections=Vector{Int}[])
return (true, x, f_x, zeros(Int, L), Y), (x, f_x)
end
function AbstractMCMC.step(rng::AbstractRNG, model::MultilevelSampledLogDensity, sampler::LykkegaardScheichl; x0=nothing, f0=nothing, L=length(model), kwargs...)
x = !isnothing(x0) ? x0 : rand(rng, sampler.proposal)
if !isnothing(x0) && !isnothing(f0)
if (f0 isa Number && L == 1)
f_x = [f0]
elseif (f0 isa Vector)
f_x = f0[1:L]
end
else
f_x = [ logdensity(model, x; level=1) ]
sizehint!(f_x, L)
for l=2:L
push!(f_x, logdensity(model, x; level=l, cache=f_x[end]))
end
end
Y = (; states=typeof(x)[], logprobs=typeof(f_x)[], rejections=Vector{Int}[])
return (true, x, f_x, zeros(Int, L), Y), (x, f_x)
end
function AbstractMCMC.step(rng::AbstractRNG, model::AbstractMultilevelModel, sampler::LykkegaardScheichl, state; L=length(model), kwargs...)
x, f_x = state
# Sample length at random
n = 1 + subchainlength(rng, sampler)
if L == 2 # reduces to iterated MH
# sample subchain
mh = MetropolisHastings(sampler.proposal)
model_1 = LogDensity(x->logdensity(model, x, level=1))
c = sample(rng, model_1, mh, n, chain_type=NamedTuple, x0=x,f0=f_x[1])
# chain in between
Y = (; states = c.states[2:end-1],
logprobs = map(x->[x], c.logprobs[2:end-1]),
rejections = map(x->[x,0], c.rejections[2:end-1])
)
# end of chain
if length(Y.states) == 0
return (false, x, f_x, Vector{Int}[], Y), (x, f_x)
end
y = c.states[end]
f_y = [c.logprobs[end], logdensity(model, y, level=2)]
r_y = [c.rejections[end], zeros(Int, L-1)... ]
else # Recursion
# sample subchain
c = sample(rng, sampler, model, n, chain_type=(;), L=L-1, x0=x,f0=f_x[1:L-1], discard_initial=1)
# chain in between
Y = (; states = c.states[2:end-1],
logprobs = c.logprobs[2:end-1],
rejections = map(x->[x,0], c.rejections[2:end-1])
)
# end of chain
if length(c.states) == 0
return (false, x, f_x, Vector{Int}[], Y), (x, f_x)
end
y = c.states[end]
f_y = [c.logprobs[end]..., logdensity(model, y, level=L)]
r_y = [c.rejections[end]..., 0 ]
end
# accept/reject step
A = min( f_y[L] - f_x[L] - f_y[L-1] + f_x[L-1], 0)
accept = log(rand(rng)) < A
if accept
return (true, y, f_y, r_y, Y), (y, f_y)
else
return (false, x, f_x, Vector{Int}[], Y), (x, f_x)
end
end
function AbstractMCMC.bundle_samples(samples, model::AbstractMultilevelModel, sampler::LykkegaardScheichl, state, chain_type::Type{<:NamedTuple}; kwargs...)
return (; states = samples.states, logprobs = samples.logprobs, rejections = samples.rejections )
end
function _chain_info(samples, model::AbstractMultilevelModel, sampler::LykkegaardScheichl)
n_total = sum( 1 .+ sum.(samples.rejections))
n_reject = sum(samples.rejections)
info = Dict(
:chain_length => n_total,
:rejection_rate => n_reject ./ n_total,
)
if model isa MultilevelSampledLogDensity # just copied from Christen-Fox sampler, is this valid ...?
evals_per_level = n_total .- cumsum([0, n_reject[1:end-1]... ])
costs_per_level = model.nlevels .- [0, model.nlevels[1:end-1]...]
info[:total_costs] = sum( evals_per_level .* costs_per_level )
end
return info
end
\ No newline at end of file
"""
Standard Metropolis-Hastings algorithm
References
* Hastings, W.K. (1970). "Monte Carlo Sampling Methods Using Markov Chains and Their Applications".
Biometrika, Volume 57, Issue 1
"""
struct MetropolisHastings{P <: AbstractProposal} <: RejectionBasedSampler
proposal :: P
end
# Initialize samples container
function AbstractMCMC.samples(sample, model::AbstractModel, sampler::MetropolisHastings; kwargs...)
(accept, x, f_x) = sample
return (;
states = typeof(x)[],
logprobs = typeof(f_x)[],
rejections = Int[],
)
end
# Store sample to container
function AbstractMCMC.save!!(samples, sample, ::Integer, ::AbstractModel, ::RejectionBasedSampler; kwargs... )
(accept, x, f_x) = sample
if sample[1] # accepted
push!(samples.states, x)
push!(samples.logprobs, f_x)
push!(samples.rejections, 0)
else # rejected
samples.rejections[end] += 1
end
return samples
end
function AbstractMCMC.step(rng::AbstractRNG, model::AbstractModel, sampler::MetropolisHastings; x0=nothing, f0=nothing, kwargs...)
# Initialize states
_resample = isnothing(x0)
x = _resample ? rand(rng, sampler.proposal) : x0
# Initialize logprobs
_recompute = _resample || isnothing(f0)
f_x = _recompute ? logdensity(model, x) : f0
return (true, x, f_x), (x, f_x)
end
function AbstractMCMC.step(rng::AbstractRNG, model::AbstractModel, sampler::MetropolisHastings, state; kwargs...)
# Load old state
(x, f_x) = state
#propose new state
y = propose(rng, sampler.proposal, x)
f_y = logdensity(model, y)
q_ratio = logpratio(sampler.proposal, x, y)
logA = min( f_y - f_x + q_ratio, 0)
# Accept / Reject step
if log(rand(rng)) < logA
return (true, y, f_y), (y, f_y) # accept
else
return (false, x, f_x), (x, f_x) # reject
end
end
function AbstractMCMC.bundle_samples(samples, ::AbstractModel, ::MetropolisHastings, state, chain_type::Type{<:NamedTuple}; kwargs...)
return samples
end
function _chain_info(samples, model::AbstractModel, ::MetropolisHastings)
n_total = sum( 1 .+ samples.rejections)
n_reject = sum(samples.rejections)
info = Dict(
:chain_length => n_total,
:rejection_rate => n_reject / n_total,
)
if model isa SampledLogDensity
info[:total_costs] = n_total .* length(model)
end
return info
end
abstract type AbstractRejectionChains <: AbstractMCMC.AbstractChains end
include("rejection.jl")
include("tables.jl")
include("stats.jl")
function Base.show(io::IO, c::AbstractRejectionChains)
print(io, "Chains (×$(nchains(c))) {", eltype(c), "} $(length(c)) elements. ")
end
function Base.display(c::AbstractRejectionChains)
println(c)
if is_multilevel(c)
ls = maximum.(levels(c))
if all( ls[2:end] .== ls[1] )
println(" proxy level L=", ls[1])
else
println(" proxy levels ", ls)
end
end
data = info(c)
for k in keys(data)
if nchains(c) == 1
println(" ", k, " : ", data[k][1])
else
m, s = mean(data[k]), std(data[k])
m = round.(m, sigdigits=5)
s = round.(s, sigdigits=3)
println(" ", k, " : ", m, " ± ", s)
end
end
end
abstract type AbstractEstimator{State} end
struct ImportanceEstimator{S, P <: Real} <: AbstractEstimator{S}
states::Vector{S}
logprobs::Vector{Vector{P}}
rejections::Vector{Int}
end
#estimate(q::Q) =
levels = length.(logprobs)
for l = 1:L
s = states[(levels .== l)]
mean(s)
end
# each chain may have different length
struct RejectionChains{S,L,R} <: AbstractRejectionChains
samples::Vector{
Vector{
@NamedTuple begin
state::S
logprob::L
reject::R
end
}
}
info::Dict{Symbol, Vector}
end
# construct with empty info
function RejectionChains(samples::Vector{Vector{@NamedTuple{state::S, logprob::L, reject::R}}}) where {S,L,R}
RejectionChains(samples, Dict{Symbol, Vector}())
end
# construct single chain
function RejectionChains(s::Vector{S}, l::Vector{L}, r::Vector{R}) where {S,L,R}
samples = NamedTuple{ (:state, :logprob, :reject) }.([zip(s, l, r)...] )
return RejectionChains([samples])
end
# concatination method
function AbstractMCMC.chainscat(c::RejectionChains ... )
infos = getfield.(c, :info)
lens = length.(c)
dict = Dict{Symbol, Vector}()
for k in union(keys.(infos) ... )
vals = [ haskey(infos[i],k) ?
infos[i][k] : repeat([missing], lens[i])
for i = 1:length(c)
]
dict[Symbol(k)] = vcat(vals ... )
end
RejectionChains( vcat( getfield.(c, :samples) ... ), dict)
end
# Properties
nchains(chains::RejectionChains) = length(chains.samples)
length(chains::RejectionChains) = sum(length.(chains.samples)) # total number of unique samples (=storage costs)
eltype(chains::RejectionChains{S,L,R}) where {S,L,R} = S # state type
# Get info on chain
info(chains::RejectionChains) = chains.info
# Get subset from list of chains samples
function getindex(chains::RejectionChains, id::Integer)
samples = [chains.samples[id]]
info = Dict((k=>[chains.info[k][id]] for k in keys(chains.info)) ... )
RejectionChains(samples, info)
end
function getindex(chains::RejectionChains, ids::OrdinalRange)
samples = chains.samples[ids]
info = Dict((k=>chains.info[k][ids] for k in keys(chains.info)) ... )
RejectionChains(samples, infos)
end
# Multilevel logprobs or rejection
is_multilevel(chains::RejectionChains{S,L,R}) where {S,L,R} = (L <: Union{AbstractVector, <:Tuple, <:NamedTuple})
levels(chains::RejectionChains{S,L,R}) where {S, L <: Number, R} = ones(Int, nchains(chains))
function levels(chains::RejectionChains{S,L,R}) where {S, L <: Union{AbstractVector, <:Tuple, <:NamedTuple}, R}
return [
length.(getfield.(chains.samples[i], :logprob))
for i=1:nchains(chains)
]
end
# get number of repetitions
function repetitions(chains::RejectionChains)
return [
1 .+ sum.(getfield.(chains.samples[i], :reject))
for i=1:nchains(chains)
]
end
# Chain of values with repetition of where rejected
function states(chains::RejectionChains{S,L,R}) where {S,L,R}
V = Vector{S}[]
sizehint!(V, nchains(chains))
reps = repetitions(chains)
for i=1:nchains(chains)
x = chains.samples[i]
s = getfield.(x, :state)
v = vcat( fill.(s, reps[i]) ... )
push!(V, v)
end
return V # optionally: vcat(V ... )
end
# Auto covariance
#=
function autocov(x::AbstractVector, lag::Int)
m = mean(x)
cov = (x[lag+1:end] .- m) .* (x[1:end-lag] .- m)
cor = (x .- m) .^ 2
return sum(cov)/sum(cor)
end
=#
function autocov(c::RejectionChains)
X = states(c)
C = [ autocov(X[i]) for i=1:nchains(c) ]
return C
end
function autocov(c::RejectionChains, lags::AbstractVector{<:AbstractVector})
X = states(c)
C = [ autocov(X[i], lags[i]) for i=1:nchains(c) ]
return C
end
function autocov(c::RejectionChains, lags::AbstractVector{<:Integer})
X = states(c)
C = [ autocov(X[i], lags) for i=1:nchains(c) ]
return C
end
# Log partition function, constant factor for normalize log density
function logpart(logp::AbstractVector)
return -log( sum( exp.(-logp) ) )
end
function logpart(c::RejectionChains)
# single level
if ! is_multilevel(c)
reps = repetitions(c)
return [
(
logprobs = getfield.(c.samples[i], :logprob);
logprobs = vcat( fill.(logprobs, reps[i])... );
logpart(logprobs)
)
for i=1:nchains(c)
]
# multi level
else
reps = repetitions(c)
lvls = levels(c); maxlvl = maximum(lvls)
return [
[ # return one constant per level
(
idx = lvls[i] <= l;
logprobs = getindex.(getfield.(c.samples[i][idx], :logprob), l);
logprobs = vcat( fill.(logprobs, reps[i][idx])... );
logpart(logprobs)
)
for l = 1:maxlvl
]
for i=1:nchains(c)
]
end
end
function empirical_cdf(c::RejectionChains, x)
x = convert(eltype(c), x)
if ! is_multilevel(c)
reps = repetitions(c)
return [
(
s = getfield.(c.samples[i], :state);
sum( reps[i] .* [ all(X .<= x) for X in s] ) / sum(reps[i])
)
for i=1:nchains(c)
]
else
reps = repetitions(c)
lvls = levels(c); maxlvl = maximum(lvls)
cdfs = [
(
idx = lvls[i] .<= 1;
s = getfield.(c.samples[i], :state)[idx];
[ sum(reps[i][idx] .* [ all(X .<= x) for X in s]) / sum(reps[i][idx]) ]
)
for i=1:nchains(c)
]
sizehint!.(cdfs, maxlvl)
logparts = logpart(c)
#Z = mean(logparts) # average over chain, otherwise for each chain seperately
for l=2:maxlvl
for i=1:nchains(c)
idx = lvls[i] <= l
s = getfield.(c.samples[i], :state)[idx]; # ?????
f = getfield.(c.samples[i], :logprob)[idx];
f0 = getindex.(f, l-1)
f1 = getindex.(f, l)
Z0,Z1 = logparts[i][l-1:l]
_cdf = sum(
reps[i][idx] .* [ all(X .<= x) for x in s ]
.* (1 .- exp.(f1 - f0 - Z1 + Z0))
) / sum(reps[i][idx])
push!(cdfs[i], _cdf)
end
end
return cdfs
end
end
\ No newline at end of file
# Write to table
function convert(::Type{<:AbstractVector{<:DataFrame}}, chains::RejectionChains)
dfs = DataFrame[]; sizehint!(dfs,length(chains.samples))
for i=1:nchains(chains)
n = length(chains.samples[i])
df = DataFrame()
for k in (:state, :logprob, :reject)
df[:, k] = getfield.(chains.samples[i], k)
end
push!(dfs, df)
end
return dfs
end
# Read from table
function convert(::Type{<:RejectionChains}, df::DataFrame)
s = df[:, :state]
l = df[:, :logprob]
r = df[:, :reject]
c = RejectionChains(s,l,r)
return c
end
function convert(::Type{<:RejectionChains}, df::Vector{<:DataFrame})
return AbstractMCMC.chainscat(convert.(RejectionChains, df) ... )
end
abstract type AbstractLogDensity <: AbstractModel end
struct LogDensity{L} <: AbstractLogDensity
density :: L
end
logdensity(model::LogDensity{<:Function}, x; kwargs...) = model.density(x)
logdensity(model::LogDensity{<:Distribution}, x; kwargs...) = logpdf(model.density, x)
logdensity(model::AbstractMCMC.LogDensityModel, x; kwargs...) = LogDensityProblems.logdensity(model.logdensity, x)
include("samplebased.jl")
include("multilevel.jl")
\ No newline at end of file
abstract type AbstractMultilevelModel <: AbstractModel end
struct MultilevelLogDensity{ L <: Tuple{Vararg{<:AbstractModel}}} <: AbstractMultilevelModel
proxies :: L
end
MultilevelLogDensity(v::L... ) where {L <: AbstractModel} = MultilevelLogDensity(v)
MultilevelLogDensity(v::Vector{ <: AbstractModel} ) = MultilevelLogDensity( tuple(v...) )
length(m::MultilevelLogDensity) = length(m.proxies)
logdensity(m::MultilevelLogDensity, x; level=length(m), kwargs...) = logdensity(m.proxies[level], x)
function Base.show(io::IO, m::MultilevelLogDensity)
println(io, "MultilevelLogDensity ", length(m))
for i=1:length(m)
println(io, " ", m.proxies[i])
end
end
struct MultilevelSampledLogDensity{X, F <: Function} <: AbstractMultilevelModel
density :: SampledLogDensity{X, F}
nlevels :: Vector{Int}
# @assert maximum(nlevels) <= length(density)
end
MultilevelSampledLogDensity(f::Function, n::Vector{<:Integer}, d::Int=1, a=0, b=1) = MultilevelSampledLogDensity(SampledLogDensity(f, maximum(n), d, a, b), n)
function Base.show(io::IO, m::MultilevelSampledLogDensity)
print(io, "MultilevelSampledLogDensity ", m.nlevels, " {", eltype(m.density), "} samples ")
end
length(m::MultilevelSampledLogDensity) = length(m.nlevels)
logdensity(m::MultilevelSampledLogDensity, x; level=length(m), cache=nothing, kwargs...) = _logdensity(m, x, level, cache)
_logdensity(m::MultilevelSampledLogDensity, x, level::Int, cache::Nothing) = logdensity(m.density, x; level=m.nlevels[level])
_logdensity(m::MultilevelSampledLogDensity, x, level::Int, cache::Number) = logdensity(m.density, x; level=m.nlevels[level], cache=(level-1, cache))
struct SampledLogDensity{T, F <: Function} <: AbstractLogDensity
samples :: Vector{T}
func :: F
end
function SampledLogDensity(f::Function, n::Int=32, d::Int=1, a=0, b=1)
p = shuffle!(primes(d^2+1)[1:d])
z = hcat((Halton(p[i])[1:n] for i=1:d) ... )
z = [Tuple(z[i,:]) for i=1:n]
return SampledLogDensity(z, f)
end
eltype(::SampledLogDensity{T,F}) where {T,F} = T
length(m::SampledLogDensity) = length(m.samples)
function Base.show(io::IO, m::SampledLogDensity)
print(io, "SampledLogDensity ", length(m), " {", eltype(m), "} samples ")
end
logdensity(m::SampledLogDensity, x; level=length(m), cache=nothing) = _logdensity(m, x, level, cache)
function _logdensity(m::SampledLogDensity, x, level::Int64, cache::Nothing)
y = [m.func(x, z) for z=m.samples[1:level]] #TODO: parallelize evaluations of m.func !
return mean(y)
end
function _logdensity(m::SampledLogDensity, x, level::Int64, cache::Tuple{Int64, <:Number})
if cache[1] <= level
y = [m.func(x, z) for z=m.samples[cache[1]+1:level]]
return mean(y) * length(y)/level + cache[2] * cache[1]/level
else
return logdensity(m, x; level, nothing)
end
end
abstract type AbstractProposal end
rand(rng::AbstractRNG, p::AbstractProposal) = rand(Random.default_rng(), p)
propose(rng::AbstractRNG, p::AbstractProposal, x=nothing) = propose(rng, p, x)
propose(rng::AbstractRNG, p::AbstractProposal, x::Nothing) = rand(rng, p)
logpratio(p::AbstractProposal, x, y) = 0 # symmetric proposal by default
struct RandomWalk{I, S, F} <: AbstractProposal
init :: I
step :: S
func :: F
end
RandomWalk(init::Distribution, step::Distribution) = RandomWalk(init, step, (.+))
rand(rng::AbstractRNG, p::RandomWalk{<:Distribution, <:Any}) = rand(rng, p.init)
propose(rng::AbstractRNG, p::RandomWalk{<:Distribution, <:Distribution}, x) = p.func(x, rand(rng, p.step))
function CyclicWalk(a=-1, b=1, s=.5)
init = Uniform(a,b); step = Uniform(-s, s)
func = (x,y) -> mod(x+y - a, b-a) + a
RandomWalk(init, step, func)
end
include("networks.jl")
\ No newline at end of file
abstract type NetworkEnsemble <: AbstractProposal end
struct ErdosRenyi <: NetworkEnsemble
n::Int64
p::Float64
s::Int64
end
ErdosRenyi(n::Int64) = ErdosRenyi(n, log(n)/n * (1 + exp(-rand())))
ErdosRenyi(n::Int64, p::AbstractFloat) = ErdosRenyi(n, p, ceil(Int, log(n)))
ErdosRenyi(n::Int64, m::Int64, s::Int64=ceil(Int, log(n))) = ErdosRenyi(n, m/(n*(n-1)/2), s)
Base.rand(rng::AbstractRNG, e::ErdosRenyi) = erdos_renyi(e.n, e.p; rng)
function propose(rng::AbstractRNG, e::ErdosRenyi, G::Graph)
G = copy(G)
for t = 1:e.s
# Choose a pair
u = rand(rng, 1:nv(G))
v = rand(rng, 1:nv(G)-1)
if (v>=u) v+=1 end
# Resample edge
if has_edge(G, u, v)
if rand(rng) < 1 - e.p
rem_edge!(G, u, v)
end
else
if rand(rng) < e.p
add_edge!(G, u, v)
end
end
end
return G
end
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment