You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

328 lines
8.8 KiB

3 months ago
  1. /**
  2. * N-queens example.
  3. * Based on work by Robert Meolic, released by him into the public domain.
  4. */
  5. #include <argp.h>
  6. #include <inttypes.h>
  7. #include <locale.h>
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <sys/time.h>
  12. #ifdef HAVE_PROFILER
  13. #include <gperftools/profiler.h>
  14. #endif
  15. #include <sylvan.h>
  16. #include <sylvan_table.h>
  17. /* Configuration */
  18. static int report_minterms = 0; // report minterms at every major step
  19. static int report_minor = 0; // report minor steps
  20. static int report_stats = 0; // report stats at end
  21. static int workers = 0; // autodetect number of workers by default
  22. static size_t size = 0; // will be set by caller
  23. #ifdef HAVE_PROFILER
  24. static char* profile_filename = NULL; // filename for profiling
  25. #endif
  26. /* argp configuration */
  27. static struct argp_option options[] =
  28. {
  29. {"workers", 'w', "<workers>", 0, "Number of workers (default=0: autodetect)", 0},
  30. #ifdef HAVE_PROFILER
  31. {"profiler", 'p', "<filename>", 0, "Filename for profiling", 0},
  32. #endif
  33. {"report-minterms", 1, 0, 0, "Report #minterms at every major step", 1},
  34. {"report-minor", 2, 0, 0, "Report minor steps", 1},
  35. {"report-stats", 3, 0, 0, "Report statistics at end", 1},
  36. {0, 0, 0, 0, 0, 0}
  37. };
  38. static error_t
  39. parse_opt(int key, char *arg, struct argp_state *state)
  40. {
  41. switch (key) {
  42. case 'w':
  43. workers = atoi(arg);
  44. break;
  45. case 1:
  46. report_minterms = 1;
  47. break;
  48. case 2:
  49. report_minor = 1;
  50. break;
  51. case 3:
  52. report_stats = 1;
  53. break;
  54. #ifdef HAVE_PROFILER
  55. case 'p':
  56. profile_filename = arg;
  57. break;
  58. #endif
  59. case ARGP_KEY_ARG:
  60. if (state->arg_num >= 1) argp_usage(state);
  61. size = atoi(arg);
  62. break;
  63. case ARGP_KEY_END:
  64. if (state->arg_num < 1) argp_usage(state);
  65. break;
  66. default:
  67. return ARGP_ERR_UNKNOWN;
  68. }
  69. return 0;
  70. }
  71. static struct argp argp = { options, parse_opt, "<size>", 0, 0, 0, 0 };
  72. /* Obtain current wallclock time */
  73. static double
  74. wctime()
  75. {
  76. struct timeval tv;
  77. gettimeofday(&tv, NULL);
  78. return (tv.tv_sec + 1E-6 * tv.tv_usec);
  79. }
  80. static double t_start;
  81. #define INFO(s, ...) fprintf(stdout, "[% 8.2f] " s, wctime()-t_start, ##__VA_ARGS__)
  82. #define Abort(...) { fprintf(stderr, __VA_ARGS__); exit(-1); }
  83. VOID_TASK_0(gc_start)
  84. {
  85. if (report_minor) {
  86. printf("\n");
  87. }
  88. INFO("(GC) Starting garbage collection...\n");
  89. }
  90. VOID_TASK_0(gc_end)
  91. {
  92. INFO("(GC) Garbage collection done.\n");
  93. }
  94. int
  95. main(int argc, char** argv)
  96. {
  97. argp_parse(&argp, argc, argv, 0, 0, 0);
  98. setlocale(LC_NUMERIC, "en_US.utf-8");
  99. t_start = wctime();
  100. // Init Lace
  101. lace_init(workers, 1000000); // auto-detect number of workers, use a 1,000,000 size task queue
  102. lace_startup(0, NULL, NULL); // auto-detect program stack, do not use a callback for startup
  103. // Lace is initialized, now set local variables
  104. LACE_ME;
  105. // Init Sylvan
  106. // Nodes table size of 1LL<<20 is 1048576 entries
  107. // Cache size of 1LL<<18 is 262144 entries
  108. // Nodes table size: 24 bytes * nodes
  109. // Cache table size: 36 bytes * cache entries
  110. // With 2^20 nodes and 2^18 cache entries, that's 33 MB
  111. // With 2^24 nodes and 2^22 cache entries, that's 528 MB
  112. sylvan_set_sizes(1LL<<20, 1LL<<24, 1LL<<18, 1LL<<22);
  113. sylvan_init_package();
  114. sylvan_set_granularity(3); // granularity 3 is decent value for this small problem - 1 means "use cache for every operation"
  115. sylvan_init_bdd();
  116. // Before and after garbage collection, call gc_start and gc_end
  117. sylvan_gc_hook_pregc(TASK(gc_start));
  118. sylvan_gc_hook_postgc(TASK(gc_end));
  119. #ifdef HAVE_PROFILER
  120. if (profile_filename != NULL) ProfilerStart(profile_filename);
  121. #endif
  122. double t1 = wctime();
  123. BDD zero = sylvan_false;
  124. BDD one = sylvan_true;
  125. // Variables 0 ... (SIZE*SIZE-1)
  126. BDD board[size*size];
  127. for (size_t i=0; i<size*size; i++) {
  128. board[i] = sylvan_ithvar(i);
  129. sylvan_protect(board+i);
  130. }
  131. BDD res = one, temp = one;
  132. // we use sylvan's "protect" marking mechanism...
  133. // that means we hardly need to do manual ref/deref when the variables change
  134. sylvan_protect(&res);
  135. sylvan_protect(&temp);
  136. // Old satcount function still requires a silly variables cube
  137. BDD vars = one;
  138. sylvan_protect(&vars);
  139. for (size_t i=0; i<size*size; i++) vars = sylvan_and(vars, board[i]);
  140. INFO("Initialisation complete!\n");
  141. if (report_minor) {
  142. INFO("Encoding rows... ");
  143. } else {
  144. INFO("Encoding rows...\n");
  145. }
  146. for (size_t i=0; i<size; i++) {
  147. if (report_minor) {
  148. printf("%zu... ", i);
  149. fflush(stdout);
  150. }
  151. for (size_t j=0; j<size; j++) {
  152. // compute "\BigAnd (!board[i][k]) \or !board[i][j]" with k != j
  153. temp = one;
  154. for (size_t k=0; k<size; k++) {
  155. if (j==k) continue;
  156. temp = sylvan_and(temp, sylvan_not(board[i*size+k]));
  157. }
  158. temp = sylvan_or(temp, sylvan_not(board[i*size+j]));
  159. // add cube to "res"
  160. res = sylvan_and(res, temp);
  161. }
  162. }
  163. if (report_minor) {
  164. printf("\n");
  165. }
  166. if (report_minterms) {
  167. INFO("We have %.0f minterms\n", sylvan_satcount(res, vars));
  168. }
  169. if (report_minor) {
  170. INFO("Encoding columns... ");
  171. } else {
  172. INFO("Encoding columns...\n");
  173. }
  174. for (size_t j=0; j<size; j++) {
  175. if (report_minor) {
  176. printf("%zu... ", j);
  177. fflush(stdout);
  178. }
  179. for (size_t i=0; i<size; i++) {
  180. // compute "\BigAnd (!board[k][j]) \or !board[i][j]" with k != i
  181. temp = one;
  182. for (size_t k=0; k<size; k++) {
  183. if (i==k) continue;
  184. temp = sylvan_and(temp, sylvan_not(board[k*size+j]));
  185. }
  186. temp = sylvan_or(temp, sylvan_not(board[i*size+j]));
  187. // add cube to "res"
  188. res = sylvan_and(res, temp);
  189. }
  190. }
  191. if (report_minor) {
  192. printf("\n");
  193. }
  194. if (report_minterms) {
  195. INFO("We have %.0f minterms\n", sylvan_satcount(res, vars));
  196. }
  197. if (report_minor) {
  198. INFO("Encoding rising diagonals... ");
  199. } else {
  200. INFO("Encoding rising diagonals...\n");
  201. }
  202. for (size_t i=0; i<size; i++) {
  203. if (report_minor) {
  204. printf("%zu... ", i);
  205. fflush(stdout);
  206. }
  207. for (size_t j=0; j<size; j++) {
  208. temp = one;
  209. for (size_t k=0; k<size; k++) {
  210. // if (j+k-i >= 0 && j+k-i < size && k != i)
  211. if (j+k >= i && j+k < size+i && k != i) {
  212. temp = sylvan_and(temp, sylvan_not(board[k*size + (j+k-i)]));
  213. }
  214. }
  215. temp = sylvan_or(temp, sylvan_not(board[i*size+j]));
  216. // add cube to "res"
  217. res = sylvan_and(res, temp);
  218. }
  219. }
  220. if (report_minor) {
  221. printf("\n");
  222. }
  223. if (report_minterms) {
  224. INFO("We have %.0f minterms\n", sylvan_satcount(res, vars));
  225. }
  226. if (report_minor) {
  227. INFO("Encoding falling diagonals... ");
  228. } else {
  229. INFO("Encoding falling diagonals...\n");
  230. }
  231. for (size_t i=0; i<size; i++) {
  232. if (report_minor) {
  233. printf("%zu... ", i);
  234. fflush(stdout);
  235. }
  236. for (size_t j=0; j<size; j++) {
  237. temp = one;
  238. for (size_t k=0; k<size; k++) {
  239. // if (j+i-k >= 0 && j+i-k < size && k != i)
  240. if (j+i >= k && j+i < size+k && k != i) {
  241. temp = sylvan_and(temp, sylvan_not(board[k*size + (j+i-k)]));
  242. }
  243. }
  244. temp = sylvan_or(temp, sylvan_not(board[i*size + j]));
  245. // add cube to "res"
  246. res = sylvan_and(res, temp);
  247. }
  248. }
  249. if (report_minor) {
  250. printf("\n");
  251. }
  252. if (report_minterms) {
  253. INFO("We have %.0f minterms\n", sylvan_satcount(res, vars));
  254. }
  255. if (report_minor) {
  256. INFO("Final computation to place a queen on every row... ");
  257. } else {
  258. INFO("Final computation to place a queen on every row...\n");
  259. }
  260. for (size_t i=0; i<size; i++) {
  261. if (report_minor) {
  262. printf("%zu... ", i);
  263. fflush(stdout);
  264. }
  265. temp = zero;
  266. for (size_t j=0; j<size; j++) {
  267. temp = sylvan_or(temp, board[i*size+j]);
  268. }
  269. res = sylvan_and(res, temp);
  270. }
  271. if (report_minor) {
  272. printf("\n");
  273. }
  274. double t2 = wctime();
  275. #ifdef HAVE_PROFILER
  276. if (profile_filename != NULL) ProfilerStop();
  277. #endif
  278. INFO("Result: NQueens(%zu) has %.0f solutions.\n", size, sylvan_satcount(res, vars));
  279. INFO("Result BDD has %zu nodes.\n", sylvan_nodecount(res));
  280. INFO("Computation time: %f sec.\n", t2-t1);
  281. if (report_stats) {
  282. sylvan_stats_report(stdout);
  283. }
  284. sylvan_quit();
  285. lace_exit();
  286. }