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