{- | Encoding cube projections as @Int@ coordinates. Explicit dictionary passing style: using a class would require explicit type annotations /anyway/. -} {-# LANGUAGE FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, ScopedTypeVariables, ViewPatterns #-} module Rubik.Cube.Coord where import Rubik.Cube.Cubie.Internal import Rubik.Misc import Control.DeepSeq import Control.Monad.Random import Control.Newtype import Data.Binary.Storable import Data.List import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Unboxed.Mutable as MU import qualified Data.Vector.Storable.Allocated as S -- * Raw coordinates -- Unwrapped coordinate type RawCoord' = Int -- MaxInt 2^29 = 479001600 (at least, according to the standard) -- | Encoding to an efficient datatype -- for which it is possible to build tables -- instead of computing functions. newtype RawCoord a = RawCoord { unRawCoord :: RawCoord' } deriving (Eq, Ord, Show, NFData, Binary) newtype RawVector a b = RawVector { unRawVector :: U.Vector b } newtype RawMove a = RawMove { unRawMove :: S.Vector RawCoord' } deriving (Eq, Ord, Show, NFData, Binary) instance Newtype (RawCoord a) Int where pack = RawCoord unpack = unRawCoord instance Newtype (RawMove a) (S.Vector Int) where pack = RawMove unpack = unRawMove {-# INLINE (!$) #-} (!$) :: RawMove a -> RawCoord a -> RawCoord a RawMove v !$ RawCoord i = RawCoord (v S.! i) (!.) :: MU.Unbox b => RawVector a b -> RawCoord a -> b RawVector v !. RawCoord i = v U.! i -- * Dictionaries -- | Encoding dictionary. -- -- Probably synonymous with instances for both -- @('Enum' a, 'Bounded' a)@. -- -- > inRange (range d) $ encode x -- > encode . decode == id -- > decode . encode == id -- -- A special constructor for dictionaries of product types -- is particularly useful to create tables of functions -- if their actions on every projection are independent. -- class RawEncodable a where -- | Number of elements that can be converted. -- Their values are to lie in @[0 .. range c - 1]@. range :: proxy a -> Int encode :: a -> RawCoord a decode :: RawCoord a -> a -- ** Instances -- | The number of elements of every set is given. -- | @8! = 40320@ instance RawEncodable CornerPermu where range _ = 40320 encode = RawCoord . encodeFact numCorners . U.toList . fromCornerPermu decode = unsafeCornerPermu' . decodeFact numCorners numCorners . unRawCoord -- | @12! = 479001600@ -- -- A bit too much to hold in memory. -- -- Holds just right in a Haskell @Int@ (@maxInt >= 2^29 - 1@). instance RawEncodable EdgePermu where range _ = 479001600 encode = RawCoord . encodeFact numEdges . U.toList . fromEdgePermu decode = unsafeEdgePermu' . decodeFact numEdges numEdges . unRawCoord -- | @3^7 = 2187@ instance RawEncodable CornerOrien where range _ = 2187 encode = RawCoord . encodeBaseV 3 . U.tail . fromCornerOrien -- The first orientation can be deduced from the others in a solvable cube decode (RawCoord x) = unsafeCornerOrien' (h : t) where h = (3 - sum t) `mod` 3 t = decodeBase 3 (numCorners - 1) x -- | @2^11 = 2048@ instance RawEncodable EdgeOrien where range _ = 2048 encode = RawCoord . encodeEdgeOrien' . fromEdgeOrien decode (RawCoord x) = unsafeEdgeOrien' (h : t) where h = sum t `mod` 2 t = decodeBase 2 (numEdges - 1) x encodeEdgeOrien' = encodeBaseV 2 . U.tail numUDS = numUDSliceEdges numUDE = numEdges - numUDS -- | 12! / 8! = 11880 instance RawEncodable UDSlicePermu where range _ = 11880 encode = RawCoord . encodeFact numEdges . U.toList . fromUDSlicePermu decode = unsafeUDSlicePermu' . decodeFact numEdges numUDS . unRawCoord -- | @12C4 = 495@ instance RawEncodable UDSlice where range _ = 495 encode = RawCoord . encodeCV . fromUDSlice decode = unsafeUDSlice . decodeCV numUDS . unRawCoord -- | @4! = 24@ instance RawEncodable UDSlicePermu2 where range _ = 24 encode = RawCoord . encodeFact numUDS . U.toList . fromUDSlicePermu2 decode = unsafeUDSlicePermu2' . decodeFact numUDS numUDS . unRawCoord -- | @8! = 40320@ instance RawEncodable UDEdgePermu2 where range _ = 40320 encode = RawCoord . encodeFact numUDE . U.toList . fromUDEdgePermu2 decode = unsafeUDEdgePermu2' . decodeFact numUDE numUDE . unRawCoord instance (RawEncodable a, RawEncodable b) => RawEncodable (a, b) where range _ = range ([] :: [a]) * range ([] :: [b]) encode (a, b) = flatCoord (encode a) (encode b) decode (splitCoord -> (a, b)) = (decode a, decode b) {-# INLINE flatCoord #-} flatCoord :: (RawEncodable a, RawEncodable b) => RawCoord a -> RawCoord b -> RawCoord (a, b) flatCoord (RawCoord a) b'@(RawCoord b) = RawCoord (flatIndex (range b') a b) {-# INLINE splitCoord #-} splitCoord :: (RawEncodable a, RawEncodable b) => RawCoord (a, b) -> (RawCoord a, RawCoord b) splitCoord (RawCoord ab_) = (a, b) where (RawCoord -> a, RawCoord -> b) = ab_ `divMod` range b -- * Table building -- | Endofunctions type Endo a = a -> a -- | Lift an endofunction to its coordinate representation, -- the dictionary provides a @RawCoord@ encoding. -- -- That is, we construct a vector @v@ such that, basically, -- -- > decode (v ! encode x) == f x -- -- So function application becomes simply vector indexing. endoVector :: RawEncodable a => Endo a -> RawMove a endoVector f = RawMove . S.generate (range f) $ under RawCoord (encode . f . decode) -- | The 'cubeAction' method is partially applied to a 'Cube' -- and turned into an 'Endo' function. -- -- The 'CA a' type argument controls the refinement of the endofunction. cubeActionToEndo :: CubeAction a => Cube -> Endo a cubeActionToEndo c = (`cubeAction` c) -- | Composition of 'endoVector' and 'cubeAction'. moveTable :: (CubeAction a, RawEncodable a) => Cube -> RawMove a moveTable = endoVector . cubeActionToEndo symToEndo :: (Cube -> a -> a) -> Cube -> Endo a symToEndo = id symTable :: RawEncodable a => (Cube -> a -> a) -> Cube -> RawMove a symTable conj = endoVector . symToEndo conj -- * Miscellaneous -- | Checks over the range @range@ that: -- -- > encode . decode == id -- checkCoord :: RawEncodable a => proxy a -> Bool checkCoord proxy = all (\(RawCoord -> k) -> encode (decode k `asProxyTypeOf` proxy) == k) [0 .. range proxy - 1] randomRawCoord :: forall a m. (MonadRandom m, RawEncodable a) => m (RawCoord a) randomRawCoord = RawCoord <$> getRandomR (0, range ([] :: [a]) - 1) -- * Helper -- | Helper functions to define the dictionaries -- ** Fixed base -- | If -- @all (`elem` [0 .. b-1]) v@ -- then @v@ is the base @b@ representation of -- @encode b v@ -- such that its least significant digit is @head v@. -- -- For any @n@, @encodeBase b@ is a bijection from lists of length @n@ -- with elements in @[0 .. b-1]@ to @[0 .. b^n - 1]@ encodeBase :: Int -> [Int] -> Int encodeBase b = foldr1 (\x y -> x + b * y) -- | Vector version of 'encodeBase'. encodeBaseV :: Int -> Vector Int -> Int encodeBaseV b = U.foldr1' (\x y -> x + b * y) -- | @len@ is the length of the resulting vector -- -- > encodeBase b . decodeBase b len == id -- > decodeBase b len . encodeBase b == id -- decodeBase :: Int -> Int -> Int -> [Int] decodeBase b len = take len . unfoldr (\x -> Just (x `modDiv` b)) where modDiv = ((.).(.)) (\(x,y) -> (y,x)) divMod -- ** Factorial radix -- | Input list must be a @k@-permutation of @[0 .. n-1]@. -- -- @encodeFact@ is a bijection between k-permutations of @[0 .. n-1]@ -- and @[0 .. (fact n / fact (n-k)) - 1]@. encodeFact :: Int -> [Int] -> Int encodeFact n [] = 0 encodeFact n (y : ys) = y + n * encodeFact (n - 1) ys' where ys' = case elemIndex (n - 1) ys of Nothing -> ys -- y == n - 1 Just k -> subs k y ys -- recovers a subpermutation of @[0 .. n-2]@ -- | Inverse of 'encodeFact'. -- -- > encodeFact n . decodeFact n k == id -- k <= n -- > decodeFact n k . encodeFact n == id -- on k-permutations -- decodeFact :: Int -> Int -> Int -> [Int] decodeFact n 0 _ = [] decodeFact n k x = y : ys where (q, y) = x `divMod` n ys' = decodeFact (n - 1) (k - 1) q ys = case elemIndex y ys' of Nothing -> ys' -- y == n - 1 Just k -> subs k (n - 1) ys' -- ** Binomial enumeration -- Bijection between @[0 .. choose n (k-1)]@ -- and @k@-combinations of @[0 .. n-1]@. -- | > cSum k z == sum [y `choose` k | y <- [k .. z-1]] -- -- requires @k < cSum_mMaz@ and @z < cSum_nMaz@. cSum :: Int -> Int -> Int cSum = \k z -> v U.! (k * n + z) where cSum' k z = sum [y `choose` k | y <- [k .. z-1]] v = U.generate (n * m) (uncurry cSum' . (`divMod` n)) m = cSum_mMax n = cSum_nMax -- | Bound on arguments accepted by @cSum@ cSum_mMax, cSum_nMax :: Int cSum_mMax = 4 cSum_nMax = 16 -- | > encodeCV == encodeCV + cSum k (y k) -- -- where @c@ is a @k@-combination, -- that is a sorted list of @k@ nonnegative elements. -- -- @encodeCV@ is in fact a bijection between increasing lists -- (of non-negative integers) and integers. -- -- Restriction: @k < cSum_mMax@, @y k < cSum_nMax@. encodeCV :: Vector Int -> Int encodeCV = U.sum . U.imap cSum -- | Inverse of 'encodeCV'. -- -- The length of the resulting list must be supplied as a hint -- (although it could technically be guessed). decodeCV :: Int -> Int -> Vector Int decodeCV k x = U.create (do v <- MU.new k let decode' (-1) _ _ = return () decode' k z x | s <= x = MU.write v k z >> decode' (k-1) (z-1) (x-s) | otherwise = decode' k (z-1) x where s = cSum k z decode' (k-1) (cSum_nMax-1) x return v)