/* A program that generates optimal selection functions. * * Copyright (c) 2002 Kenneth Oksanen <cessu@iki.fi> * * This file is distributed under the GNU General Public License * version 2 or newer. */ /* Begin of configuration section. */ /* Maximum number of elements to select from. */ #ifndef N #define N 13 #endif #ifndef I #define I 7 #endif /* How much memory are we allowed to allocate from the heap? */ /* #define MAX_N_BYTES (1024 * 1024) */ #define MAX_N_BYTES (400 * 1024 * 1024) /* How much work will we spend on graph (orderset_t) isomorphism recognition code? */ #define N_TOPO_ITERATIONS 3 #define N_TOPO_ITERATIONS2 2 /* Shall we automatically perform the pairwise comparisons? */ /* #define ASSUME_INITIAL_PAIRWISE */ /* Is it possible that to items are equal? Define this macro if yes. */ /* #define MAY_BE_EQUAL */ /* Is it possible that to items may have undefined order, i.e. the input set is only partially ordered. Define this macro if yes. */ /* #define MAY_BE_UNDEFINED */ /* Define NDEBUG if you want to omit all assertions and thereby speed things up a little. */ #define NDEBUG 1 /* Shall the output be C code instead of a functional pseudo-code */ /* #define PRODUCE_C */ /* Shall the output be input to GraphViz's dot input file? */ /* #define PRODUCE_DOT */ /* A value larger or equal to `ceil(log_2(N))' */ #define BITS_IN_N 4 typedef unsigned int cost_t; #define COST_INFINITE 0x3FF /* Current cost at the root of the search tree. */ cost_t current_search_cost; /* Parameters that control the format of output. */ #define VAR_NAME(i) ("abcdefghijklmnopqrstuvxyz"[i]) /* Humm, should write a configure script, but too lazy for now.... :-S */ typedef unsigned int uint32_t; /* End of configuration section. */ #define VERSION "1.6" #ifdef MAY_BE_UNDEFINED #ifndef MAY_BE_EQUAL #warning Can not handle partial orders without possible equality. #warning Defining MAY_BE_EQUAL and trying in any case... #define MAY_BE_EQUAL #endif #endif #ifndef __GNUC__ #error Sorry, this code contains gcc-specific extensions. #endif #include <stdio.h> #include <stdlib.h> #include <malloc.h> #include <assert.h> #include <string.h> #include <sys/times.h> #include <unistd.h> #include <math.h> /* Requires GNU readline library. */ #include <readline/readline.h> typedef enum { UNTESTED = 0, /* Must be 0 */ GREATER, #ifdef MAY_BE_EQUAL EQUAL, #endif #ifdef MAY_BE_UNDEFINED GREATER_OR_EQUAL, INDIFFERENT, LESS_OR_EQUAL, #endif LESS, N_ORDERS /* Must be last */ } order_t; static order_t opposite[N_ORDERS] = { UNTESTED, LESS, #ifdef MAY_BE_EQUAL EQUAL, #endif #ifdef MAY_BE_UNDEFINED LESS_OR_EQUAL, INDIFFERENT, GREATER_OR_EQUAL, #endif GREATER }; static const char *order_name[N_ORDERS] = { "UNTESTED", "GREATER", #ifdef MAY_BE_EQUAL "EQUAL", #endif #ifdef MAY_BE_UNDEFINED "GREATER_OR_EQUAL", "INDIFFERENT", "LESS_OR_EQUAL", #endif "LESS" }; static const char order_char[N_ORDERS] = { ' ', '>', #ifdef MAY_BE_EQUAL '=', #endif #ifdef MAY_BE_UNDEFINED '»', '?', '«', #endif '<' }; struct parents_t; typedef struct orderset_t { /* Non-zero iff this orderset has been solved. */ unsigned int solved : 1; /* Non-zero iff this orderset can not be removed from the cache. */ unsigned int locked : 1; /* The two variables to optimally compare in this orderset. */ unsigned int a : BITS_IN_N; unsigned int b : BITS_IN_N; /* How many variables are there in this orderset. (The algorithm discards uninteresting ones.) */ unsigned char n : BITS_IN_N; /* Which variable was asked from this orderset? 0 < i < n */ unsigned char i : BITS_IN_N; /* Cost for solving this orderset, or if this orderset has not been solved, then a cost that is not sufficient to solve this orderset. */ cost_t cost : 10; #define N_PROBES 4 #define MAX_PRIORITY 0xFFFF #define INITIAL_PRIORITY 0xFF unsigned int priority : 16; /* This is used to get some theoretically human-readable output. */ unsigned int number : 16; #ifdef MAY_BE_UNDEFINED #define N_CMPS ((((N * (N - 1)) / 2) + 15) / 10) #else #define N_CMPS ((((N * (N - 1)) / 2) + 15) / 16) #endif uint32_t order[N_CMPS]; } orderset_t; /* Forward declaration. */ static cost_t search(orderset_t *, cost_t); static int order_offset[N]; static void init_offsets(void) { int s = -1, i; for (i = 0; i < N; i++) { order_offset[i] = s; s += N - i - 2; } } static inline void set_order(orderset_t *orderset, int a, int b, order_t order) { int n = orderset->n; unsigned int ix, shmt; uint32_t mask; assert(a != b); assert(a < n); assert(b < n); /* assert(order > UNTESTED); */ if (a < b) ix = order_offset[a] + b; else { ix = order_offset[b] + a; order = opposite[order]; } #ifdef MAY_BE_UNDEFINED shmt = (ix % 10) * 3; ix = ix / 10; mask = 0x7 << shmt; #else shmt = (ix % 16) * 2; ix = ix / 16; mask = 0x3 << shmt; #endif assert(ix < N_CMPS); assert(shmt < 32); orderset->order[ix] = (orderset->order[ix] & ~mask) | (order << shmt); } static inline order_t get_order(orderset_t *orderset, int a, int b) { int n = orderset->n; order_t order; unsigned int ix, shmt; uint32_t mask; assert(a < n); assert(b < n); if (a == b) { #ifdef MAY_BE_EQUAL return EQUAL; #else assert(0); return N_ORDERS; #endif } if (a < b) ix = order_offset[a] + b; else ix = order_offset[b] + a; #ifdef MAY_BE_UNDEFINED shmt = (ix % 10) * 3; ix = ix / 10; mask = 0x7; #else shmt = (ix % 16) * 2; ix = ix / 16; mask = 0x3; #endif order = (orderset->order[ix] >> shmt) & mask; if (a > b) order = opposite[order]; assert(order < N_ORDERS); return order; } static inline void copy_orderset(orderset_t *to, orderset_t *from) { assert(to != NULL); assert(from != NULL); memcpy(to, from, sizeof(orderset_t)); to->locked = 0; } static inline int orderset_is_equal(orderset_t *x, orderset_t *y) { if (x == y) return 1; if (x->n != y->n || x->i != y->i || memcmp(x->order, y->order, N_CMPS * sizeof(x->order[0]))) return 0; return 1; } static void inline clear_orderset(orderset_t *orderset) { memset(orderset, 0, sizeof(*orderset)); } static void print_orderset(orderset_t *orderset, unsigned char new_n, unsigned char *new_a) { order_t order; int a, b, c, n = orderset->n, is_first = 1; unsigned char new_a_reserve[N], old_a[N]; if (new_a != NULL) { n = new_n; for (a = 0; a < n; a++) old_a[new_a[a]] = a; } else { for (a = 0; a < n; a++) old_a[a] = new_a_reserve[a] = a; new_a = new_a_reserve; } #ifdef PRODUCE_DOT orderset_t os; if (new_a != new_a_reserve) /* PRODUCE_DOT not been implemented for new_a-indirection. */ abort(); clear_orderset(&os); copy_orderset(&os, orderset); for (a = 0; a < os.n; a++) for (b = 0; b < os.n; b++) if (a != b && get_order(&os, b, a) == LESS) for (c = 0; c < os.n; c++) if (a != c && b != c && get_order(&os, c, b) == LESS) set_order(&os, a, c, UNTESTED); printf(" subgraph cluster_find%d {\n", os.number); for (a = 0; a < n; a++) printf(" %c%d [label=\"%c\",peripheries=0];\n", VAR_NAME(a), os.number, VAR_NAME(a)); for (a = 0; a < n - 1; a++) for (b = a + 1; b < n; b++) switch (get_order(&os, a, b)) { case GREATER: printf(" %c%d -> %c%d\n", VAR_NAME(a), os.number, VAR_NAME(b), os.number); break; case LESS: printf(" %c%d -> %c%d\n", VAR_NAME(b), os.number, VAR_NAME(a), os.number); break; default: break; } #if 0 if (os.a != os.b) printf(" edge [style=dotted,arrowhead=none,arrowtail=none]\n" " %c%d -> %c%d [style=dotted];\n" " { rank=same; %c%d %c%d }\n", VAR_NAME(os.a), os.number, VAR_NAME(os.b), os.number, VAR_NAME(os.a), os.number, VAR_NAME(os.b), os.number); #endif puts(" }"); #else printf("n=%d, i=%d", n, orderset->i + 1); if (orderset->solved) printf(", %c?%c", VAR_NAME(new_a[orderset->a]), VAR_NAME(new_a[orderset->b])); for (a = 0; a < n - 1; a++) for (b = a + 1; b < n; b++) { order_t o; int is_transitive; if ((o = get_order(orderset, a, b)) == UNTESTED) continue; for (is_transitive = c = 0; c < n; c++) if (c != a && c != b && get_order(orderset, a, c) == o && get_order(orderset, c, b) == o) { is_transitive = 1; break; } if (is_transitive) continue; if (is_first) { is_first = 0; printf(": "); } else printf(","); printf("%c%c%c", VAR_NAME(new_a[a]), order_char[get_order(orderset, a, b)], VAR_NAME(new_a[b])); } if (is_first) printf(": no comparisons done"); putchar('\n'); #endif } /* Return non-zero on success, zero on failure. */ static int set_order_and_closure(orderset_t *orderset, int a, int b, order_t order) { int i, n = orderset->n; order_t old_order = get_order(orderset, a, b); switch (order) { case LESS: switch (old_order) { case LESS: break; case UNTESTED: #ifdef MAY_BE_UNDEFINED case LESS_OR_EQUAL: case INDIFFERENT: #endif set_order(orderset, a, b, LESS); for (i = 0; i < n; i++) if (i != a && i != b) { switch (get_order(orderset, b, i)) { case LESS: #ifdef MAY_BE_EQUAL case EQUAL: #endif if (!set_order_and_closure(orderset, a, i, LESS)) return 0; break; } switch (get_order(orderset, a, i)) { case GREATER: #ifdef MAY_BE_EQUAL case EQUAL: #endif if (!set_order_and_closure(orderset, b, i, GREATER)) return 0; break; } } break; default: return 0; break; } break; #ifdef MAY_BE_EQUAL case EQUAL: switch (old_order) { case EQUAL: break; case UNTESTED: #ifdef MAY_BE_UNDEFINED case GREATER_OR_EQUAL: case INDIFFERENT: case LESS_OR_EQUAL: #endif make_a_b_equal: set_order(orderset, a, b, EQUAL); for (i = 0; i < n; i++) if (i != a && i != b) { switch (get_order(orderset, b, i)) { case EQUAL: if (!set_order_and_closure(orderset, a, i, EQUAL)) return 0; break; case GREATER: if (!set_order_and_closure(orderset, a, i, GREATER)) return 0; break; case LESS: if (!set_order_and_closure(orderset, a, i, LESS)) return 0; break; } switch (get_order(orderset, a, i)) { case EQUAL: if (!set_order_and_closure(orderset, b, i, EQUAL)) return 0; break; case GREATER: if (!set_order_and_closure(orderset, b, i, GREATER)) return 0; break; case LESS: if (!set_order_and_closure(orderset, b, i, LESS)) return 0; break; } } break; default: return 0; break; } break; #endif #ifdef MAY_BE_UNDEFINED case GREATER_OR_EQUAL: switch (old_order) { case GREATER: case GREATER_OR_EQUAL: case EQUAL: break; case LESS_OR_EQUAL: goto make_a_b_equal; break; case UNTESTED: case INDIFFERENT: set_order(orderset, a, b, GREATER_OR_EQUAL); for (i = 0; i < n; i++) if (i != a && i != b) { switch (get_order(orderset, a, i)) { case LESS: if (!set_order_and_closure(orderset, b, i, LESS)) return 0; break; case LESS_OR_EQUAL: case EQUAL: if (!set_order_and_closure(orderset, b, i, LESS)) return 0; break; } switch (get_order(orderset, b, i)) { case GREATER: if (!set_order_and_closure(orderset, a, i, GREATER)) return 0; break; case GREATER_OR_EQUAL: case EQUAL: if (!set_order_and_closure(orderset, a, i, GREATER_OR_EQUAL)) return 0; break; } } break; } break; case INDIFFERENT: if (old_order == UNTESTED) { set_order(orderset, a, b, INDIFFERENT); for (i = 0; i < n; i++) if (i != a && i != b) { switch (get_order(orderset, a, i)) { case GREATER: case GREATER_OR_EQUAL: if (!set_order_and_closure(orderset, b, i, GREATER_OR_EQUAL)) return 0; break; case EQUAL: case INDIFFERENT: if (!set_order_and_closure(orderset, b, i, INDIFFERENT)) return 0; break; case LESS: case LESS_OR_EQUAL: if (!set_order_and_closure(orderset, b, i, LESS_OR_EQUAL)) return 0; break; } switch (get_order(orderset, b, i)) { case GREATER: case GREATER_OR_EQUAL: if (!set_order_and_closure(orderset, a, i, GREATER_OR_EQUAL)) return 0; break; case EQUAL: case INDIFFERENT: if (!set_order_and_closure(orderset, a, i, INDIFFERENT)) return 0; break; case LESS: case LESS_OR_EQUAL: if (!set_order_and_closure(orderset, a, i, LESS_OR_EQUAL)) return 0; break; } } } break; case LESS_OR_EQUAL: switch (old_order) { case LESS: case LESS_OR_EQUAL: case EQUAL: break; case GREATER_OR_EQUAL: goto make_a_b_equal; break; case UNTESTED: case INDIFFERENT: set_order(orderset, a, b, LESS_OR_EQUAL); for (i = 0; i < n; i++) if (i != a && i != b) { switch (get_order(orderset, a, i)) { case GREATER: if (!set_order_and_closure(orderset, b, i, GREATER)) return 0; break; case GREATER_OR_EQUAL: case EQUAL: if (!set_order_and_closure(orderset, b, i, GREATER)) return 0; break; } switch (get_order(orderset, b, i)) { case LESS: if (!set_order_and_closure(orderset, a, i, LESS)) return 0; break; case LESS_OR_EQUAL: case EQUAL: if (!set_order_and_closure(orderset, a, i, LESS_OR_EQUAL)) return 0; break; } } break; } break; #endif case GREATER: switch (old_order) { case GREATER: break; case UNTESTED: #ifdef MAY_BE_UNDEFINED case GREATER_OR_EQUAL: case INDIFFERENT: #endif set_order(orderset, a, b, GREATER); for (i = 0; i < n; i++) if (i != a && i != b) { switch (get_order(orderset, b, i)) { case GREATER: #ifdef MAY_BE_EQUAL case EQUAL: #endif if (!set_order_and_closure(orderset, a, i, GREATER)) return 0; break; } switch (get_order(orderset, a, i)) { case LESS: #ifdef MAY_BE_EQUAL case EQUAL: #endif if (!set_order_and_closure(orderset, b, i, LESS)) return 0; break; } } break; default: return 0; break; } break; } return 1; } /* The orderset must have been closured before calling the following two functions. */ static int orderset_is_complete(orderset_t *orderset) { int a, b; /* No point in calling this for subordersets. */ assert(orderset->n == N); assert(orderset->i == N); for (a = 0; a < N - 1; a++) for (b = a + 1; b < N; b++) if (get_order(orderset, a, b) == UNTESTED) return 0; return 1; } static int orderset_selects_ith(orderset_t *orderset, int i, unsigned char *varp) { int a, b, n = orderset->n, n_orders[N_ORDERS], j, slop; for (a = 0; a < N; a++) { for (j = 0; j < N_ORDERS; j++) n_orders[j] = 0; for (b = 0; b < N; b++) if (a != b) n_orders[get_order(orderset, b, a)]++; slop = 0; #ifdef MAY_BE_EQUAL slop += n_orders[EQUAL]; #endif #ifdef MAY_BE_UNDEFINED slop += n_orders[INDIFFERENT] + n_orders[GREATER_OR_EQUAL]; #endif if (n_orders[GREATER] + n_orders[UNTESTED] <= i && i + n_orders[UNTESTED] <= n_orders[GREATER] + slop) { if (varp != NULL) *varp = a; return 1; } } return 0; } /* min_n_comparisons[n][i] contains the lowest number of comparisons required to solve the corresponding selection problem from the beginning. Contains zero if no lower bound known. */ static unsigned char min_n_comparisons[1 << BITS_IN_N][1 << BITS_IN_N] = { /* i=1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 */ /* n= 0 */ { }, /* n= 1 */ { }, /* n= 2 */ { 1, 1 }, /* n= 3 */ { 2, 3, 2 }, /* n= 4 */ { 3, 4, 4, 3 }, /* n= 5 */ { 4, 6, 6, 6, 4 }, /* n= 6 */ { 5, 7, 8, 8, 7, 5 }, /* n= 7 */ { 6, 8, 10, 10, 10, 8, 6 }, /* n= 8 */ { 7, 9, 11, 12, 12, 11, 9, 7 }, /* n= 9 */ { 8, 11, 12, 14, 14, 14, 12, 11, 8 }, /* n=10 */ { 9, 12, 14, 15, 16, 16, 15, 14, 12, 9 }, /* n=11 */ { 10, 13, 15, 17, 18, 18, 18, 17, 15, 13, 10 }, /* n=12 */ { 11, 14, 17, 18, 19, 21, 21, 19, 18, 17, 14, 11 }, #if 0 /* n=13 */ { 12, 15, 18, 20, 21, 22, 22, 22, 21, 20, 18, 15, 12 }, /* ^^^^^^^^^^^ From theory */ /* n=14 */ { 13, 16, 19, 21, 22, 21, 22, 22, 21, 22, 21, 19, 16, 13 }, /* ^^^^^^^^^^^^^^^^^^^^^^ From theory */ /* n=15 */ { 14, 17, 20, 22, 22, 23, 23, 24, 23, 23, 22, 22, 20, 17, 14 }, /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From theory */ #endif }; orderset_t *hash_table = NULL; uint32_t hash_table_size; unsigned long long n_hits = 0, n_misses = 0; static inline unsigned int cache_value(unsigned int n, unsigned int i) { #if 1 int shmt = min_n_comparisons[n][i]; if (shmt > current_search_cost) shmt = current_search_cost; return shmt; /* 48.66s */ #endif #if 0 unsigned int mid = n / 2; #if 1 int shmt = min_n_comparisons[n][i]; if (shmt > current_search_cost) shmt = current_search_cost; return shmt; /* 48.66s */ return shmt << 2; /* 79.22s */ shmt >>= 1; if (i <= mid) /* =>>0: 92.56s, =>>1: 47.43s, =>>2: 67.07s, =>>3: 82.86s, =>>4: 51.01s, =>>5: 53.59s */ return n + (i << shmt); else return n + ((n - i - 1) << shmt); #else if (i <= mid) /* 63.52s */ return i << (n + i / 4); else return (n - i - 1) << (n + (n - i - 1) / 4); #endif #endif #if 0 return n; /* 53.70s */ return 1; /* 57.48s */ #endif } static inline uint32_t orderset_hash(orderset_t *orderset) { uint32_t h = (orderset->n << 8) | orderset->i; int i; for (i = 0; i < N_CMPS; i++) { h += orderset->order[i] >> 20; h += h << 10; h ^= h >> 7; h += orderset->order[i] >> 9; h += h << 10; h ^= h >> 7; h += orderset->order[i]; h += h << 10; h ^= h >> 7; } h += h << 3; h ^= h >> 11; h += h << 15; return h; } static orderset_t *probe_orderset_cache(orderset_t *orderset, orderset_t **alts) { orderset_t *alts_reserve[N_PROBES]; orderset_t *p; int i, j; uint32_t h; /* Some random data to prevent possible infinite loops, taken from `cat /dev/random | od -x' */ #define N_RNDS 16 static uint32_t rnd[N_RNDS] = { 0x33432BD5, 0xD9DA2857, 0xC16CA349, 0xB48E786F, 0xC280E04C, 0xB6419EF7, 0x35E68035, 0x7327028D, 0x1618C293, 0x9E0A336E, 0x1709F35C, 0x5102EB06, 0x2B02AD5B, 0x8E4D16D4, 0x2DEA4A71, 0x2A3DDDC0 }; int rnd_ix = 0; if (hash_table == NULL) { hash_table_size = MAX_N_BYTES / sizeof(orderset_t); hash_table = calloc(hash_table_size, sizeof(orderset_t)); if (hash_table == NULL) { fprintf(stderr, "Malloc failed when allocating the %u-entry hash table. " "Sorry.\n", hash_table_size); exit(1); } } h = orderset_hash(orderset); if (alts == NULL) alts = alts_reserve; for (i = 0; i < N_PROBES; i++) alts[i] = NULL; for (i = 0; i < N_PROBES; i++) { p = &hash_table[h % hash_table_size]; if (orderset_is_equal(p, orderset)) { /* Hit! Increment priority and return. */ unsigned long v = cache_value(p->n, p->i); if (p->priority <= MAX_PRIORITY - v) p->priority += v; n_hits++; return p; } else if (!p->locked) { for (j = 0; j < i; j++) if (alts[j] == p) { /* We don't want to identical pointers in `alts'. Stir up `h' and pick another slot in the hash table. */ h ^= rnd[rnd_ix++]; if (rnd_ix == N_RNDS) rnd_ix = 0; i--; goto next_probe; } alts[i] = p; } h += h << 3; h ^= h >> 11; h += h << 15; next_probe: ; } /* Miss. */ n_misses++; return NULL; } static inline void store_orderset_cache(orderset_t *orderset, orderset_t **alts) { int i, j; for (i = j = 0; i < N_PROBES; i++) { if (alts[i] == NULL) { if (i == j) j++; continue; } if (i == j || alts[j]->priority > alts[i]->priority) j = i; } if (j < N_PROBES && alts[j] != NULL) { copy_orderset(alts[j], orderset); alts[j]->priority = cache_value(orderset->n, orderset->i); } /* Decrease weight of all other alternatives. */ for (i = 0; i < N_PROBES; i++) if (i != j && alts[i] != NULL && alts[i]->priority > 1) alts[i]->priority--; } static void report_orderset_cache(void) { unsigned long long priority_sum = 0, cost_sum = 0; unsigned int i, n_solved = 0, n_unsolved = 0; orderset_t *p; for (i = 0; i < hash_table_size; i++) { p = &hash_table[i]; if (p->cost == 0) /* Empty. */ continue; if (p->solved) n_solved++; else n_unsolved++; priority_sum += p->priority; cost_sum += p->cost; } printf("# hash_table_size = %u\n", hash_table_size); printf("# n_solved = %u\n", n_solved); printf("# n_unsolved = %u\n", n_unsolved); printf("# n_hits = %llu\n", n_hits); printf("# n_misses = %llu\n", n_misses); printf("# avg_priority = %.3f\n", priority_sum / (double) (n_solved + n_unsolved)); printf("# avg_cost = %.3f\n", cost_sum / (double) (n_solved + n_unsolved)); } /* Perform various simplifications and canonifications in the orderset, and detects whether the orderset has become solved. */ static inline void canonify_orderset(orderset_t *orderset, unsigned char *new_a) { unsigned char dropped[N], new_a_reserve[N], n_orders[N][N_ORDERS]; unsigned int lessness[N], n_topo_iterations; order_t order; uint32_t topo_hash_old[N], topo_hash_new[N], h; int n = orderset->n, new_n, n_less_dropped, a, b, c, j; int have_dropped, n_cmps; orderset_t new_orderset; if (new_a == NULL) new_a = new_a_reserve; /* Compute the n_orders array. Given some order, say `GREATER', and some variable `a', then `n_orders[a][GREATER]' is computed below to tell how many other variables are known to be greater than `a'. */ memset(n_orders, 0, sizeof(n_orders)); for (a = 0; a < n - 1; a++) for (b = a + 1; b < n; b++) { order_t order = get_order(orderset, b, a); n_orders[a][order]++; n_orders[b][opposite[order]]++; } /* Mark into `dropped' each variable which is either greater that `i' other variables or less than `n-i' other variables. */ memset(dropped, 0, sizeof(dropped)); for (n_less_dropped = a = 0; a < n; a++) if (n_orders[a][GREATER] > orderset->i) dropped[a] = 1; else if (n_orders[a][LESS] >= n - orderset->i) { dropped[a] = 1; n_less_dropped++; } /* Compute into `new_n' the number of variables not dropped and let `new_a[a]' be the new name (number) of variable `a'. The `new_a' is an indirection that will be worked on further. */ for (new_n = a = 0, b = n - 1; a < n; a++) if (!dropped[a]) new_a[new_n++] = a; else new_a[b--] = a; if (new_n == n) have_dropped = 0; else { have_dropped = 1; n = new_n; /* `new_n' is a cumbersome name... */ /* We have dropped some variables, therefore recompute the `n_orders'-array. Since it is used later in the orderset isomorphism/canonifying code, we might otherwise fail to detect the isomorphism of two ordersets, one of which was constructed by dropping a variable. */ memset(n_orders, 0, sizeof(n_orders)); for (a = 0; a < n - 1; a++) for (b = a + 1; b < n; b++) { order_t order = get_order(orderset, new_a[b], new_a[a]); n_orders[new_a[a]][order]++; n_orders[new_a[b]][opposite[order]]++; } } /* Sort the variables into an as canonical order as possible according to a number of characteristics: - The variable's "lessness" based on already performed comparisons (and their transitive closure). - Hash values based on the lessness of the variable (`new_a[a]') and the relative order and lessness of the other variables (`new_a[b]') to which the first variable (`new_a[a]') has an established order. - Hash values based on the previously computed hash value of the variable (`new_a[a]') and the relative order and previously computed hash values of the other variables (`new_a[b]') to which the first variable (`new_a[a]') has an established order. - The hash values are recomputed a total of `N_TOPO_ITERATIONS' times, where each iteration propagates the topology of the graph to the variables. After computing the lessnesses and hash values of variables, we sort the variables according to increasing lessness and hash value. But this still leaves one significant kind of ordering unsolved. Consider the case of `a>b,c>d'. The sorting can produce either the order `abcd' or `abdc' (`bacd' and `badc' will become either one in the later variable renaming). Therefore, in the last pass we do the following: - if two or more variables have the same lessness and hash values, and if so, we pin their relative order, and use their relative orders to rehash variables which have not yet been pinned. It is still possible that some graph (orderset_t) isomorphishms go undetected: - hash values are only 32-bit and they can always collide, particularly since we can not use arbitrary hash functions, but have to choose commutative ones. - the last pass performs only one recomputation of the hash values, therefore it is possible that isomorphic "zig-zag" or "fence-like" orders (`a>b,a>d,c>b,c>d'+same again) do not become fully canonified. But I hope they would be so infrequent that they cause little excess computation. */ for (a = 0; a < n; a++) topo_hash_old[new_a[a]] = lessness[new_a[a]] = N*N - (n_orders[new_a[a]][GREATER] << BITS_IN_N) - n_orders[new_a[a]][LESS]; for (n_topo_iterations = 0; n_topo_iterations < N_TOPO_ITERATIONS; n_topo_iterations++) { /* Compute new hash values based on the old ones. */ for (a = 0; a < n; a++) { h = topo_hash_old[new_a[a]]; h += h << 10; for (b = 0; b < n; b++) if (a != b && (order = get_order(orderset, new_a[a], new_a[b])) != UNTESTED) h += (topo_hash_old[new_a[b]] << 4) + order; /* NOTE: This hash function must be commutative w.r.t. all other compared variables `new_a[b]'. Therefore we can *not* issue the typical `h += h << 10; h ^= h >> 7;' here. */ topo_hash_new[new_a[a]] = h; } #if 0 /* This was useful for testing. */ for (a = 0; a < n - 1; a++) for (b = a + 1; b < n; b++) if (topo_hash_old[new_a[a]] != topo_hash_old[new_a[b]] && topo_hash_new[new_a[a]] == topo_hash_new[new_a[b]]) { /* print_orderset uses new_a differently now. */ print_orderset(orderset, n, new_a); printf("n_topo_iterations = %d:\nold:\n", n_topo_iterations); for (a = 0; a < n; a++) printf("%c:%d-%08X ", VAR_NAME(new_a[a]), lessness[new_a[a]], topo_hash_old[new_a[a]]); printf("\nnew:\n"); for (a = 0; a < n; a++) printf("%c:%d-%08X ", VAR_NAME(new_a[a]), lessness[new_a[a]], topo_hash_new[new_a[a]]); printf("\n"); goto foo; } foo: #endif /* Propagate new hash values to become the old ones in the next iteration. */ for (a = 0; a < n; a++) topo_hash_old[new_a[a]] = topo_hash_new[new_a[a]]; } /* A simple O(n^2) sort based on the lessness and hash values. */ for (a = 0; a < n - 1; a++) for (b = a + 1; b < n; b++) if (lessness[new_a[a]] < lessness[new_a[b]] || (lessness[new_a[a]] == lessness[new_a[b]] && topo_hash_old[new_a[a]] < topo_hash_old[new_a[b]])) { j = new_a[a]; new_a[a] = new_a[b]; new_a[b] = j; } /* The final pass. */ for (a = 1; a < n; a++) if (lessness[new_a[a]] == lessness[new_a[a - 1]] && topo_hash_old[new_a[a]] == topo_hash_old[new_a[a - 1]]) { /* Skip over the equivalent variables. */ for (a++; a < n && lessness[new_a[a]] == lessness[new_a[a - 1]] && topo_hash_old[new_a[a]] == topo_hash_old[new_a[a - 1]]; a++) ; if (a == n) continue; for (n_topo_iterations = 0; n_topo_iterations < N_TOPO_ITERATIONS2; n_topo_iterations++) { /* Recompute the hash values. */ for (c = a; c < n; c++) { h = topo_hash_old[new_a[c]]; h += h << 10; for (b = 0; b < a; b++) if ((order = get_order(orderset, new_a[c], new_a[b])) != UNTESTED) h += (topo_hash_old[new_a[b]] << (4 + BITS_IN_N)) - (order << BITS_IN_N) + b; topo_hash_new[new_a[c]] = h; } /* Propagate hash values in case we should hash again. */ for (c = a; c < n; c++) topo_hash_old[new_a[c]] = topo_hash_new[new_a[c]]; } /* Re-sort the variables below the equivalent ones we just skipped over. */ for (c = a; c < n - 1; c++) for (b = c + 1; b < n; b++) if (lessness[new_a[c]] < lessness[new_a[b]] || (lessness[new_a[c]] == lessness[new_a[b]] && topo_hash_old[new_a[c]] < topo_hash_old[new_a[b]])) { j = new_a[c]; new_a[c] = new_a[b]; new_a[b] = j; } } /* Construct a new orderset based on `new_a' and `n'. */ clear_orderset(&new_orderset); new_orderset.n = n; new_orderset.i = orderset->i - n_less_dropped; for (a = 0; a < n - 1; a++) for (b = a + 1; b < n; b++) set_order(&new_orderset, a, b, get_order(orderset, new_a[a], new_a[b])); copy_orderset(orderset, &new_orderset); /* Detect if the problem became solved, and feed the killer heuristic with a guesstimate of how difficult a problem we have left. */ if (n == 1) orderset->solved = 1; } static unsigned int inline highest_bit(uint32_t x) { unsigned int i; if (x-- == 0) return 0; for (i = 0; x != 0; i++) x >>= 1; return i; } typedef int n_orders_t[N][N_ORDERS]; typedef enum { SOLVED, IMPOSSIBLE, UNCLEAR } solvability_t; /* Return non-zero if the given orderset might be solvable in `cost_limit' comparisons. Returns zero only if it is completely certain that a solution can not be found in the given number of comparisons. */ static inline int estimate_hardness(orderset_t *orderset, int *hardness, int cost_limit, int changed) { int n_orders[N][N_ORDERS]; int n = orderset->n, a, b, c; orderset_t os; cost_t cost; /* Compute the n_orders array. Given some order, say `GREATER', and some variable `a', then `n_orders[a][GREATER]' is computed below to tell how many other variables are known to be greater than `a'. */ memset(n_orders, 0, sizeof(n_orders)); for (a = 0; a < n - 1; a++) for (b = a + 1; b < n; b++) { order_t order = get_order(orderset, b, a); n_orders[a][order]++; n_orders[b][opposite[order]]++; } if (hardness != NULL) { *hardness = 0; for (a = 0; a < n; a++) { int d = n_orders[a][GREATER] - n_orders[a][LESS]; int u = n_orders[a][UNTESTED]; if (d < 0) d = - d; *hardness -= d + 2 * u; } } if (orderset->i == 0 || orderset->i == n - 1) return cost_limit >= n - 1; /* The following two estimates are based on a lower bound given in TAOCP, Volume 3, Section 5.3.3, Exercise 6. */ else if (orderset->i == 1) { uint32_t n_groups = 0, s = 0; for (a = 0; a < n; a++) if (n_orders[a][GREATER] == 0) { n_groups++; s += 1 << n_orders[a][LESS]; } return cost_limit >= n_groups - 2 + highest_bit(s); } else if (orderset->i == n - 2) { uint32_t n_groups = 0, s = 0; for (a = 0; a < n; a++) if (n_orders[a][LESS] == 0) { n_groups++; s += 1 << n_orders[a][GREATER]; } return cost_limit >= n_groups - 2 + highest_bit(s); } else { #if 1 int n_cmps = min_n_comparisons[n][orderset->i]; /* Subtract from `n_cmps' every order which is not deducible from some other order. Try first the most easily computed ones. */ for (a = 0; a < n; a++) if (n_orders[a][LESS] == 1) { n_cmps--; if (n_cmps <= cost_limit) goto out; } for (a = 0; a < n - 1; a++) if (n_orders[a][LESS] >= 2) for (b = a + 1; b < n; b++) { if (get_order(orderset, b, a) == LESS) if (n_orders[b][GREATER] == 1) { n_cmps--; if (n_cmps <= cost_limit) goto out; } else { for (c = 0; c < n; c++) if (a != c && b != c && get_order(orderset, b, c) == LESS && get_order(orderset, c, a) == LESS) goto next_b; n_cmps--; if (n_cmps <= cost_limit) goto out; next_b: ; } } out: if (n_cmps > cost_limit) return 0; #endif #if 0 /* XXX THIS IS INCORRECT! */ /* Without this heuristic: 697s, with it 330s */ for (a = 0; a < n; a++) { int n_gt, n_lt, n_both; if (n - n_orders[a][LESS] - n_orders[a][GREATER] - 1 <= cost_limit) /* A quick pre-test. */ continue; n_gt = n_lt = n_both = 0; for (b = 0; b < n; b++) if (a != b && get_order(orderset, a, b) == UNTESTED) { if (n_orders[b][LESS] == 0) if (n_orders[b][GREATER] == 0) n_both++; else n_gt++; else if (n_orders[b][GREATER] == 0) n_lt++; } while (n_lt < orderset->i - n_orders[a][LESS] && n_both > 0) { n_lt++; n_both--; } while (n_gt < n - orderset->i - 1 - n_orders[a][GREATER] && n_both > 0) { n_gt++; n_both--; } if (n_lt + n_gt > cost_limit) return 0; } #endif #if 0 if (n >= 5 && n <= 8 && changed) { copy_orderset(&os, orderset); /* Remove from `os' all comparisons whose utility is one (or more). */ for (n_cmps = a = 0; a < n; a++) if (n_orders[a][LESS] == 1) for (b = 0; b < n; b++) if (a != b && n_orders[b][GREATER] == 1 && get_order(&os, a, b) == GREATER) { set_order(&os, a, b, UNTESTED); n_cmps++; break; } canonify_orderset(&os, NULL); cost = search(&os, cost_limit + n_cmps); return cost <= cost_limit + n_cmps; } #endif #if 0 if (n >= 8 && changed) { int c; copy_orderset(&os, orderset); /* Add to `os' comparisons that are most probably pretty useless. */ for (a = 0; a < n; a++) if (n_orders[a][LESS] == 0 && n_orders[a][GREATER] >= 2) { for (c = 0; c < n; c++) if (a != c && n_orders[c][LESS] >= 1 && n_orders[c][GREATER] >= 1 && get_order(orderset, a, c) == LESS) { for (b = 0; b < n; b++) if (a != b && n_orders[b][GREATER] == 0 && n_orders[b][LESS] >= 2 && get_order(orderset, a, b) == UNTESTED) { for (c = 0; c < n; c++) if (b != c && n_orders[c][GREATER] >= 1 && n_orders[c][LESS] >= 1 && get_order(orderset, b, c) == GREATER) { set_order(&os, a, b, LESS); goto gout1; } gout1: ; } goto gout2; } gout2: ; } canonify_orderset(&os, NULL); cost = search(&os, cost_limit); if (cost > cost_limit) return 0; } #endif } return 1; } typedef struct { unsigned long hash : 28; unsigned int n_hits : 4; } fw_cache_entry_t; #define N_FW_CACHE_ENTRIES (1024*1024) static fw_cache_entry_t fw_cache[N_FW_CACHE_ENTRIES]; static inline solvability_t fw_search(orderset_t *orderset, n_orders_t n_orders, int initial_a, int initial_b, int cost_limit, int depth) { int a, b, d, n = orderset->n; orderset_t os, *cached_os; solvability_t s; copy_orderset(&os, orderset); if (initial_a != 0 || initial_b != 0) { canonify_orderset(&os, NULL); if (!estimate_hardness(&os, NULL, cost_limit, 0)) return IMPOSSIBLE; cached_os = probe_orderset_cache(&os, NULL); if (cached_os != NULL) if (cached_os->solved) if (cached_os->cost <= cost_limit) return SOLVED; else return IMPOSSIBLE; else if (cached_os->cost >= cost_limit) return IMPOSSIBLE; else goto unclear; } /* Experiments showed there's little benefit from depth-limiting the loop below. */ for (a = initial_a; a < n; a++) if (n_orders[a][LESS] == 0 && n_orders[a][GREATER] >= 2) for (b = (a == initial_a ? initial_b : 0); b < n; b++) if (a != b && n_orders[b][GREATER] == 0 && n_orders[b][LESS] >= 2 /* && (d = n_orders[a][GREATER] + n_orders[b][LESS]) >= 5 */ && get_order(orderset, a, b) == UNTESTED) { set_order(orderset, a, b, LESS); s = fw_search(orderset, n_orders, a, b + 1, cost_limit, depth + 1); set_order(orderset, a, b, UNTESTED); if (s == IMPOSSIBLE) return IMPOSSIBLE; } unclear: if (initial_a != 0 || initial_b != 0) { uint32_t h = orderset_hash(&os); uint32_t i = h % N_FW_CACHE_ENTRIES; if (fw_cache[i].hash == (h >> 4)) { if (fw_cache[i].n_hits >= 6 && depth <= 4) { int cost = search(&os, cost_limit); if (cost > cost_limit) return IMPOSSIBLE; } else if (fw_cache[i].n_hits < 15) fw_cache[i].n_hits++; } else { fw_cache[i].hash = h >> 4; fw_cache[i].n_hits = 1; } } return UNCLEAR; } static unsigned long long n_nodes = 0; static unsigned long nth_orderset = 0; static cost_t search(orderset_t *parent, cost_t cost_limit) { orderset_t *alts[N_PROBES], *cached_parent, child[N_ORDERS]; cost_t cost, child_cost; order_t order, killer_order; int a, b, c, d, hardness, worst_hardness, n, new_n, delta = 0; if (parent->solved) return parent->cost; if (cost_limit <= 0) return COST_INFINITE; cached_parent = probe_orderset_cache(parent, alts); if (cached_parent != NULL) { if (cached_parent->solved) return cached_parent->cost; if (cached_parent->cost >= cost_limit) return COST_INFINITE; cached_parent->locked = 1; } n = parent->n; { n_orders_t n_orders; memset(n_orders, 0, sizeof(n_orders)); for (a = 0; a < n - 1; a++) for (b = a + 1; b < n; b++) { order_t order = get_order(parent, b, a); n_orders[a][order]++; n_orders[b][opposite[order]]++; } if (fw_search(parent, n_orders, 0, 0, cost_limit, 0) == IMPOSSIBLE) return COST_INFINITE; } if (cached_parent != NULL) parent = cached_parent; #if 1 if (nth_orderset++ % 1024 == 0) { printf("limit=%d, ", current_search_cost); print_orderset(parent, 0, NULL); } if (nth_orderset % 65536 == 0) report_orderset_cache(); #endif for (a = 0; a < n - 1; a++) for (b = a + 1; b < n; b++) if (get_order(parent, a, b) == UNTESTED) { worst_hardness = -1000000; killer_order = GREATER; for (order = GREATER; order <= LESS; order++) { copy_orderset(&child[order], parent); set_order_and_closure(&child[order], a, b, order); canonify_orderset(&child[order], NULL); if (!estimate_hardness(&child[order], &hardness, cost_limit - 1 - delta, n != child[order].n)) goto next_comparison; { orderset_t *cached_child = probe_orderset_cache(&child[order], NULL); if (cached_child != NULL) { if (!cached_child->solved && cached_child->cost >= cost_limit - 1 - delta) goto next_comparison; } } #if 1 if (hardness > worst_hardness) { worst_hardness = hardness; killer_order = order; } #endif } cost = 0; for (order = UNTESTED; order <= LESS; order++) { if (order == UNTESTED) child_cost = search(&child[killer_order], cost_limit - 1 - delta); else if (order == killer_order) continue; else child_cost = search(&child[order], cost_limit - 1 - delta); if (child_cost > cost_limit - 1 - delta) /* This test is equivalent to an alpha-beta cutoff. */ goto next_comparison; child_cost++; if (child_cost > cost) cost = child_cost; } if (cost <= cost_limit) { /* The `delta' is a little trick to cause the comparison to most preferably occur on the first variables, thereby slightly improving readability of the resulting algorithms. This also corresponds to alpha-beta windowing. */ parent->solved = 1; parent->cost = cost; parent->a = a; parent->b = b; delta++; } next_comparison: ; } n_nodes++; if (!parent->solved) parent->cost = cost_limit; if (cached_parent == NULL) store_orderset_cache(parent, alts); else parent->locked = 0; if (parent->solved) return parent->cost; else return COST_INFINITE; } static unsigned int number = 1; static void research(orderset_t *parent, int cost_limit) { orderset_t *alts[N_PROBES], *cached_parent, child, *cached_child; unsigned char new_a[N]; cost_t cost; order_t order; int a, b, i, n, greater_done = 1, less_done = 1; n = parent->n; cached_parent = probe_orderset_cache(parent, alts); if (cached_parent == NULL) { cost = search(parent, cost_limit); cached_parent = probe_orderset_cache(parent, alts); } if (cached_parent->number == 0) cached_parent->number = number++; a = cached_parent->a; b = cached_parent->b; #ifdef PRODUCE_DOT print_orderset(cached_parent, 0, NULL); #else #ifdef PRODUCE_C printf("/*\n"); #else printf("# "); #endif print_orderset(cached_parent, 0, NULL); #ifdef PRODUCE_C printf(" */\n"); printf("int find_%d(", cached_parent->number); #else printf("find_%d(", cached_parent->number); #endif for (i = 0; i < n; i++) { #ifdef PRODUCE_C printf("int %c", VAR_NAME(i)); if (i < n-1) printf(", "); #else putchar(VAR_NAME(i)); if (i < n-1) putchar(','); #endif } #ifdef PRODUCE_C printf(")\n{\n return %c > %c\n ? ", VAR_NAME(a), VAR_NAME(b)); #else printf(") =\n if %c > %c\n then ", VAR_NAME(a), VAR_NAME(b)); #endif #endif /* !PRODUCE_DOT */ copy_orderset(&child, parent); set_order_and_closure(&child, a, b, GREATER); canonify_orderset(&child, new_a); if (child.solved) #ifdef PRODUCE_DOT ; #else putchar(VAR_NAME(new_a[0])); #endif else { search(&child, cost_limit - 1); cached_child = probe_orderset_cache(&child, alts); if (cached_child->number == 0) { greater_done = 0; cached_child->number = number++; } #ifdef PRODUCE_DOT printf(" %c%d -> %c%d [ltail = cluster_find%d, lhead = cluster_find%d, " "label=\"%c>%c\"];\n", VAR_NAME(0), cached_parent->number, VAR_NAME(0), cached_child->number, cached_parent->number, cached_child->number, VAR_NAME(a), VAR_NAME(b)); #else n = child.n; printf("find_%d(", cached_child->number); for (i = 0; i < n; i++) { putchar(VAR_NAME(new_a[i])); if (i < n-1) putchar(','); } putchar(')'); #endif /* !PRODUCE_DOT */ } #ifndef PRODUCE_DOT #ifdef PRODUCE_C printf("\n : "); #else printf("\n else "); #endif #endif copy_orderset(&child, parent); set_order_and_closure(&child, a, b, LESS); canonify_orderset(&child, new_a); if (child.solved) #ifdef PRODUCE_DOT ; #else putchar(VAR_NAME(new_a[0])); #endif else { search(&child, cost_limit - 1); cached_child = probe_orderset_cache(&child, alts); if (cached_child->number == 0) { less_done = 0; cached_child->number = number++; } #ifdef PRODUCE_DOT printf(" %c%d -> %c%d [ltail = cluster_find%d, lhead = cluster_find%d, " "label=\"%c<%c\"];\n", VAR_NAME(0), cached_parent->number, VAR_NAME(0), cached_child->number, cached_parent->number, cached_child->number, VAR_NAME(a), VAR_NAME(b)); #else n = child.n; printf("find_%d(", cached_child->number); for (i = 0; i < n; i++) { putchar(VAR_NAME(new_a[i])); if (i < n-1) putchar(','); } putchar(')'); #endif /* !PRODUCE_DOT */ } #ifndef PRODUCE_DOT #ifdef PRODUCE_C printf(";\n}\n\n"); #else puts(""); #endif #endif /* !PRODUCE_DOT */ if (!less_done) { copy_orderset(&child, parent); set_order_and_closure(&child, a, b, LESS); canonify_orderset(&child, NULL); research(&child, cost_limit - 1); } if (!greater_done) { copy_orderset(&child, parent); set_order_and_closure(&child, a, b, GREATER); canonify_orderset(&child, NULL); research(&child, cost_limit - 1); } } /* Returns wall clock time in seconds since the beginning of the program. Typical use involves two calls; one before and one after the benchmark, and the consumed time is the difference of these two times. */ static double give_time(void) { struct tms tms; long ticks_per_second; long ticks; ticks = times(&tms); if (ticks == -1) { fprintf(stderr, "'times' failed\n"); exit(1); } ticks_per_second = sysconf(_SC_CLK_TCK); if (ticks_per_second == -1) { fprintf(stderr, "'sysconf(_SC_CLK_TCK)' failed.\n"); exit(1); } return ticks / (double) ticks_per_second; } /* An interactive decision tree studying routine. Returns non-zero if full exit not requested. */ static int study(orderset_t *orderset, unsigned char *new_a) { orderset_t *cached_orderset, os; char *cmd, old_a[N], newing_a[N], newer_a[N]; cost_t cost, max_cost; int a, b; order_t order; for (a = 0; a < N; a++) { /* printf("new_a[%d] = %d\n", a, new_a[a]); */ old_a[new_a[a]] = a; } while (1) { cached_orderset = probe_orderset_cache(orderset, NULL); if (cached_orderset != NULL) orderset = cached_orderset; print_orderset(orderset, N, new_a); fflush(stdout); cmd = readline("# "); if (cmd == NULL) return 0; if (cmd[0] == '-' && cmd[1] == '\0') /* Go one step up towards to root of the decision tree. */ return 1; else if (cmd[0] == '!' && cmd[1] == '\0') { printf(" "); for (b = 1; b < N; b++) printf(" %c", VAR_NAME(b)); for (a = 0; a < N - 1; a++) { puts(""); if (a > 0) printf("%*c", 3*a, ' '); printf("%c ?", VAR_NAME(a)); for (b = a + 1; b < N; b++) if (get_order(orderset, old_a[a], old_a[b]) != UNTESTED) printf(" "); else { fflush(stdout); current_search_cost = 0; do { copy_orderset(&os, orderset); set_order_and_closure(&os, old_a[a], old_a[b], LESS); canonify_orderset(&os, NULL); cost = search(&os, current_search_cost); } while (cost > current_search_cost++); max_cost = cost; current_search_cost = 0; do { copy_orderset(&os, orderset); set_order_and_closure(&os, old_a[a], old_a[b], GREATER); canonify_orderset(&os, NULL); cost = search(&os, current_search_cost); } while (cost > current_search_cost++); if (cost > max_cost) max_cost = cost; printf("%3d", max_cost); } } puts(""); } else if (cmd[0] == '?' && cmd[1] == '\0') { printf(" "); for (b = 0; b < N; b++) printf(" %c", VAR_NAME(b)); for (a = 0; a < N; a++) { printf("\n%c <", VAR_NAME(a)); for (b = 0; b < N; b++) if (get_order(orderset, old_a[a], old_a[b]) != UNTESTED) printf(" "); else { fflush(stdout); current_search_cost = 0; do { copy_orderset(&os, orderset); set_order_and_closure(&os, old_a[a], old_a[b], LESS); canonify_orderset(&os, NULL); cost = search(&os, current_search_cost); } while (cost > current_search_cost++); printf("%3d", cost); } } puts(""); } else if (cmd[0] >= 'a' && cmd[0] < 'a' + N && (cmd[1] == '<' || cmd[1] == '>') && cmd[2] >= 'a' && cmd[2] < 'a' + N && cmd[3] == '\0') { a = cmd[0] - 'a'; b = cmd[2] - 'a'; order = cmd[1] == '<' ? LESS : GREATER; if (get_order(orderset, old_a[a], old_a[b]) != UNTESTED) printf("The relative order of %c and %c is already known.\n", VAR_NAME(a), VAR_NAME(b)); else { copy_orderset(&os, orderset); set_order_and_closure(&os, old_a[a], old_a[b], order); canonify_orderset(&os, newing_a); for (a = 0; a < N; a++) { newer_a[a] = new_a[newing_a[a]]; /* printf("a=%d, new=%d, newing=%d, newer=%d\n", a, new_a[a], newing_a[a], newer_a[a]); */ } if (!study(&os, newer_a)) return 0; } } else printf("Unrecognized command.\n"); } } int main(int argc, char **argv) { orderset_t initial; cost_t found_cost; double t_start, t_end; unsigned char new_a[N]; int a; init_offsets(); t_start = give_time(); clear_orderset(&initial); initial.n = N; initial.i = I - 1; #ifdef ASSUME_INITIAL_PAIRWISE for (a = 0; a < N - 1; a += 2) set_order(&initial, a, a + 1, GREATER); #endif #ifdef PRINT_COST set_order_and_closure(&initial, 0, 1, GREATER); set_order_and_closure(&initial, 0, 2, GREATER); set_order_and_closure(&initial, 2, 3, GREATER); set_order_and_closure(&initial, 3, 4, GREATER); #endif canonify_orderset(&initial, NULL); if (initial.solved) found_cost = 0; else { current_search_cost = 1; do { #ifndef PRINT_COST fprintf(stderr, "searching V(%d,%d) with maximum cost %d\n", N, I, current_search_cost); #endif /* n_nodes = n_hits = n_misses = 0; */ found_cost = search(&initial, current_search_cost); } while (found_cost > current_search_cost++); } #ifdef PRINT_COST printf("%3d", min_n_comparisons[N][I - 1] - found_cost); return 0; #endif t_end = give_time(); fprintf(stderr, "solved V(%d,%d) = %d\n", N, I, found_cost); #ifdef PRODUCE_DOT printf("digraph V_%d_%d {\n", N, I); puts(" compound=true;"); puts(" size=\"8.4,11.9\";"); #else #ifdef PRODUCE_C puts("/*"); #endif #ifdef ASSUME_INITIAL_PAIRWISE puts("# NOTE: This code has been generated using the assumption that we"); puts("# first compare pairwise all variables, which in this case requires"); printf("# performing %d comparisons. Gasarch et al. conjecture that this\n", N / 2); puts("# should not lead to suboptimal selection algorithms. In this"); printf("# particular case (N=%d, I=%d) the found algorithm's complexity\n", N, I); puts("# coincides with the best known theoretical result, and we can hence"); puts("# deduce that this algorithm is optimal.\n"); #endif printf("# cost = %d\n", (int) found_cost); printf("# n_nodes = %llu\n", n_nodes); printf("# version = " VERSION "\n"); printf("# time = %.3f\n", t_end - t_start); report_orderset_cache(); #ifdef PRODUCE_C puts("*/"); #else puts(""); #endif #endif /* PRODUCE_DOT */ fflush(stdout); research(&initial, found_cost); #ifdef PRODUCE_DOT puts("}"); #endif #ifdef STUDY for (a = 0; a < N; a++) new_a[a] = a; study(&initial, new_a); #endif return 0; }