PaGMO  1.1.5
spheres_q.cpp
1 /*****************************************************************************
2  * Copyright (C) 2004-2015 The PaGMO development team, *
3  * Advanced Concepts Team (ACT), European Space Agency (ESA) *
4  * *
5  * https://github.com/esa/pagmo *
6  * *
7  * act@esa.int *
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  * This program is distributed in the hope that it will be useful, *
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17  * GNU General Public License for more details. *
18  * *
19  * You should have received a copy of the GNU General Public License *
20  * along with this program; if not, write to the *
21  * Free Software Foundation, Inc., *
22  * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
23  *****************************************************************************/
24 
25 #include<gsl/gsl_odeiv2.h>
26 #include<gsl/gsl_errno.h>
27 #include<cmath>
28 
29 #include "../exceptions.h"
30 #include "../types.h"
31 #include "../population.h"
32 #include "base_stochastic.h"
33 #include "spheres_q.h"
34 
35 static const int nr_input = 8;
36 static const int nr_output = 3;
37 static const int nr_spheres = 3;
38 static const int nr_eq = 21;
39 static const double target_distance2 = 0.25;
40 static const double target_distance = 0.5;
41 
42 
43 static double norm(double v[3]) {
44  return(std::sqrt(v[0]*v[0] + v[1]*v[1] +v[2]*v[2]));
45 }
46 
47 static double norm2(double v[3]) {
48  return(v[0]*v[0] + v[1]*v[1] +v[2]*v[2]);
49 }
50 
51 static void versify(double v[3]) {
52  double V = norm(v);
53  if (V<=0) printf("Warning zero norm vector passed to versify");
54  v[0] = v[0] / V;
55  v[1] = v[1] / V;
56  v[2] = v[2] / V;
57 }
58 
59 static void cross_vector(double vout[3], const double vin1[3], const double vin2[3]) {
60  vout[0] = vin1[1]*vin2[2] - vin1[2]*vin2[1];
61  vout[1] = vin1[2]*vin2[0] - vin1[0]*vin2[2];
62  vout[2] = vin1[0]*vin2[1] - vin1[1]*vin2[0];
63 }
64 
65 /* efficient, robust conversion from any quaternion — whether unit, nonunit, or even zero — to
66  * a 3×3 rotation matrix. Taken from http://en.wikipedia.org/wiki/Rotation_matrix#Quaternion
67  */
68 void q2C( double Cout[3][3], const double q[4] )
69 {
70 #define x q[0]
71 #define y q[1]
72 #define z q[2]
73 #define w q[3]
74 
75  double Nq = w*w + x*x + y*y + z*z;
76  double s = ( (Nq > 0.0) ? (2.0 / Nq) : (0.0) );
77  double X = x * s;
78  double Y = y * s;
79  double Z = z * s;
80 
81  Cout[0][0] = 1.0 - (y*Y + z*Z);
82  Cout[0][1] = x*Y - w*Z;
83  Cout[0][2] = x*Z + w*Y;
84 
85  Cout[1][0] = x*Y + w*Z;
86  Cout[1][1] = 1.0 - (x*X + z*Z);
87  Cout[1][2] = y*Z - w*X;
88 
89  Cout[2][0] = x*Z - w*Y;
90  Cout[2][1] = y*Z + w*X;
91  Cout[2][2] = 1.0 - (x*X + y*Y);
92 
93 #undef w
94 #undef x
95 #undef y
96 #undef z
97 }
98 
99 //row by column matrix multiplication
100 static void matrix_transformation(double vout[3], const double R[3][3]) {
101  double vout_cp[3];
102  vout_cp[0] = vout[0]; vout_cp[1] = vout[1]; vout_cp[2] = vout[2];
103  for (int i=0;i<3;++i) {
104  vout[i] = R[i][0] * vout_cp[0] + R[i][1] * vout_cp[1] + R[i][2] * vout_cp[2];
105  }
106 }
107 
108 //column by row matrix multiplication
109 static void matrix_inv_transformation(double vout[3], const double R[3][3]) {
110  double vout_cp[3];
111  vout_cp[0] = vout[0]; vout_cp[1] = vout[1]; vout_cp[2] = vout[2];
112  for (int i=0;i<3;++i) {
113  vout[i] = R[0][i] * vout_cp[0] + R[1][i] * vout_cp[1] + R[2][i] * vout_cp[2];
114  }
115 }
116 
117 static void quat_dyn(double dq[4], const double q[4], const double w[3]) {
118  dq[0] = 0.5 * (q[1]*w[2] - q[2]*w[1] + q[3]*w[0]);
119  dq[1] = 0.5 * (q[2]*w[0] - q[0]*w[2] + q[3]*w[1]);
120  dq[2] = 0.5 * (q[0]*w[1] - q[1]*w[0] + q[3]*w[2]);
121  dq[3] = -0.5 * (q[0]*w[0] + q[1]*w[1] + q[2]*w[2]);
122 }
123 
124 namespace pagmo { namespace problem {
125 
126 spheres_q::spheres_q(int n_evaluations, int n_hidden_neurons,
127  double numerical_precision, unsigned int seed) :
128  base_stochastic((nr_input + 1) * n_hidden_neurons + (n_hidden_neurons + 1) * nr_output, seed),
129  m_ffnn(nr_input,n_hidden_neurons,nr_output), m_n_evaluations(n_evaluations),
130  m_n_hidden_neurons(n_hidden_neurons), m_numerical_precision(numerical_precision),
131  m_ic(nr_eq) {
132  // Here we set the bounds for the problem decision vector, i.e. the nn weights
133  set_lb(-1);
134  set_ub(1);
135  // We then instantiate the ode integrator system using gsl
136  gsl_odeiv2_system sys = {ode_func,NULL,nr_eq,&m_ffnn};
137  m_sys = sys;
138  m_gsl_drv_pntr = gsl_odeiv2_driver_alloc_y_new(&m_sys, gsl_odeiv2_step_rk8pd, 1e-6,m_numerical_precision,0.0);
139 }
140 
142  base_stochastic(other),
143  m_ffnn(other.m_ffnn),
144  m_n_evaluations(other.m_n_evaluations),m_n_hidden_neurons(other.m_n_hidden_neurons),
145  m_numerical_precision(other.m_numerical_precision),m_ic(other.m_ic)
146 {
147  // Here we set the bounds for the problem decision vector, i.e. the nn weights
148  gsl_odeiv2_system sys = {ode_func,NULL,nr_eq,&m_ffnn};
149  m_sys = sys;
150  m_gsl_drv_pntr = gsl_odeiv2_driver_alloc_y_new(&m_sys, gsl_odeiv2_step_rk8pd, 1e-6,m_numerical_precision,0.0);
151 }
152 
154  gsl_odeiv2_driver_free(m_gsl_drv_pntr);
155 }
156 
159 {
160  return base_ptr(new spheres_q(*this));
161 }
162 
163 //This function evaluates the fitness of a given spheres configuration ....
164 double spheres_q::single_fitness( const std::vector<double> &y, const ffnn& neural_net) const {
165  double fit = 0.0;
166  double context[8], vel_f[3], C[3][3];
167  int k;
168 
169  // For each sphere
170  for( int i = 0; i < nr_spheres; i++ ){ // i - is the sphere counter 0 .. 1 .. 2 ..
171  k = 0;
172 
173  // We now load in context the perceived data (as decoded from the world state y)
174  for( int n = 1; n <= nr_spheres - 1; n++ ){ // consider the vector from each other sphere
175  for( int j = 0; j < 3; j++ ){ // consider each component from the vectors
176  context[k++] = y[i*3 + j] - y[ (i*3 + j + n*3) % 9 ];
177  }
178  }
179 
180  // Context now contains the relative position vectors (6 components) in the absolute frame
181  // we write, on the last two components of context, the norms of these relative positions
182  context[6] = context[0]*context[0] + context[1]*context[1] + context[2]*context[2];
183  context[7] = context[3]*context[3] + context[4]*context[4] + context[5]*context[5];
184 
185  // We put the perception in body axis
186  q2C(C,&y[9 + i*4]);
187  matrix_transformation(&context[0],C);
188  matrix_transformation(&context[3],C);
189 
190  //We evaluate the output from the neural net (no need to rotate back in this case)
191  neural_net.eval(vel_f, context);
192  vel_f[0] = vel_f[0] * 2 * 0.3 - 0.3;
193  vel_f[1] = vel_f[1] * 2 * 0.3 - 0.3;
194  vel_f[2] = vel_f[2] * 2 * 0.3 - 0.3;
195 
196  //first add |L² - R²| to the fitness
197  double temp = std::abs(target_distance2 - context[6]);
198  fit += temp;
199  temp = std::abs(target_distance2 - context[7]);
200  fit += temp;
201 
202  // and then the final velocity
203  //fit += norm2(vel_f);
204  }
205  return (fit / 2);
206 }
207 
208 int spheres_q::ode_func( double t, const double y[], double f[], void *params ) {
209 
210  // Here we recover the neural network
211  ffnn *ptr_ffnn = (ffnn*)params;
212 
213  // The fixed-size vector context represent the sensory data perceived from each sphere. These are
214  // the body axis components of the relative positions of the other spheres, and their modules
215  double context[nr_input];
216  double out[nr_output];
217 
218  // Here are some counters
219  int k;
220  // and the rotation matrix
221  double C[3][3];
222 
223  for( int i = 0; i < nr_spheres; i++ ){ // i - is the sphere counter 0 .. 1 .. 2 ..
224  k = 0;
225 
226  // we now load in context the perceived data (as decoded from the world state y)
227  for( int n = 1; n <= nr_spheres - 1; n++ ){ // consider the vector from each other sphere
228  for( int j = 0; j < 3; j++ ){ // consider each component from the vectors
229  context[k++] = y[i*3 + j] - y[ (i*3 + j + n*3) % 9 ];
230  }
231  }
232 
233  // context now contains the relative position vectors (6 components) in the absolute frame
234  // we write, on the last two components of context, the norms of these relative positions
235  context[6] = context[0]*context[0] + context[1]*context[1] + context[2]*context[2];
236  context[7] = context[3]*context[3] + context[4]*context[4] + context[5]*context[5];
237 
238  // We put the perception in body axis
239  q2C(C,&y[9 + i*4]);
240  matrix_transformation(&context[0],C);
241  matrix_transformation(&context[3],C);
242 
243  //We evaluate the output from the neural net
244  ptr_ffnn->eval(out, context);
245  out[0] = out[0] * 0.3 * 2 - 0.3;
246  out[1] = out[1] * 0.3 * 2 - 0.3;
247  out[2] = out[2] * 0.3 * 2 - 0.3;
248 
249  // We transform back from body axis to absolute reference
250  matrix_inv_transformation(&out[0],C);
251 
252  // Here we set the dynamics of positions ...
253  f[i*3] = out[0];
254  f[i*3+1] = out[1];
255  f[i*3+2] = out[2];
256 
257  // ... and quaternion
258 // double wd[3], tmp[8]={0.1,0.03,-1.4,0.2,0.09,0.1,0.02,-0.4};
259 // ptr_ffnn->eval(wd,tmp);
260 // quat_dyn(&f[9+i*4],&y[9+i*4],wd);
261  f[9+i*4] = 0; f[10+i*4] = 0; f[11+i*4] = 0; f[12+i*4] = 0;
262 
263  }
264  return GSL_SUCCESS;
265 }
266 
267 spheres_q::ffnn::ffnn(const unsigned int n_inputs, const unsigned int n_hidden,const unsigned int n_outputs) :
268  m_n_inputs(n_inputs), m_n_hidden(n_hidden), m_n_outputs(n_outputs),
269  m_weights((n_inputs + 1) * n_hidden + (n_hidden + 1) * n_outputs), m_hidden(n_hidden)
270 {}
271 
272 void spheres_q::ffnn::set_weights(const std::vector<double> &weights) {
273  m_weights = weights;
274 }
275 
276 void spheres_q::ffnn::eval(double out[], const double in[]) const {
277  // Offset for the weights to the output nodes
278  unsigned int offset = m_n_hidden * (m_n_inputs + 1);
279 
280  // -- PROCESS CONTEXT USING THE NEURAL NETWORK --
281  for( unsigned int i = 0; i < m_n_hidden; i++ ){
282  // Set the bias (the first weight to the i'th hidden node)
283  m_hidden[i] = m_weights[i * (m_n_inputs + 1)];
284 
285  for( unsigned int j = 0; j < m_n_inputs; j++ ){
286  // Compute the weight number
287  int ji = i * (m_n_inputs + 1) + (j + 1);
288  // Add the weighted input
289  m_hidden[i] += m_weights[ji] * in[j];
290  }
291 
292  // Apply the transfer function (a sigmoid with output in [0,1])
293  m_hidden[i] = 1.0 / ( 1 + std::exp( -m_hidden[i] ));
294  }
295 
296  // generate values for the output nodes
297  for( unsigned int i = 0; i < m_n_outputs; i++ ){
298  // add the bias (weighted by the first weight to the i^th output node
299  out[i] = m_weights[offset + i * (m_n_hidden + 1)];
300 
301  for( unsigned int j = 0; j < m_n_hidden; j++ ){
302  // compute the weight number
303  int ji = offset + i * (m_n_hidden + 1) + (j + 1);
304  // add the weighted input
305  out[i] += m_weights[ji] * m_hidden[j];
306  }
307 
308  out[i] = 1.0 / ( 1 + std::exp( -out[i] ));
309  }
310 }
311 
313  f[0]=0;
314 
315  // Make sure the pseudorandom sequence will always be the same
316  m_drng.seed(m_seed);
317 
318  // Set the ffnn weights
319  m_ffnn.set_weights(x);
320 
321  // Loop over the number of repetitions
322  for (int count=0;count<m_n_evaluations;++count) {
323 
324  // Creates the initial conditions at random
325  // Position starts in a [-2,2] box
326  for (int i=0; i<9; ++i) {
327  m_ic[i] = (m_drng()*4 - 2);
328  }
329 
330  // randomly initialize Spheres' quaternion using the equations in
331  // http://planning.cs.uiuc.edu/node198.html
332  for( int it = 0; it< nr_spheres; ++it) {
333  double u1 = m_drng();
334  double u2 = m_drng();
335  double u3 = m_drng();
336  double radice = sqrt(1-u1);
337  m_ic[9 + 4*it] = radice*sin(2*u2*M_PI);
338  m_ic[10 + 4*it] = radice*cos(2*u2*M_PI);
339  radice = sqrt(u1);
340  m_ic[11 + 4*it] = radice*sin(2*u3*M_PI);
341  m_ic[12 + 4*it] = radice*cos(2*u3*M_PI);
342  }
343 
344  // Integrate the system
345  double t0 = 0.0;
346  double tf = 50.0;
347  //gsl_odeiv2_driver_set_hmin (m_gsl_drv_pntr, 1e-6);
348  int status = gsl_odeiv2_driver_apply( m_gsl_drv_pntr, &t0, tf, &m_ic[0] );
349  // Not sure if this help or what it does ....
350  gsl_odeiv2_driver_reset (m_gsl_drv_pntr);
351  if( status != GSL_SUCCESS ){
352  printf ("ERROR: gsl_odeiv2_driver_apply returned value = %d\n", status);
353  break;
354  }
355  f[0] += single_fitness(m_ic,m_ffnn);
356 
357  }
358  f[0] /= m_n_evaluations;
359 }
360 
361 static bool my_sort_function (std::vector<double> i,std::vector<double> j) { return (i[nr_eq] < j[nr_eq]); }
362 
363 std::vector<std::vector<double> > spheres_q::post_evaluate(const decision_vector & x, int N, unsigned int seed) const {
364  std::vector<double> one_row(nr_eq+1,0.0);
365  std::vector<std::vector<double> > ret(N,one_row);
366  // Make sure the pseudorandom sequence will always be the same
367  m_drng.seed(seed);
368  // Set the ffnn weights
369  m_ffnn.set_weights(x);
370  // Loop over the number of repetitions
371  for (int count=0;count<N;++count) {
372  // Creates the initial conditions at random
373  // Position starts in a [-1,1] box (evolution is in [-2,2])
374  for (int i=0; i<9; ++i) {
375  m_ic[i] = (m_drng()*2 - 1);
376  one_row[i] = m_ic[i];
377  }
378 
379  // randomly initialize Spheres' quaternion using the equations in
380  // http://planning.cs.uiuc.edu/node198.html
381  for( int it = 0; it< nr_spheres; ++it) {
382  double u1 = m_drng();
383  double u2 = m_drng();
384  double u3 = m_drng();
385  double radice = sqrt(1-u1);
386  m_ic[9 + 4*it] = radice*sin(2*u2*M_PI);
387  m_ic[10 + 4*it] = radice*cos(2*u2*M_PI);
388  radice = sqrt(u1);
389  m_ic[11 + 4*it] = radice*sin(2*u3*M_PI);
390  m_ic[12 + 4*it] = radice*cos(2*u3*M_PI);
391  one_row[9+ 4*it] = m_ic[9 + 4*it];
392  one_row[10+ 4*it] = m_ic[10 + 4*it];
393  one_row[11+ 4*it] = m_ic[11 + 4*it];
394  one_row[12+ 4*it] = m_ic[12 + 4*it];
395  }
396 
397 
398  // Integrate the system
399  double t0 = 0.0;
400  double tf = 50.0;
401  //gsl_odeiv2_driver_set_hmin (m_gsl_drv_pntr, 1e-6);
402  int status = gsl_odeiv2_driver_apply( m_gsl_drv_pntr, &t0, tf, &m_ic[0] );
403  // Not sure if this help or what it does ....
404  //gsl_odeiv2_driver_reset (m_gsl_drv_pntr);
405  if( status != GSL_SUCCESS ){
406  printf ("ERROR: gsl_odeiv2_driver_apply returned value = %d\n", status);
407  break;
408  }
409  one_row[nr_eq] = single_fitness(m_ic,m_ffnn);
410  ret[count] = one_row;
411  }
412  // sorting by fitness
413  std::sort (ret.begin(), ret.end(), my_sort_function);
414  return ( ret );
415 }
416 
417 std::vector<std::vector<double> > spheres_q::simulate(const decision_vector &x, const std::vector<double> &ic, int N) const {
418  std::vector<double> y0(ic);
419  std::vector<double> one_row(nr_eq+1,0.0);
420  std::vector<std::vector<double> > ret;
421  // Set the ffnn weights
422  m_ffnn.set_weights(x);
423  // Integrate the system
424  double ti, t0=0;
425  double tf = 70.0;
426 
427  // pushing back the initial conditions
428  one_row[0] = 0.0;
429  std::copy(y0.begin(),y0.end(),one_row.begin()+1);
430  ret.push_back(one_row);
431 
432  for( int i = 1; i <= N; i++ ){
433  ti = i * tf / N;
434  int status = gsl_odeiv2_driver_apply( m_gsl_drv_pntr, &t0, ti, &y0[0] );
435  //pushing_back the result
436  one_row[0] = ti;
437  std::copy(y0.begin(),y0.end(),one_row.begin()+1);
438  ret.push_back(one_row);
439  if( status != GSL_SUCCESS ){
440  printf ("ERROR: gsl_odeiv2_driver_apply returned value = %d\n", status);
441  break;
442  }
443  }
444  return ( ret );
445 }
446 } //namespace problem
447 } //namespace pagmo
448 
449 BOOST_CLASS_EXPORT_IMPLEMENT(pagmo::problem::spheres_q);
Root PaGMO namespace.
boost::shared_ptr< base > base_ptr
Alias for shared pointer to base problem.
Definition: problem/base.h:62
std::vector< double > decision_vector
Decision vector type.
Definition: types.h:40
spheres_q(int n_evaluations=10, int n_hidden=10, double ode_prec=1E-3, unsigned int seed=0)
Constructor.
Definition: spheres_q.cpp:126
~spheres_q()
Destructor.
Definition: spheres_q.cpp:153
rng_double m_drng
Random number generator for double-precision floating point values.
void set_lb(const decision_vector &)
Set lower bounds from pagmo::decision_vector.
unsigned int m_seed
Seed of the random number generator.
void set_ub(const decision_vector &)
Set upper bounds from pagmo::decision_vector.
std::vector< std::vector< double > > simulate(const decision_vector &x, const std::vector< double > &ic, int N) const
Performs one "detailed" simulation.
Definition: spheres_q.cpp:417
std::vector< double > fitness_vector
Fitness vector type.
Definition: types.h:42
std::vector< std::vector< double > > post_evaluate(const decision_vector &x, int N=25000, unsigned int seed=0) const
Post evaluation of the neural controller.
Definition: spheres_q.cpp:363
Base Stochastic Optimization Problem.
base_ptr clone() const
Clone method.
Definition: spheres_q.cpp:158
Evolutionary Neuro-Controller for the MIT Spheres (perception-action defined in the body frame) ...
Definition: spheres_q.h:66
void objfun_impl(fitness_vector &, const decision_vector &) const
Objective function implementation.
Definition: spheres_q.cpp:312