packages feed

sum-pyramid-0.0.1: src/UniqueLogic.hs

module UniqueLogic where

import Common

import qualified UniqueLogic.ST.TF.Rule as Rule
import qualified UniqueLogic.ST.TF.System.Simple as Sys
import qualified UniqueLogic.ST.TF.ZeroFractional as ZeroFrac

import Control.Monad.ST (ST, runST)

import qualified Data.Array.Comfort.Boxed as BoxedArray
import qualified Data.Array.Comfort.Shape as Shape
import qualified Data.Traversable as Trav
import qualified Data.Foldable as Fold
import qualified Data.Map as Map
import Data.Array.Comfort.Boxed ((!))
import Data.Map (Map)
import Data.Set (Set)
import Data.Foldable (for_, traverse_)
import Data.Maybe (isJust)


{- $setup
>>> import qualified Data.Array.Comfort.Boxed as BoxedArray
>>> import qualified Data.Array.Comfort.Shape as Shape
>>> import qualified Data.Map as Map
>>> import Common
-}


type Variable s a = Sys.Variable (ST s) a
type System s = Sys.T (ST s)

system ::
   BoxedArray.Array
      (Shape.LowerTriangular ShapeOp)
      (Variable s a -> Variable s a -> Variable s a -> System s ()) ->
   ST s
      (BoxedArray.Array (Shape.LowerTriangular ShapeInt) (Variable s a),
       System s ())
system ops = do
   let n = sizeFromOps ops
   vars <-
      Trav.sequence $
      BoxedArray.replicate
         (Shape.lowerTriangular $ Shape.ZeroBased n)
         Sys.globalVariable
   return
      (vars,
       traverse_
         (\((i,j),rule) -> rule (vars!(i+1,j)) (vars!(i+1,j+1)) (vars!(i,j)))
         (BoxedArray.toAssociations ops))


{- |
>>> solve 3 $ Map.fromList [((0,0),1), ((1,0),1), ((2,0),1::Integer)]
BoxedArray...Triangular... 3... [Just 1,Just 1,Just 0,Just 1,Just 0,Just 0]

>>> solve 3 $ Map.fromList [((0,0),1), ((2,0),1), ((2,2),1::Integer)]
BoxedArray...Triangular... 3... [Just 1,Nothing,Nothing,Just 1,Nothing,Just 1]
-}
solve ::
   (Num a) =>
   Int -> Map (Int,Int) a ->
   BoxedArray.Array (Shape.LowerTriangular ShapeInt) (Maybe a)
solve n xs =
   runST
      (do
         (vars, sys) <-
            system $
            BoxedArray.replicate (Shape.lowerTriangular $ ShapeOp n) Rule.add
         Sys.solve $ do
            sys
            for_ (Map.toList xs) $ \(ij,x) ->
               Rule.equ (vars!ij) =<< Sys.constant x
         traverse Sys.query vars)

{- |
>>> solveMixed (BoxedArray.fromList (Shape.lowerTriangular $ ShapeOp 2) [Mul]) (Map.fromList [((0,0),0), ((1,1),0::Rational)])
BoxedArray...Triangular... 2... [Just (0 % 1),Nothing,Just (0 % 1)]
>>> solveMixed (BoxedArray.fromList (Shape.lowerTriangular $ ShapeOp 2) [Mul]) (Map.fromList [((0,0),0), ((1,1),5::Rational)])
BoxedArray...Triangular... 2... [Just (0 % 1),Just (0 % 1),Just (5 % 1)]
-}
solveMixed ::
   (ZeroFrac.C a) =>
   BoxedArray.Array (Shape.LowerTriangular ShapeOp) Op ->
   Map (Int,Int) a ->
   BoxedArray.Array (Shape.LowerTriangular ShapeInt) (Maybe a)
solveMixed ops xs =
   runST
      (do
         let rule op =
               case op of
                  Add -> Rule.add
                  Mul -> Rule.mul
         (vars, sys) <- system $ fmap rule ops
         Sys.solve $ do
            sys
            for_ (Map.toList xs) $ \(ij,x) ->
               Rule.equ (vars!ij) =<< Sys.constant x
         traverse Sys.query vars)


solvable :: Int -> Set (Int, Int) -> Bool
solvable n = Fold.all isJust . solve n . Map.fromSet (const (0::Integer))

{- |
>>> map (length . solvables) [0..5]
[1,1,3,16,122,1188]
-}
solvables :: Int -> [Set (Int,Int)]
solvables n = filter (solvable n) $ allCellSelections n


solvableMixed :: SolutionCheck Integer
solvableMixed ops puzzle =
   Fold.all isJust $ solveMixed ops $ fmap toRational puzzle