bbea8523a3f823e0b15624169f41ccc7a3485338
[universe.git] / main.cc
1
2 // Written and (C) by Francois Fleuret
3 // Contact <francois.fleuret@idiap.ch> for comments & bug reports
4
5 #include <iostream>
6 #include <fstream>
7 #include <cmath>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <stdint.h>
11 #include <errno.h>
12 #include <string.h>
13
14 using namespace std;
15
16 #include "misc.h"
17 #include "task.h"
18 #include "simple_window.h"
19 #include "universe.h"
20 #include "retina.h"
21 #include "manipulator.h"
22 #include "intelligence.h"
23 #include "xfig_tracer.h"
24
25 #ifdef CAIRO_SUPPORT
26 #include <cairo.h>
27
28 static cairo_status_t write_cairo_to_file(void *closure,
29                                           const unsigned char *data,
30                                           unsigned int length) {
31   fwrite(data, 1, length, (FILE *) closure);
32   return CAIRO_STATUS_SUCCESS;
33 }
34
35 void generate_png(Universe *universe, scalar_t scale, FILE *file) {
36   const int depth = 4;
37   const int width = int(universe->width() * scale);
38   const int height = int(universe->height() * scale);
39
40   cairo_surface_t *image;
41   cairo_t* context_resource;
42   unsigned char *data;
43
44   data = new unsigned char [width * height * depth];
45
46   image = cairo_image_surface_create_for_data(data,
47                                               CAIRO_FORMAT_RGB24,
48                                               width,
49                                               height,
50                                               width * depth);
51
52   context_resource = cairo_create(image);
53
54   cairo_scale(context_resource, scale, scale);
55
56   cairo_set_source_rgb(context_resource, 1.0, 1.0, 1.0);
57
58   cairo_rectangle(context_resource, 0, 0,
59                   universe->width(), universe->height());
60
61   cairo_fill(context_resource);
62
63   universe->draw(context_resource);
64
65   cairo_surface_write_to_png_stream(image, write_cairo_to_file, file);
66
67   cairo_destroy(context_resource);
68   cairo_surface_destroy(image);
69
70   delete[] data;
71 }
72 #endif
73
74 // To train
75 // ./main --task hit_shape.task 0 --action-mode=random --nb-ticks=5000 --proportion-for-training=0.5 --save-file=dump.mem --no-window
76
77 // To test
78 // ./main --task hit_shape.task 0 --action-mode=intelligent --load-file=dump.mem
79
80 //////////////////////////////////////////////////////////////////////
81
82 void check_opt(int argc, char **argv, int n_opt, int n, const char *help) {
83   if(n_opt + n >= argc) {
84     cerr << "Missing argument for " << argv[n_opt] << "." << endl;
85     cerr << "Expecting " << help << "." << endl;
86     exit(1);
87   }
88 }
89
90 void print_help_and_exit(int e) {
91   cout << "Arguments:" << endl;
92   cout << "  --no-window" << endl;
93   cout << "  --nb-ticks=<int: number of ticks>" << endl;
94   cout << "  --nb-training-iterations=<int: number of training iterations>" << endl;
95   cout << "  --load-file=<filename: dump file>" << endl;
96   cout << "  --save-file=<filename: dump file>" << endl;
97   cout << "  --proportion-for-training=<float: proportion of samples for training>" << endl;
98   cout << "  --action-mode=<idle|random|intelligent>" << endl;
99   cout << "  --task <filename: task to load> <int: degree>" << endl;
100   exit(e);
101 }
102
103 //////////////////////////////////////////////////////////////////////
104
105 int main(int argc, char **argv) {
106
107   const int buffer_size = 256;
108   char intelligence_load_file[buffer_size] = "", intelligence_save_file[buffer_size] = "";
109
110   Task *task = 0;
111   int task_degree = 0;
112
113   int nb_ticks = 10000;
114   int nb_training_iterations = 10;
115   scalar_t proportion_for_training = -1;
116
117   Polygon *grabbed_polygon = 0;
118   scalar_t relative_grab_x = 0, relative_grab_y = 0;
119
120   bool quit = false;
121   bool press_shift = false;
122   enum { IDLE, RANDOM, INTELLIGENT } action_mode = IDLE;
123   bool got_event = true;
124   int current_action = 0;
125
126   scalar_t last_hand_x = 0, last_hand_y = 0;
127   Polygon *last_grabbing = 0;
128   bool no_window = false;
129
130   //////////////////////////////////////////////////////////////////////
131   //                    Parsing the shell arguments
132   //////////////////////////////////////////////////////////////////////
133
134   int i = 1;
135   while(i < argc) {
136     if(argc == 1 || strcmp(argv[i], "--help") == 0) print_help_and_exit(0);
137
138     else if(strcmp(argv[i], "--test") == 0) {
139       test_approximer();
140       exit(0);
141     }
142
143     else if(strcmp(argv[i], "--task") == 0) {
144       check_opt(argc, argv, i, 2, "<filename: task to load> <int: degree>");
145       if(task) {
146         cerr << "Can not load two tasks." << endl;
147         exit(1);
148       }
149       task = load_task(argv[i+1]);
150       task_degree = atoi(argv[i+2]);
151       i += 3;
152
153     } else if(strncmp(argv[i], "--", 2) == 0) {
154       char variable_name[buffer_size] = "", variable_value[buffer_size] = "";
155       char *o = argv[i]+2, *s = variable_name, *u = variable_value;
156       while(*o && *o != '=') *s++ = *o++;
157       if(*o) {
158         o++;
159         while(*o) *u++ = *o++;
160       }
161
162       if(strcmp(variable_name, "nb-ticks") == 0)
163         nb_ticks = atoi(variable_value);
164       else if(strcmp(variable_name, "nb-training-iterations") == 0)
165         nb_training_iterations = atoi(variable_value);
166       else if(strcmp(variable_name, "proportion-for-training") == 0)
167         proportion_for_training = atof(variable_value);
168       else if(strcmp(variable_name, "no-window") == 0)
169         no_window = true;
170       else if(strcmp(variable_name, "save-file") == 0)
171         strcpy(intelligence_save_file, variable_value);
172       else if(strcmp(variable_name, "load-file") == 0)
173         strcpy(intelligence_load_file, variable_value);
174       else if(strcmp(variable_name, "action-mode") == 0) {
175         if(strcmp(variable_value, "idle") == 0) action_mode = IDLE;
176         else if(strcmp(variable_value, "random") == 0) action_mode = RANDOM;
177         else if(strcmp(variable_value, "intelligent") == 0) action_mode = INTELLIGENT;
178         else {
179           cerr << "The only known modes are idle, random and intelligent" << endl;
180           exit(1);
181         }
182       } else {
183         cerr << "Unknown option " << argv[i] << endl;
184         print_help_and_exit(1);
185       }
186       i++;
187     } else {
188       cerr << "Unknown option " << argv[i] << endl;
189       print_help_and_exit(1);
190     }
191   }
192
193   cout << "FlatLand, a toy universe for goal-planning experiments." << endl;
194
195   if(!task) {
196     task = load_task("dummy.so");
197     task_degree = 0;
198   }
199
200   cout << "Loaded task " << task->name()
201        << " with degree " << task_degree << "/" << task->nb_degrees()
202        << endl;
203
204   if(task_degree < 0 || task_degree >= task->nb_degrees()) {
205     cout << "Invalid degree: " << task_degree << "." << endl;
206     exit(1);
207   }
208
209   //////////////////////////////////////////////////////////////////////
210   //                      Various initializations
211   //////////////////////////////////////////////////////////////////////
212
213   Universe universe(100, task->width(), task->height());
214   task->init(&universe, task_degree);
215   Retina retina(&universe);
216   Manipulator manipulator(task);
217   manipulator.force_move(task->width()/2, task->height()/2);
218   retina.set_location(manipulator.hand_x(), manipulator.hand_y());
219
220   SimpleWindow *window_main = 0, *window_brain = 0;
221   int window_main_fd = -1;
222
223 #ifdef CAIRO_SUPPORT
224   cairo_t *window_main_cairo_cr = 0;
225 #endif
226
227   MapConcatener sensory_map(2);
228   sensory_map.add_map(&retina);
229   sensory_map.add_map(&manipulator);
230   sensory_map.init();
231
232   MapExpander expanded_map(1000);
233   expanded_map.set_input(&sensory_map);
234   expanded_map.init();
235
236   Intelligence intelligence(&expanded_map, &manipulator, nb_ticks + 1, nb_training_iterations);
237   intelligence.update(0, 0.0);
238
239   if(intelligence_load_file[0]) {
240     cout << "Loading from " << intelligence_load_file << " ... " ;
241     cout.flush();
242     ifstream in(intelligence_load_file);
243     if(in.fail()) {
244       cerr << "error reading " << intelligence_load_file << "." << endl;
245       exit(1);
246     }
247     intelligence.load(in);
248     cout << "done." << endl ;
249   }
250
251   if(no_window) {
252     cout << "Started without windows." << endl;
253   } else {
254     window_main = new SimpleWindow("Universe (main window)", 4, 4, task->width(), task->height());
255     window_main_fd = window_main->file_descriptor();
256     window_main->map();
257 #ifdef CAIRO_SUPPORT
258     window_main_cairo_cr = window_main->get_cairo_context_resource();
259 #endif
260     cout << "When the main window has the focus, press `q' to quit and click and drag to move" << endl
261          << "objects." << endl;
262     window_brain = new SimpleWindow("Universe (brain)",
263                                     12 + task->width(), 4,
264                                     retina.width(), retina.height() + manipulator.parameter_height());
265     window_brain->map();
266   }
267
268   int tick = 0;
269   time_t last_t = 0;
270   scalar_t sum_reward = 0;
271
272   //////////////////////////////////////////////////////////////////////
273   //                         The main loop
274   //////////////////////////////////////////////////////////////////////
275
276   while(!quit && tick != nb_ticks) {
277
278     int r;
279     fd_set fds;
280
281
282     if(window_main) {
283       struct timeval tv;
284       FD_ZERO (&fds);
285       FD_SET (window_main_fd, &fds);
286       tv.tv_sec = 0;
287       tv.tv_usec = 5000; // 0.05s
288       r = select(window_main_fd + 1, &fds, 0, 0, &tv);
289     } else r = 0;
290
291     time_t t = time(0);
292
293     if(t > last_t) {
294       last_t = t;
295       cout << tick << " " << sum_reward << "              \r"; cout.flush();
296     }
297
298     if(r == 0) { // No window event, thus it's the clock tick
299
300         int nb_it = 10;
301
302         bool changed = got_event;
303         got_event = false;
304
305         switch(action_mode) {
306         case IDLE:
307           break;
308         case RANDOM:
309           current_action = manipulator.random_action();
310           break;
311         case INTELLIGENT:
312           current_action = intelligence.best_action();
313           //           if(drand48() < 0.5) current_action = intelligence.best_action();
314           //           else                current_action = manipulator.random_action();
315           break;
316         }
317
318         manipulator.do_action(current_action);
319
320         scalar_t dt = 1.0/scalar_t(nb_it);
321         for(int k = 0; k < nb_it; k++) {
322           manipulator.update(dt, &universe);
323           task->update(dt, &universe, &manipulator);
324           changed |= universe.update(dt);
325         }
326
327         tick++;
328
329         changed |= manipulator.hand_x() != last_hand_x ||
330           manipulator.hand_y() != last_hand_y ||
331           manipulator.grabbing() != last_grabbing;
332
333         scalar_t reward = task->reward(&universe, &manipulator);
334         sum_reward += abs(reward);
335         intelligence.update(current_action, reward);
336         expanded_map.update_map();
337
338         if(changed) {
339           last_hand_x = manipulator.hand_x();
340           last_hand_y = manipulator.hand_y();
341           last_grabbing = manipulator.grabbing();
342
343           retina.set_location(manipulator.hand_x(),
344                               manipulator.hand_y());
345
346           if(window_main) {
347             window_main->color(0.0, 0.0, 0.0);
348             window_main->color(1.0, 1.0, 1.0);
349             window_main->fill();
350
351 #ifdef CAIRO_SUPPORT
352             universe.draw(window_main_cairo_cr);
353 #else
354             universe.draw(window_main);
355 #endif
356
357             task->draw(window_main);
358             manipulator.draw_on_universe(window_main);
359             retina.draw_on_universe(window_main);
360
361             if(grabbed_polygon) {
362               int x, y, delta = 3;
363               x = int(grabbed_polygon->absolute_x(relative_grab_x, relative_grab_y));
364               y = int(grabbed_polygon->absolute_y(relative_grab_x, relative_grab_y));
365               window_main->color(0.0, 0.0, 0.0);
366               window_main->draw_line(x - delta, y, x + delta, y);
367               window_main->draw_line(x, y - delta, x, y + delta);
368             }
369
370             window_main->show();
371
372             if(window_brain) {
373               retina.draw_parameters(0, 0, window_brain);
374               manipulator.draw_parameters(0, retina.height() + 1, window_brain);
375               window_brain->show();
376             }
377           }
378         }
379
380     } else if(r > 0) { // We got window events, let's process them
381
382       got_event = true;
383
384       if(FD_ISSET(window_main_fd, &fds)) {
385
386         SimpleEvent se;
387
388         do {
389           se = window_main->event();
390
391           switch(se.type) {
392
393           case SimpleEvent::MOUSE_CLICK_PRESS:
394             {
395               switch(se.button) {
396
397               case 1:
398                 if(press_shift) {
399                   manipulator.force_move(se.x, se.y);
400                   manipulator.do_action(Manipulator::ACTION_GRAB);
401                 } else {
402                   grabbed_polygon = universe.pick_polygon(se.x, se.y);
403                   if(grabbed_polygon) {
404                     relative_grab_x = grabbed_polygon->relative_x(se.x, se.y);
405                     relative_grab_y = grabbed_polygon->relative_y(se.x, se.y);
406                   }
407                 }
408                 break;
409               case 4:
410                 {
411                   Polygon *g = universe.pick_polygon(se.x, se.y);
412                   if(g) g->_theta += M_PI/32;
413                 }
414                 break;
415               case 5:
416                 {
417                   Polygon *g = universe.pick_polygon(se.x, se.y);
418                   if(g) g->_theta -= M_PI/32;
419                 }
420                 break;
421               }
422             }
423             break;
424
425           case SimpleEvent::MOUSE_CLICK_RELEASE:
426             switch(se.button) {
427             case 1:
428               if(press_shift) manipulator.do_action(Manipulator::ACTION_RELEASE);
429               else            grabbed_polygon = 0;
430               break;
431             default:
432               break;
433             }
434
435           case SimpleEvent::MOUSE_MOTION:
436             {
437               if(press_shift) {
438                 manipulator.force_move(se.x, se.y);
439               } else if(grabbed_polygon) {
440                 scalar_t xf, yf, force_x, force_y, f, fmax = 100;
441                 xf = grabbed_polygon->absolute_x(relative_grab_x, relative_grab_y);
442                 yf = grabbed_polygon->absolute_y(relative_grab_x, relative_grab_y);
443                 force_x = se.x - xf;
444                 force_y = se.y - yf;
445                 f = sqrt(sq(force_x) + sq(force_y));
446                 if(f > fmax) { force_x = (force_x * fmax)/f; force_y = (force_y * fmax)/f; }
447                 grabbed_polygon->apply_force(0.1, xf, yf, force_x, force_y);
448               }
449               break;
450             }
451             break;
452
453           case SimpleEvent::KEY_PRESS:
454             {
455               if(strcmp(se.key, "q") == 0) {
456                 quit = true;
457               }
458
459               else if(strcmp(se.key, "s") == 0) {
460
461                 retina.save_as_ppm("/tmp/retina.ppm");
462                 cout << "Retina screen shot saved in /tmp/retina.ppm" << endl;
463
464                 {
465                   XFigTracer tracer("/tmp/universe.fig");
466                   universe.print_xfig(&tracer);
467                 }
468
469 #ifdef CAIRO_SUPPORT
470                 {
471                   FILE *file = fopen("/tmp/universe.png", "w");
472                   generate_png(&universe, 0.25, file);
473                   // generate_png(task->width(), task->height(), &universe, "/tmp/universe.png");
474                   cout << "Universe image saved in /tmp/universe.png" << endl;
475                 }
476 #endif
477
478               }
479
480               else if(strcmp(se.key, "Shift_L") == 0 || strcmp(se.key, "Shift_R") == 0) {
481                 press_shift = true;
482               }
483
484               else if(strcmp(se.key, "Up") == 0) {
485                 manipulator.do_action(Manipulator::ACTION_MOVE_UP);
486               }
487
488               else if(strcmp(se.key, "Right") == 0) {
489                 manipulator.do_action(Manipulator::ACTION_MOVE_RIGHT);
490               }
491
492               else if(strcmp(se.key, "Down") == 0) {
493                 manipulator.do_action(Manipulator::ACTION_MOVE_DOWN);
494               }
495
496               else if(strcmp(se.key, "Left") == 0) {
497                 manipulator.do_action(Manipulator::ACTION_MOVE_LEFT);
498               }
499
500               else if(strcmp(se.key, "g") == 0) {
501                 manipulator.do_action(Manipulator::ACTION_GRAB);
502               }
503
504               else if(strcmp(se.key, "r") == 0) {
505                 manipulator.do_action(Manipulator::ACTION_RELEASE);
506               }
507
508               else if(strcmp(se.key, "space") == 0) {
509                 switch(action_mode) {
510                 case IDLE:
511                   action_mode = RANDOM;
512                   cout << "Switched to random mode" << endl;
513                   break;
514                 case RANDOM:
515                   action_mode = INTELLIGENT;
516                   cout << "Switched to intelligent mode" << endl;
517                   break;
518                 case INTELLIGENT:
519                   cout << "Switched to idle mode" << endl;
520                   action_mode = IDLE;
521                   break;
522                 }
523               }
524
525               else cout << "Undefined key " << se.key << endl;
526             }
527             break;
528
529           case SimpleEvent::KEY_RELEASE:
530             {
531               if(strcmp(se.key, "Shift_L") == 0 || strcmp(se.key, "Shift_R") == 0) press_shift = false;
532             }
533             break;
534
535           default:
536             break;
537
538           }
539         } while(se.type != SimpleEvent::NO_EVENT);
540       } else {
541         cerr << "Error on select: " << strerror(errno) << endl;
542         exit(1);
543       }
544     }
545   }
546
547   if(proportion_for_training > 0) {
548     cout << "Learning ... "; cout.flush();
549     intelligence.learn(proportion_for_training);
550     cout << "done." << endl;
551   }
552
553   if(intelligence_save_file[0]) {
554     cout << "Saving to " << intelligence_save_file << endl; cout.flush();
555     ofstream os(intelligence_save_file);
556     if(os.fail()) {
557       cerr << "error writing " << intelligence_save_file << "." << endl;
558       exit(1);
559     }
560     cout << "done." << endl;
561     intelligence.save(os);
562   }
563
564   delete window_brain;
565   delete window_main;
566
567 }