packages feed

sum-pyramid-0.0: src/LinearAlgebra.hs

module LinearAlgebra where

import qualified Numeric.LAPACK.Matrix.Triangular as Triangular
import qualified Numeric.LAPACK.Matrix.Layout as Layout
import qualified Numeric.LAPACK.Matrix as Matrix
import qualified Numeric.LAPACK.Singular as Singular
import qualified Numeric.LAPACK.Vector as Vector
import Numeric.LAPACK.Matrix (ShapeInt, (#*|))
import Numeric.LAPACK.Format ((##))

import qualified Combinatorics

import qualified Data.Array.Comfort.Boxed as BoxedArray
import qualified Data.Array.Comfort.Storable as Array
import qualified Data.Array.Comfort.Shape as Shape
import qualified Data.IntSet as IntSet
import qualified Data.Set as Set
import Data.Array.Comfort.Storable (Array)

import Data.Foldable (for_)
import Data.Tuple.HT (mapSnd)



{- $setup
>>> import qualified Data.Array.Comfort.Storable as Array
>>> import Data.Tuple.HT (mapSnd)
>>>
>>> myRound :: Double -> Integer
>>> myRound = round
-}


example0 :: [Double]
example0 = [3,1,4,1,5]

{-
        ...
      ... ...
    ... ... ...
  ... 100 ... .79
.31 .41 ... .26 ...
-}
example2 :: [[Maybe Double]]
example2 =
   let __ = Nothing; d = Just in
                [__] :
             [__,    __] :
          [__,   __,    __] :
       [__,  d 100,  __,  d 79] :
   [d 31, d 41,  __,   d 26,  __] :
   []

pyramid ::
   Array ShapeInt Double ->
   Array (Shape.LowerTriangular ShapeInt) Double
pyramid xs =
   let shape = Array.shape xs
       baseRow = Shape.size shape - 1
       arr =
         fmap (\(i,j) ->
            if i==baseRow
               then xs Array.! j
               else arr BoxedArray.! (i+1,j) + arr BoxedArray.! (i+1,j+1)) $
         BoxedArray.indices $ Shape.lowerTriangular shape
   in Array.fromBoxed arr

basis ::
   ShapeInt -> Matrix.General ShapeInt (Shape.LowerTriangular ShapeInt) Double
basis shape@(Shape.ZeroBased n) =
   Matrix.fromRows (Shape.lowerTriangular shape) $
   map (pyramid . Vector.unit shape) $ take n [0..]

addIndices :: [[Maybe a]] -> [((Int,Int), a)]
addIndices puzzle = do
   (i, xs) <- zip [0..] puzzle
   (j, Just x) <- zip [0..] xs
   return ((i,j),x)

{- |
>>> mapSnd (map myRound . Array.toList) $ solve 3 [((0,0),8), ((2,0),1), ((2,2),3)]
(3,[8,3,5,1,2,3])
-}
solve ::
   Int -> [((Int,Int),Double)] ->
   (Int, Array (Shape.LowerTriangular ShapeInt) Double)
solve n indexed =
   let fullBasis = Matrix.transpose $ basis (Matrix.shapeInt n)
       selected =
         Matrix.takeRowArray
            (BoxedArray.vectorFromList (map fst indexed))
            fullBasis
   in mapSnd ((fullBasis #*|) . Matrix.flattenColumn)
         (Singular.leastSquaresMinimumNormRCond 1e-5 selected $
          Matrix.singleColumn Layout.ColumnMajor $
          Vector.autoFromList (map snd indexed))

solvable :: Int -> [(Int,Int)] -> Bool
solvable n =
   let shape = Matrix.shapeInt n
       fullBasis = basis shape
   in \ixs ->
         (n==) $ length $ takeWhile (1e-5<) $ Vector.toList $
         (#*| Vector.one shape) $ Singular.values $
         Matrix.takeColumnArray (BoxedArray.vectorFromList ixs) fullBasis

solvables :: Int -> [[(Int,Int)]]
solvables n =
   let check = solvable n
   in filter check $
      Combinatorics.tuples n $ Shape.indices $
      Shape.lowerTriangular $ Matrix.shapeInt n

{-
Check, whether a sum pyramid contains a sub-pyramid of size k
with more than k given fields.
If yes, then the pyramid has redundancies in a sub-pyramid
and is not solvable.
-}
wellcrowded :: Int -> [(Int,Int)] -> Bool
wellcrowded n ixs =
   let triShape = Shape.lowerTriangular $ Matrix.shapeInt n
       set = Set.fromList ixs
       countSubTriangle (i,j) k =
         length $
         filter (\(si,sj) -> Set.member (i+si,j+sj) set) $
         Shape.indices $ Shape.lowerTriangular $ Matrix.shapeInt k
   in and $ do
         (i,j) <- Shape.indices triShape
         k <- [0..n-i]
         return $ countSubTriangle (i,j) k <= k

wellcrowdedIntSet :: Int -> [(Int,Int)] -> Bool
wellcrowdedIntSet n ixs =
   let triShape = Shape.lowerTriangular $ Matrix.shapeInt n
       set = IntSet.fromList $ map (Shape.offset triShape) ixs
       countSubTriangle (i,j) k =
         length $
         filter (\(si,sj) ->
            flip IntSet.member set $ Shape.offset triShape (i+si,j+sj)) $
         Shape.indices $ Shape.lowerTriangular $ Matrix.shapeInt k
   in and $ do
         (i,j) <- Shape.indices triShape
         k <- [0..n-i]
         return $ countSubTriangle (i,j) k <= k


{-
Check whether the linear independence criterion matches
the subpyramid criterion 'wellcrowded'.
Well, it does not, the smallest counterexample is:

   *
  . .
 . * .
* . . *
-}
counterexamples :: Int -> [[(Int, Int)]]
counterexamples n =
   let check = solvable n
   in filter (\ixs -> check ixs /= wellcrowded n ixs) $
      Combinatorics.tuples n $ Shape.indices $
      Shape.lowerTriangular $ Matrix.shapeInt n


boolMatrix :: Int -> [(Int, Int)] -> Matrix.Lower ShapeInt Float
boolMatrix n ixs =
   Triangular.fromLowerRowMajor $
   Array.fromAssociations 0
      (Shape.lowerTriangular $ Matrix.shapeInt n)
      (map (\ix -> (ix,1::Float)) ixs)


test :: IO ()
test = do
   Triangular.fromLowerRowMajor (pyramid (Vector.autoFromList example0))
      ## "%.0f"

   let xs = example2
    in mapSnd Triangular.fromLowerRowMajor (solve (length xs) (addIndices xs))
          ## "%.0f"

   putStrLn "\nsolvable:"
   let n = 3
    in for_ (solvables n) $ \ixs -> do
          putStrLn ""
          boolMatrix n ixs ## "%.0f"

   putStrLn "\nunsolvable:"
   for_ [0..5] $ \n ->
      for_ (counterexamples n) $ \ixs -> do
         putStrLn ""
         boolMatrix n ixs ## "%.0f"

{-
https://oeis.org/A014068

map (\n -> Comb.binomial (div (n*(n+1)) 2) n) [1..10::Integer]

Possibilities of choosing n numbers from a n-sized pyramid.
-}