packages feed

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 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+  ]