Skip to content

Commit b6c2a73

Browse files
authored
Update AdvancedMH.jl wrapper for Monte Carlo sampling. (#325)
1. AdvancedMH or one of its dependencies may have updated a keyword from "initial_config" to "initial_params". This caused NQCD to not deliver initial positions to the sampling chain correctly, leading to weird results for systems not centred around 0. 2. Updated new keywords `movement_ratio` and `stop_ratio`, where `movement_ratio=1-stop_ratio` as the old `move_ratio` was confusing. Warning for deprecated kwargs was added and documentation updated with information about new keywords * Drop compat for AdvancedMH.jl<0.8 for #320,#321 * Version bump for #325
1 parent 39a9603 commit b6c2a73

File tree

3 files changed

+97
-53
lines changed

3 files changed

+97
-53
lines changed

Project.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "NQCDynamics"
22
uuid = "36248dfb-79eb-4f4d-ab9c-e29ea5f33e14"
33
authors = ["James <[email protected]>"]
4-
version = "0.13.4"
4+
version = "0.13.5"
55

66
[deps]
77
AdvancedHMC = "0bf59076-c3b1-5ca4-86bd-e02cd72cde3d"
@@ -47,7 +47,7 @@ UnitfulAtomic = "a7773ee8-282e-5fa2-be4e-bd808c38a91a"
4747

4848
[compat]
4949
AdvancedHMC = "0.5, 0.6"
50-
AdvancedMH = "0.6, 0.7, 0.8"
50+
AdvancedMH = "0.8"
5151
ComponentArrays = "0.11, 0.12, 0.13, 0.14, 0.15"
5252
DEDataArrays = "0.2"
5353
Dictionaries = "0.3"

docs/src/initialconditions/metropolishastings.md

+47-31
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@ random walk starting from an initial configuration.
1414
These are accepted or rejected based upon the Metropolis-Hastings criteria.
1515
The result is a Markov chain that samples the canonical distribution.
1616

17-
## Example
17+
!!! Legacy version
18+
19+
Prior to the use of [`AdvancedMH.jl`](https://github.com/TuringLang/AdvancedMH.jl),
20+
an alternative version of the algorithm was implemented that works for both classical
21+
and ring polymer systems: `MetropolisHastings.run_monte_carlo_sampling(sim, R0, Δ, passes)`
22+
23+
This is currently still included in the code but should be regarded as deprecated and
24+
will likely be removed/combined with the [`AdvancedMH.jl`](https://github.com/TuringLang/AdvancedMH.jl)
25+
version.
26+
27+
## Example 1
1828

1929
We can perform the sampling by setting up a classical simulation in the usual way and
2030
providing an appropriate initial configuration.
@@ -33,14 +43,30 @@ steps = 1e4
3343
step_size = Dict(:H=>1)
3444
```
3545

36-
Now we can run the sampling. The extra keyword argument `move_ratio` is used to specify
46+
Now we can run the sampling. The extra keyword argument `movement_ratio` is used to specify
3747
the fraction of the system moved during each Monte Carlo step.
3848
If we attempt to move the entire system at once, we can expect a very low acceptance ratio,
3949
whereas is we move only a single atom, the sampling will take much longer.
4050
You will likely have to experiment with this parameter to achieve optimal sampling.
51+
52+
!!! note
53+
54+
Keyword arguments relating to how much of the system to sample were recently changed. The existing `move_ratio` and `internal_ratio` arguments are no longer used.
55+
56+
Instead, there are now two options to specify how much of the system to move:
57+
58+
`movement_ratio`: Defines which fraction of the system to move. 1 moves the entire system.
59+
60+
`stop_ratio`: Defines which fraction of the system *not* to move. 1 stops the entire system.
61+
62+
`movement_ratio_internal`: Defines which proportion of ring polymer normal modes to perturb. 1 moves the entire system.
63+
64+
`stop_ratio_internal`: Defines which proportion of ring polymer normal modes not to perturb. 1 stops the entire system.
65+
66+
4167
```@example mh
4268
using NQCDynamics.InitialConditions: ThermalMonteCarlo
43-
chain = ThermalMonteCarlo.run_advancedmh_sampling(sim, r0, steps, step_size; move_ratio=0.5)
69+
chain = ThermalMonteCarlo.run_advancedmh_sampling(sim, r0, steps, step_size; movement_ratio=0.5)
4470
```
4571

4672
Now that our sampling is complete we can evaluate the potential energy expectation value.
@@ -53,17 +79,10 @@ Estimators.@estimate potential_energy(sim, chain)
5379
sim.temperature / 2 * 5
5480
```
5581

56-
## Legacy version
5782

58-
Prior to the use of [`AdvancedMH.jl`](https://github.com/TuringLang/AdvancedMH.jl),
59-
an alternative version of the algorithm was implemented that works for both classical
60-
and ring polymer systems.
61-
This is currently still included in the code but should be regarded as deprecated and
62-
will likely be removed/combined with the [`AdvancedMH.jl`](https://github.com/TuringLang/AdvancedMH.jl)
63-
version.
64-
65-
Here, we use the legacy version to obtain a thermal distribution in a simple
66-
model system.
83+
## Example 2
84+
Here, we obtain a thermal distribution in a simple model system with some additional tweaks to
85+
try and sample a larger configuration space.
6786

6887
```@setup monte
6988
using NQCDynamics
@@ -83,40 +102,37 @@ nothing # hide
83102
```
84103

85104
Then we have to specify the parameters for the Monte Carlo simulation and perform the sampling.
86-
`Δ` contains the step sizes for each of the species, `R0` the initial geometry and `passes` the
87-
number of monte carlo passes we perform (`passes*n_atoms` steps total).
105+
`Δ` contains the step sizes for each of the species, `R0` the initial geometry and `samples` the
106+
number of configurations we want to obtain.
107+
108+
*AdvancedMH.jl* provides some additional options to control the sampling process. To (hopefully) include
109+
a larger configuration space in the final results, we set a `thinning` of 10, meaning that we only keep
110+
every 10th proposed configuration. In addition, we discard the first 100 samples, since our initial configuration might not
111+
lie in the equilibrium. This is set with `discard_initial`.
112+
Further explanations of the keyword arguments can be found in the *AbstractMCMC.jl* [documentation](https://turinglang.org/AbstractMCMC.jl/dev/api/#Common-keyword-arguments).
113+
88114
```@example monte
89-
Δ = Dict([(:N, 0.1), (:O, 0.1)])
115+
Δ = Dict(:N => 0.1, :O => 0.1)
90116
R0 = [1.0 0.0; 0.0 0.0; 0.0 0.0]
91-
passes = 1000
92-
output = InitialConditions.MetropolisHastings.run_monte_carlo_sampling(sim, R0, Δ, passes)
117+
samples = 1000
118+
output = InitialConditions.ThermalMonteCarlo.run_advancedmh_sampling(sim, R0, samples, Δ; movement_ratio=0.5, thinning=10, discard_initial=100)
93119
nothing # hide
94120
```
95121

96-
Output has three fields: the acceptance rates for each species and the energies and geometries
97-
obtained during sampling.
98-
```@repl monte
99-
output.acceptance
100-
```
101-
```@example monte
102-
plot(output.energy)
103-
xlabel!("Step") # hide
104-
ylabel!("Energy") # hide
105-
```
106-
107122
We can calculate the distance between each atom and plot the bond length throughout the sampling.
108123
```@example monte
109124
using LinearAlgebra
110-
plot([norm(R[:,1] .- R[:,2]) for R in output.R])
125+
plot([norm(R[:,1] .- R[:,2]) for R in output])
111126
xlabel!("Step") # hide
112127
ylabel!("Bond length") # hide
113128
```
114129

115130
The result of this simulation seamlessly interfaces with the `DynamicalDistribution`
116-
presented in the previous section and `output.R` can be readily passed to provide
131+
presented in the previous section and `output` can be readily passed to provide
117132
the position distribution.
118133
The Monte Carlo sampling does not include velocities but these can be readily
119134
obtained from the Maxwell-Boltzmann distribution.
135+
120136
```@setup logging
121137
runtime = round(time() - start_time; digits=2)
122138
@info "...done after $runtime s."

src/InitialConditions/ThermalMonteCarlo.jl

+48-20
Original file line numberDiff line numberDiff line change
@@ -27,40 +27,68 @@ using NQCDynamics:
2727
masses
2828

2929
"""
30-
run_advancedhmc_sampling(sim, r, steps, σ; move_ratio=0.0, internal_ratio=0.0)
30+
run_advancedmh_sampling(sim, r, steps, σ; movement_ratio=nothing, movement_ratio_internal=nothing, kwargs...)
3131
3232
Sample the configuration space for the simulation `sim` starting from `r`.
3333
3434
Total number of steps is given by `steps` and `σ` is the dictionary of
3535
step sizes for each species.
3636
37-
`move_ratio` defaults to `0.0` and denotes the fraction of system moved each step.
38-
If `move_ratio = 0`, every degree of freedom is moved at each step.
39-
If `move_ratio = 1`, then nothing will happen. Experiment with this parameter to achieve
40-
optimal sampling.
37+
`movement_ratio` denotes the fraction of system moved each step.
38+
`internal_ratio` works as for `movement_ratio` but for the internal modes of the ring polymer.
39+
For `movement_ratio = 0`, every degree of freedom is moved at each step, if `movement_ratio = 1`, then nothing will happen.
4140
42-
`internal_ratio` works as for `move_ratio` but for the internal modes of the ring polymer.
41+
If neither arguments are defined, default behaviour is to move one atom (and one ring polymer normal mode) per step on average.
42+
43+
Further kwargs are passed to `AdvancedMH.sample` to allow for [extra functionality](https://turinglang.org/AbstractMCMC.jl/dev/api/#Common-keyword-arguments).
4344
"""
4445
function run_advancedmh_sampling(
4546
sim::AbstractSimulation,
4647
r,
4748
steps::Real,
4849
σ::Dict{Symbol,<:Real};
49-
move_ratio=0.0,
50-
internal_ratio=0.0
50+
movement_ratio=nothing,
51+
stop_ratio=nothing,
52+
movement_ratio_internal=nothing,
53+
stop_ratio_internal=nothing,
54+
move_ratio=nothing, # deprecated
55+
internal_ratio=nothing, # deprecated
56+
kwargs...
5157
)
5258

59+
# Give a warning if move_ratio or internal_ratio are used
60+
if move_ratio !== nothing || internal_ratio !== nothing
61+
@warn "move_ratio and internal_ratio kwargs are deprecated and may be removed in future. More information: https://nqcd.github.io/NQCDynamics.jl/stable/initialconditions/metropolishastings/"
62+
stop_ratio=move_ratio === nothing ? nothing : move_ratio
63+
stop_ratio_internal=internal_ratio === nothing ? nothing : internal_ratio
64+
end
65+
66+
# If no kwargs for system fraction to move are given, perturb one atom and one normal mode at a time
67+
if movement_ratio===nothing && stop_ratio===nothing
68+
@debug "No movement restriction for atoms provided, automatically setting to move one atom per step on average."
69+
movement_ratio=1/size(sim)[2]
70+
end
71+
72+
if movement_ratio_internal===nothing && stop_ratio_internal===nothing
73+
@debug "No movement restriction for ring polymer normal modes provided, automatically setting to move one mode per step on average."
74+
movement_ratio_internal=length(size(sim))==3 ? 1/size(sim)[3] : 0.0
75+
end
76+
77+
# Set atom movement ratio by using whichever keyword is defined
78+
stop_ratio = stop_ratio===nothing ? 1-movement_ratio : stop_ratio
79+
stop_ratio_internal = stop_ratio_internal===nothing ? 1-movement_ratio_internal : stop_ratio_internal
80+
5381
density = get_density_function(sim)
5482

5583
density_model = AdvancedMH.DensityModel(density)
56-
proposal = get_proposal(sim, σ, move_ratio, internal_ratio)
84+
proposal = get_proposal(sim, σ, stop_ratio, stop_ratio_internal)
5785

5886
sampler = AdvancedMH.MetropolisHastings(proposal)
5987

6088
initial_config = reshape_input(sim, copy(r))
6189

6290
chain = AdvancedMH.sample(density_model, sampler, convert(Int, steps);
63-
init_params=initial_config)
91+
initial_params=initial_config, kwargs...)
6492

6593
return reshape_output(sim, chain)
6694
end
@@ -116,29 +144,29 @@ end
116144

117145
struct ClassicalProposal{P,T,S<:Simulation} <: AdvancedMH.Proposal{P}
118146
proposal::P
119-
move_ratio::T
147+
stop_ratio::T
120148
sim::S
121149
end
122150

123151
struct RingPolymerProposal{P,T,S<:RingPolymerSimulation} <: AdvancedMH.Proposal{P}
124152
proposal::P
125-
move_ratio::T
126-
internal_ratio::T
153+
stop_ratio::T
154+
stop_ratio_internal::T
127155
sim::S
128156
end
129157

130158
const MolecularProposal{P,T,S} = Union{RingPolymerProposal{P,T,S},ClassicalProposal{P,T,S}}
131159

132-
function ClassicalProposal(sim::Simulation, σ, move_ratio)
160+
function ClassicalProposal(sim::Simulation, σ, stop_ratio)
133161
proposals = Matrix{UnivariateDistribution}(undef, size(sim))
134162
for (i, symbol) in enumerate(sim.atoms.types) # Position proposals
135163
distribution = σ[symbol] == 0 ? Dirac(0) : Normal(0, σ[symbol])
136164
proposals[:, i] .= distribution
137165
end
138-
ClassicalProposal(proposals[:], move_ratio, sim)
166+
ClassicalProposal(proposals[:], stop_ratio, sim)
139167
end
140168

141-
function RingPolymerProposal(sim::RingPolymerSimulation, σ, move_ratio, internal_ratio)
169+
function RingPolymerProposal(sim::RingPolymerSimulation, σ, stop_ratio, stop_ratio_internal)
142170
ωₖ = RingPolymers.get_matsubara_frequencies(nbeads(sim), sim.beads.ω_n)
143171
proposals = Array{UnivariateDistribution}(undef, size(sim))
144172
for i = 1:nbeads(sim)
@@ -159,12 +187,12 @@ function RingPolymerProposal(sim::RingPolymerSimulation, σ, move_ratio, interna
159187
end
160188
end
161189
end
162-
RingPolymerProposal(proposals[:], move_ratio, internal_ratio, sim)
190+
RingPolymerProposal(proposals[:], stop_ratio, stop_ratio_internal, sim)
163191
end
164192

165193
function Base.rand(rng::Random.AbstractRNG, p::ClassicalProposal)
166194
result = map(x -> rand(rng, x), p.proposal)
167-
result[Random.randsubseq(eachindex(result), p.move_ratio)] .= 0
195+
result[Random.randsubseq(findall(x -> x!=0.0, result), p.stop_ratio)] .=0
168196
return result
169197
end
170198

@@ -173,12 +201,12 @@ function Base.rand(rng::Random.AbstractRNG, p::RingPolymerProposal)
173201
reshaped_result = reshape(result, size(p.sim))
174202

175203
# Zero some of the centroid moves
176-
sequence = Random.randsubseq(CartesianIndices(size(p.sim)[1:2]), p.move_ratio)
204+
sequence = Random.randsubseq(findall(x -> x != 0, CartesianIndices(size(p.sim)[1:2])), p.stop_ratio)
177205
reshaped_result[sequence, 1] .= 0
178206

179207
# Zero some of the internal mode moves
180208
for i = 2:nbeads(p.sim)
181-
sequence = Random.randsubseq(CartesianIndices((ndofs(p.sim), natoms(p.sim))), p.internal_ratio)
209+
sequence = Random.randsubseq(findall(x -> x != 0, CartesianIndices((ndofs(p.sim), natoms(p.sim)))), p.stop_ratio_internal)
182210
reshaped_result[sequence, i] .= 0
183211
end
184212

0 commit comments

Comments
 (0)