-
Notifications
You must be signed in to change notification settings - Fork 2
/
hmc.jl
150 lines (110 loc) · 4.26 KB
/
hmc.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#################### Hamiltonian Monte Carlo ####################
#################### Types and Constructors ####################
mutable struct HMCTune{F<:Function,G<:GradType} <: GradSampler{G}
logf::F
epsilon::Float64
L::Int
SigmaL::Union{UniformScaling{Bool},LowerTriangular{Float64}}
HMCTune(x, epsilon::Real, L::Integer) =
new{typeof(identity),fwd}(identity, epsilon, L, I)
HMCTune(x, epsilon::Real, L::Integer, logfgrad::F) where {F} =
new{F,fwd}(logfgrad, epsilon, L, I)
function HMCTune(x, epsilon::Real, L::Integer, Sigma::Matrix{T}) where {T<:Real}
new{typeof(identity),fwd}(identity, epsilon, L, cholesky(Sigma).L)
end
function HMCTune(
x,
epsilon::Real,
L::Integer,
Sigma::Matrix{T},
logfgrad::F,
::Type{G},
) where {T<:Real,F,G<:GradType}
new{F,G}(logfgrad, epsilon, L, cholesky(Sigma).L)
end
function HMCTune(
x,
epsilon::Real,
L::Integer,
Sigma::UniformScaling{Bool},
logfgrad::F,
::Type{G},
) where {F,G<:GradType}
new{F,G}(logfgrad, epsilon, L, Sigma)
end
end
const HMCVariate = Sampler{HMCTune{F},T} where {T,F}
validate(v::Sampler{HMCTune{F},T}) where {F,T} = validate(v, v.tune.SigmaL)
validate(v::Sampler{HMCTune{F},T}, SigmaL::UniformScaling) where {F,T} = v
function validate(v::Sampler{HMCTune{F},T}, SigmaL::LowerTriangular) where {F,T}
n = length(v)
size(SigmaL, 1) == n ||
throw(ArgumentError("Sigma dimension differs from variate length $n"))
v
end
#################### Sampler Constructor ####################
"""
HMC(params::ElementOrVector{Symbol}, epsilon::Real, L::Integer; args...)
Construct a `Sampler` object for HMC sampling. Parameters are assumed to be
continuous, but may be constrained or unconstrained.
Returns a `Sampler{HMCTune}` type object.
* `params`: stochastic node(s) to be updated with the sampler. Constrained parameters are mapped to unconstrained space according to transformations defined by the Stochastic `unlist()` function.
* `epsilon`: step size.
* `L`: number of steps to take in the Leapfrog algorithm.
* `Sigma`: covariance matrix for the multivariate normal proposal distribution. The covariance matrix is relative to the unconstrained parameter space, where candidate draws are generated. If omitted, the identity matrix is assumed.
"""
function HMC(
params::ElementOrVector{Symbol},
epsilon::Real,
L::Int,
::Type{G} = fwd,
) where {G<:GradType}
tune = HMCTune(Float64[], epsilon, L, I, logpdfgrad!, G)
Sampler(Float64[], params, tune, Symbol[], true)
end
function HMC(
params::ElementOrVector{Symbol},
epsilon::Real,
L::Int,
Sigma::Matrix{<:Real},
::Type{G} = fwd,
) where {G<:GradType}
tune = HMCTune(Float64[], epsilon, L, Sigma, logpdfgrad!, G)
Sampler(Float64[], params, tune, Symbol[], true)
end
#################### Sampling Functions ####################
"""
sample!(v::HMCVariate, logfgrad::Function)
Draw one sample from a target distribution using the HMC sampler. Parameters
are assumed to be continuous and unconstrained.
Returns `v` updated with simulated values and associated tuning parameters.
"""
function sample!(v::Sampler{HMCTune{F,G},T}, logfgrad::Function; kwargs...) where {F,T,G}
tune = v.tune
x1 = v.value[:]
logf0, grad0 = logf1, grad1 = logfgrad(x1)
## Momentum variables
p0 = p1 = tune.SigmaL * randn(length(v))
## Make a half step for a momentum at the beginning
p1 += 0.5 * tune.epsilon * grad0
## Alternate full steps for position and momentum
for i = 1:tune.L
## Make a full step for the position
x1 += tune.epsilon * p1
logf1, grad1 = logfgrad(x1)
## Make a full step for the momentum
p1 += tune.epsilon * grad1
end
## Make a half step for momentum at the end
p1 -= 0.5 * tune.epsilon * grad1
## Negate momentum at end of trajectory to make the proposal symmetric
p1 *= -1.0
## Evaluate potential and kinetic energies at start and end of trajectory
SigmaLinv = inv(tune.SigmaL)
Kp0 = 0.5 * sum(abs2, SigmaLinv * p0)
Kp1 = 0.5 * sum(abs2, SigmaLinv * p1)
if rand() < exp((logf1 - Kp1) - (logf0 - Kp0))
v[:] = x1
end
v
end