Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add acceptance rate info to Transition struct #88

Merged
merged 6 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Documentation for AdvancedMH.jl
## Structs
```@docs
MetropolisHastings
Transition
cnrrobertson marked this conversation as resolved.
Show resolved Hide resolved
```

## Functions
Expand Down
24 changes: 14 additions & 10 deletions src/AdvancedMH.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ export
# Reexports
export sample, MCMCThreads, MCMCDistributed, MCMCSerial

# Abstract type for MH-style samplers. Needs better name?
# Abstract type for MH-style samplers. Needs better name?
abstract type MHSampler <: AbstractMCMC.AbstractSampler end

# Abstract type for MH-style transitions.
abstract type AbstractTransition end

# Define a model type. Stores the log density function and the data to
# Define a model type. Stores the log density function and the data to
# evaluate the log density on.
"""
DensityModel{F} <: AbstractModel
Expand All @@ -53,17 +53,21 @@ end

const DensityModelOrLogDensityModel = Union{<:DensityModel,<:AbstractMCMC.LogDensityModel}

# Create a very basic Transition type, only stores the
# parameter draws and the log probability of the draw.
struct Transition{T,L<:Real} <: AbstractTransition
# Create a very basic Transition type, stores the
# parameter draws, the log probability of the draw,
# and the draw information until this point
struct Transition{T,L<:Real,M<:Bool,D<:Int} <: AbstractTransition
cnrrobertson marked this conversation as resolved.
Show resolved Hide resolved
params :: T
lp :: L
accepted :: M
accepted_draws :: D
cnrrobertson marked this conversation as resolved.
Show resolved Hide resolved
cnrrobertson marked this conversation as resolved.
Show resolved Hide resolved
total_draws :: D
end

# Store the new draw and its log density.
Transition(model::DensityModelOrLogDensityModel, params) = Transition(params, logdensity(model, params))
function Transition(model::AbstractMCMC.LogDensityModel, params)
return Transition(params, LogDensityProblems.logdensity(model.logdensity, params))
# Store the new draw, its log density, and draw information
Transition(model::DensityModelOrLogDensityModel, params, accepted, accepted_draws, total_draws) = Transition(params, logdensity(model, params), accepted, accepted_draws, total_draws)
function Transition(model::AbstractMCMC.LogDensityModel, params, accepted, accepted_draws, total_draws)
return Transition(params, LogDensityProblems.logdensity(model.logdensity, params), accepted, accepted_draws, total_draws)
end

# Calculate the density of the model given some parameterization.
Expand Down Expand Up @@ -128,7 +132,7 @@ function __init__()
if exc.f === logdensity_and_gradient && length(arg_types) == 2 && first(arg_types) <: DensityModel && isempty(kwargs)
print(io, "\\nDid you forget to load ForwardDiff?")
end
end
end
@static if !isdefined(Base, :get_extension)
@require MCMCChains="c7f686f2-ff18-58e9-bc7b-31028e88f75d" include("../ext/AdvancedMHMCMCChainsExt.jl")
@require StructArrays="09ab397b-f2b6-538f-b94a-2f83cf4a842a" include("../ext/AdvancedMHStructArraysExt.jl")
Expand Down
21 changes: 15 additions & 6 deletions src/MALA.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ MALA(d::RandomWalkProposal) = MALA{typeof(d)}(d)
MALA(d) = MALA(RandomWalkProposal(d))


struct GradientTransition{T<:Union{Vector, Real, NamedTuple}, L<:Real, G<:Union{Vector, Real, NamedTuple}} <: AbstractTransition
struct GradientTransition{T<:Union{Vector, Real, NamedTuple}, L<:Real, G<:Union{Vector, Real, NamedTuple},M<:Bool,D<:Int} <: AbstractTransition
cnrrobertson marked this conversation as resolved.
Show resolved Hide resolved
params::T
lp::L
gradient::G
accepted :: M
accepted_draws :: D
total_draws :: D
cnrrobertson marked this conversation as resolved.
Show resolved Hide resolved
end

logdensity(model::DensityModelOrLogDensityModel, t::GradientTransition) = t.lp

propose(rng::Random.AbstractRNG, ::MALA, model) = error("please specify initial parameters")
function transition(sampler::MALA, model::DensityModelOrLogDensityModel, params)
return GradientTransition(params, logdensity_and_gradient(model, params)...)
function transition(sampler::MALA, model::DensityModelOrLogDensityModel, params, accepted, accepted_draws, total_draws)
return GradientTransition(params, logdensity_and_gradient(model, params)..., accepted, accepted_draws, total_draws)
end

check_capabilities(model::DensityModelOrLogDensityModel) = nothing
Expand All @@ -44,7 +47,7 @@ function AbstractMCMC.step(
kwargs...
)
check_capabilities(model)

# Extract value and gradient of the log density of the current state.
state = transition_prev.params
logdensity_state = transition_prev.lp
Expand All @@ -68,10 +71,16 @@ function AbstractMCMC.step(
logα = logdensity_candidate - logdensity_state + logratio_proposal_density

# Decide whether to return the previous params or the new one.
total_draws = transition_prev.total_draws + 1
transition = if -Random.randexp(rng) < logα
GradientTransition(candidate, logdensity_candidate, gradient_logdensity_candidate)
accepted_draws = transition_prev.accepted_draws + 1
GradientTransition(candidate, logdensity_candidate, gradient_logdensity_candidate, true, accepted_draws, total_draws)
else
transition_prev
accepted_draws = transition_prev.accepted_draws
candidate = transition_prev.params
lp = transition_prev.lp
gradient = transition_prev.gradient
GradientTransition(candidate, lp, gradient, false, accepted_draws, total_draws)
end

return transition, transition
Expand Down
37 changes: 22 additions & 15 deletions src/emcee.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ struct Ensemble{D} <: MHSampler
proposal::D
end

function transition(sampler::Ensemble, model::DensityModelOrLogDensityModel, params)
return [Transition(model, x) for x in params]
function transition(sampler::Ensemble, model::DensityModelOrLogDensityModel, params, accepted, accepted_draws, total_draws)
return [Transition(model, x, accepted, accepted_draws, total_draws) for x in params]
end

# Define the other sampling steps.
Expand Down Expand Up @@ -68,7 +68,7 @@ end
StretchProposal(p) = StretchProposal(p, 2.0)

function move(
rng::Random.AbstractRNG,
rng::Random.AbstractRNG,
spl::Ensemble{<:StretchProposal},
model::DensityModelOrLogDensityModel,
walker::Transition,
Expand All @@ -84,16 +84,23 @@ function move(
# Make new parameters
y = @. other_walker.params + z * (walker.params - other_walker.params)

# Construct a new walker
new_walker = Transition(model, y)
# Construct a temporary new walker
temp_walker = Transition(model, y, true, walker.accepted_draws, walker.total_draws)

# Calculate accept/reject value.
alpha = alphamult + new_walker.lp - walker.lp
alpha = alphamult + temp_walker.lp - walker.lp

total_draws = walker.total_draws + 1
if -Random.randexp(rng) <= alpha
accepted_draws = walker.accepted_draws + 1
new_walker = Transition(model, y, true, accepted_draws, total_draws)
return new_walker
else
return walker
accepted_draws = walker.accepted_draws
params = walker.params
lp = walker.lp
old_walker = Transition(params, lp, false, accepted_draws, total_draws)
return old_walker
end
end

Expand Down Expand Up @@ -124,7 +131,7 @@ end

# theta_min = theta - 2.0*π
# theta_max = theta

# f = walker.params
# while true
# stheta, ctheta = sincos(theta)
Expand All @@ -136,15 +143,15 @@ end
# if new_walker.lp > y
# return new_walker
# else
# if theta < 0
# if theta < 0
# theta_min = theta
# else
# theta_max = theta
# end

# theta = theta_min + (theta_max - theta_min) * rand()
# end
# end
# end
# end

#####################
Expand Down Expand Up @@ -180,15 +187,15 @@ end

# theta_min = theta - 2.0*π
# theta_max = theta

# f = walker.params

# i = 0
# while true
# i += 1

# stheta, ctheta = sincos(theta)

# f_prime = f .* ctheta + nu .* stheta

# new_walker = Transition(model, f_prime)
Expand All @@ -198,13 +205,13 @@ end
# if new_walker.lp > y
# return new_walker
# else
# if theta < 0
# if theta < 0
# theta_min = theta
# else
# theta_max = theta
# end

# theta = theta_min + (theta_max - theta_min) * rand()
# end
# end
# end
# end
23 changes: 14 additions & 9 deletions src/mh-core.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
MetropolisHastings{D}

`MetropolisHastings` has one field, `proposal`.
`MetropolisHastings` has one field, `proposal`.
`proposal` is a `Proposal`, `NamedTuple` of `Proposal`, or `Array{Proposal}` in the shape of your data.
For example, if you wanted the sampler to return a `NamedTuple` with shape

Expand Down Expand Up @@ -38,7 +38,7 @@ none is given, the initial parameters will be drawn from the sampler's proposals
- `param_names` is a vector of strings to be assigned to parameters. This is only
used if `chain_type=Chains`.
- `chain_type` is the type of chain you would like returned to you. Supported
types are `chain_type=Chains` if `MCMCChains` is imported, or
types are `chain_type=Chains` if `MCMCChains` is imported, or
`chain_type=StructArray` if `StructArrays` is imported.
"""
struct MetropolisHastings{D} <: MHSampler
Expand All @@ -62,12 +62,12 @@ function propose(
return propose(rng, sampler.proposal, model, transition_prev.params)
end

function transition(sampler::MHSampler, model::DensityModelOrLogDensityModel, params)
function transition(sampler::MHSampler, model::DensityModelOrLogDensityModel, params, accepted, accepted_draws, total_draws)
logdensity = AdvancedMH.logdensity(model, params)
return transition(sampler, model, params, logdensity)
return transition(sampler, model, params, logdensity, accepted, accepted_draws, total_draws)
end
function transition(sampler::MHSampler, model::DensityModelOrLogDensityModel, params, logdensity::Real)
return Transition(params, logdensity)
function transition(sampler::MHSampler, model::DensityModelOrLogDensityModel, params, logdensity::Real, accepted, accepted_draws, total_draws)
return Transition(params, logdensity, accepted, accepted_draws, total_draws)
end

# Define the first sampling step.
Expand All @@ -81,7 +81,7 @@ function AbstractMCMC.step(
kwargs...
)
params = init_params === nothing ? propose(rng, sampler, model) : init_params
cpfiffer marked this conversation as resolved.
Show resolved Hide resolved
transition = AdvancedMH.transition(sampler, model, params)
transition = AdvancedMH.transition(sampler, model, params, false, 0, 0)
return transition, transition
end

Expand All @@ -105,10 +105,15 @@ function AbstractMCMC.step(
logratio_proposal_density(sampler, transition_prev, candidate)

# Decide whether to return the previous params or the new one.
total_draws = transition_prev.total_draws + 1
transition = if -Random.randexp(rng) < logα
AdvancedMH.transition(sampler, model, candidate, logdensity_candidate)
accepted_draws = transition_prev.accepted_draws + 1
AdvancedMH.transition(sampler, model, candidate, logdensity_candidate, true, accepted_draws, total_draws)
else
transition_prev
params = transition_prev.params
lp = transition_prev.lp
accepted_draws = transition_prev.accepted_draws
Transition(params, lp, false, accepted_draws, total_draws)
end

return transition, transition
Expand Down
Loading