2

I am trying to write a haskell program which can solve rubiks' cube. Firstly I tried this, but did not figure a way out to avoid writing a whole lot of codes, so I tried using an IDA* search for this task.

But I do not know which heuristic is appropriate here: I tried dividing the problem into subproblems, and measuring the distance from being in a reduced state, but the result is disappointing: the program cannot reduce a cube that is three moves from a standard cube in a reasonable amount of time. I tried measuring parts of edges, and then either summing them, or using the maximums... but none of these works, and the result is almost identical.

So I want to know what the problem with the code is: is the heuristic I used non-admissible? Or is my code causing some infinite loops that I did not detect? Or both? And how to fix that? The code (the relevant parts) goes as follows:

--Some type declarations
data Colors = R | B | W | Y | G | O
type R3 = (Int, Int, Int)
type Cube = R3 -> Colors
points :: [R3] --list of coordinates of facelets of a cube; there are 48 of them.
mU :: Cube -> Cube --and other 5 similar moves.

type Actions = [Cube -> Cube]

turn :: Cube -> Actions -> Cube --chains the actions and turns the cube.

edges :: [R3] --The edges of cubes

totheu1 :: Cube -> Int  -- Measures how far away the cube is from having the cross of the first layer solved.
totheu1 c = sum $ map (\d -> if d then 0 else 1)
                      [c (-2, 3, 0) == c (0, 3, 0),
                       c (2, 3, 0) == c (0, 3, 0),
                       c (0, 3, -2) == c (0, 3, 0),
                       c (0, 3, 2) == c (0, 3, 0),
                       c (0, 2, -3) == c (0, 0, -3),
                       c (-3, 2, 0) == c (-3, 0, 0),
                       c (0, 2, 3) == c (0, 0, 3),
                       c (3, 2, 0) == c (3, 0, 0)]

expandnr :: (Cube -> Cube) -> Cube -> [(Cube, String)] -- Generates a list of tuples of cubes and strings, 

-- the result after applying a move, and the string represents that move, while avoiding moving on the same face as the last one, 

-- and avoiding repetitions caused by commuting moves, like U * D = D * U.

type StateSpace = (Int, [String], Cube) -- Int -> f value, [String] = actions applied so far, Cube = result cube.

fstst :: StateSpace -> Int
fstst s@(x, y, z) = x

stst :: StateSpace -> [String]
stst s@(x, y, z) = y

cbst :: StateSpace -> Cube
cbst s@(x, y, z) = z

stage1 :: Cube -> StateSpace
stage1 c = (\(x, y, z) -> (x, [sconcat y], z)) t
 where
 bound = totheu1 c
 t = looping c bound
 looping c bound = do let re = search (c, [""]) (\j -> j) 0 bound
                      let found = totheu1 $ cbst re
                      if found == 0 then re else looping c found
 sconcat [] = ""
 sconcat (x:xs) = x ++ (sconcat xs)

straction :: String -> Actions -- Converts strings to actions

search :: (Cube, [String]) -> (Cube -> Cube) -> Int -> Int -> StateSpace
search cs@(c, s) k g bound
 | f > bound = (f, s, c)
 | totheu1 c == 0 = (0, s, c)
 | otherwise = ms
 where
 f = g + totheu1 c
 olis = do
         (succs, st) <- expandnr k c
         let [newact] = straction st
         let t = search (succs, s ++ [st]) newact (g + 1) bound
         return t
 lis = map fstst olis
 mlis = minimum lis
 ms = olis !! (ind)
 Just ind = elemIndex mlis lis

I know that this heuristic is inconsistent, but am not sure if it is really admissible, maybe the problem is its non-admissibility?

Any ideas, hints, and suggestions are well appreciated, thanks in advance.

Community
  • 1
  • 1
awllower
  • 571
  • 1
  • 9
  • 21
  • 1
    If it's three moves away from being solved, you should have no trouble solving it with IDA* even with a trivial heuristic - making no cuts and putting the expansions in any order. Getting rid of the heuristic and trying that would let you eliminate or identify the heuristic as a source of the problem. – Cirdec Jul 26 '15 at 16:14
  • @Cirdec I changed the heuristic to 0 if it is reduced, and 1 otherwise, but the program still outputs nothing... I think the code must be wrong somewhere. – awllower Jul 30 '15 at 01:37

1 Answers1

1

Your heuristic is inadmissible. An admissible heuristic must be a lower bound on the real cost of a solution.

You are trying to use as a heuristic the number of side pieces of the first layer that aren't correct, or perhaps the number of faces of the side pieces of the first layer that aren't correct, which is what you have actually written. Either way the heuristic is inadmissible.

The following cube is only 1 move away from being solved, but 4 of the pieces in the first layer are in incorrect positions and 4 of the faces have the wrong color. Either heuristic would say that this puzzle will take at least 4 moves to solve when it can be solved in only 1 move. The heuristics are inadmissible, because they are not lower bounds on the real cost of the solution.

enter image description here

Cirdec
  • 24,019
  • 2
  • 50
  • 100
  • Thanks for the explanation. But changing the heuristic to 0 if reduced and 1 otherwise didn't help much: in this case the heuristic should be admissible, right? Maybe I should re-write the code again. – awllower Jul 30 '15 at 01:38
  • I'd encourage you to refactor the code so that IDA* search is written generically for any problem and successor Rubik's cube moves are independent of the heuristic. This will let you test each part separately from the others. IDA* search can be written in terms of a `heuristic :: Num cost => node -> cost` and a successors function `expand :: node -> [(cost, node)]` giving the whole function the type `idastar :: Num cost, Alternative f => (node -> cost) -> (node -> [(cost, node)]) -> node -> f node`. – Cirdec Jul 30 '15 at 02:47
  • I am not enough experienced in Haskell to understand your proposed function: What does it have to do with Alternative here? Why the result of idastar is in f node? Should I employ of something that both forms a Monoid and is an Applicative functor? And thanks for the suggestion to separate the heuristic and expand out! Lastly, may I ask is there any problem with the stage1 I wrote? Namely, does it cause any obvious infinite loops? Or is that code too chaotic to understand properly? – awllower Jul 30 '15 at 05:15
  • @awllower You can just use `Maybe` for "there might be a result" instead of any `Alternative f => f`. It might still be useful to use `<|>` from `Alternative` since the monoid instance for `Maybe` is useless. The `Applicative`/`Alternative` instance for `Maybe` is just `pure = Just`, `<*>` and `liftA2` combine `Just`s, `empty = Nothing`, and `<|>` takes the first `Just`. Using `Alternative` you could substitute a list and get all the answers in order as well. – Cirdec Jul 30 '15 at 06:51
  • You mean when it is not possible to reduce the cube then the result would be a Nothing? OK, I shall try to work out in this direction. Thanks again! – awllower Jul 30 '15 at 07:55