1 /*
2 * Copyright (c) 2007 - 2015 Joseph Gaeddert
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <math.h>
27 #include "liquid.internal.h"
28
29 // gradient search algorithm (steepest descent) object
30 struct gradsearch_s {
31 float * v; // vector to optimize (externally allocated)
32 unsigned int num_parameters;// ...
33 float u; // utility at current position
34
35 // properties
36 float delta; // gradient approximation step size
37 float alpha; // line search step size
38
39 float * p; // gradient estimate
40 float pnorm; // L2-norm of gradient estimate
41
42 utility_function utility; // utility function pointer
43 void * userdata; // object to optimize (user data)
44 int direction; // search direction (minimize/maximimze utility)
45 };
46
47 // create a gradient search object
48 // _userdata : user data object pointer
49 // _v : array of parameters to optimize
50 // _num_parameters : array length (number of parameters to optimize)
51 // _u : utility function pointer
52 // _direction : search direction (e.g. LIQUID_OPTIM_MAXIMIZE)
gradsearch_create(void * _userdata,float * _v,unsigned int _num_parameters,utility_function _utility,int _direction)53 gradsearch gradsearch_create(void * _userdata,
54 float * _v,
55 unsigned int _num_parameters,
56 utility_function _utility,
57 int _direction)
58 {
59 gradsearch q = (gradsearch) malloc( sizeof(struct gradsearch_s) );
60
61 // set user-defined properties
62 q->userdata = _userdata;
63 q->v = _v;
64 q->num_parameters = _num_parameters;
65 q->utility = _utility;
66 q->direction = _direction;
67
68 // set internal properties
69 // TODO : make these user-configurable properties
70 q->delta = 1e-6f; // gradient approximation step size
71 q->alpha = q->delta; // line search step size
72
73 // allocate array for gradient estimate
74 q->p = (float*) malloc(q->num_parameters*sizeof(float));
75 q->pnorm = 0.0f;
76 q->u = 0.0f;
77
78 return q;
79 }
80
gradsearch_destroy(gradsearch _q)81 void gradsearch_destroy(gradsearch _q)
82 {
83 // free gradient estimate array
84 free(_q->p);
85
86 // free main object memory
87 free(_q);
88 }
89
90 // print status
gradsearch_print(gradsearch _q)91 void gradsearch_print(gradsearch _q)
92 {
93 //printf("gradient search:\n");
94 printf("u=%12.4e ", _q->u); // utility
95 #if 0
96 // enable more verbose output
97 printf("|p|=%7.1e ", _q->pnorm); // norm(p)
98 printf("del=%7.1e ", _q->delta); // delta
99 #endif
100 printf("step=%7.1e ", _q->alpha); // alpha (step size)
101
102 unsigned int i;
103 printf("{");
104 for (i=0; i<_q->num_parameters; i++)
105 printf("%8.4f", _q->v[i]);
106 printf("}\n");
107 }
108
gradsearch_step(gradsearch _q)109 float gradsearch_step(gradsearch _q)
110 {
111 unsigned int i;
112
113 // ensure norm(p) > 0, otherwise increase delta
114 unsigned int n=20;
115 for (i=0; i<n; i++) {
116 // compute gradient
117 gradsearch_gradient(_q->utility, _q->userdata, _q->v, _q->num_parameters, _q->delta, _q->p);
118
119 // normalize gradient vector
120 _q->pnorm = gradsearch_norm(_q->p, _q->num_parameters);
121
122 if (_q->pnorm > 0.0f) {
123 // try to keep delta about 1e-4 * pnorm
124 if (1e-4f*_q->pnorm < _q->delta)
125 _q->delta *= 0.90f;
126 else if ( 1e-5f*_q->pnorm > _q->delta)
127 _q->delta *= 1.10f;
128
129 break;
130 } else {
131 // step size is too small to approximate gradient
132 _q->delta *= 10.0f;
133 }
134 }
135
136 if (i == n) {
137 fprintf(stderr,"warning: gradsearch_step(), function ill-conditioned\n");
138 return _q->utility(_q->userdata, _q->v, _q->num_parameters);
139 }
140
141 // run line search
142 _q->alpha = gradsearch_linesearch(_q->utility,
143 _q->userdata,
144 _q->direction,
145 _q->num_parameters,
146 _q->v,
147 _q->p,
148 _q->delta);
149
150 // step in the negative direction of the gradient
151 float dir = _q->direction == LIQUID_OPTIM_MINIMIZE ? 1.0f : -1.0f;
152 for (i=0; i<_q->num_parameters; i++)
153 _q->v[i] = _q->v[i] - dir*_q->alpha*_q->p[i];
154
155 // evaluate utility at current position
156 _q->u = _q->utility(_q->userdata, _q->v, _q->num_parameters);
157
158 // return utility
159 return _q->u;
160 }
161
162 // batch execution of gradient search : run many steps and stop
163 // when criteria are met
gradsearch_execute(gradsearch _q,unsigned int _max_iterations,float _target_utility)164 float gradsearch_execute(gradsearch _q,
165 unsigned int _max_iterations,
166 float _target_utility)
167 {
168 int continue_running = 1;
169 unsigned int num_iterations = 0;
170
171 float u = 0.0f;
172 while (continue_running) {
173 // increment number of iterations
174 num_iterations++;
175
176 // step gradient search algorithm
177 u = gradsearch_step(_q);
178
179 // check exit criteria (any one of the following)
180 // * maximum number of iterations met
181 // * maximize utility and target exceeded
182 // * minimize utility and target exceeded
183 if ( (num_iterations >= _max_iterations ) ||
184 (_q->direction == LIQUID_OPTIM_MINIMIZE && u < _target_utility) ||
185 (_q->direction == LIQUID_OPTIM_MAXIMIZE && u > _target_utility) )
186 {
187 continue_running = 0;
188 }
189
190 }
191
192 // return final utility
193 return u;
194 }
195
196
197 //
198 // internal (generic functions)
199 //
200
201 // compute the gradient of a function at a particular point
202 // _utility : user-defined function
203 // _userdata : user-defined data object
204 // _x : operating point, [size: _n x 1]
205 // _n : dimensionality of search
206 // _delta : step value for which to compute gradient
207 // _gradient : resulting gradient
gradsearch_gradient(utility_function _utility,void * _userdata,float * _x,unsigned int _n,float _delta,float * _gradient)208 void gradsearch_gradient(utility_function _utility,
209 void * _userdata,
210 float * _x,
211 unsigned int _n,
212 float _delta,
213 float * _gradient)
214 {
215 // operating point for evaluation
216 float x_prime[_n];
217 float u_prime;
218
219 // evaluate function at current operating point
220 float u0 = _utility(_userdata, _x, _n);
221
222 unsigned int i;
223 for (i=0; i<_n; i++) {
224 // copy operating point
225 memmove(x_prime, _x, _n*sizeof(float));
226
227 // increment test vector by delta along dimension 'i'
228 x_prime[i] += _delta;
229
230 // evaluate new utility
231 u_prime = _utility(_userdata, x_prime, _n);
232
233 // compute gradient estimate
234 _gradient[i] = (u_prime - u0) / _delta;
235 }
236 }
237
238 // execute line search; loosely solve:
239 //
240 // min|max phi(alpha) := f(_x - alpha*_p)
241 //
242 // and return best guess at alpha that achieves this
243 //
244 // _utility : user-defined function
245 // _userdata : user-defined data object
246 // _direction : search direction (e.g. LIQUID_OPTIM_MINIMIZE)
247 // _n : dimensionality of search
248 // _x : operating point, [size: _n x 1]
249 // _p : normalized gradient, [size: _n x 1]
250 // _alpha : initial step size
gradsearch_linesearch(utility_function _utility,void * _userdata,int _direction,unsigned int _n,float * _x,float * _p,float _alpha)251 float gradsearch_linesearch(utility_function _utility,
252 void * _userdata,
253 int _direction,
254 unsigned int _n,
255 float * _x,
256 float * _p,
257 float _alpha)
258 {
259 // evaluate utility at base point
260 float u0 = _utility(_userdata, _x, _n);
261
262 // initialize step size estimate
263 float alpha = _alpha;
264
265 // step direction
266 float dir = _direction == LIQUID_OPTIM_MINIMIZE ? 1.0f : -1.0f;
267
268 // test vector, TODO : dynamic allocation?
269 float x_prime[_n];
270
271 // run line search
272 int continue_running = 1;
273 unsigned int num_iterations = 0;
274 while (continue_running) {
275 // increment iteration counter
276 num_iterations++;
277
278 // update evaluation point
279 unsigned int i;
280 for (i=0; i<_n; i++)
281 x_prime[i] = _x[i] - dir*alpha*_p[i];
282
283 // compute utility for line search step
284 float uls = _utility(_userdata, x_prime, _n);
285 //printf(" linesearch %6u : alpha=%12.6f, u0=%12.8f, uls=%12.8f\n", num_iterations, alpha, u0, uls);
286
287 // check exit criteria
288 if ( (_direction == LIQUID_OPTIM_MINIMIZE && uls > u0) ||
289 (_direction == LIQUID_OPTIM_MAXIMIZE && uls < u0) )
290 {
291 // compared this utility to previous; went too far.
292 // backtrack step size and stop line search
293 alpha *= 0.5f;
294 continue_running = 0;
295 } else if ( num_iterations >= 20 ) {
296 // maximum number of iterations met: stop line search
297 continue_running = 0;
298 } else {
299 // save new best estimate, increase step size, and continue
300 u0 = uls;
301 alpha *= 2.0f;
302 }
303 }
304
305 return alpha;
306 }
307
308 // normalize vector, returning its l2-norm
gradsearch_norm(float * _v,unsigned int _n)309 float gradsearch_norm(float * _v,
310 unsigned int _n)
311 {
312 float vnorm = 0.0f;
313
314 unsigned int i;
315 for (i=0; i<_n; i++)
316 vnorm += _v[i]*_v[i];
317
318 vnorm = sqrtf(vnorm);
319
320 for (i=0; i<_n; i++)
321 _v[i] /= vnorm;
322
323 return vnorm;
324 }
325
326