wolffd@0
|
1 function [samples, energies, diagn] = hmc(f, x, options, gradf, varargin)
|
wolffd@0
|
2 %HMC Hybrid Monte Carlo sampling.
|
wolffd@0
|
3 %
|
wolffd@0
|
4 % Description
|
wolffd@0
|
5 % SAMPLES = HMC(F, X, OPTIONS, GRADF) uses a hybrid Monte Carlo
|
wolffd@0
|
6 % algorithm to sample from the distribution P ~ EXP(-F), where F is the
|
wolffd@0
|
7 % first argument to HMC. The Markov chain starts at the point X, and
|
wolffd@0
|
8 % the function GRADF is the gradient of the `energy' function F.
|
wolffd@0
|
9 %
|
wolffd@0
|
10 % HMC(F, X, OPTIONS, GRADF, P1, P2, ...) allows additional arguments to
|
wolffd@0
|
11 % be passed to F() and GRADF().
|
wolffd@0
|
12 %
|
wolffd@0
|
13 % [SAMPLES, ENERGIES, DIAGN] = HMC(F, X, OPTIONS, GRADF) also returns a
|
wolffd@0
|
14 % log of the energy values (i.e. negative log probabilities) for the
|
wolffd@0
|
15 % samples in ENERGIES and DIAGN, a structure containing diagnostic
|
wolffd@0
|
16 % information (position, momentum and acceptance threshold) for each
|
wolffd@0
|
17 % step of the chain in DIAGN.POS, DIAGN.MOM and DIAGN.ACC respectively.
|
wolffd@0
|
18 % All candidate states (including rejected ones) are stored in
|
wolffd@0
|
19 % DIAGN.POS.
|
wolffd@0
|
20 %
|
wolffd@0
|
21 % [SAMPLES, ENERGIES, DIAGN] = HMC(F, X, OPTIONS, GRADF) also returns
|
wolffd@0
|
22 % the ENERGIES (i.e. negative log probabilities) corresponding to the
|
wolffd@0
|
23 % samples. The DIAGN structure contains three fields:
|
wolffd@0
|
24 %
|
wolffd@0
|
25 % POS the position vectors of the dynamic process.
|
wolffd@0
|
26 %
|
wolffd@0
|
27 % MOM the momentum vectors of the dynamic process.
|
wolffd@0
|
28 %
|
wolffd@0
|
29 % ACC the acceptance thresholds.
|
wolffd@0
|
30 %
|
wolffd@0
|
31 % S = HMC('STATE') returns a state structure that contains the state of
|
wolffd@0
|
32 % the two random number generators RAND and RANDN and the momentum of
|
wolffd@0
|
33 % the dynamic process. These are contained in fields randstate,
|
wolffd@0
|
34 % randnstate and mom respectively. The momentum state is only used for
|
wolffd@0
|
35 % a persistent momentum update.
|
wolffd@0
|
36 %
|
wolffd@0
|
37 % HMC('STATE', S) resets the state to S. If S is an integer, then it
|
wolffd@0
|
38 % is passed to RAND and RANDN and the momentum variable is randomised.
|
wolffd@0
|
39 % If S is a structure returned by HMC('STATE') then it resets the
|
wolffd@0
|
40 % generator to exactly the same state.
|
wolffd@0
|
41 %
|
wolffd@0
|
42 % The optional parameters in the OPTIONS vector have the following
|
wolffd@0
|
43 % interpretations.
|
wolffd@0
|
44 %
|
wolffd@0
|
45 % OPTIONS(1) is set to 1 to display the energy values and rejection
|
wolffd@0
|
46 % threshold at each step of the Markov chain. If the value is 2, then
|
wolffd@0
|
47 % the position vectors at each step are also displayed.
|
wolffd@0
|
48 %
|
wolffd@0
|
49 % OPTIONS(5) is set to 1 if momentum persistence is used; default 0,
|
wolffd@0
|
50 % for complete replacement of momentum variables.
|
wolffd@0
|
51 %
|
wolffd@0
|
52 % OPTIONS(7) defines the trajectory length (i.e. the number of leap-
|
wolffd@0
|
53 % frog steps at each iteration). Minimum value 1.
|
wolffd@0
|
54 %
|
wolffd@0
|
55 % OPTIONS(9) is set to 1 to check the user defined gradient function.
|
wolffd@0
|
56 %
|
wolffd@0
|
57 % OPTIONS(14) is the number of samples retained from the Markov chain;
|
wolffd@0
|
58 % default 100.
|
wolffd@0
|
59 %
|
wolffd@0
|
60 % OPTIONS(15) is the number of samples omitted from the start of the
|
wolffd@0
|
61 % chain; default 0.
|
wolffd@0
|
62 %
|
wolffd@0
|
63 % OPTIONS(17) defines the momentum used when a persistent update of
|
wolffd@0
|
64 % (leap-frog) momentum is used. This is bounded to the interval [0,
|
wolffd@0
|
65 % 1).
|
wolffd@0
|
66 %
|
wolffd@0
|
67 % OPTIONS(18) is the step size used in leap-frogs; default 1/trajectory
|
wolffd@0
|
68 % length.
|
wolffd@0
|
69 %
|
wolffd@0
|
70 % See also
|
wolffd@0
|
71 % METROP
|
wolffd@0
|
72 %
|
wolffd@0
|
73
|
wolffd@0
|
74 % Copyright (c) Ian T Nabney (1996-2001)
|
wolffd@0
|
75
|
wolffd@0
|
76 % Global variable to store state of momentum variables: set by set_state
|
wolffd@0
|
77 % Used to initialise variable if set
|
wolffd@0
|
78 global HMC_MOM
|
wolffd@0
|
79 if nargin <= 2
|
wolffd@0
|
80 if ~strcmp(f, 'state')
|
wolffd@0
|
81 error('Unknown argument to hmc');
|
wolffd@0
|
82 end
|
wolffd@0
|
83 switch nargin
|
wolffd@0
|
84 case 1
|
wolffd@0
|
85 samples = get_state(f);
|
wolffd@0
|
86 return;
|
wolffd@0
|
87 case 2
|
wolffd@0
|
88 set_state(f, x);
|
wolffd@0
|
89 return;
|
wolffd@0
|
90 end
|
wolffd@0
|
91 end
|
wolffd@0
|
92
|
wolffd@0
|
93 display = options(1);
|
wolffd@0
|
94 if (round(options(5) == 1))
|
wolffd@0
|
95 persistence = 1;
|
wolffd@0
|
96 % Set alpha to lie in [0, 1)
|
wolffd@0
|
97 alpha = max(0, options(17));
|
wolffd@0
|
98 alpha = min(1, alpha);
|
wolffd@0
|
99 salpha = sqrt(1-alpha*alpha);
|
wolffd@0
|
100 else
|
wolffd@0
|
101 persistence = 0;
|
wolffd@0
|
102 end
|
wolffd@0
|
103 L = max(1, options(7)); % At least one step in leap-frogging
|
wolffd@0
|
104 if options(14) > 0
|
wolffd@0
|
105 nsamples = options(14);
|
wolffd@0
|
106 else
|
wolffd@0
|
107 nsamples = 100; % Default
|
wolffd@0
|
108 end
|
wolffd@0
|
109 if options(15) >= 0
|
wolffd@0
|
110 nomit = options(15);
|
wolffd@0
|
111 else
|
wolffd@0
|
112 nomit = 0;
|
wolffd@0
|
113 end
|
wolffd@0
|
114 if options(18) > 0
|
wolffd@0
|
115 step_size = options(18); % Step size.
|
wolffd@0
|
116 else
|
wolffd@0
|
117 step_size = 1/L; % Default
|
wolffd@0
|
118 end
|
wolffd@0
|
119 x = x(:)'; % Force x to be a row vector
|
wolffd@0
|
120 nparams = length(x);
|
wolffd@0
|
121
|
wolffd@0
|
122 % Set up strings for evaluating potential function and its gradient.
|
wolffd@0
|
123 f = fcnchk(f, length(varargin));
|
wolffd@0
|
124 gradf = fcnchk(gradf, length(varargin));
|
wolffd@0
|
125
|
wolffd@0
|
126 % Check the gradient evaluation.
|
wolffd@0
|
127 if (options(9))
|
wolffd@0
|
128 % Check gradients
|
wolffd@0
|
129 feval('gradchek', x, f, gradf, varargin{:});
|
wolffd@0
|
130 end
|
wolffd@0
|
131
|
wolffd@0
|
132 samples = zeros(nsamples, nparams); % Matrix of returned samples.
|
wolffd@0
|
133 if nargout >= 2
|
wolffd@0
|
134 en_save = 1;
|
wolffd@0
|
135 energies = zeros(nsamples, 1);
|
wolffd@0
|
136 else
|
wolffd@0
|
137 en_save = 0;
|
wolffd@0
|
138 end
|
wolffd@0
|
139 if nargout >= 3
|
wolffd@0
|
140 diagnostics = 1;
|
wolffd@0
|
141 diagn_pos = zeros(nsamples, nparams);
|
wolffd@0
|
142 diagn_mom = zeros(nsamples, nparams);
|
wolffd@0
|
143 diagn_acc = zeros(nsamples, 1);
|
wolffd@0
|
144 else
|
wolffd@0
|
145 diagnostics = 0;
|
wolffd@0
|
146 end
|
wolffd@0
|
147
|
wolffd@0
|
148 n = - nomit + 1;
|
wolffd@0
|
149 Eold = feval(f, x, varargin{:}); % Evaluate starting energy.
|
wolffd@0
|
150 nreject = 0;
|
wolffd@0
|
151 if (~persistence | isempty(HMC_MOM))
|
wolffd@0
|
152 p = randn(1, nparams); % Initialise momenta at random
|
wolffd@0
|
153 else
|
wolffd@0
|
154 p = HMC_MOM; % Initialise momenta from stored state
|
wolffd@0
|
155 end
|
wolffd@0
|
156 lambda = 1;
|
wolffd@0
|
157
|
wolffd@0
|
158 % Main loop.
|
wolffd@0
|
159 while n <= nsamples
|
wolffd@0
|
160
|
wolffd@0
|
161 xold = x; % Store starting position.
|
wolffd@0
|
162 pold = p; % Store starting momenta
|
wolffd@0
|
163 Hold = Eold + 0.5*(p*p'); % Recalculate Hamiltonian as momenta have changed
|
wolffd@0
|
164
|
wolffd@0
|
165 if ~persistence
|
wolffd@0
|
166 % Choose a direction at random
|
wolffd@0
|
167 if (rand < 0.5)
|
wolffd@0
|
168 lambda = -1;
|
wolffd@0
|
169 else
|
wolffd@0
|
170 lambda = 1;
|
wolffd@0
|
171 end
|
wolffd@0
|
172 end
|
wolffd@0
|
173 % Perturb step length.
|
wolffd@0
|
174 epsilon = lambda*step_size*(1.0 + 0.1*randn(1));
|
wolffd@0
|
175
|
wolffd@0
|
176 % First half-step of leapfrog.
|
wolffd@0
|
177 p = p - 0.5*epsilon*feval(gradf, x, varargin{:});
|
wolffd@0
|
178 x = x + epsilon*p;
|
wolffd@0
|
179
|
wolffd@0
|
180 % Full leapfrog steps.
|
wolffd@0
|
181 for m = 1 : L - 1
|
wolffd@0
|
182 p = p - epsilon*feval(gradf, x, varargin{:});
|
wolffd@0
|
183 x = x + epsilon*p;
|
wolffd@0
|
184 end
|
wolffd@0
|
185
|
wolffd@0
|
186 % Final half-step of leapfrog.
|
wolffd@0
|
187 p = p - 0.5*epsilon*feval(gradf, x, varargin{:});
|
wolffd@0
|
188
|
wolffd@0
|
189 % Now apply Metropolis algorithm.
|
wolffd@0
|
190 Enew = feval(f, x, varargin{:}); % Evaluate new energy.
|
wolffd@0
|
191 p = -p; % Negate momentum
|
wolffd@0
|
192 Hnew = Enew + 0.5*p*p'; % Evaluate new Hamiltonian.
|
wolffd@0
|
193 a = exp(Hold - Hnew); % Acceptance threshold.
|
wolffd@0
|
194 if (diagnostics & n > 0)
|
wolffd@0
|
195 diagn_pos(n,:) = x;
|
wolffd@0
|
196 diagn_mom(n,:) = p;
|
wolffd@0
|
197 diagn_acc(n,:) = a;
|
wolffd@0
|
198 end
|
wolffd@0
|
199 if (display > 1)
|
wolffd@0
|
200 fprintf(1, 'New position is\n');
|
wolffd@0
|
201 disp(x);
|
wolffd@0
|
202 end
|
wolffd@0
|
203
|
wolffd@0
|
204 if a > rand(1) % Accept the new state.
|
wolffd@0
|
205 Eold = Enew; % Update energy
|
wolffd@0
|
206 if (display > 0)
|
wolffd@0
|
207 fprintf(1, 'Finished step %4d Threshold: %g\n', n, a);
|
wolffd@0
|
208 end
|
wolffd@0
|
209 else % Reject the new state.
|
wolffd@0
|
210 if n > 0
|
wolffd@0
|
211 nreject = nreject + 1;
|
wolffd@0
|
212 end
|
wolffd@0
|
213 x = xold; % Reset position
|
wolffd@0
|
214 p = pold; % Reset momenta
|
wolffd@0
|
215 if (display > 0)
|
wolffd@0
|
216 fprintf(1, ' Sample rejected %4d. Threshold: %g\n', n, a);
|
wolffd@0
|
217 end
|
wolffd@0
|
218 end
|
wolffd@0
|
219 if n > 0
|
wolffd@0
|
220 samples(n,:) = x; % Store sample.
|
wolffd@0
|
221 if en_save
|
wolffd@0
|
222 energies(n) = Eold; % Store energy.
|
wolffd@0
|
223 end
|
wolffd@0
|
224 end
|
wolffd@0
|
225
|
wolffd@0
|
226 % Set momenta for next iteration
|
wolffd@0
|
227 if persistence
|
wolffd@0
|
228 p = -p;
|
wolffd@0
|
229 % Adjust momenta by a small random amount.
|
wolffd@0
|
230 p = alpha.*p + salpha.*randn(1, nparams);
|
wolffd@0
|
231 else
|
wolffd@0
|
232 p = randn(1, nparams); % Replace all momenta.
|
wolffd@0
|
233 end
|
wolffd@0
|
234
|
wolffd@0
|
235 n = n + 1;
|
wolffd@0
|
236 end
|
wolffd@0
|
237
|
wolffd@0
|
238 if (display > 0)
|
wolffd@0
|
239 fprintf(1, '\nFraction of samples rejected: %g\n', ...
|
wolffd@0
|
240 nreject/(nsamples));
|
wolffd@0
|
241 end
|
wolffd@0
|
242 if diagnostics
|
wolffd@0
|
243 diagn.pos = diagn_pos;
|
wolffd@0
|
244 diagn.mom = diagn_mom;
|
wolffd@0
|
245 diagn.acc = diagn_acc;
|
wolffd@0
|
246 end
|
wolffd@0
|
247 % Store final momentum value in global so that it can be retrieved later
|
wolffd@0
|
248 HMC_MOM = p;
|
wolffd@0
|
249 return
|
wolffd@0
|
250
|
wolffd@0
|
251 % Return complete state of sampler (including momentum)
|
wolffd@0
|
252 function state = get_state(f)
|
wolffd@0
|
253
|
wolffd@0
|
254 global HMC_MOM
|
wolffd@0
|
255 state.randstate = rand('state');
|
wolffd@0
|
256 state.randnstate = randn('state');
|
wolffd@0
|
257 state.mom = HMC_MOM;
|
wolffd@0
|
258 return
|
wolffd@0
|
259
|
wolffd@0
|
260 % Set complete state of sampler (including momentum) or just set randn
|
wolffd@0
|
261 % and rand with integer argument.
|
wolffd@0
|
262 function set_state(f, x)
|
wolffd@0
|
263
|
wolffd@0
|
264 global HMC_MOM
|
wolffd@0
|
265 if isnumeric(x)
|
wolffd@0
|
266 rand('state', x);
|
wolffd@0
|
267 randn('state', x);
|
wolffd@0
|
268 HMC_MOM = [];
|
wolffd@0
|
269 else
|
wolffd@0
|
270 if ~isstruct(x)
|
wolffd@0
|
271 error('Second argument to hmc must be number or state structure');
|
wolffd@0
|
272 end
|
wolffd@0
|
273 if (~isfield(x, 'randstate') | ~isfield(x, 'randnstate') ...
|
wolffd@0
|
274 | ~isfield(x, 'mom'))
|
wolffd@0
|
275 error('Second argument to hmc must contain correct fields')
|
wolffd@0
|
276 end
|
wolffd@0
|
277 rand('state', x.randstate);
|
wolffd@0
|
278 randn('state', x.randnstate);
|
wolffd@0
|
279 HMC_MOM = x.mom;
|
wolffd@0
|
280 end
|
wolffd@0
|
281 return
|