edges (empty) → 0.4
raw patch · 14 files changed
+635/−0 lines, 14 filesdep +QuickCheckdep +basedep +cerealsetup-changed
Dependencies added: QuickCheck, base, cereal, contravariant, deferred-folds, edges, foldl, monad-par, pointed, potoki, potoki-cereal, primitive, primitive-extras, profunctors, quickcheck-instances, rerebase, semigroupoids, tasty, tasty-hunit, tasty-quickcheck, text, vector
Files
- LICENSE +22/−0
- Setup.hs +2/−0
- edges.cabal +93/−0
- library/Edges/Cereal/Get.hs +22/−0
- library/Edges/Cereal/Instances.hs +21/−0
- library/Edges/Cereal/Put.hs +22/−0
- library/Edges/Edges.hs +80/−0
- library/Edges/IO.hs +18/−0
- library/Edges/Node.hs +21/−0
- library/Edges/NodeCounts.hs +67/−0
- library/Edges/Potoki/Produce.hs +25/−0
- library/Edges/Prelude.hs +149/−0
- library/Edges/Types.hs +14/−0
- test/Main.hs +79/−0
+ LICENSE view
@@ -0,0 +1,22 @@+Copyright (c) 2018, Metrix.AI++Permission is hereby granted, free of charge, to any person+obtaining a copy of this software and associated documentation+files (the "Software"), to deal in the Software without+restriction, including without limitation the rights to use,+copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the+Software is furnished to do so, subject to the following+conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR+OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ edges.cabal view
@@ -0,0 +1,93 @@+name:+ edges+version:+ 0.4+category:+ Graphs+synopsis:+ Tools for efficient immutable graphs+description:+ A set of tools for constructing immutable graphs which are both memory and performance-efficient.+homepage:+ https://github.com/metrix-ai/edges+bug-reports:+ https://github.com/metrix-ai/edges/issues+author:+ Nikita Volkov <nikita.y.volkov@mail.ru>+maintainer:+ Metrix Ninjas <ninjas@metrix.ai>+copyright:+ (c) 2018, Metrix.AI+license:+ MIT+license-file:+ LICENSE+build-type:+ Simple+cabal-version:+ >=1.10++source-repository head+ type:+ git+ location:+ git://github.com/metrix-ai/edges.git++library+ hs-source-dirs:+ library+ default-extensions:+ Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, PatternSynonyms, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples, OverloadedLists+ default-language:+ Haskell2010+ exposed-modules:+ Edges.Edges+ Edges.NodeCounts+ Edges.Node+ Edges.Potoki.Produce+ Edges.IO+ other-modules:+ Edges.Cereal.Instances+ Edges.Cereal.Get+ Edges.Cereal.Put+ Edges.Prelude+ Edges.Types+ build-depends:+ base >=4.7 && <5,+ cereal >=0.5.5 && <0.6,+ contravariant >=1.4 && <2,+ deferred-folds >=0.6 && <0.7,+ foldl >=1.4.2 && <2,+ monad-par >=0.3.4.8 && <0.4,+ pointed >=5 && <6,+ potoki >=2.0.2 && <2.1,+ potoki-cereal >=0.2.1.1 && <0.3,+ primitive >=0.6.4 && <0.7,+ primitive-extras >=0.3 && <0.4,+ profunctors >=5 && <6,+ QuickCheck >=2.8.1 && <3,+ semigroupoids >=5.2 && <6,+ text >=1 && <2,+ vector >=0.12 && <0.13++test-suite test+ type:+ exitcode-stdio-1.0+ hs-source-dirs:+ test+ main-is:+ Main.hs+ default-extensions:+ Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, PatternSynonyms, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples, OverloadedLists+ default-language:+ Haskell2010+ build-depends:+ cereal,+ edges,+ foldl,+ QuickCheck >=2.8.1 && <3,+ quickcheck-instances >=0.3.11 && <0.4,+ rerebase >=1.1 && <2,+ tasty >=1.0.1 && <1.2,+ tasty-hunit >=0.10 && <0.11,+ tasty-quickcheck >=0.10 && <0.11
+ library/Edges/Cereal/Get.hs view
@@ -0,0 +1,22 @@+module Edges.Cereal.Get+where++import Edges.Prelude+import Edges.Types+import Data.Serialize.Get+import PrimitiveExtras.Cereal.Get+++nodeCounts :: Get (NodeCounts entity)+nodeCounts =+ NodeCounts <$> primArray getWord32le++edges :: Get (Edges a b)+edges =+ do+ targetSpace <- fromIntegral <$> getInt64le+ pma <- primMultiArray getWord32le+ return (Edges targetSpace pma)++node :: Get (Node a)+node = Node . fromIntegral <$> getInt64le
+ library/Edges/Cereal/Instances.hs view
@@ -0,0 +1,21 @@+module Edges.Cereal.Instances+where++import Edges.Prelude+import Edges.Types+import Data.Serialize+import qualified Edges.Cereal.Get as A+import qualified Edges.Cereal.Put as B+++instance Serialize (Node a) where+ get = A.node+ put = B.node++instance Serialize (NodeCounts a) where+ get = A.nodeCounts+ put = B.nodeCounts++instance Serialize (Edges a b) where+ get = A.edges+ put = B.edges
+ library/Edges/Cereal/Put.hs view
@@ -0,0 +1,22 @@+module Edges.Cereal.Put+where++import Edges.Prelude+import Edges.Types+import Data.Serialize.Put+import PrimitiveExtras.Cereal.Put+++nodeCounts :: Putter (NodeCounts entity)+nodeCounts (NodeCounts pa) =+ primArray putWord32le pa++edges :: Putter (Edges a b)+edges (Edges targetSpaceValue mpaValue) =+ targetSpace <> mpa+ where+ targetSpace = putInt64le (fromIntegral targetSpaceValue)+ mpa = primMultiArray putWord32le mpaValue++node :: Putter (Node a)+node (Node int) = putInt64le (fromIntegral int)
+ library/Edges/Edges.hs view
@@ -0,0 +1,80 @@+module Edges.Edges+(+ Edges,+ list,+ listBipartite,+ primListBipartite,+ toAssocUnfoldM,+ toAssocList,+ genBipartiteWithLimits,+)+where++import Edges.Prelude+import Edges.Types+import Edges.Cereal.Instances ()+import qualified PrimitiveExtras.Monad as Monad+import qualified Control.Foldl as Foldl+import qualified Control.Monad.Par as Par+import qualified PrimitiveExtras.UnfoldM as UnfoldM+import qualified DeferredFolds.UnfoldM as UnfoldM+import qualified Test.QuickCheck.Gen as Gen++deriving instance Eq (Edges a b)++deriving instance Show (Edges a b)++list :: [(Node a, Node b)] -> Edges a b+list list =+ Par.runPar $ do+ aSizeFuture <- Par.spawnP $ succ $ fromMaybe 0 $ flip Foldl.fold list $ flip lmap Foldl.maximum $ \ (Node x, _) -> x+ bSizeFuture <- Par.spawnP $ succ $ fromMaybe 0 $ flip Foldl.fold list $ flip lmap Foldl.maximum $ \ (_, Node x) -> x+ aToBPrimFoldableFuture <- Par.spawnP $ flip fmap list $ \ (Node aInt, Node bInt) -> (aInt, fromIntegral bInt)+ aSize <- Par.get aSizeFuture+ bSize <- Par.get bSizeFuture+ aToBEdges <- primFoldableWithAmounts aSize bSize <$> Par.get aToBPrimFoldableFuture+ return aToBEdges++listBipartite :: [(Node a, Node b)] -> (Edges a b, Edges b a)+listBipartite = coerce primListBipartite++primListBipartite :: [(Int, Int)] -> (Edges a b, Edges b a)+primListBipartite list =+ Par.runPar $ do+ aSizeFuture <- Par.spawnP $ succ $ fromMaybe 0 $ flip Foldl.fold list $ flip lmap Foldl.maximum fst+ bSizeFuture <- Par.spawnP $ succ $ fromMaybe 0 $ flip Foldl.fold list $ flip lmap Foldl.maximum snd+ aToBPrimFoldableFuture <- Par.spawnP $ flip fmap list $ \ (aInt, bInt) -> (aInt, fromIntegral bInt)+ bToAPrimFoldableFuture <- Par.spawnP $ flip fmap list $ \ (aInt, bInt) -> (bInt, fromIntegral aInt)+ aSize <- Par.get aSizeFuture+ bSize <- Par.get bSizeFuture+ aToBEdgesFuture <- Par.spawn_ $ primFoldableWithAmounts aSize bSize <$> Par.get aToBPrimFoldableFuture+ bToAEdgesFuture <- Par.spawn_ $ primFoldableWithAmounts bSize aSize <$> Par.get bToAPrimFoldableFuture+ aToBEdges <- Par.get aToBEdgesFuture+ bToAEdges <- Par.get bToAEdgesFuture+ return (aToBEdges, bToAEdges)++primFoldableWithAmounts :: Foldable f => Int -> Int -> f (Int, Word32) -> Edges a b+primFoldableWithAmounts aAmount bAmount foldable =+ Edges bAmount $ runIdentity $ Monad.primMultiArray aAmount $ \ fold ->+ Identity $ Foldl.fold fold foldable++toAssocUnfoldM :: Monad m => Edges a b -> UnfoldM m (Node a, Node b)+toAssocUnfoldM (Edges _ mpa) =+ fmap (\ (aInt, bWord32) -> (Node aInt, Node (fromIntegral bWord32))) $+ UnfoldM.primMultiArrayAssocs mpa++toAssocList :: Edges a b -> [(Node a, Node b)]+toAssocList edges =+ UnfoldM.fold Foldl.list (toAssocUnfoldM edges)++genBipartiteWithLimits :: Int -> Int -> Gen.Gen (Edges a b, Edges b a)+genBipartiteWithLimits nodeLimit edgeLimit =+ do+ aMaxIndex <- Gen.choose (0, pred nodeLimit)+ bMaxIndex <- Gen.choose (0, pred nodeLimit)+ edgesAmount <- Gen.choose (0, edgeLimit)+ if aMaxIndex == 0 || bMaxIndex == 0+ then return (primListBipartite [])+ else do+ edges <- replicateM edgesAmount $ (,) <$> Gen.choose (0, aMaxIndex) <*> Gen.choose (0, bMaxIndex)+ return (primListBipartite edges)
+ library/Edges/IO.hs view
@@ -0,0 +1,18 @@+module Edges.IO+where++import Edges.Prelude+import Edges.Types+import Edges.Node ()+import Edges.NodeCounts ()+import Potoki.IO+import qualified Edges.Potoki.Produce as A+import qualified Potoki.Transform as B+import qualified Potoki.Cereal.Consume as C+++encodeNodeCountsToFile :: FilePath -> Edges a x -> (Node a -> NodeCounts z) -> IO (Either IOException ())+encodeNodeCountsToFile file edges nodeCounts =+ produceAndConsume+ (A.nodeCounts edges nodeCounts)+ (C.encodeToFile file)
+ library/Edges/Node.hs view
@@ -0,0 +1,21 @@+module Edges.Node+(+ Node(..),+ genWithLimit,+)+where++import Edges.Prelude+import Edges.Types+import Edges.Cereal.Instances ()+import qualified Test.QuickCheck.Gen as Gen++instance Show (Node a) where+ show (Node int) = show int++deriving instance Eq (Node a)++deriving instance Ord (Node a)++genWithLimit :: Int -> Gen.Gen (Node a)+genWithLimit max = Node <$> Gen.choose (0, max)
+ library/Edges/NodeCounts.hs view
@@ -0,0 +1,67 @@+module Edges.NodeCounts+(+ NodeCounts,+ node,+ nodeTargets,+ targets,+ toList,+ toUnboxedVector,+)+where++import Edges.Prelude hiding (index, toList)+import Edges.Types+import Edges.Cereal.Instances ()+import qualified PrimitiveExtras.UnfoldM as A+import qualified PrimitiveExtras.Pure as C+import qualified PrimitiveExtras.IO as D+import qualified PrimitiveExtras.Fold as E+import qualified DeferredFolds.UnfoldM as B+import qualified Data.Vector.Unboxed as F+import qualified Control.Monad.Par.IO as Par+import qualified Control.Monad.Par as Par hiding (runParIO)+++instance Show (NodeCounts a) where+ show = show . toList++node :: Edges entity anyEntity -> Node entity -> NodeCounts entity+node (Edges _ edgesPma) =+ let size = C.primMultiArrayOuterLength edgesPma+ in nodeWithSize size++nodeWithSize :: Int -> Node entity -> NodeCounts entity+nodeWithSize size (Node index) =+ NodeCounts (C.oneHotPrimArray size index 1)++nodeTargets :: Edges surce target -> Node source -> NodeCounts target+nodeTargets (Edges targetAmount edgesPma) (Node sourceIndex) =+ let indexUnfold = fmap fromIntegral (A.primMultiArrayAt edgesPma sourceIndex)+ indexFold = E.indexCounts targetAmount+ countPa = B.fold indexFold indexUnfold+ in NodeCounts countPa++{-|+Count the occurrences of targets based on the occurrences of sources.++Utilizes concurrency.+-}+targets :: Edges source target -> NodeCounts source -> NodeCounts target+targets (Edges targetAmount edgesPma) (NodeCounts sourceCountsPa) =+ unsafePerformIO $ Par.runParIO $ do+ targetCountVarTable <- liftIO (D.newTVarArray 0 targetAmount)+ Par.parFor (Par.InclusiveRange 0 (pred (sizeofPrimArray sourceCountsPa))) $ \ sourceIndex ->+ case indexPrimArray sourceCountsPa sourceIndex of+ 0 -> return ()+ sourceCount ->+ liftIO $+ B.forM_ (A.primMultiArrayAt edgesPma sourceIndex) $ \ targetIndex ->+ D.modifyTVarArrayAt targetCountVarTable (fromIntegral targetIndex) (+ sourceCount)+ targetCountsPa <- liftIO (D.freezeTVarArrayAsPrimArray targetCountVarTable)+ return (NodeCounts targetCountsPa)++toList :: NodeCounts entity -> [Word32]+toList (NodeCounts pa) = foldrPrimArray' (:) [] pa++toUnboxedVector :: NodeCounts entity -> F.Vector Word32+toUnboxedVector (NodeCounts pa) = C.primArrayUnboxedVector pa
+ library/Edges/Potoki/Produce.hs view
@@ -0,0 +1,25 @@+module Edges.Potoki.Produce+where++import Edges.Prelude+import Edges.Types+import Edges.NodeCounts ()+import Potoki.Produce+import qualified PrimitiveExtras.Pure as A+import qualified Potoki.Transform as B+import qualified Potoki.Cereal.Produce as C+++nodes :: Edges a x -> Produce (Node a)+nodes (Edges _ pma) =+ coerce $+ enumInRange 0 (pred (A.primMultiArrayOuterLength pma))++nodeCounts :: Edges a x -> (Node a -> NodeCounts b) -> Produce (Node a, NodeCounts b)+nodeCounts edges nodeCounts =+ transform (B.concurrently numCapabilities (arr (\ node -> case nodeCounts node of x -> (node, x)))) $+ nodes edges++nodeCountsFromFile :: FilePath -> Produce (Either IOException (Either Text (Node a, NodeCounts b)))+nodeCountsFromFile file =+ C.fileDecoded file
+ library/Edges/Prelude.hs view
@@ -0,0 +1,149 @@+module Edges.Prelude+(+ module Exports,+ UnboxedVector,+ modifyTVar',+ forMToZero_,+ forMFromZero_,+ (.),+)+where+++-- base+-------------------------+import Control.Applicative as Exports+import Control.Arrow as Exports+import Control.Category as Exports hiding ((.))+import Control.Concurrent as Exports+import Control.Exception as Exports+import Control.Monad as Exports hiding (mapM_, sequence_, forM_, msum, mapM, sequence, forM)+import Control.Monad.IO.Class as Exports+import Control.Monad.Fix as Exports hiding (fix)+import Control.Monad.ST as Exports+import Data.Bits as Exports+import Data.Bool as Exports+import Data.Char as Exports+import Data.Coerce as Exports+import Data.Complex as Exports+import Data.Data as Exports+import Data.Dynamic as Exports+import Data.Either as Exports+import Data.Fixed as Exports+import Data.Foldable as Exports+import Data.Function as Exports hiding (id, (.))+import Data.Functor as Exports+import Data.Functor.Identity as Exports+import Data.Int as Exports+import Data.IORef as Exports+import Data.Ix as Exports+import Data.List as Exports hiding (sortOn, isSubsequenceOf, uncons, concat, foldr, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, find, maximumBy, minimumBy, mapAccumL, mapAccumR, foldl')+import Data.Maybe as Exports+import Data.Monoid as Exports hiding (Last(..), First(..))+import Data.Ord as Exports+import Data.Proxy as Exports+import Data.Ratio as Exports+import Data.STRef as Exports+import Data.String as Exports+import Data.Traversable as Exports+import Data.Tuple as Exports+import Data.Unique as Exports+import Data.Version as Exports+import Data.Word as Exports+import Debug.Trace as Exports+import Foreign.ForeignPtr as Exports+import Foreign.Ptr as Exports+import Foreign.StablePtr as Exports+import Foreign.Storable as Exports hiding (sizeOf, alignment)+import GHC.Conc as Exports hiding (withMVar, threadWaitWriteSTM, threadWaitWrite, threadWaitReadSTM, threadWaitRead)+import GHC.Exts as Exports (lazy, inline, sortWith, groupWith)+import GHC.Generics as Exports (Generic)+import GHC.IO.Exception as Exports+import Numeric as Exports+import Prelude as Exports hiding (concat, foldr, mapM_, sequence_, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, mapM, sequence, id, (.))+import System.Environment as Exports+import System.Exit as Exports+import System.IO as Exports+import System.IO.Error as Exports+import System.IO.Unsafe as Exports+import System.Mem as Exports+import System.Mem.StableName as Exports+import System.Timeout as Exports+import Text.ParserCombinators.ReadP as Exports (ReadP, ReadS, readP_to_S, readS_to_P)+import Text.ParserCombinators.ReadPrec as Exports (ReadPrec, readPrec_to_P, readP_to_Prec, readPrec_to_S, readS_to_Prec)+import Text.Printf as Exports (printf, hPrintf)+import Text.Read as Exports (Read(..), readMaybe, readEither)+import Unsafe.Coerce as Exports++-- contravariant+-------------------------+import Data.Functor.Contravariant as Exports+import Data.Functor.Contravariant.Divisible as Exports++-- pointed+-------------------------+import Data.Pointed as Exports+import Data.Copointed as Exports++-- profunctors+-------------------------+import Data.Profunctor.Unsafe as Exports+import Data.Profunctor.Choice as Exports+import Data.Profunctor.Strong as Exports++-- semigroupoids+-------------------------+import Data.Semigroupoid as Exports++-- cereal+-------------------------+import Data.Serialize as Exports (Serialize)++-- deferred-folds+-------------------------+import DeferredFolds.Unfold as Exports (Unfold(..))+import DeferredFolds.UnfoldM as Exports (UnfoldM(..))++-- foldl+-------------------------+import Control.Foldl as Exports (Fold(..), FoldM(..))++-- primitive+-------------------------+import Data.Primitive as Exports+import Control.Monad.Primitive as Exports++-- vector+-------------------------+import Data.Vector as Exports (Vector)++-- primitive-extras+-------------------------+import PrimitiveExtras.Data as Exports++-- text+-------------------------+import Data.Text as Exports (Text)++import qualified Data.Vector.Unboxed as UnboxedVector++type UnboxedVector = UnboxedVector.Vector++{-# INLINE modifyTVar' #-}+modifyTVar' :: TVar a -> (a -> a) -> STM ()+modifyTVar' var f = do+ x <- readTVar var+ writeTVar var $! f x++{-# INLINE forMToZero_ #-}+forMToZero_ :: Applicative m => Int -> (Int -> m a) -> m ()+forMToZero_ !startN f =+ ($ pred startN) $ fix $ \loop !n -> if n >= 0 then f n *> loop (pred n) else pure ()++{-# INLINE forMFromZero_ #-}+forMFromZero_ :: Applicative m => Int -> (Int -> m a) -> m ()+forMFromZero_ !endN f =+ ($ 0) $ fix $ \loop !n -> if n < endN then f n *> loop (succ n) else pure ()++(.) :: Semigroupoid s => s b c -> s a b -> s a c+(.) = o
+ library/Edges/Types.hs view
@@ -0,0 +1,14 @@+module Edges.Types+where++import Edges.Prelude+++data Edges source target =+ Edges+ {-# UNPACK #-} !Int {-^ Target array size -}+ {-# UNPACK #-} !(PrimMultiArray Word32)++newtype Node entity = Node Int++newtype NodeCounts entity = NodeCounts (PrimArray Word32)
+ test/Main.hs view
@@ -0,0 +1,79 @@+module Main where++import Prelude+import Test.QuickCheck+import Test.QuickCheck.Instances+import Test.Tasty+import Test.Tasty.Runners+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck+import qualified Edges.Edges as A+import qualified Edges.NodeCounts as B+import qualified Edges.Node as C+import qualified Data.Serialize as D+++main =+ defaultMain $+ testGroup "All tests" $+ [+ testGroup "Predefined bipartite" $ let+ edgeList :: [(C.Node (Proxy 1), C.Node (Proxy 2))]+ edgeList =+ fmap (bimap C.Node C.Node) $+ [+ (0, 0),+ (0, 1),+ (0, 2),+ (1, 0),+ (1, 1),+ (2, 0)+ ]+ (edges1, edges2) = A.listBipartite edgeList+ in+ [+ testCase "Constructs the forward edges correctly" $ let+ reconstructedEdgeList = A.toAssocList edges1+ in assertEqual (show reconstructedEdgeList) edgeList reconstructedEdgeList+ ,+ testCase "Constructs the backward edges correctly" $ let+ reconstructedEdgeList = sort $ A.toAssocList edges2+ expectedEdgeList = sort $ fmap swap edgeList+ in assertEqual (show reconstructedEdgeList) expectedEdgeList reconstructedEdgeList+ ,+ testGroup "Counting at depth" $ let+ node = C.Node 1 :: C.Node (Proxy 1)+ in+ [+ testCase "0" $ let+ nodeCountsList = B.node edges1 node & B.toList+ in assertEqual (show nodeCountsList) [0, 1, 0] nodeCountsList+ ,+ testCase "1, unoptimized" $ let+ nodeCountsList = B.node edges1 node & B.targets edges1 & B.toList+ in assertEqual (show nodeCountsList) [1, 1, 0] nodeCountsList+ ,+ testCase "1" $ let+ nodeCountsList = B.nodeTargets edges1 node & B.toList+ in assertEqual (show nodeCountsList) [1, 1, 0] nodeCountsList+ ,+ testCase "2" $ let+ nodeCountsList = B.nodeTargets edges1 node & B.targets edges2 & B.toList+ in assertEqual (show nodeCountsList) [2, 2, 1] nodeCountsList+ ,+ testCase "3" $ let+ nodeCountsList = B.nodeTargets edges1 node & B.targets edges2 & B.targets edges1 & B.toList+ in+ {-+ [0, 1]+ [[0, 1, 2], [0, 1]]+ [[[0, 1, 2], [0, 1], [0]], [[0, 1, 2], [0, 1]]]+ [5, 4, 2]+ -}+ assertEqual (show nodeCountsList) [5, 4, 2] nodeCountsList+ ]+ ]+ ,+ testProperty "Encoding/decoding with Cereal" $ forAll (A.genBipartiteWithLimits 10 20) $ \ (edges1, edges2) ->+ D.decode (D.encode edges1) === Right edges1+ ]