sparse 0.5 → 0.6
raw patch · 16 files changed
+1449/−268 lines, 16 filesdep +QuickCheckdep +arraydep +bytestringdep −monadplusdep −natsdep −pointeddep ~basedep ~semigroupsbuild-type:Customsetup-changednew-uploader
Dependencies added: QuickCheck, array, bytestring, containers, contravariant, criterion, deepseq, directory, doctest, filepath, hybrid-vectors, lens, linear, mtl, primitive, simple-reflect, sparse, test-framework, test-framework-quickcheck2, test-framework-th, transformers, vector, vector-algorithms
Dependencies removed: monadplus, nats, pointed
Dependency ranges changed: base, semigroups
Files
- .ghci +1/−0
- .gitignore +16/−0
- .travis.yml +8/−0
- .vim.custom +31/−0
- COPYING +0/−26
- LICENSE +26/−0
- Setup.lhs +54/−3
- benchmarks/mm.hs +49/−0
- sparse.cabal +145/−24
- src/Data/Sparse.hs +0/−215
- src/Sparse/Matrix.hs +410/−0
- src/Sparse/Matrix/Internal/Fusion.hs +122/−0
- src/Sparse/Matrix/Internal/Heap.hs +211/−0
- src/Sparse/Matrix/Internal/Key.hs +253/−0
- tests/doctests.hsc +73/−0
- tests/properties.hs +50/−0
+ .ghci view
@@ -0,0 +1,1 @@+:set -isrc -idist/build/autogen -optP-include -optPdist/build/autogen/cabal_macros.h
+ .gitignore view
@@ -0,0 +1,16 @@+dist+docs+wiki+TAGS+tags+wip+stats+.DS_Store+.*.swp+.*.swo+*.o+*.hi+*~+*#+.cabal-sandbox+cabal.sandbox.config
+ .travis.yml view
@@ -0,0 +1,8 @@+language: haskell+notifications:+ irc:+ channels:+ - "irc.freenode.org#haskell-lens"+ skip_join: true+ template:+ - "\x0313sparse\x03/\x0306%{branch}\x03 \x0314%{commit}\x03 %{build_url} %{message}"
+ .vim.custom view
@@ -0,0 +1,31 @@+" Add the following to your .vimrc to automatically load this on startup++" if filereadable(".vim.custom")+" so .vim.custom+" endif++function StripTrailingWhitespace()+ let myline=line(".")+ let mycolumn = col(".")+ silent %s/ *$//+ call cursor(myline, mycolumn)+endfunction++" enable syntax highlighting+syntax on++" search for the tags file anywhere between here and /+set tags=TAGS;/++" highlight tabs and trailing spaces+set listchars=tab:‗‗,trail:‗+set list++" f2 runs hasktags+map <F2> :exec ":!hasktags -x -c --ignore src"<CR><CR>++" strip trailing whitespace before saving+" au BufWritePre *.hs,*.markdown silent! cal StripTrailingWhitespace()++" rebuild hasktags after saving+au BufWritePost *.hs silent! :exec ":!hasktags -x -c --ignore src"
− COPYING
@@ -1,26 +0,0 @@--Copyright (c) 2013, Hans Höglund-All rights reserved.--Redistribution and use in source and binary forms, with or without-modification, are permitted provided that the following conditions are met:- * Redistributions of source code must retain the above copyright- notice, this list of conditions and the following disclaimer.- * Redistributions in binary form must reproduce the above copyright- notice, this list of conditions and the following disclaimer in the- documentation and/or other materials provided with the distribution.- * Neither the name of the <organization> nor the- names of its contributors may be used to endorse or promote products- derived from this software without specific prior written permission.--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE-DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.-
+ LICENSE view
@@ -0,0 +1,26 @@+Copyright 2013 Edward Kmett++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
Setup.lhs view
@@ -1,4 +1,55 @@-#! /usr/bin/env runhaskell+#!/usr/bin/runhaskell+\begin{code}+{-# OPTIONS_GHC -Wall #-}+module Main (main) where -> import Distribution.Simple-> main = defaultMain+import Data.List ( nub )+import Data.Version ( showVersion )+import Distribution.Package ( PackageName(PackageName), Package, PackageId, InstalledPackageId, packageVersion, packageName )+import Distribution.PackageDescription ( PackageDescription(), TestSuite(..) )+import Distribution.Simple ( defaultMainWithHooks, UserHooks(..), simpleUserHooks )+import Distribution.Simple.Utils ( rewriteFile, createDirectoryIfMissingVerbose, copyFiles )+import Distribution.Simple.BuildPaths ( autogenModulesDir )+import Distribution.Simple.Setup ( BuildFlags(buildVerbosity), Flag(..), fromFlag, HaddockFlags(haddockDistPref))+import Distribution.Simple.LocalBuildInfo ( withLibLBI, withTestLBI, LocalBuildInfo(), ComponentLocalBuildInfo(componentPackageDeps) )+import Distribution.Text ( display )+import Distribution.Verbosity ( Verbosity, normal )+import System.FilePath ( (</>) )++main :: IO ()+main = defaultMainWithHooks simpleUserHooks+ { buildHook = \pkg lbi hooks flags -> do+ generateBuildModule (fromFlag (buildVerbosity flags)) pkg lbi+ buildHook simpleUserHooks pkg lbi hooks flags+ , postHaddock = \args flags pkg lbi -> do+ copyFiles normal (haddockOutputDir flags pkg) []+ postHaddock simpleUserHooks args flags pkg lbi+ }++haddockOutputDir :: Package p => HaddockFlags -> p -> FilePath+haddockOutputDir flags pkg = destDir where+ baseDir = case haddockDistPref flags of+ NoFlag -> "."+ Flag x -> x+ destDir = baseDir </> "doc" </> "html" </> display (packageName pkg)++generateBuildModule :: Verbosity -> PackageDescription -> LocalBuildInfo -> IO ()+generateBuildModule verbosity pkg lbi = do+ let dir = autogenModulesDir lbi+ createDirectoryIfMissingVerbose verbosity True dir+ withLibLBI pkg lbi $ \_ libcfg -> do+ withTestLBI pkg lbi $ \suite suitecfg -> do+ rewriteFile (dir </> "Build_" ++ testName suite ++ ".hs") $ unlines+ [ "module Build_" ++ testName suite ++ " where"+ , "deps :: [String]"+ , "deps = " ++ (show $ formatdeps (testDeps libcfg suitecfg))+ ]+ where+ formatdeps = map (formatone . snd)+ formatone p = case packageName p of+ PackageName n -> n ++ "-" ++ showVersion (packageVersion p)++testDeps :: ComponentLocalBuildInfo -> ComponentLocalBuildInfo -> [(InstalledPackageId, PackageId)]+testDeps xs ys = nub $ componentPackageDeps xs ++ componentPackageDeps ys++\end{code}
+ benchmarks/mm.hs view
@@ -0,0 +1,49 @@+import Control.Applicative+import Control.DeepSeq+import Criterion.Main+import Data.Array.Unboxed as A+import Data.Vector.Generic as G+import Data.Vector.Unboxed as U+import Sparse.Matrix as M+import Sparse.Matrix.Heap as Heap++instance NFData (UArray i e)++main :: IO ()+main = defaultMain+ [ bench "naive I_32" $ nf (\x -> mmul x x) $ array ((0,0),(31,31)) $ [ ((i, j), if i == j then 1 else 0) | i <- [0..31], j <- [0..31] ]+ , bench "I_32 new" $ nf (\x -> x * x) (ident 32 :: Mat U.Vector Int)+ , bench "I_64 new" $ nf (\x -> x * x) (ident 64 :: Mat U.Vector Int)+ , bench "I_128 new" $ nf (\x -> x * x) (ident 128 :: Mat U.Vector Int)+ -- , bench "I_256" $ nf (\x -> x * x) (ident 256 :: Mat U.Vector Int)+ -- , bench "I_512" $ nf (\x -> x * x) (ident 1024 :: Mat U.Vector Int)+ -- , bench "I_1024" $ nf (\x -> x * x) (ident 1024 :: Mat U.Vector Int)+ , bench "naive 32x32" $ nf (\x -> mmul x x) $ listArray ((0,0),(31,31)) $ Prelude.replicate (32*32) 1+ , bench "32x32 Int" $ nf (\x -> x * x) blockInt+ , bench "32x32 ()" $ nf (\x -> multiplyWith const (Heap.streamHeapWith const) x x) blockUnit+ , bench "naive 128x128" $ nf (\x -> mmul x x) $ listArray ((0,0),(127,127)) $ Prelude.replicate (128*128) 1+ , bench "128x128 Int" $ nf (\x -> x * x) blockInt128+ , bench "128x128 ()" $ nf (\x -> multiplyWith const (Heap.streamHeapWith const) x x) blockUnit128+ ]++blockInt :: Mat U.Vector Int+blockInt = M.fromList $ Prelude.zip (Key <$> [0..31] <*> [0..31]) (repeat 1)++blockInt128 :: Mat U.Vector Int+blockInt128 = M.fromList $ Prelude.zip (Key <$> [0..127] <*> [0..127]) (repeat 1)++blockUnit :: Mat U.Vector ()+blockUnit = M.fromList $ Prelude.zip (Key <$> [0..31] <*> [0..31]) (repeat ())++blockUnit128 :: Mat U.Vector ()+blockUnit128 = M.fromList $ Prelude.zip (Key <$> [0..127] <*> [0..127]) (repeat ())++mmul :: UArray (Int,Int) Int -> UArray (Int,Int) Int -> UArray (Int,Int) Int+mmul x y = accumArray (+) 0 ((i0,k0),(i1,k1)) $ do+ i <- range (i0,i1)+ j <- range (max j0 j0',min j1 j1')+ k <- range (k0,k1)+ return ((i,k),x A.!(i,j) * y A.!(j,k))+ where+ ((i0,j0),(i1,j1)) = bounds x+ ((j0',k0),(j1',k1)) = bounds y
sparse.cabal view
@@ -1,32 +1,153 @@+name: sparse+category: Data, Vector+version: 0.6+license: BSD3+cabal-version: >= 1.8+license-file: LICENSE+author: Edward A. Kmett+maintainer: Edward A. Kmett <ekmett@gmail.com>+stability: experimental+homepage: http://github.com/ekmett/sparse+bug-reports: http://github.com/ekmett/sparse/issues+copyright: Copyright (C) 2013 Edward A. Kmett+build-type: Custom+synopsis: A playground of sparse linear algebra primitives using Morton ordering -name: sparse-version: 0.5-cabal-version: >= 1.10-author: Hans Hoglund-maintainer: Hans Hoglund <hans@hanshoglund.se>-license: BSD3-license-file: COPYING-synopsis: Lightweight parsing library based on partial functions.-category: -tested-with: GHC-build-type: Simple+extra-source-files:+ .ghci+ .travis.yml+ .gitignore+ .vim.custom description:- To be written.+ A playground of sparse linear algebra primitives using Morton ordering+ .+ The design of this library is describe in the following articles on FP Complete's School of Haskell.+ .+ 1. <https://www.fpcomplete.com/user/edwardk/revisiting-matrix-multiplication-part-1>+ .+ 2. <https://www.fpcomplete.com/user/edwardk/revisiting-matrix-multiplication-part-2>+ .+ 3. <https://www.fpcomplete.com/user/edwardk/revisiting-matrix-multiplication-part-3>+ .+ 4. <https://www.fpcomplete.com/user/edwardk/revisiting-matrix-multiplication-part-4>+ .+ 5. <https://www.fpcomplete.com/user/edwardk/revisiting-matrix-multiplication-part-5> source-repository head- type: git- location: git://github.com/hanshoglund/sparse.git+ type: git+ location: git://github.com/ekmett/sparse.git +-- Build the properties test if we're building tests+flag test-properties+ default: True+ manual: True++-- You can disable the doctests test suite with -f-test-doctests+flag test-doctests+ default: True+ manual: True++flag optimize+ default: False+ manual: True++flag llvm+ default: False+ manual: True+ library+ build-depends:+ base >= 4 && < 5,+ contravariant >= 0.4.2 && < 1,+ deepseq >= 1.1 && < 1.4,+ hybrid-vectors >= 0.1 && < 1,+ lens >= 3.9 && < 4,+ primitive >= 0.5 && < 0.6,+ transformers >= 0.3 && < 0.4,+ vector >= 0.10 && < 0.11,+ vector-algorithms >= 0.5 && < 0.6++ hs-source-dirs: src++ exposed-modules:+ Sparse.Matrix+ Sparse.Matrix.Internal.Fusion+ Sparse.Matrix.Internal.Heap+ Sparse.Matrix.Internal.Key++ ghc-options: -Wall++ if flag(optimize)+ ghc-options: -O2+ else+ ghc-options: -O0++ if flag(llvm)+ ghc-options: -fllvm++ if impl(ghc<6.13)+ Ghc-Options: -finline-if-enough-args -fno-method-sharing++test-suite properties+ type: exitcode-stdio-1.0+ main-is: properties.hs+ hs-source-dirs: tests+ ghc-options: -w -threaded -rtsopts -with-rtsopts=-N -fsimpl-tick-factor=400+ if !flag(test-properties)+ buildable: False+ else build-depends:- base >= 4 && < 5,- nats,- semigroups,- pointed,- monadplus- - hs-source-dirs: src- default-language: Haskell2010- exposed-modules:- Data.Sparse+ base,+ containers,+ hybrid-vectors,+ lens,+ linear >= 1.2 && < 2,+ QuickCheck >= 2.5,+ sparse,+ test-framework >= 0.6,+ test-framework-quickcheck2 >= 0.3,+ test-framework-th >= 0.2,+ transformers,+ vector++-- Verify the results of the examples+test-suite doctests+ type: exitcode-stdio-1.0+ main-is: doctests.hs+ ghc-options: -Wall -threaded+ hs-source-dirs: tests++ if !flag(test-doctests)+ buildable: False+ else+ build-depends:+ base,+ bytestring,+ containers,+ directory >= 1.0,+ deepseq,+ doctest >= 0.9.1,+ filepath,+ mtl,+ semigroups >= 0.9,+ simple-reflect >= 0.3.1++ if impl(ghc<7.6.1)+ ghc-options: -Werror++-- matrix-matrix multiplication+benchmark mm+ type: exitcode-stdio-1.0+ main-is: mm.hs+ ghc-options: -Wall -O2 -threaded -fdicts-cheap -funbox-strict-fields -fsimpl-tick-factor=400000+ hs-source-dirs: benchmarks+ build-depends:+ array,+ base,+ criterion,+ deepseq,+ sparse,+ vector++
− src/Data/Sparse.hs
@@ -1,215 +0,0 @@--{-# LANGUAGE GeneralizedNewtypeDeriving,- OverloadedStrings,- TypeOperators,- DeriveFunctor,- DeriveFoldable,- FlexibleInstances- #-}------------------------------------------------------------------------------------------- |--- Copyright : (c) Hans Hoglund 2012------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (GNTD, DeriveFunctor, OverloadedStrings)------ Lightweight parsing library based on partial functions.-------------------------------------------------------------------------------------------module Data.Sparse (- -- * Sparse- SparseT,- Sparse,- asSparse,-- -- * Running- runSparseT,- runSparseT',- runSparse,- runSparse',-- -- * Primitives- headP,- splitP,-- -- * Basic parsers- char,- charIs,- string,- stringIs,-- -- * Combinators- optionally,- optionallyMaybe,- Data.Sparse.optional,- between,- skipMany1,- skipMany,- many1,- sepBy,- sepBy1,- sepEndBy1,- sepEndBy,- endBy1,- endBy,- count-) where--import Data.String-import Data.Semigroup-import Data.Foldable(Foldable)-import Control.Applicative-import Control.Monad.Plus---- TODO-instance Semigroup (Partial a b) where (<>) = mplus-------newtype a ?-> b = PartialP { getPartialP :: a -> Maybe (a, b) }--instance Functor ((?->) r) where- fmap f (PartialP g) = PartialP (fmap (fmap f) . g)--instance Monad ((?->) r) where- return x = PartialP (\a -> Just (a, x))- PartialP f >>= k = PartialP $ \r -> (f r >>= \(r1, x) -> getPartialP (k x) r1)--instance MonadPlus ((?->) r) where- mzero = PartialP (const Nothing)- PartialP f `mplus` PartialP g = PartialP $ \x -> f x `mplus` g x--instance Applicative ((?->) r) where- pure = return- (<*>) = ap--instance Alternative ((?->) r) where- empty = mzero- (<|>) = mplus--instance Semigroup ((?->) a b) where- (<>) = mplus--instance Monoid ((?->) a b) where- mempty = mzero- mappend = mplus---------------newtype SparseT a b = SparseT { getSparseT :: a ?-> b }- deriving (Semigroup, Monoid, Functor, Applicative, Alternative, Monad, MonadPlus)--instance IsString (SparseT String String) where- fromString = string--type Sparse = SparseT String--runSparseT :: SparseT a b -> a -> Maybe b-runSparseT = fmap (fmap snd) . runSparseT'--runSparseT' :: SparseT a b -> a -> Maybe (a, b)-runSparseT' = getPartialP . getSparseT--runSparse :: Sparse a -> String -> Maybe a-runSparse = runSparseT--runSparse' :: Sparse a -> String -> Maybe (String, a)-runSparse' = runSparseT'---------------- | Consumes one input element.------ Fails if the predicate fails, or if there is no more input.----headP = SparseT . PartialP . headP'---- | Consume one or more input elements.------ The given function receives the /entire/ remaining input, and must return--- the number of consumed elements.------ Fails if the predicate return 0 or less, or if there is no more input.----splitP = SparseT . PartialP . splitP'--headP' :: (a -> Bool) -> [a] -> Maybe ([a], a)-headP' p [] = Nothing-headP' p (x:xs) = if not (p x) then Nothing else Just (xs, x)--splitP' :: ([a] -> Int) -> [a] -> Maybe ([a], [a])-splitP' p [] = Nothing-splitP' p ys = let n = p ys in if n < 1 then Nothing else Just (drop n ys, take n ys)--------------char :: Char -> Sparse Char-char c = charIs (== c)--charIs :: (Char -> Bool) -> Sparse Char-charIs p = headP p--string :: String -> Sparse String-string s = stringIs (length s) (== s)--stringIs :: Int -> (String -> Bool) -> Sparse String-stringIs n p = splitP (\xs -> if p (take n xs) then n else 0)--asSparse = id-asSparse :: Sparse a -> Sparse a--------------optionally x p = p <|> return x-optionallyMaybe p = optionally Nothing (liftM Just p)-optional p = do{ p; return ()} <|> return ()-between open close p- = do{ open; x <- p; close; return x }-skipMany1 p = do{ p; skipMany p }-skipMany p = scan- where- scan = do{ p; scan } <|> return ()-many1 p = do{ x <- p; xs <- many p; return (x:xs) }-sepBy p sep = sepBy1 p sep <|> return []-sepBy1 p sep = do{ x <- p- ; xs <- many (sep >> p)- ; return (x:xs)- }-sepEndBy1 p sep = do{ x <- p- ; do{ sep- ; xs <- sepEndBy p sep- ; return (x:xs)- }- <|> return [x]- }-sepEndBy p sep = sepEndBy1 p sep <|> return []-endBy1 p sep = many1 (do{ x <- p; sep; return x })-endBy p sep = many (do{ x <- p; sep; return x })-count n p | n <= 0 = return []- | otherwise = sequence (replicate n p)----------------- test :: Sparse [String]-test = asSparse $ string "hans" >> many1 (string ";")-----single x = [x]-list z f xs = case xs of- [] -> z- ys -> f ys--[a,b,c,d,e,f,g,x,y,z,m,n,o,p,q,r] = undefined
+ src/Sparse/Matrix.hs view
@@ -0,0 +1,410 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE UndecidableInstances #-}++-----------------------------------------------------------------------------+-- |+-- Copyright : (C) 2013 Edward Kmett+-- License : BSD-style (see the file LICENSE)+-- Maintainer : Edward Kmett <ekmett@gmail.com>+-- Stability : experimental+-- Portability : non-portable+--+-- Sparse Matrices in Morton order+--+----------------------------------------------------------------------------++module Sparse.Matrix+ (+ -- * Sparse Matrices+ Mat(..)+ -- * Keys+ , Key(..)+ -- * Construction+ , Sparse.Matrix.fromList+ , Sparse.Matrix.singleton+ , transpose+ , ident+ , empty+ -- * Consumption+ , size+ , null+ -- * Distinguishable Zero+ , Eq0(..)+ -- * Customization+ , addWith+ , multiplyWith+ -- * Lenses+ , _Mat, keys, values+ ) where++import Control.Applicative hiding (empty)+import Control.Arrow+import Control.DeepSeq+import Control.Lens+import Data.Bits+import Data.Complex+import Data.Foldable+import Data.Function (on)+import qualified Data.Vector as V+import qualified Data.Vector.Algorithms.Insertion as Sort+import qualified Data.Vector.Generic as G+import qualified Data.Vector.Hybrid as H+import qualified Data.Vector.Hybrid.Internal as H+import qualified Data.Vector.Unboxed as U+import Data.Vector.Fusion.Stream (Stream, sized)+import Data.Vector.Fusion.Stream.Size+import Data.Word+import Prelude hiding (head, last, null)+import Sparse.Matrix.Internal.Fusion as Fusion+import Sparse.Matrix.Internal.Key+import Sparse.Matrix.Internal.Heap as Heap hiding (head)+import Text.Read++-- import Debug.Trace+-- import Numeric.Lens++-- * Distinguishable Zero++class Num a => Eq0 a where+ -- | Return whether or not the element is 0.+ --+ -- It may be okay to never return 'True', but you won't be+ -- able to thin spurious zeroes introduced into your matrix.+ --+ isZero :: a -> Bool+#ifndef HLINT+ default isZero :: (Num a, Eq a) => a -> Bool+ isZero = (0 ==)+ {-# INLINE isZero #-}+#endif++ -- | Remove results that are equal to zero from a simpler function.+ --+ -- When used with @addWith@ or @multiplyWith@'s additive argument+ -- this can help retain the sparsity of the matrix.+ nonZero :: (x -> y -> a) -> x -> y -> Maybe a+ nonZero f a b = case f a b of+ c | isZero c -> Nothing+ | otherwise -> Just c+ {-# INLINE nonZero #-}++ -- |+ -- Add two matrices. By default this assumes 'isZero' can+ -- possibly return 'True' after an addition. For some+ -- ring-like structures, this doesn't hold. There you can+ -- use:+ --+ -- @+ -- 'addMats' = 'addWith' ('+')+ -- @+ --+ -- By default this will use+ --+ -- @+ -- 'addMats' = 'addWith0' '$' 'nonZero' ('+')+ -- @+ addMats :: G.Vector v a => Mat v a -> Mat v a -> Mat v a+ addMats = addWith0 $ nonZero (+)+ {-# INLINE addMats #-}++ -- | Convert from a 'Heap' to a 'Stream'.+ --+ -- If addition of non-zero valus in your ring-like structure+ -- cannot yield zero, then you can use+ --+ -- @+ -- 'addHeap' = 'Heap.streamHeapWith' ('+')+ -- @+ --+ -- instead of the default definition:+ --+ -- @+ -- 'addHeap' = 'Heap.streamHeapWith0' '$' 'nonZero' ('+')+ -- @+ addHeap :: Maybe (Heap a) -> Stream (Key, a)+ addHeap = Heap.streamHeapWith0 $ nonZero (+)++instance Eq0 Int+instance Eq0 Word+instance Eq0 Integer+instance Eq0 Float+instance Eq0 Double+instance (RealFloat a, Eq0 a) => Eq0 (Complex a) where+ isZero (a :+ b) = isZero a && isZero b+ {-# INLINE isZero #-}++-- * Sparse Matrices++-- invariant: all vectors are the same length+data Mat v a = Mat {-# UNPACK #-} !Int !(U.Vector Word) !(U.Vector Word) !(v a)+ deriving (Eq,Ord)++instance (G.Vector v a, Show a) => Show (Mat v a) where+ showsPrec d m = G.showsPrec d (m^._Mat)++instance (G.Vector v a, Read a) => Read (Mat v a) where+ readPrec = (_Mat #) <$> G.readPrec++instance NFData (v a) => NFData (Mat v a) where+ rnf (Mat _ xs ys vs) = rnf xs `seq` rnf ys `seq` rnf vs `seq` ()++-- | bundle up the matrix in a form suitable for vector-algorithms+_Mat :: Iso (Mat u a) (Mat v b) (H.Vector U.Vector u (Key, a)) (H.Vector U.Vector v (Key, b))+_Mat = iso (\(Mat n xs ys vs) -> H.V (V_Key n xs ys) vs)+ (\(H.V (V_Key n xs ys) vs) -> Mat n xs ys vs)+{-# INLINE _Mat #-}++-- | Access the keys of a matrix+keys :: Lens' (Mat v a) (U.Vector Key)+keys f (Mat n xs ys vs) = f (V_Key n xs ys) <&> \ (V_Key n' xs' ys') -> Mat n' xs' ys' vs+{-# INLINE keys #-}++-- | Access the keys of a matrix+values :: Lens (Mat u a) (Mat v b) (u a) (v b)+values f (Mat n xs ys vs) = Mat n xs ys <$> f vs+{-# INLINE values #-}++instance Functor v => Functor (Mat v) where+ fmap = over (values.mapped)+ {-# INLINE fmap #-}++instance Foldable v => Foldable (Mat v) where+ foldMap = foldMapOf (values.folded)+ {-# INLINE foldMap #-}++instance Traversable v => Traversable (Mat v) where+ traverse = values.traverse+ {-# INLINE traverse #-}++type instance IxValue (Mat v a) = a+type instance Index (Mat v a) = Key++-- traverse a Vector+eachV :: (Applicative f, G.Vector v a, G.Vector v b) => (a -> f b) -> v a -> f (v b)+eachV f v = G.fromListN (G.length v) <$> traverse f (G.toList v)++instance (Applicative f, G.Vector v a, G.Vector v b) => Each f (Mat v a) (Mat v b) a b where+ each f = _Mat $ eachV $ \(k,v) -> (,) k <$> indexed f k v+ {-# INLINE each #-}++instance (Functor f, Contravariant f, G.Vector v a) => Contains f (Mat v a) where+ contains = containsIx++instance (Applicative f, G.Vector v a) => Ixed f (Mat v a) where+ ix ij@(Key i j) f m@(Mat n xs ys vs)+ | Just i' <- xs U.!? l, i == i'+ , Just j' <- ys U.!? l, j == j' = indexed f ij (vs G.! l) <&> \v -> Mat n xs ys (vs G.// [(l,v)])+ | otherwise = pure m+ where l = search (\k -> Key (xs U.! k) (ys U.! k) >= ij) 0 n+ {-# INLINE ix #-}++instance (G.Vector v a, Num a, Eq0 a) => Eq0 (Mat v a) where+ isZero (Mat n _ _ _) = n == 0+ {-# INLINE isZero #-}++-- * Construction++-- | Build a sparse matrix.+fromList :: G.Vector v a => [(Key, a)] -> Mat v a+fromList xs = _Mat # H.modify (Sort.sortBy (compare `on` fst)) (H.fromList xs)+{-# INLINABLE fromList #-}++-- | Transpose a matrix+transpose :: G.Vector v a => Mat v a -> Mat v a+transpose xs = xs & _Mat %~ H.modify (Sort.sortBy (compare `on` fst)) . H.map (first swap)+{-# INLINE transpose #-}++-- | @singleton@ makes a matrix with a singleton value at a given location+singleton :: G.Vector v a => Key -> a -> Mat v a+singleton k v = _Mat # H.singleton (k,v)+{-# INLINE singleton #-}++-- | @ident n@ makes an @n@ x @n@ identity matrix+--+-- >>> ident 4 :: Mat U.Vector Int+-- fromList [(Key 0 0,1),(Key 1 1,1),(Key 2 2,1),(Key 3 3,1)]+ident :: (G.Vector v a, Num a) => Int -> Mat v a+ident w = Mat w (U.generate w fromIntegral) (U.generate w fromIntegral) (G.replicate w 1)+{-# INLINE ident #-}++-- | The empty matrix+--+-- >>> empty :: Mat U.Vector Int+-- fromList []+empty :: G.Vector v a => Mat v a+empty = Mat 0 U.empty U.empty G.empty+{-# INLINE empty #-}++-- * Consumption++-- | Count the number of non-zero entries in the matrix+--+-- >>> size (ident 4 :: Mat U.Vector Int)+-- 4+size :: Mat v a -> Int+size (Mat n _ _ _) = n+{-# INLINE size #-}++-- |+-- >>> null (empty :: Mat U.Vector Int)+-- True+null :: Mat v a -> Bool+null (Mat n _ _ _) = n == 0+{-# INLINE null #-}++instance (G.Vector v a, Num a, Eq0 a) => Num (Mat v a) where+ {-# SPECIALIZE instance (Num a, Eq0 a) => Num (Mat V.Vector a) #-}+ {-# SPECIALIZE instance Num (Mat U.Vector Int) #-}+ {-# SPECIALIZE instance Num (Mat U.Vector Double) #-}+ {-# SPECIALIZE instance Num (Mat U.Vector (Complex Double)) #-}+ abs = over each abs+ {-# INLINE abs #-}+ signum = over each signum+ {-# INLINE signum #-}+ negate = over each negate+ {-# INLINE negate #-}+ fromInteger 0 = empty+ fromInteger _ = error "Mat: fromInteger n"+ {-# INLINE fromInteger #-}+ (+) = addMats+ {-# INLINE (+) #-}+ (-) = addWith0 $ nonZero (-)+ {-# INLINE (-) #-}+ (*) = multiplyWith (*) addHeap+ {-# INLINEABLE (*) #-}++-- * Utilities++-- | assuming @l <= h@. Returns @h@ if the predicate is never @True@ over @[l..h)@+search :: (Int -> Bool) -> Int -> Int -> Int+search p = go where+ go l h+ | l == h = l+ | p m = go l m+ | otherwise = go (m+1) h+ where m = l + div (h-l) 2+{-# INLINE search #-}++split1 :: G.Vector v a => Word -> Word -> Mat v a -> (Mat v a, Mat v a)+split1 ai bi (Mat n xs ys vs) = (m0,m1)+ where+ !aibi = xor ai bi+ !k = search (\l -> xor (xs U.! l) bi `lts` aibi) 0 n+ (xs0,xs1) = U.splitAt k xs+ (ys0,ys1) = U.splitAt k ys+ (vs0,vs1) = G.splitAt k vs+ !m0 = Mat k xs0 ys0 vs0+ !m1 = Mat (n-k) xs1 ys1 vs1+{-# INLINE split1 #-}++split2 :: G.Vector v a => Word -> Word -> Mat v a -> (Mat v a, Mat v a)+split2 aj bj (Mat n xs ys vs) = (m0,m1)+ where+ !ajbj = xor aj bj+ !k = search (\l -> xor (ys U.! l) bj `lts` ajbj) 0 n+ (xs0,xs1) = U.splitAt k xs+ (ys0,ys1) = U.splitAt k ys+ (vs0,vs1) = G.splitAt k vs+ !m0 = Mat k xs0 ys0 vs0+ !m1 = Mat (n-k) xs1 ys1 vs1+{-# INLINE split2 #-}++-- | Merge two matrices where the indices coincide into a new matrix. This provides for generalized+-- addition, but where the summation of two non-zero entries is necessarily non-zero.+addWith :: G.Vector v a => (a -> a -> a) -> Mat v a -> Mat v a -> Mat v a+addWith f xs ys = _Mat # G.unstream (mergeStreamsWith f (G.stream (xs^._Mat)) (G.stream (ys^._Mat)))+{-# INLINE addWith #-}++-- | Merge two matrices where the indices coincide into a new matrix. This provides for generalized+-- addition. Return 'Nothing' for zero.+addWith0 :: G.Vector v a => (a -> a -> Maybe a) -> Mat v a -> Mat v a -> Mat v a+addWith0 f xs ys = _Mat # G.unstream (mergeStreamsWith0 f (G.stream (xs^._Mat)) (G.stream (ys^._Mat)))+{-# INLINE addWith0 #-}++-- | Multiply two matrices using the specified multiplication and addition operation.+multiplyWith :: G.Vector v a => (a -> a -> a) -> (Maybe (Heap a) -> Stream (Key, a)) -> Mat v a -> Mat v a -> Mat v a+{-# INLINEABLE multiplyWith #-}+multiplyWith times make x0 y0 = case compare (size x0) 1 of+ LT -> empty+ EQ | size y0 == 1 -> _Mat # (G.unstream $ hint $ make $ go11 (lo x0) (head x0) (lo y0) (head y0))+ | otherwise -> _Mat # (G.unstream $ hint $ make $ go12 (lo x0) (head x0) (lo y0) y0 (hi y0))+ GT -> case compare (size y0) 1 of+ LT -> empty+ EQ -> _Mat # (G.unstream $ hint $ make $ go21 (lo x0) x0 (hi x0) (lo y0) (head y0))+ GT -> _Mat # (G.unstream $ hint $ make $ go22 (lo x0) x0 (hi x0) (lo y0) y0 (hi y0))+ where+ hint x = sized x $ Max (size x0 * size y0)+ go11 (Key i j) a (Key j' k) b+ | j == j' = Just $ Heap.singleton (Key i k) (times a b)+ | otherwise = Nothing++ -- internal cases in go22+ go22L0 xa x ya y yb+ | size x == 1 = go12 xa (head x) ya y yb+ | otherwise = go22 xa x (hi x) ya y yb+ {-# INLINE go22L0 #-}++ go22L1 x xb ya y yb+ | size x == 1 = go12 xb (head x) ya y yb+ | otherwise = go22 (lo x) x xb ya y yb+ {-# INLINE go22L1 #-}++ go22R0 xa x xb ya y+ | size y == 1 = go21 xa x xb ya (head y)+ | otherwise = go22 xa x xb ya y (hi y)+ {-# INLINE go22R0 #-}++ go22R1 xa x xb y yb+ | size y == 1 = go21 xa x xb yb (head y)+ | otherwise = go22 xa x xb (lo y) y yb+ {-# INLINE go22R1 #-}++ -- x and y have at least 2 non-zero elements each+ go22 xa@(Key xai xaj) x xb@(Key xbi xbj) ya@(Key yaj yak) y yb@(Key ybj ybk)+ | gts (xor xaj yaj) (xiyj .|. ykxj) = Nothing+ | ges xiyj ykxj+ = if ges xi yj then case split1 xai xbi x of (m0,m1) -> go22L0 xa m0 ya y yb `mfby` go22L1 m1 xb ya y yb -- we can split on i, fby+ else case split1 yaj ybj y of (m0,m1) -> go22R0 xa x xb ya m0 `madd` go22R1 xa x xb m1 yb -- we split on j, mix+ | ges yk xj = case split2 yak ybk y of (m0,m1) -> go22R0 xa x xb ya m0 `mfby` go22R1 xa x xb m1 yb -- we can split on k, fby+ | otherwise = case split2 xaj xbj x of (m0,m1) -> go22L0 xa m0 ya y yb `madd` go22L1 m1 xb ya y yb -- we split on j, mix+ where+ xi = xor xai xbi+ xj = xor xaj xbj+ yj = xor yaj ybj+ yk = xor yak ybk+ xiyj = xi .|. yj+ ykxj = yk .|. xj++ go21 _ mx _ yb b = Heap.timesSingleton times (G.stream (mx^._Mat)) yb b -- linear scan. use tree and fast rejects?+ go12 xa a _ my _ = Heap.singletonTimes times xa a (G.stream (my^._Mat))++ madd Nothing xs = xs+ madd xs Nothing = xs+ madd (Just x) (Just y) = Just (mix x y)+ {-# INLINE madd #-}++ mfby Nothing xs = xs+ mfby xs Nothing = xs+ mfby (Just x) (Just y) = Just (fby x y)+ {-# INLINE mfby #-}++ lo (Mat _ xs ys _) = Key (U.head xs) (U.head ys)+ {-# INLINE lo #-}++ hi (Mat _ xs ys _) = Key (U.last xs) (U.last ys)+ {-# INLINE hi #-}++ head :: G.Vector v a => Mat v a -> a+ head (Mat _ _ _ vs) = G.head vs+ {-# INLINE head #-}
+ src/Sparse/Matrix/Internal/Fusion.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE BangPatterns #-}+-----------------------------------------------------------------------------+-- |+-- Copyright : (C) 2013 Edward Kmett+-- License : BSD-style (see the file LICENSE)+-- Maintainer : Edward Kmett <ekmett@gmail.com>+-- Stability : experimental+-- Portability : non-portable+--+-- Matrix stream fusion internals+--+-----------------------------------------------------------------------------+module Sparse.Matrix.Internal.Fusion+ ( mergeStreamsWith, mergeStreamsWith0+ ) where++import Data.Vector.Fusion.Stream.Monadic (Step(..), Stream(..))+import Data.Vector.Fusion.Stream.Size+import Sparse.Matrix.Internal.Key++-- | The state for 'Stream' fusion that is used by 'mergeStreamsWith'.+--+-- This form permits cancellative addition.+data MergeState sa sb i a+ = MergeL sa sb i a+ | MergeR sa sb i a+ | MergeLeftEnded sb+ | MergeRightEnded sa+ | MergeStart sa sb++-- | This is the internal stream fusion combinator used to merge streams for addition.+--+-- This form permits cancellative addition.+mergeStreamsWith0 :: Monad m => (a -> a -> Maybe a) -> Stream m (Key, a) -> Stream m (Key, a) -> Stream m (Key, a)+mergeStreamsWith0 f (Stream stepa sa0 na) (Stream stepb sb0 nb)+ = Stream step (MergeStart sa0 sb0) (toMax na + toMax nb) where+ step (MergeStart sa sb) = do+ r <- stepa sa+ return $ case r of+ Yield (i, a) sa' -> Skip (MergeL sa' sb i a)+ Skip sa' -> Skip (MergeStart sa' sb)+ Done -> Skip (MergeLeftEnded sb)+ step (MergeL sa sb i a) = do+ r <- stepb sb+ return $ case r of+ Yield (j, b) sb' -> case compare i j of+ LT -> Yield (i, a) (MergeR sa sb' j b)+ EQ -> case f a b of+ Just c -> Yield (i, c) (MergeStart sa sb')+ Nothing -> Skip (MergeStart sa sb')+ GT -> Yield (j, b) (MergeL sa sb' i a)+ Skip sb' -> Skip (MergeL sa sb' i a)+ Done -> Yield (i, a) (MergeRightEnded sa)+ step (MergeR sa sb j b) = do+ r <- stepa sa+ return $ case r of+ Yield (i, a) sa' -> case compare i j of+ LT -> Yield (i, a) (MergeR sa' sb j b)+ EQ -> case f a b of+ Just c -> Yield (i, c) (MergeStart sa' sb)+ Nothing -> Skip (MergeStart sa' sb)+ GT -> Yield (j, b) (MergeL sa' sb i a)+ Skip sa' -> Skip (MergeR sa' sb j b)+ Done -> Yield (j, b) (MergeLeftEnded sb)+ step (MergeLeftEnded sb) = do+ r <- stepb sb+ return $ case r of+ Yield (j, b) sb' -> Yield (j, b) (MergeLeftEnded sb')+ Skip sb' -> Skip (MergeLeftEnded sb')+ Done -> Done+ step (MergeRightEnded sa) = do+ r <- stepa sa+ return $ case r of+ Yield (i, a) sa' -> Yield (i, a) (MergeRightEnded sa')+ Skip sa' -> Skip (MergeRightEnded sa')+ Done -> Done+ {-# INLINE [0] step #-}+{-# INLINE [1] mergeStreamsWith0 #-}+++-- | This is the internal stream fusion combinator used to merge streams for addition.+mergeStreamsWith :: Monad m => (a -> a -> a) -> Stream m (Key, a) -> Stream m (Key, a) -> Stream m (Key, a)+mergeStreamsWith f (Stream stepa sa0 na) (Stream stepb sb0 nb)+ = Stream step (MergeStart sa0 sb0) (toMax na + toMax nb) where+ step (MergeStart sa sb) = do+ r <- stepa sa+ return $ case r of+ Yield (i, a) sa' -> Skip (MergeL sa' sb i a)+ Skip sa' -> Skip (MergeStart sa' sb)+ Done -> Skip (MergeLeftEnded sb)+ step (MergeL sa sb i a) = do+ r <- stepb sb+ return $ case r of+ Yield (j, b) sb' -> case compare i j of+ LT -> Yield (i, a) (MergeR sa sb' j b)+ EQ -> Yield (i, f a b) (MergeStart sa sb')+ GT -> Yield (j, b) (MergeL sa sb' i a)+ Skip sb' -> Skip (MergeL sa sb' i a)+ Done -> Yield (i, a) (MergeRightEnded sa)+ step (MergeR sa sb j b) = do+ r <- stepa sa+ return $ case r of+ Yield (i, a) sa' -> case compare i j of+ LT -> Yield (i, a) (MergeR sa' sb j b)+ EQ -> Yield (i, f a b) (MergeStart sa' sb)+ GT -> Yield (j, b) (MergeL sa' sb i a)+ Skip sa' -> Skip (MergeR sa' sb j b)+ Done -> Yield (j, b) (MergeLeftEnded sb)+ step (MergeLeftEnded sb) = do+ r <- stepb sb+ return $ case r of+ Yield (j, b) sb' -> Yield (j, b) (MergeLeftEnded sb')+ Skip sb' -> Skip (MergeLeftEnded sb')+ Done -> Done+ step (MergeRightEnded sa) = do+ r <- stepa sa+ return $ case r of+ Yield (i, a) sa' -> Yield (i, a) (MergeRightEnded sa')+ Skip sa' -> Skip (MergeRightEnded sa')+ Done -> Done+ {-# INLINE [0] step #-}+{-# INLINE [1] mergeStreamsWith #-}
+ src/Sparse/Matrix/Internal/Heap.hs view
@@ -0,0 +1,211 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+-----------------------------------------------------------------------------+-- |+-- Copyright : (C) 2013 Edward Kmett+-- License : BSD-style (see the file LICENSE)+-- Maintainer : Edward Kmett <ekmett@gmail.com>+-- Stability : experimental+-- Portability : non-portable+--+-- Bootstrapped catenable non-empty pairing heaps as described in+--+-- <https://www.fpcomplete.com/user/edwardk/revisiting-matrix-multiplication-part-5>+-----------------------------------------------------------------------------+module Sparse.Matrix.Internal.Heap+ ( Heap(..)+ , fby+ , mix+ , singleton+ , head+ , tail+ , fromList+ , fromAscList+ , streamHeapWith+ , streamHeapWith0+ , timesSingleton+ , singletonTimes+ ) where++import Control.Applicative+import Control.Lens+import Data.Foldable+import Data.Monoid+import Data.Vector.Fusion.Stream.Monadic hiding (singleton, fromList, head, tail)+import Data.Vector.Fusion.Stream.Size+import Data.Vector.Fusion.Util+import Sparse.Matrix.Internal.Key+import Prelude hiding (head, tail)++-- | Bootstrapped _catenable_ non-empty pairing heaps+data Heap a = Heap {-# UNPACK #-} !Key a [Heap a] [Heap a] [Heap a]+ deriving (Show,Read)++-- | Append two heaps where we know every key in the first occurs before every key in the second+--+-- >>> head $ singleton (Key 1 1) 1 `fby` singleton (Key 2 2) 2+-- (Key 1 1,1)+fby :: Heap a -> Heap a -> Heap a+fby (Heap i a as ls rs) r = Heap i a as ls (r:rs)++-- | Interleave two heaps making a new 'Heap'+--+-- >>> head $ singleton (Key 1 1) 1 `mix` singleton (Key 2 2) 2+-- (Key 1 1,1)+mix :: Heap a -> Heap a -> Heap a+mix x@(Heap i a as al ar) y@(Heap j b bs bl br)+ | i <= j = Heap i a (y:pops as al ar) [] []+ | otherwise = Heap j b (x:pops bs bl br) [] []++-- |+-- >>> head $ singleton (Key 1 1) 1+-- (Key 1 1,1)+head :: Heap a -> (Key, a)+head (Heap i a _ _ _) = (i, a)++-- |+-- >>> tail $ singleton (Key 1 1) 1+-- Nothing+tail :: Heap a -> Maybe (Heap a)+tail (Heap _ _ xs fs rs) = pop xs fs rs++-- |+-- >>> singleton (Key 1 1) 1+-- Heap (Key 1 1) 1 [] [] []+singleton :: Key -> a -> Heap a+singleton k v = Heap k v [] [] []++-- | Build a 'Heap' from a jumbled up list of elements.+fromList :: [(Key,a)] -> Heap a+fromList ((k0,v0):xs) = Prelude.foldr (\(k,v) r -> mix (singleton k v) r) (singleton k0 v0) xs+fromList [] = error "empty Heap"++-- | Build a 'Heap' from an list of elements that must be in strictly ascending Morton order.+fromAscList :: [(Key,a)] -> Heap a+fromAscList ((k0,v0):xs) = Prelude.foldr (\(k,v) r -> fby (singleton k v) r) (singleton k0 v0) xs+fromAscList [] = error "empty Heap"++-- * Internals++fbys :: Heap a -> [Heap a] -> [Heap a] -> Heap a+fbys (Heap i a as [] []) ls' rs' = Heap i a as ls' rs'+fbys (Heap i a as ls []) ls' rs' = Heap i a as ls $ rs' <> reverse ls'+fbys (Heap i a as ls rs) ls' rs' = Heap i a as ls $ rs' <> reverse ls' <> rs++pops :: [Heap a] -> [Heap a] -> [Heap a] -> [Heap a]+pops xs [] [] = xs+pops (x:xs) ls rs = [fbys (Prelude.foldl mix x xs) ls rs]+pops [] (l:ls) rs = [fbys l ls rs]+pops [] [] rs = case reverse rs of+ f:fs -> [fbys f fs []]+ _ -> [] -- caught above by the 'go as [] []' case++pop :: [Heap a] -> [Heap a] -> [Heap a] -> Maybe (Heap a)+pop (x:xs) ls rs = Just $ fbys (Prelude.foldl mix x xs) ls rs+pop [] (l:ls) rs = Just $ fbys l ls rs+pop [] [] rs = case reverse rs of+ f:fs -> Just (fbys f fs [])+ [] -> Nothing++-- * Instances++instance Functor Heap where+ fmap f (Heap k a xs ls rs) = Heap k (f a) (fmap f <$> xs) (fmap f <$> ls) (fmap f <$> rs)++instance FunctorWithIndex Key Heap where+ imap f (Heap k a xs ls rs) = Heap k (f k a) (imap f <$> xs) (imap f <$> ls) (imap f <$> rs)++instance Foldable Heap where+ foldMap f = go where+ go (Heap _ a xs ls rs) = case pop xs ls rs of+ Nothing -> f a+ Just h -> f a `mappend` go h+ {-# INLINE foldMap #-}++instance FoldableWithIndex Key Heap where+ ifoldMap f = go where+ go (Heap i a xs ls rs) = case pop xs ls rs of+ Nothing -> f i a+ Just h -> f i a `mappend` go h+ {-# INLINE ifoldMap #-}++instance Traversable Heap where+ traverse f xs = fromAscList <$> traverse (traverse f) (itoList xs)+ {-# INLINE traverse #-}++instance TraversableWithIndex Key Heap where+ itraverse f xs = fromAscList <$> traverse (\(k,v) -> (,) k <$> f k v) (itoList xs)+ {-# INLINE itraverse #-}++data HeapState a+ = Start !(Heap a)+ | Ready {-# UNPACK #-} !Key a !(Heap a)+ | Final {-# UNPACK #-} !Key a+ | Finished++-- | Convert a 'Heap' into a 'Stream' folding together values with identical keys using the supplied+-- addition operator.+streamHeapWith :: Monad m => (a -> a -> a) -> Maybe (Heap a) -> Stream m (Key, a)+streamHeapWith f h0 = Stream step (maybe Finished Start h0) Unknown where+ step (Start (Heap i a xs ls rs)) = return $ Skip $ maybe (Final i a) (Ready i a) $ pop xs ls rs+ step (Ready i a (Heap j b xs ls rs)) = return $ case compare i j of+ LT -> Yield (i, a) $ maybe (Final j b) (Ready j b) $ pop xs ls rs+ EQ | c <- f a b -> Skip $ maybe (Final i c) (Ready i c) $ pop xs ls rs+ GT -> Yield (j, b) $ maybe (Final i a) (Ready i a) $ pop xs ls rs+ step (Final i a) = return $ Yield (i,a) Finished+ step Finished = return Done+ {-# INLINE [1] step #-}+{-# INLINE [0] streamHeapWith #-}++-- | Convert a 'Heap' into a 'Stream' folding together values with identical keys using the supplied+-- addition operator that is allowed to return a sparse 0, by returning 'Nothing'.+streamHeapWith0 :: Monad m => (a -> a -> Maybe a) -> Maybe (Heap a) -> Stream m (Key, a)+streamHeapWith0 f h0 = Stream step (maybe Finished Start h0) Unknown where+ step (Start (Heap i a xs ls rs)) = return $ Skip $ maybe (Final i a) (Ready i a) $ pop xs ls rs+ step (Ready i a (Heap j b xs ls rs)) = return $ case compare i j of+ LT -> Yield (i, a) $ maybe (Final j b) (Ready j b) $ pop xs ls rs+ EQ -> case f a b of+ Nothing -> Skip $ maybe Finished Start $ pop xs ls rs+ Just c -> Skip $ maybe (Final i c) (Ready i c) $ pop xs ls rs+ GT -> Yield (j, b) $ maybe (Final i a) (Ready i a) $ pop xs ls rs+ step (Final i a) = return $ Yield (i,a) Finished+ step Finished = return Done+ {-# INLINE [1] step #-}+{-# INLINE [0] streamHeapWith0 #-}++-- | This is an internal 'Heap' fusion combinator used to multiply on the right by a singleton 'Key'/value pair.+timesSingleton :: (a -> b -> c) -> Stream Id (Key, a) -> Key -> b -> Maybe (Heap c)+timesSingleton f (Stream stepa sa0 _) (Key j k) b = start sa0 where+ start sa = case unId (stepa sa) of+ Yield (Key i j', a) sa'+ | j == j' -> Just $ run (singleton (Key i k) (f a b)) sa'+ | otherwise -> start sa'+ Skip sa' -> start sa'+ Done -> Nothing+ run h sa = case unId (stepa sa) of+ Yield (Key i j', a) sa'+ | j == j' -> run (h `mix` singleton (Key i k) (f a b)) sa'+ | otherwise -> run h sa'+ Skip sa' -> run h sa'+ Done -> h+{-# INLINE timesSingleton #-}++-- | This is an internal 'Heap' fusion combinator used to multiply on the right by a singleton 'Key'/value pair.+singletonTimes :: (a -> b -> c) -> Key -> a -> Stream Id (Key, b) -> Maybe (Heap c)+singletonTimes f (Key i j) a (Stream stepb sb0 _) = start sb0 where+ start sb = case unId (stepb sb) of+ Yield (Key j' k, b) sb'+ | j == j' -> Just $ run (singleton (Key i k) (f a b)) sb'+ | otherwise -> start sb'+ Skip sb' -> start sb'+ Done -> Nothing+ run h sb = case unId (stepb sb) of+ Yield (Key j' k, b) sb'+ | j == j' -> run (h `mix` singleton (Key i k) (f a b)) sb'+ | otherwise -> run h sb'+ Skip sb' -> run h sb'+ Done -> h+{-# INLINE singletonTimes #-}
+ src/Sparse/Matrix/Internal/Key.hs view
@@ -0,0 +1,253 @@+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE GADTs #-}+-----------------------------------------------------------------------------+-- |+-- Copyright : (C) 2013 Edward Kmett+-- License : BSD-style (see the file LICENSE)+-- Maintainer : Edward Kmett <ekmett@gmail.com>+-- Stability : experimental+-- Portability : non-portable+--+-- Keys in Morton order+--+-- This module provides combinators for shuffling together the bits of two+-- key components to get a key that is based on their interleaved bits.+--+-- See <http://en.wikipedia.org/wiki/Z-order_curve> for more information+-- about Morton order.+--+-- How to perform the comparison without interleaving is described in+--+-- <https://www.fpcomplete.com/user/edwardk/revisiting-matrix-multiplication-part-2>+--+----------------------------------------------------------------------------+module Sparse.Matrix.Internal.Key+ (+ -- * Keys in Morton order+ Key(..)+ , swap+ -- * Most significant bit comparisons+ , compares+ , lts, les, eqs, nes, ges, gts+ -- * Unboxed vector constructors+ , U.MVector(..)+ , U.Vector(..)+ ) where++import Data.Bits+import Control.Monad+import Control.Lens+import qualified Data.Vector.Generic as G+import qualified Data.Vector.Generic.Mutable as GM+import qualified Data.Vector.Unboxed as U+import Data.Word++-- * Morton Order++-- | @Key i j@ logically orders the keys as if the bits of the keys @i@ and @j@+-- were interleaved. This is equivalent to storing the keys in \"Morton Order\".+--+-- >>> Key 100 200 ^. _1+-- 100+--+-- >>> Key 100 200 ^. _2+-- 200+data Key = Key {-# UNPACK #-} !Word {-# UNPACK #-} !Word+ deriving (Show, Read, Eq)++instance Ord Key where+ Key a b `compare` Key c d+ | xor a c `lts` xor b d = compare b d+ | otherwise = compare a c++instance (a ~ Word, b ~ Word) => Field1 Key Key a b where+ _1 f (Key i j) = indexed f (0 :: Int) i <&> (Key ?? j)+ {-# INLINE _1 #-}++instance (a ~ Word, b ~ Word) => Field2 Key Key a b where+ _2 f (Key i j) = indexed f (1 :: Int) j <&> Key i+ {-# INLINE _2 #-}++instance U.Unbox Key++data instance U.MVector s Key = MV_Key {-# UNPACK #-} !Int !(U.MVector s Word) !(U.MVector s Word)+data instance U.Vector Key = V_Key {-# UNPACK #-} !Int !(U.Vector Word) !(U.Vector Word)++instance GM.MVector U.MVector Key where+ {-# INLINE basicLength #-}+ {-# INLINE basicUnsafeSlice #-}+ {-# INLINE basicOverlaps #-}+ {-# INLINE basicUnsafeNew #-}+ {-# INLINE basicUnsafeReplicate #-}+ {-# INLINE basicUnsafeRead #-}+ {-# INLINE basicUnsafeWrite #-}+ {-# INLINE basicClear #-}+ {-# INLINE basicSet #-}+ {-# INLINE basicUnsafeCopy #-}+ {-# INLINE basicUnsafeGrow #-}+ basicLength (MV_Key l _ _) = l+ basicUnsafeSlice i n (MV_Key _ u v) = MV_Key n (GM.basicUnsafeSlice i n u) (GM.basicUnsafeSlice i n v)+ basicOverlaps (MV_Key _ u1 v1) (MV_Key _ u2 v2) = GM.basicOverlaps u1 u2 || GM.basicOverlaps v1 v2+ basicUnsafeNew n = liftM2 (MV_Key n) (GM.basicUnsafeNew n) (GM.basicUnsafeNew n)+ basicUnsafeReplicate n (Key x y) = liftM2 (MV_Key n) (GM.basicUnsafeReplicate n x) (GM.basicUnsafeReplicate n y)+ basicUnsafeRead (MV_Key _ u v) i = liftM2 Key (GM.basicUnsafeRead u i) (GM.basicUnsafeRead v i)+ basicUnsafeWrite (MV_Key _ u v) i (Key x y) = GM.basicUnsafeWrite u i x >> GM.basicUnsafeWrite v i y+ basicClear (MV_Key _ u v) = GM.basicClear u >> GM.basicClear v+ basicSet (MV_Key _ u v) (Key x y) = GM.basicSet u x >> GM.basicSet v y+ basicUnsafeCopy (MV_Key _ u1 v1) (MV_Key _ u2 v2) = GM.basicUnsafeCopy u1 u2 >> GM.basicUnsafeCopy v1 v2+ basicUnsafeMove (MV_Key _ u1 v1) (MV_Key _ u2 v2) = GM.basicUnsafeMove u1 u2 >> GM.basicUnsafeMove v1 v2+ basicUnsafeGrow (MV_Key _ u v) n = liftM2 (MV_Key n) (GM.basicUnsafeGrow u n) (GM.basicUnsafeGrow v n)++instance G.Vector U.Vector Key where+ {-# INLINE basicLength #-}+ {-# INLINE basicUnsafeFreeze #-}+ {-# INLINE basicUnsafeThaw #-}+ {-# INLINE basicUnsafeSlice #-}+ {-# INLINE basicUnsafeIndexM #-}+ {-# INLINE elemseq #-}+ basicLength (V_Key v _ _) = v+ basicUnsafeFreeze (MV_Key n u v) = liftM2 (V_Key n) (G.basicUnsafeFreeze u) (G.basicUnsafeFreeze v)+ basicUnsafeThaw (V_Key n u v) = liftM2 (MV_Key n) (G.basicUnsafeThaw u) (G.basicUnsafeThaw v)+ basicUnsafeSlice i n (V_Key _ u v) = V_Key n (G.basicUnsafeSlice i n u) (G.basicUnsafeSlice i n v)+ basicUnsafeIndexM (V_Key _ u v) i = liftM2 Key (G.basicUnsafeIndexM u i) (G.basicUnsafeIndexM v i)+ basicUnsafeCopy (MV_Key _ mu mv) (V_Key _ u v) = G.basicUnsafeCopy mu u >> G.basicUnsafeCopy mv v+ elemseq _ (Key x y) z = G.elemseq (undefined :: U.Vector Word) x+ $ G.elemseq (undefined :: U.Vector Word) y z++-- | Swaps the key components around+--+-- >>> swap (Key 100 200)+-- Key 200 100+swap :: Key -> Key+swap (Key i j) = Key j i+{-# INLINE swap #-}++-- | compare the position of the most significant bit of two words+--+-- >>> compares 4 7+-- EQ+--+-- >>> compares 7 9+-- LT+--+-- >>> compares 9 7+-- GT+compares :: Word -> Word -> Ordering+compares a b = case compare a b of+ LT | a < xor a b -> LT+ GT | b < xor a b -> GT+ _ -> EQ+{-# INLINE compares #-}++-- | @'lts' a b@ returns 'True' when the position of the most significant bit of @a@ is less than the position of the most signficant bit of @b@.+--+-- >>> lts 4 10+-- True+--+-- >>> lts 4 7+-- False+--+-- >>> lts 7 8+-- True+lts :: Word -> Word -> Bool+lts a b = a < b && a < xor a b+{-# INLINE lts #-}++-- | @'les' a b@ returns 'True' when the position of the most significant bit of @a@ is less than or equal to the position of the most signficant bit of @b@.+--+-- >>> les 4 10+-- True+--+-- >>> les 4 7+-- True+--+-- >>> les 7 4+-- True+--+-- >>> les 10 4+-- False+les :: Word -> Word -> Bool+les a b = a <= b || xor a b <= b+{-# INLINE les #-}++-- | @'eqs' a b@ returns 'True' when the position of the most significant bit of @a@ is equal to the position of the most signficant bit of @b@.+--+-- >>> eqs 4 7+-- True+--+-- >>> eqs 4 8+-- False+--+-- >>> eqs 7 4+-- True+--+-- >>> eqs 8 4+-- False+eqs :: Word -> Word -> Bool+eqs a b = case compare a b of+ LT -> a >= xor a b+ GT -> b >= xor a b+ EQ -> True+{-# INLINE eqs #-}++-- | @'nes' a b@ returns 'True' when the position of the most significant bit of @a@ is not equal to the position of the most signficant bit of @b@.+--+-- >>> nes 4 7+-- False+--+-- >>> nes 4 8+-- True+--+-- >>> nes 7 4+-- False+--+-- >>> nes 8 4+-- True+nes :: Word -> Word -> Bool+nes a b = case compare a b of+ LT -> a < xor a b+ GT -> b < xor a b+ EQ -> False+{-# INLINE nes #-}++-- | @'gts' a b@ returns 'True' when the position of the most significant bit of @a@ is greater than to the position of the most signficant bit of @b@.+--+-- >>> gts 4 10+-- False+--+-- >>> gts 4 7+-- False+--+-- >>> gts 7 4+-- False+--+-- >>> gts 10 4+-- True+gts :: Word -> Word -> Bool+gts a b = a > b && xor a b > b+{-# INLINE gts #-}++-- | @'gts' a b@ returns 'True' when the position of the most significant bit of @a@ is greater than or equal to the position of the most signficant bit of @b@.+--+-- >>> ges 4 10+-- False+--+-- >>> ges 4 7+-- True+--+-- >>> ges 7 4+-- True+--+-- >>> ges 10 4+-- True+ges :: Word -> Word -> Bool+ges a b = a >= b || a >= xor a b+{-# INLINE ges #-}
+ tests/doctests.hsc view
@@ -0,0 +1,73 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ForeignFunctionInterface #-}+-----------------------------------------------------------------------------+-- |+-- Module : Main (doctests)+-- Copyright : (C) 2012-13 Edward Kmett+-- License : BSD-style (see the file LICENSE)+-- Maintainer : Edward Kmett <ekmett@gmail.com>+-- Stability : provisional+-- Portability : portable+--+-- This module provides doctests for a project based on the actual versions+-- of the packages it was built with. It requires a corresponding Setup.lhs+-- to be added to the project+-----------------------------------------------------------------------------+module Main where++import Build_doctests (deps)+import Control.Applicative+import Control.Monad+import Data.List+import System.Directory+import System.FilePath+import Test.DocTest++##if defined(mingw32_HOST_OS)+##if defined(i386_HOST_ARCH)+##define USE_CP+import Control.Applicative+import Control.Exception+import Foreign.C.Types+foreign import stdcall "windows.h SetConsoleCP" c_SetConsoleCP :: CUInt -> IO Bool+foreign import stdcall "windows.h GetConsoleCP" c_GetConsoleCP :: IO CUInt+##elif defined(x86_64_HOST_ARCH)+##define USE_CP+import Control.Applicative+import Control.Exception+import Foreign.C.Types+foreign import ccall "windows.h SetConsoleCP" c_SetConsoleCP :: CUInt -> IO Bool+foreign import ccall "windows.h GetConsoleCP" c_GetConsoleCP :: IO CUInt+##endif+##endif++-- | Run in a modified codepage where we can print UTF-8 values on Windows.+withUnicode :: IO a -> IO a+##ifdef USE_CP+withUnicode m = do+ cp <- c_GetConsoleCP+ (c_SetConsoleCP 65001 >> m) `finally` c_SetConsoleCP cp+##else+withUnicode m = m+##endif++main :: IO ()+main = withUnicode $ getSources >>= \sources -> doctest $+ "-isrc"+ : "-idist/build/autogen"+ : "-optP-include"+ : "-optPdist/build/autogen/cabal_macros.h"+ : "-hide-all-packages"+ : map ("-package="++) deps ++ sources++getSources :: IO [FilePath]+getSources = filter (isSuffixOf ".hs") <$> go "src"+ where+ go dir = do+ (dirs, files) <- getFilesAndDirectories dir+ (files ++) . concat <$> mapM go dirs++getFilesAndDirectories :: FilePath -> IO ([FilePath], [FilePath])+getFilesAndDirectories dir = do+ c <- map (dir </>) . filter (`notElem` ["..", "."]) <$> getDirectoryContents dir+ (,) <$> filterM doesDirectoryExist c <*> filterM doesFileExist c
+ tests/properties.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE PatternGuards #-}+module Main where++import Control.Applicative+import Control.Monad (guard)+import Control.Lens+import Data.List (nub)+import Data.Map as M+import Data.Maybe (fromMaybe)+import Data.Vector as B+import Data.Vector.Unboxed as U+import Data.Vector.Hybrid as H+import Data.Vector.Generic as G+import Data.Word+import Instances+import Sparse.Matrix as SM+import Test.Framework.TH+import Test.Framework.Providers.QuickCheck2+import Test.QuickCheck+import Test.QuickCheck.Function+import Linear++-- model for matrix multiplication+type Linear a = Map Word (Map Word a)++nonEmpty :: Lens' (Maybe (Map k Int)) (Map k Int)+nonEmpty f m = f (fromMaybe M.empty m) <&> \ m -> m <$ guard (not (M.null m))++-- | matrix multiplication in linear will leave empty maps inside the outer map in sparse multiplication+sane :: Linear Int -> Linear Int+sane = M.filter (not . M.null)++toLinear :: Mat U.Vector Int -> Linear Int+toLinear = sane . H.foldr (\(k,v) r -> r & at (k^._1) . nonEmpty . at (k^._2) ?~ v) M.empty . view _Mat++fromLinear :: Linear Int -> Mat U.Vector Int+fromLinear m = SM.fromList $ do+ (i, n) <- M.toList m+ (j, a) <- M.toList n+ return (Key i j, a)++prop_to_from x = toLinear (fromLinear x) == sane x+prop_from_to x = fromLinear (toLinear x) == x++prop_model :: Mat U.Vector Int -> Mat U.Vector Int -> Gen Prop+prop_model x y | z <- x * y, z' <- fromLinear (toLinear x !*! toLinear y)+ = label (show z Prelude.++ " == " Prelude.++ show z') (z == z')++main = $defaultMainGenerator