twentyseven-0.0.0: src/Rubik/Solver.hs
{-# LANGUAGE ScopedTypeVariables, RecordWildCards, TypeFamilies, TypeOperators,
ViewPatterns #-}
module Rubik.Solver where
import Rubik.Cube
import Rubik.IDA
import Rubik.Misc
import Rubik.Symmetry
import Control.Applicative
import Data.Coerce
import Data.Foldable
import Data.Int (Int8)
import Data.Maybe
import Data.Tuple.Extra
import qualified Data.Vector as V
import qualified Data.Vector.Storable.Allocated as S
type MaybeFace = Int
type SubIndex = Int
type DInt = Int8
data Projection x a0 as a = Projection
{ convertP :: x -> a
, isIdenP :: a -> Bool
, indexP :: as -> a -> a
, subIndexSize :: Int
, unfoldP :: a0 -> SubIndex -> [as]
, subIndexP :: a -> SubIndex
}
type Projection' m a = Projection Cube (MoveTag m [RawMove a]) (RawMove a) (RawCoord a)
type SymProjection m sym a = Projection Cube (MoveTag m [SymMove sym a]) (SymMove sym a) (SymCoord sym a)
newtype Distance m a = Distance { distanceP :: a -> DInt }
infixr 4 |*|, |.|
{-# INLINE (|*|) #-}
(|*|) :: (TupleCons b0, TupleCons bs, TupleCons b)
=> Projection x a0 as a
-> Projection x b0 bs b
-> Projection x (a0 :| b0) (as :| bs) (a :| b)
a |*| b = Projection
{ convertP = liftA2 (|:|) (convertP a) (convertP b)
, isIdenP = \(split -> (a_, b_)) -> isIdenP a a_ && isIdenP b b_
, indexP = \(split -> (as_, bs_)) (split -> (a_, b_)) -> indexP a as_ a_ |:| indexP b bs_ b_
, subIndexSize = subIndexSize a * subIndexSize b
, unfoldP = \(split -> (a0_, b0_)) ci ->
let (ai, bi) = ci `divMod` subIndexSize b
in zipWith (|:|) (unfoldP a a0_ ai) (unfoldP b b0_ bi)
, subIndexP = \(split -> (a_, b_)) -> flatIndex (subIndexSize b) (subIndexP a a_) (subIndexP b b_) }
{-# INLINE (|.|) #-}
(|.|) :: forall x a0 as a b0 bs b
. Projection x a0 as a
-> Projection x b0 bs b
-> Projection x (a0, b0) (as, bs) (a, b)
a |.| b = a |*| (coerce b :: Projection x (Tuple1 b0) (Tuple1 bs) (Tuple1 b))
{-# INLINE (>$<) #-}
(>$<) :: forall m a b. (b -> a) -> Distance m a -> Distance m b
(>$<) = coerce (flip (.) :: (b -> a) -> (a -> DInt) -> (b -> DInt))
{-# INLINE maxDistance #-}
maxDistance :: forall f m a. Foldable f => f (Distance m a) -> Distance m a
maxDistance = foldl' (\(Distance f) (Distance g) -> Distance $ \x -> max (f x) (g x)) (Distance $ const 0)
-- | ==Branching reduction
--
-- The @Int@ projection keeps track of the latest move (@== 6@
-- for the starting point).
--
-- 18 moves
--
-- We can indeed reduce the branching factor from 18 to 15
-- by considering that successive moves on the same face
-- can and will be shortened as a single move.
--
-- Furthermore, since moves on opposite faces commute, we may force
-- them to be in an arbitrary order, reducing the branching factor
-- to 12 after half of the moves (U, L, F).
--
-- 10 moves
--
-- Instead of a factor 10, we have factors
--
-- - 9 after R, B;
-- - 8 after L, F;
-- - 7 after D;
-- - 4 after U.
{-# INLINE solveWith #-}
solveWith
:: Eq a
=> MoveTag m [ElemMove] -> a0
-> Projection Cube a0 as a
-> Distance m a
-> Cube -> Move
solveWith (MoveTag moveNames) ms ps pd
= fromJust . search Search{..} . tag . convertP ps
where
goal = isIdenP ps . snd
estm = distanceP pd . snd
edges (i, t)
= fmap
(\(l, succs, j') ->
let x = indexP ps succs t in Succ l 1 (j', x))
(succVector V.! (subIndexP ps t * 7 + i))
-- For every move, filter out "larger" moves for an arbitrary total order of faces
succVector = V.fromList $ do
subi <- [0 .. subIndexSize ps - 1]
let as = unfoldP ps ms subi
i' <- [0 .. 6]
return
[ (l, m, fromEnum j)
| (l@(_, j), m) <- zip moveNames as
, i' == 6 || (let i = toEnum i' in not (i == j || oppositeAndGT j i)) ]
type Tag a = (Int, a)
tag :: a -> Tag a
tag = (,) 6
{-# INLINE rawProjection #-}
rawProjection :: (FromCube a, RawEncodable a) => Projection' m a
rawProjection = Projection
{ convertP = convert
, isIdenP = (== convert iden)
, indexP = (!$)
, subIndexSize = 1
, unfoldP = \(MoveTag as) _ -> as
, subIndexP = \_ -> 0
}
where
convert = encode . fromCube
{-# INLINE symProjection #-}
symProjection :: (FromCube a, RawEncodable a)
=> (a -> SymCoord sym a) -> SymProjection m sym a
symProjection convert = Projection
{ convertP = convert'
, isIdenP = let (x0, _) = convert' iden in \(x, _) -> x == x0
, indexP = symMove' 16
, subIndexSize = 16
, unfoldP = \(MoveTag as) i -> [ as !! j | j <- symAsMovePerm (sym16 !! i) ]
, subIndexP = \(_, SymCode i) -> i
}
where
convert' = convert . fromCube
-- TODO newtype this
{-# INLINE symmetricProj #-}
symmetricProj :: Eq c => Symmetry sym
-> Projection Cube (MoveTag m [b]) as c
-> Projection Cube (MoveTag m [b]) as c
symmetricProj sym proj = proj
{ convertP = convert
, unfoldP = \as i -> rawMoveSym sym (unfoldP proj as i)
}
where
convert = convertP proj . conjugate (inverse (symAsCube sym))
{-# INLINE distanceWith2 #-}
distanceWith2
:: (RawEncodable a, RawEncodable b)
=> S.Vector DInt -> Distance m (RawCoord a, RawCoord b)
distanceWith2 v = Distance $ \(RawCoord a_, b@(RawCoord b_)) ->
v S.! flatIndex (range b) a_ b_