diff options
| author | Sebastiano Tronto <sebastiano@tronto.net> | 2025-11-23 16:16:31 +0100 |
|---|---|---|
| committer | Sebastiano Tronto <sebastiano@tronto.net> | 2025-11-24 16:32:11 +0100 |
| commit | 147b0c3c4615c32478a4923242909b8ae5a30d03 (patch) | |
| tree | 5294d9b3655031535085a5163c2c5b5cbe7413b1 /src/solvers/solutions.h | |
| parent | 78ec0d22d927bc4287aa090469d5ba5f84e8780b (diff) | |
| download | nissy-core-147b0c3c4615c32478a4923242909b8ae5a30d03.tar.gz nissy-core-147b0c3c4615c32478a4923242909b8ae5a30d03.zip | |
Fix duplicate solutions, overflow in maxsols and improve symmetry reduction for H48.
This commit fixes two bugs:
- A bug that caused duplicates solutions for symmetric scrambles.
- An overflow in the maxsols parameter for the H48 solver, which
caused it to find much fewer solutions than existed.
Moreover, the H48 solvers has been improved by reducing by symmetry not
only from the starting position, but also up to the first 4 moves.
Diffstat (limited to 'src/solvers/solutions.h')
| -rw-r--r-- | src/solvers/solutions.h | 224 |
1 files changed, 152 insertions, 72 deletions
diff --git a/src/solvers/solutions.h b/src/solvers/solutions.h index 9f209d1..d92d5ad 100644 --- a/src/solvers/solutions.h +++ b/src/solvers/solutions.h | |||
| @@ -1,17 +1,23 @@ | |||
| 1 | STATIC void solution_moves_reset(solution_moves_t [static 1]); | 1 | STATIC void solution_moves_reset(solution_moves_t [static 1]); |
| 2 | STATIC void solution_moves_transform(solution_moves_t [static 1], uint8_t); | 2 | STATIC void solution_moves_transform(solution_moves_t [static 1], size_t, |
| 3 | uint8_t); | ||
| 3 | STATIC void solution_moves_reorient(solution_moves_t [static 1], uint8_t); | 4 | STATIC void solution_moves_reorient(solution_moves_t [static 1], uint8_t); |
| 4 | STATIC bool solution_list_init(solution_list_t [static 1], size_t, char *); | 5 | STATIC bool solution_list_init(solution_list_t [static 1], size_t, char *); |
| 5 | STATIC bool solution_moves_equal( | 6 | STATIC bool solution_moves_equal( |
| 6 | const solution_moves_t [static 1], const solution_moves_t [static 1]); | 7 | const solution_moves_t [static 1], const solution_moves_t [static 1]); |
| 7 | STATIC bool solution_moves_is_duplicate(size_t, const solution_moves_t *); | 8 | STATIC bool last_solution_is_duplicate(const solution_list_t [static 1]); |
| 8 | STATIC bool appendchar(solution_list_t [static 1], char); | 9 | STATIC bool appendchar(solution_list_t [static 1], char); |
| 9 | STATIC bool appendnormal( | 10 | STATIC bool appendnormal( |
| 10 | const solution_moves_t [static 1], solution_list_t [static 1]); | 11 | const solution_moves_t [static 1], solution_list_t [static 1]); |
| 11 | STATIC bool appendinverse( | 12 | STATIC bool appendinverse( |
| 12 | const solution_moves_t [static 1], solution_list_t [static 1]); | 13 | const solution_moves_t [static 1], solution_list_t [static 1]); |
| 14 | STATIC void appendsolution_dfs(const solution_moves_t [static 1], size_t, | ||
| 15 | const uint64_t *, size_t, uint8_t *, const solution_settings_t [static 1], | ||
| 16 | solution_list_t [static 1], | ||
| 17 | solution_moves_t [static NTRANS * SOLUTION_MAXLEN], int64_t [static 1]); | ||
| 13 | STATIC int64_t appendsolution(const solution_moves_t [static 1], | 18 | STATIC int64_t appendsolution(const solution_moves_t [static 1], |
| 14 | const solution_settings_t [static 1], solution_list_t [static 1]); | 19 | size_t, const uint64_t *, const solution_settings_t [static 1], |
| 20 | solution_list_t [static 1]); | ||
| 15 | STATIC bool solutions_done(const solution_list_t [static 1], | 21 | STATIC bool solutions_done(const solution_list_t [static 1], |
| 16 | const solution_settings_t [static 1], int8_t depth); | 22 | const solution_settings_t [static 1], int8_t depth); |
| 17 | 23 | ||
| @@ -23,11 +29,11 @@ solution_moves_reset(solution_moves_t sol[static 1]) | |||
| 23 | } | 29 | } |
| 24 | 30 | ||
| 25 | STATIC void | 31 | STATIC void |
| 26 | solution_moves_transform(solution_moves_t moves[static 1], uint8_t t) | 32 | solution_moves_transform(solution_moves_t moves[static 1], size_t z, uint8_t t) |
| 27 | { | 33 | { |
| 28 | uint8_t i; | 34 | uint8_t i; |
| 29 | 35 | ||
| 30 | for (i = 0; i < moves->nmoves; i++) | 36 | for (i = z; i < moves->nmoves; i++) |
| 31 | moves->moves[i] = transform_move(moves->moves[i], t); | 37 | moves->moves[i] = transform_move(moves->moves[i], t); |
| 32 | 38 | ||
| 33 | for (i = 0; i < moves->npremoves; i++) | 39 | for (i = 0; i < moves->npremoves; i++) |
| @@ -87,13 +93,27 @@ solution_moves_equal( | |||
| 87 | } | 93 | } |
| 88 | 94 | ||
| 89 | STATIC bool | 95 | STATIC bool |
| 90 | solution_moves_is_duplicate(size_t n, const solution_moves_t *s) | 96 | last_solution_is_duplicate(const solution_list_t l[static 1]) |
| 91 | { | 97 | { |
| 92 | size_t i; | 98 | size_t i, j; |
| 99 | |||
| 100 | if (l->nsols == 1) | ||
| 101 | return false; | ||
| 93 | 102 | ||
| 94 | for (i = 0; i < n; i++) | 103 | /* We assume the list is newline-terminated */ |
| 95 | if (solution_moves_equal(&s[i], &s[n])) | 104 | j = l->used-2; |
| 96 | return true; | 105 | while (true) { |
| 106 | for ( ; l->buf[j] != '\n'; j--) | ||
| 107 | if (j == 0) return false; | ||
| 108 | j--; | ||
| 109 | for (i = l->used-2; l->buf[i] == l->buf[j]; i--, j--) { | ||
| 110 | if (l->buf[i-1] == '\n') { | ||
| 111 | if (l->buf[j-1] == '\n' || j == 0) | ||
| 112 | return true; | ||
| 113 | else break; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
| 97 | 117 | ||
| 98 | return false; | 118 | return false; |
| 99 | } | 119 | } |
| @@ -150,97 +170,157 @@ appendinverse( | |||
| 150 | return appendchar(list, ')'); | 170 | return appendchar(list, ')'); |
| 151 | } | 171 | } |
| 152 | 172 | ||
| 153 | STATIC int64_t | 173 | STATIC void |
| 154 | appendsolution( | 174 | appendsolution_dfs( |
| 155 | const solution_moves_t moves[static 1], | 175 | const solution_moves_t moves[static 1], |
| 176 | size_t ntmask, | ||
| 177 | const uint64_t *tmask, | ||
| 178 | size_t itm, | ||
| 179 | uint8_t *tt, | ||
| 156 | const solution_settings_t settings[static 1], | 180 | const solution_settings_t settings[static 1], |
| 157 | solution_list_t list[static 1] | 181 | solution_list_t list[static 1], |
| 182 | solution_moves_t tsol[static NTRANS * SOLUTION_MAXLEN], | ||
| 183 | int64_t r[static 1] | ||
| 158 | ) | 184 | ) |
| 159 | { | 185 | { |
| 160 | int64_t r; | 186 | /* |
| 161 | int i; | 187 | The logic here is quit complex because we have to address H48 |
| 162 | uint8_t t; | 188 | solutions that may be reduced by symmetry in the first few moves. |
| 163 | solution_moves_t tsol[NTRANS]; | 189 | */ |
| 164 | 190 | ||
| 165 | if (moves->nmoves + moves->npremoves > SOLUTION_MAXLEN) | 191 | size_t i, last_start; |
| 166 | goto appendsolution_error_solution_length; | 192 | uint8_t t; |
| 193 | solution_moves_t moves_copy; | ||
| 167 | 194 | ||
| 168 | for ( | 195 | if (list->nsols >= settings->maxsolutions) |
| 169 | t = 0, r = 0; | 196 | return; |
| 170 | t < NTRANS && list->nsols < settings->maxsolutions; | ||
| 171 | t++ | ||
| 172 | ) { | ||
| 173 | if (!(settings->tmask & TM_SINGLE(t))) | ||
| 174 | continue; | ||
| 175 | 197 | ||
| 176 | tsol[r] = *moves; | 198 | if (ntmask == itm) { |
| 177 | if (settings->unniss) { | 199 | tsol[*r] = *moves; |
| 178 | tsol[r].nmoves += moves->npremoves; | ||
| 179 | tsol[r].npremoves = 0; | ||
| 180 | for (i = moves->npremoves-1; i >= 0; i--) | ||
| 181 | tsol[r].moves[tsol[r].nmoves - i - 1] = | ||
| 182 | inverse_move(moves->premoves[i]); | ||
| 183 | 200 | ||
| 184 | /* | 201 | for (i = ntmask; i > 0; i--) |
| 185 | This is a bit ugly: we have to sort now and then again | 202 | solution_moves_transform(&tsol[*r], i-1, tt[i-1]); |
| 186 | later, because the allowedmoves check would fail with | ||
| 187 | improperly sorted parallel moves, but then transforming | ||
| 188 | could swap the pairs the wrong way around. | ||
| 189 | */ | ||
| 190 | sortparallel_moves(tsol[r].nmoves, tsol[r].moves); | ||
| 191 | 203 | ||
| 192 | /* Check if unnissed premoves cancel with normal. */ | 204 | solution_moves_reorient(&tsol[*r], settings->orientation); |
| 193 | if (!allowedmoves(tsol[r].nmoves, tsol[r].moves)) | 205 | sortparallel_moves(tsol[*r].nmoves, tsol[*r].moves); |
| 194 | continue; | 206 | sortparallel_moves(tsol[*r].npremoves, tsol[*r].premoves); |
| 195 | } | ||
| 196 | solution_moves_transform(&tsol[r], t); | ||
| 197 | solution_moves_reorient(&tsol[r], settings->orientation); | ||
| 198 | sortparallel_moves(tsol[r].nmoves, tsol[r].moves); | ||
| 199 | sortparallel_moves(tsol[r].npremoves, tsol[r].premoves); | ||
| 200 | 207 | ||
| 201 | /* Skip duplicates that may appear after transforming */ | 208 | last_start = list->used; |
| 202 | if (solution_moves_is_duplicate(r, tsol)) | ||
| 203 | continue; | ||
| 204 | 209 | ||
| 205 | /* Append first the moves on the side that has more */ | 210 | /* Append first the moves on the side that has more */ |
| 206 | /* E.g. write (U L F) B instead of B (U L F) */ | 211 | /* E.g. write (U L F) B instead of B (U L F) */ |
| 207 | if (tsol[r].nmoves >= tsol[r].npremoves) { | 212 | if (tsol[*r].nmoves >= tsol[*r].npremoves) { |
| 208 | if (!appendnormal(&tsol[r], list)) | 213 | if (!appendnormal(&tsol[*r], list)) |
| 209 | goto appendsolution_error_buffer; | 214 | goto appendsolution_dfs_error_buffer; |
| 210 | 215 | ||
| 211 | if (tsol[r].nmoves > 0 && tsol[r].npremoves > 0) | 216 | if (tsol[*r].nmoves > 0 && tsol[*r].npremoves > 0) |
| 212 | if (!appendchar(list, ' ')) | 217 | if (!appendchar(list, ' ')) |
| 213 | return false; | 218 | goto appendsolution_dfs_error_buffer; |
| 214 | 219 | ||
| 215 | if (!appendinverse(&tsol[r], list)) | 220 | if (!appendinverse(&tsol[*r], list)) |
| 216 | goto appendsolution_error_buffer; | 221 | goto appendsolution_dfs_error_buffer; |
| 217 | } else { | 222 | } else { |
| 218 | if (!appendinverse(&tsol[r], list)) | 223 | if (!appendinverse(&tsol[*r], list)) |
| 219 | goto appendsolution_error_buffer; | 224 | goto appendsolution_dfs_error_buffer; |
| 220 | 225 | ||
| 221 | if (tsol[r].nmoves > 0 && tsol[r].npremoves > 0) | 226 | if (tsol[*r].nmoves > 0 && tsol[*r].npremoves > 0) |
| 222 | if (!appendchar(list, ' ')) | 227 | if (!appendchar(list, ' ')) |
| 223 | return false; | 228 | goto appendsolution_dfs_error_buffer; |
| 224 | 229 | ||
| 225 | if (!appendnormal(&tsol[r], list)) | 230 | if (!appendnormal(&tsol[*r], list)) |
| 226 | goto appendsolution_error_buffer; | 231 | goto appendsolution_dfs_error_buffer; |
| 227 | } | 232 | } |
| 228 | 233 | ||
| 229 | if (!appendchar(list, '\n')) | 234 | if (!appendchar(list, '\n')) |
| 230 | goto appendsolution_error_buffer; | 235 | goto appendsolution_dfs_error_buffer; |
| 231 | |||
| 232 | ++list->nsols; | 236 | ++list->nsols; |
| 237 | |||
| 238 | /* | ||
| 239 | Normaly, it would be enough to check for duplicates in the | ||
| 240 | current "pack" of transformation-equivalent solutions. | ||
| 241 | However, in rare cases, the H48 solver may produce equivalent | ||
| 242 | "packs" of solutions. It would be more elegant to filter out | ||
| 243 | the corresponding tasks in solve_h48_maketasks(), but doing so | ||
| 244 | is not trivial. In the end, duplicate solutions are never | ||
| 245 | desirable, so we might as well do this clean up here. | ||
| 246 | */ | ||
| 247 | if (last_solution_is_duplicate(list)) { | ||
| 248 | --list->nsols; | ||
| 249 | list->used = last_start; | ||
| 250 | return; | ||
| 251 | } | ||
| 252 | |||
| 233 | list->shortest_sol = MIN( | 253 | list->shortest_sol = MIN( |
| 234 | list->shortest_sol, tsol[r].nmoves + tsol[r].npremoves); | 254 | list->shortest_sol, tsol[*r].nmoves + tsol[*r].npremoves); |
| 235 | r++; | 255 | (*r)++; |
| 256 | } else { | ||
| 257 | for (t = 0; t < NTRANS; t++) { | ||
| 258 | if (!(tmask[itm] & TM_SINGLE(t))) | ||
| 259 | continue; | ||
| 260 | moves_copy = *moves; | ||
| 261 | tt[itm] = t; | ||
| 262 | appendsolution_dfs(&moves_copy, ntmask, tmask, | ||
| 263 | itm+1, tt, settings, list, tsol, r); | ||
| 264 | if (*r < 0) | ||
| 265 | return; | ||
| 266 | } | ||
| 236 | } | 267 | } |
| 237 | 268 | ||
| 238 | list->buf[list->used] = '\0'; | 269 | return; |
| 239 | return r; | ||
| 240 | 270 | ||
| 241 | appendsolution_error_buffer: | 271 | appendsolution_dfs_error_buffer: |
| 242 | list->buf[0] = '\0'; | 272 | list->buf[0] = '\0'; |
| 243 | return NISSY_ERROR_BUFFER_SIZE; | 273 | *r = NISSY_ERROR_BUFFER_SIZE; |
| 274 | return; | ||
| 275 | } | ||
| 276 | |||
| 277 | STATIC int64_t | ||
| 278 | appendsolution( | ||
| 279 | const solution_moves_t moves[static 1], | ||
| 280 | size_t ntmask, | ||
| 281 | const uint64_t *tmask, | ||
| 282 | const solution_settings_t settings[static 1], | ||
| 283 | solution_list_t list[static 1] | ||
| 284 | ) | ||
| 285 | { | ||
| 286 | int64_t r; | ||
| 287 | int i; | ||
| 288 | uint8_t tt[SOLUTION_MAXLEN]; | ||
| 289 | solution_moves_t moves_copy, tsol[NTRANS * SOLUTION_MAXLEN]; | ||
| 290 | |||
| 291 | if (moves->nmoves + moves->npremoves > SOLUTION_MAXLEN) | ||
| 292 | goto appendsolution_error_solution_length; | ||
| 293 | |||
| 294 | moves_copy = *moves; | ||
| 295 | if (settings->unniss) { | ||
| 296 | moves_copy.nmoves += moves->npremoves; | ||
| 297 | moves_copy.npremoves = 0; | ||
| 298 | for (i = moves->npremoves-1; i >= 0; i--) | ||
| 299 | moves_copy.moves[moves_copy.nmoves - i - 1] = | ||
| 300 | inverse_move(moves->premoves[i]); | ||
| 301 | |||
| 302 | /* | ||
| 303 | This is a bit ugly: we have to sort now and then again | ||
| 304 | later, because the allowedmoves check would fail with | ||
| 305 | improperly sorted parallel moves, but then transforming | ||
| 306 | could swap the pairs the wrong way around. | ||
| 307 | */ | ||
| 308 | sortparallel_moves(moves_copy.nmoves, moves_copy.moves); | ||
| 309 | |||
| 310 | /* Check if unnissed premoves cancel with normal. */ | ||
| 311 | if (!allowedmoves(moves_copy.nmoves, moves_copy.moves)) | ||
| 312 | return 0; | ||
| 313 | } | ||
| 314 | |||
| 315 | r = 0; | ||
| 316 | memset(tt, TRANS_UFr, SOLUTION_MAXLEN); | ||
| 317 | appendsolution_dfs( | ||
| 318 | &moves_copy, ntmask, tmask, 0, tt, settings, list, tsol, &r); | ||
| 319 | if (r < 0) | ||
| 320 | return r; | ||
| 321 | |||
| 322 | list->buf[list->used] = '\0'; | ||
| 323 | return r; | ||
| 244 | 324 | ||
| 245 | appendsolution_error_solution_length: | 325 | appendsolution_error_solution_length: |
| 246 | list->buf[0] = '\0'; | 326 | list->buf[0] = '\0'; |
