packages feed

set-cover-0.1: example/ConwayPuzzle.hs

{- |
Conway's puzzle:

Assemble a 5x5x5 cube from the following cuboids:
 3x 1x1x3
29x 1x2x2

https://en.wikipedia.org/wiki/Conway_puzzle
-}
module Main where

import qualified Math.SetCover.Cuboid as Cuboid
import qualified Math.SetCover.Exact as ESC
import Math.SetCover.Cuboid (Size, Coords(Coords))

import qualified Control.Concurrent.PooledIO.Independent as Pool
import qualified Control.Monad.Trans.State as MS
import Control.Applicative (pure, liftA2, liftA3)

import qualified Data.Foldable as Fold
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.List as List
import Data.IntSet (IntSet)
import Data.Foldable (foldMap)

import qualified System.IO as IO
import Text.Printf (printf)
import Utility (hPutStrLnImmediate)


size :: Size
size = pure 5


stick :: [Int]
stick = [0..2]

type Mask = Set.Set (Maybe (Coords Int))
type FormatMask = Set.Set (Coords Int)

sticksX, sticksY, sticksZ :: [FormatMask]
sticksX =
   map Set.fromList $ Cuboid.allPositions size $ map (\x -> Coords x 0 0) stick
sticksY =
   map Set.fromList $ Cuboid.allPositions size $ map (\x -> Coords 0 x 0) stick
sticksZ =
   map Set.fromList $ Cuboid.allPositions size $ map (\x -> Coords 0 0 x) stick

data Coord = X | Y | Z deriving (Eq, Ord, Show)

count :: FormatMask -> Map.Map (Coord, Int, Int) Int
count =
   Map.fromListWith (+) .
   concatMap (\(Coords x y z) -> [((X,y,z),1), ((Y,x,z),1), ((Z,x,y),1)]) .
   Set.toList

minimal :: FormatMask -> Bool
minimal =
   let center = pure 2
       transforms =
         map (\trans -> liftA2 (+) (liftA2 (-) center (trans center)) . trans) $
         liftA2 (.) [id, fmap negate] Cuboid.rotations
   in  \mask -> all (\trans -> mask <= Set.map trans mask) transforms

assignsFromSticks ::
   [(FormatMask, FormatMask, FormatMask)] ->
   [ESC.Assign [FormatMask] FormatMask]
assignsFromSticks =
   map fst .
   filter (minimal . snd) .
   filter (Fold.all odd . count . snd) .
   map
      (\(x,y,z) ->
         let u = x `Set.union` y `Set.union` z
         in  (ESC.assign [x,y,z] u, u))

{-
Make use of the fact
that all slices must contain an odd number of elements from 1x1x3 cuboids.
-}
threeSticksOrtho :: [ESC.Assign [FormatMask] FormatMask]
threeSticksOrtho =
   assignsFromSticks $
   filter
      (\(x,y,z) -> ESC.disjoint x y && ESC.disjoint y z && ESC.disjoint z x) $
   liftA3 (,,) sticksX sticksY sticksZ


selectDisjoint :: MS.StateT [FormatMask] [] FormatMask
selectDisjoint =
   MS.StateT $ \masks -> do
      m:ms <- List.tails masks
      return (m, filter (ESC.disjoint m) ms)

threeSticks :: [ESC.Assign [FormatMask] FormatMask]
threeSticks =
   assignsFromSticks $
   MS.evalStateT
      (liftA3 (,,) selectDisjoint selectDisjoint selectDisjoint)
      (sticksX ++ sticksY ++ sticksZ)


square :: [(Int,Int)]
square = liftA2 (,) [0,1] [0,1]

squares :: [FormatMask]
squares =
   (map Set.fromList $
    concatMap (Cuboid.allPositions size)
      [map (\(x,y) -> Coords 0 x y) square,
       map (\(x,y) -> Coords x 0 y) square,
       map (\(x,y) -> Coords x y 0) square])

allAssigns :: [ESC.Assign [FormatMask] Mask]
allAssigns =
   map (\mask -> ESC.assign [mask] (Set.map Just mask)) squares
   ++
   map (fmap (Set.insert Nothing . Set.map Just)) threeSticks


threeSticksCanonical :: ESC.Assign [FormatMask] FormatMask
threeSticksCanonical =
   let x = Set.fromList [Coords 0 0 0, Coords 1 0 0, Coords 2 0 0]
       y = Set.fromList [Coords 3 1 1, Coords 3 2 1, Coords 3 3 1]
       z = Set.fromList [Coords 4 4 2, Coords 4 4 3, Coords 4 4 4]
   in  ESC.assign [x,y,z] (x `Set.union` y `Set.union` z)

initState ::
   ESC.Assign [FormatMask] FormatMask ->
   ESC.State [FormatMask] IntSet
initState s3asn =
   case ESC.intSetFromSetAssigns $
         s3asn : map (\mask -> ESC.assign [mask] mask) squares of
      asns@(s3:_) -> ESC.updateState s3 $ ESC.initState asns
      [] -> error "ESC.bitVectorFromSetAssigns lost first assignment"


formatIdent :: Int -> Char
formatIdent n =
   toEnum $
   if n<10
     then n + fromEnum '0'
     else n-10 + fromEnum 'A'

format :: [FormatMask] -> String
format v =
   let cubex =
          Map.unions $
          zipWith (\n -> foldMap (flip Map.singleton n)) [0..] $
          reverse v
   in  Cuboid.forNestedCoords
          unlines (List.intercalate " | ") (List.intercalate " ")
          (\c -> maybe "." (\n -> [formatIdent n]) $ Map.lookup c cubex)
          size

printMask :: [FormatMask] -> IO ()
printMask = hPutStrLnImmediate IO.stdout . format


mainSimple, mainCanonical, mainParallel :: IO ()
mainSimple = do
   let sol =
         map concat $ ESC.partitions $ ESC.bitVectorFromSetAssigns allAssigns
   mapM_ printMask sol
   print $ length sol

mainCanonical = do
   let sol = map concat $ ESC.search $ initState threeSticksCanonical
   mapM_ printMask sol
   print $ length sol

mainParallel =
   Pool.run $
      (\f -> zipWith f [0..] threeSticks) $ \n s3 ->
         IO.withFile (printf "conway%04d.txt" (n::Int)) IO.WriteMode $ \h ->
            mapM_ (hPutStrLnImmediate h . format . concat) $
            ESC.search $ initState s3


main :: IO ()
main = mainCanonical