diff --git a/Etage-Graph.cabal b/Etage-Graph.cabal
new file mode 100644
--- /dev/null
+++ b/Etage-Graph.cabal
@@ -0,0 +1,47 @@
+Name:                Etage-Graph
+Version:             0.1
+Synopsis:            Data-flow based graph algorithms
+Description:         Data-flow based graph algorithms using the "Control.Etage" framework, showcasing its use for data-flow
+                     computations. It is meant to be used with the "Data.Graph.Inductive" package which provides graph structures
+                     these algorithms take as an input.
+                     .
+                     Currently only the shortest paths computation is implemented (from all to all nodes).
+                     .
+                     Also a benchmarking @test@ program is provided, comparing the performance with algorithms found in the
+                     "Data.Graph.Inductive" package.
+License:             LGPL-3
+License-file:        LICENSE
+Author:              Mitar Milutinovic
+Maintainer:          mitar.haskell@tnode.com
+Copyright:           (c) 2011 Mitar Milutinovic
+Category:            Data Structures
+Build-type:          Simple
+Cabal-version:       >= 1.8
+Stability:           experimental
+Homepage:            http://mitar.tnode.com
+
+Library
+  Exposed-modules:     Data.Graph.Etage
+  Build-depends:       base >= 4.3 && < 5,
+                       Etage >= 0.1.8 && < 0.2,
+                       fgl >= 5.4.2 && < 5.5,
+                       mtl >= 2.0 && < 3,
+                       containers >= 0.4 && < 1
+  HS-source-dirs:      lib
+  GHC-options:         -Wall
+  GHC-prof-options:    -Wall
+  GHC-shared-options:  -Wall
+
+Executable test
+  Main-is:             Test.hs
+  HS-source-dirs:      src
+  Build-depends:       base >= 4.3 && < 5,
+                       fgl >= 5.4.2 && < 5.5,
+                       random >= 1.0 && < 2,
+                       containers >= 0.4 && < 1,
+                       deepseq >= 1.1 && < 2,
+                       array >= 0.3 && < 1,
+                       time >= 1.1 && < 2,
+                       Etage == 0.1.8,
+                       Etage-Graph == 0.1
+  GHC-options:         -Wall -rtsopts -threaded
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,165 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions. 
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version. 
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/lib/Data/Graph/Etage.hs b/lib/Data/Graph/Etage.hs
new file mode 100644
--- /dev/null
+++ b/lib/Data/Graph/Etage.hs
@@ -0,0 +1,186 @@
+{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FlexibleInstances, ScopedTypeVariables, TypeSynonymInstances, StandaloneDeriving, DeriveDataTypeable, NamedFieldPuns #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+{-|
+Please read the "Control.Etage" framework documentation for general information how it works. Also check included @test@ program for
+an example of how to work with the algorithms bellow.
+-}
+
+module Data.Graph.Etage (
+  shortestPaths,
+  sendTopologyChange,
+  GraphImpulse(..)
+) where
+
+import Control.Exception
+import Control.Monad.State
+import Data.Data
+import Data.Graph.Inductive hiding (inn, inn', out, out', node', nodes, run)
+import qualified Data.Map as M
+import Data.Map hiding (filter, map, empty, null, lookup)
+import Data.Tuple
+import Control.Etage
+import System.IO
+
+type SPath b = (LPath b, b)
+type SPaths a b = M.Map Node (a, SPath b) -- node is destination, last element of SPath
+
+{-|
+Shortest paths algorithm (from all to all nodes) using message ('Impulse's in the "Control.Etage" terminology) passing between the
+nodes along the edges of the graph to compute shortest paths. Loosely based on the algorithm used in the Babel routing
+protocol, <http://www.pps.jussieu.fr/~jch/software/babel/>.
+
+It takes a "Data.Graph.Inductive" graph as an input and produces a map between source nodes and its corresponding 'Nerve's, over which
+'Impulse's about shortest paths search will be send. To trigger the search 'sendTopologyChange' should be used on returned 'Nerve's.
+
+One way how to collect this 'Impulse's into an array for querying about shortest paths can be found in the @test@ program found in
+this package.
+
+While shortest paths search is lasting, information about suboptimal paths is already available. This algorithm also allows effective
+incremental search after graph topology changes (new nodes are added or removed, weights are changed) but this is not yet implemented.
+-}
+shortestPaths :: (DynGraph gr, Show a, Data a, Data b, Real b, Bounded b) => gr a b -> Incubation (M.Map Node (Nerve (GraphImpulse a b) AxonConductive (GraphImpulse a b) AxonConductive))
+shortestPaths = ufoldM' growGraph M.empty
+
+growGraph :: forall a b. (Show a, Data a, Data b, Real b, Bounded b) => Context a b -> M.Map Node (Nerve (GraphImpulse a b) AxonConductive (GraphImpulse a b) AxonConductive) -> Incubation (M.Map Node (Nerve (GraphImpulse a b) AxonConductive (GraphImpulse a b) AxonConductive))
+growGraph (inn, node, label, out) nodes = do
+  -- TODO: Sometimes nerve is not connected in both directions, how to fix memory leak then?
+  liftIO $ do
+    assertIO $ node `notMember` nodes
+    assertIO $ all ((`member` nodes) . snd) inn'
+    assertIO $ all ((`member` nodes) . snd) out'
+  nodeNerve <- (growNeuron :: NerveBoth (NodeNeuron a b)) (\o -> o { lnode = (node, label) })
+  mapM_ ((`attachTo` [TranslatableFor nodeNerve]) . (nodes !) . snd) out'
+  nodeNerve `attachTo` map (TranslatableFor . (nodes !) . snd) inn'
+  liftIO $ do
+    time <- getCurrentImpulseTime
+    unless (null out') $ sendForNeuron nodeNerve $ AddOutEdges time out'
+    mapM_ (\(l, n) -> sendForNeuron (nodes ! n) $ AddOutEdges time [(l, node)]) inn'
+  return $ insert node nodeNerve nodes
+    where inn' = filter ((node /=) . snd) inn -- we ignore loopbacks
+          out' = filter ((node /=) . snd) out -- we ignore loopbacks
+
+-- TODO: Also make functions to manipulate graph
+-- TODO: We have to send TopologyChange to all nodes because currently it is not propagated correctly around (just along inbound edges, but it should along all)
+{-|
+Inform nodes that topology has changed (new nodes have been added or removed, weights changed).
+
+Currently it should only be invoked after the data-flow graph structure has been built (for example with 'shortestPaths'). As
+graph topology changing interface (and thus incremental nature of algorithms) is not yet implemented.
+-}
+sendTopologyChange :: M.Map Node (Nerve (GraphImpulse a b) AxonConductive (GraphImpulse a b) AxonConductive) -> Incubation ()
+sendTopologyChange nodes = liftIO $ do
+  time <- getCurrentImpulseTime
+  forM_ (elems nodes) $ \n ->
+    sendForNeuron n $ TopologyChange time
+
+data NodeState a b = NodeState {
+    lastTopologyChangeTimestamp :: ImpulseTime,
+    currentPaths :: SPaths a b,
+    outedges :: M.Map Node b
+  }
+
+type NodeIO a b = StateT (NodeState a b) IO
+
+data NodeNeuron a b = NodeNeuron Node a deriving (Typeable, Data)
+
+deriving instance Typeable1 LPath
+deriving instance Data a => Data (LPath a)
+
+data GraphImpulse a b = TopologyUpdate {
+    impulseTimestamp :: ImpulseTime,
+    originator :: LNode a,
+    destination :: LNode a,
+    path :: SPath b
+  } -- ^ Informs nodes about possible improvement in the topology information, like a newly discovered shortest path.
+  | TopologyChange {
+    impulseTimestamp :: ImpulseTime
+  } -- ^ Informs nodes that topology has changed and the algorithm should be triggered (again).
+  | AddOutEdges {
+    impulseTimestamp :: ImpulseTime,
+    newOutEdges :: Adj b
+  } -- ^ Inform the node that new outbound edges have been 'attach'ed to it, giving the node their weights.
+  deriving (Eq, Ord, Show, Typeable, Data)
+
+instance (Show a, Typeable a, Show b, Typeable b, Real b, Bounded b) => Impulse (GraphImpulse a b) where
+  impulseTime TopologyUpdate { impulseTimestamp } = impulseTimestamp
+  impulseTime TopologyChange { impulseTimestamp } = impulseTimestamp
+  impulseTime AddOutEdges { impulseTimestamp } = impulseTimestamp
+  impulseValue TopologyUpdate { originator, path } = toRational o : (value . fst $ path)
+    where (o, _) = originator
+          value (LP p) = concatMap (\(n, l) -> [toRational n, toRational l]) p
+  impulseValue TopologyChange {} = []
+  impulseValue AddOutEdges { newOutEdges } = concatMap (\(l, n) -> [toRational l, toRational n]) newOutEdges
+
+instance (Show a, Data a, Show b, Data b, Real b, Bounded b) => Neuron (NodeNeuron a b) where
+  type NeuronFromImpulse (NodeNeuron a b) = GraphImpulse a b
+  type NeuronForImpulse (NodeNeuron a b) = GraphImpulse a b
+  data NeuronOptions (NodeNeuron a b) = NodeOptions {
+      lnode :: LNode a
+    } deriving (Eq, Ord, Read, Show) -- TODO: Derive Data when it will work
+
+  mkDefaultOptions = return NodeOptions {
+      lnode = undefined
+    }
+
+  grow NodeOptions { lnode = (node, label) } = return $ NodeNeuron node label
+  
+  live nerve neuron@(NodeNeuron node label) = evalStateT (run nerve neuron) (NodeState 0 (singleton node (label, (LP [(node, 0)], 0))) M.empty)
+
+run :: (Data b, Real b, Bounded b) => Nerve (GraphImpulse a b) fromConductivity (GraphImpulse a b) forConductivity -> NodeNeuron a b -> NodeIO a b ()
+run nerve (NodeNeuron node label) = forever $ do
+  -- TODO: We could process multiple impulses at the same time, so that we do not send out updates for paths where better paths are already queued for us
+  impulse <- liftIO $ getForNeuron nerve
+  case impulse of
+    TopologyChange { impulseTimestamp } -> do
+      lastTimestamp <- gets lastTopologyChangeTimestamp
+      when (impulseTimestamp > lastTimestamp) $ do
+        modify (\s -> s { lastTopologyChangeTimestamp = impulseTimestamp })
+        paths <- gets currentPaths
+        liftIO $ do
+          sendFromNeuron nerve impulse
+          t <- liftIO getCurrentImpulseTime
+          -- TODO: TopologyChange should be propagated correctly (along all edges and not just along inbound edges, as it is now)
+          forM_ (toList paths) $ \(n, (l, p)) ->
+            sendFromNeuron nerve TopologyUpdate { impulseTimestamp = t, originator = (node, label), destination = (n, l), path = p }
+    TopologyUpdate { impulseTimestamp, originator = (o, _), destination = (d, l), path = (LP path, cost) } -> do
+      liftIO $ do
+        assertIO $ abs (cost - (sum . map snd $ path)) * 100000 < 1 -- we have to do compare it like that to account for approximate nature of float values
+        assertIO $ (fst . last $ path) == d
+      out <- gets outedges
+      case M.lookup o out of
+        Nothing    -> liftIO $ hPutStrLn stderr "Warning: TopologyUpdate message arrived before AddOutEdges message."
+        Just ocost -> do
+          paths <- gets currentPaths
+          let (_, (_, c)) = findWithDefault (undefined, (undefined, maxBound)) d paths
+              cost' = cost + ocost
+          when (cost' < c) $ do
+            let path' = LP $ (node, ocost) : path
+                paths' = insert d (l, (path', cost')) paths
+            modify (\s -> s { currentPaths = paths' })
+            liftIO $ sendFromNeuron nerve TopologyUpdate { impulseTimestamp, originator = (node, label), destination = (d, l), path = (path', cost') }
+    AddOutEdges { newOutEdges } -> do
+      out <- gets outedges
+      let out' = foldl (\i (l, n) -> insert n l i) out newOutEdges
+      modify (\s -> s { outedges = out' })
+
+ufoldM' :: (Graph gr, Monad m) => (Context a b -> c -> m c) -> c -> gr a b -> m c
+ufoldM' f u g | isEmpty g = return u
+              | otherwise = ufoldM' f u g' >>= \u' -> f c u'
+                  where (c, g') = matchAny g
+
+{-
+gmapM' :: (DynGraph gr, Monad m) => (Context a b -> m (Context c d)) -> gr a b -> m (gr c d)
+gmapM' f = ufoldM' (\c u -> f c >>= \c' -> return $ c' & u) empty
+-}
+
+instance Bounded Float where
+  minBound = -1/0
+  maxBound = 1/0
+
+instance Bounded Double where
+  minBound = -1/0
+  maxBound = 1/0
+
+assertIO :: Bool -> IO ()
+assertIO cond = evaluate (assert cond ())
diff --git a/src/Test.hs b/src/Test.hs
new file mode 100644
--- /dev/null
+++ b/src/Test.hs
@@ -0,0 +1,203 @@
+{-# LANGUAGE TypeFamilies, ScopedTypeVariables, DeriveDataTypeable, NamedFieldPuns, BangPatterns #-}
+
+module Main (
+  main
+) where
+
+import Control.Applicative
+import Control.DeepSeq
+import Control.Exception
+import Control.Monad
+import Control.Monad.ST
+import Data.Array hiding (elems)
+import Data.Array.ST
+import Data.Data
+import Data.Graph.Etage
+import Data.Graph.Inductive hiding (edges, defaultGraphSize)
+import qualified Data.Map as M
+import Data.List
+import Data.Maybe
+import Data.Ratio
+import Data.Time.Clock.POSIX
+import GHC.Arr
+import System.Console.GetOpt
+import System.Environment
+import System.Exit
+import System.IO
+import System.Random
+import System.Timeout
+import Text.Printf
+
+import Control.Etage
+
+defaultGraphSize :: Int
+defaultGraphSize = 6
+
+minCollectTimeout :: Int
+minCollectTimeout = 500000 -- microseconds
+
+initialCollectTimeout :: Int
+initialCollectTimeout = 5000000 -- microseconds
+
+data Option = InputGraph String | OutputGraph String | OutputDot String | GraphSize Int | Help deriving (Eq, Show)
+
+options :: [OptDescr Option]
+options = [
+    Option "g" ["graph"]  (ReqArg InputGraph "filename")            "read graph grom a file, default is to generate one randomly",
+    Option "o" ["output"] (ReqArg OutputGraph "filename")           "save graph to a file",
+    Option "d" ["dot"]    (ReqArg OutputDot "filename")             "save graph to a file in a GraphViz format",
+    Option "s" ["size"]   (ReqArg (GraphSize . readParam) "number") ("size of the randomly generated graph, default is " ++ show defaultGraphSize),
+    Option "h" ["help"]   (NoArg Help)                              "show this help"
+  ]
+    where readParam param = case reads param of
+                              [(p,[])] -> if p < 1
+                                            then error $ "invalid parameter `" ++ param ++ "'"
+                                            else p
+                              _        -> error $ "invalid parameter `" ++ param ++ "'"
+
+isInputGraph :: Option -> Bool
+isInputGraph (InputGraph _) = True
+isInputGraph _              = False
+
+isGraphSize :: Option -> Bool
+isGraphSize (GraphSize _) = True
+isGraphSize _             = False
+
+isOutputGraph :: Option -> Bool
+isOutputGraph (OutputGraph _) = True
+isOutputGraph _               = False
+
+isOutputDot :: Option -> Bool
+isOutputDot (OutputDot _) = True
+isOutputDot _             = False
+
+generateGraph :: Int -> IO (Gr String Double)
+generateGraph graphSize = do
+  when (graphSize < 1) $ throwIO $ AssertionFailed $ "Graph size out of bounds " ++ show graphSize
+  let ns = map (\n -> (n, show n)) [1..graphSize]
+  edges <- fmap concat $ forM [1..graphSize] $ \node -> do
+    nedges <- randomRIO (0, graphSize)
+    others <- fmap (filter (node /=) . nub) $ forM [1..nedges] $ \_ -> randomRIO (1, graphSize)
+    gen <- getStdGen
+    let weights = randomRs (1, 10) gen
+    return $ zip3 (repeat node) others weights
+  return $ mkGraph ns edges
+
+data TestNeuron a b = TestNeuron Int (Array (Node, Node) (b, [Node])) deriving (Typeable)
+
+instance (Show a, Data a, Show b, Data b, Real b, Bounded b, NFData b) => Neuron (TestNeuron a b) where
+  type NeuronFromImpulse (TestNeuron a b) = NoImpulse
+  type NeuronForImpulse (TestNeuron a b) = GraphImpulse a b
+  data NeuronOptions (TestNeuron a b) = NodeOptions {
+      graphSize :: Int,
+      knownPaths :: Array (Node, Node) (b, [Node])
+    }
+
+  mkDefaultOptions = return NodeOptions {
+      graphSize = undefined,
+      knownPaths = undefined
+    }
+
+  grow NodeOptions { graphSize, knownPaths } = return $ TestNeuron graphSize knownPaths
+  
+  live nerve (TestNeuron graphSize knownPaths) = do
+    before <- getPOSIXTime
+    pathsLazy <- stToIO $ newArray ((1, 1), (graphSize, graphSize)) (maxBound, [])
+    collectTimeout <- collectPaths initialCollectTimeout pathsLazy
+    pathsLazy' <- stToIO $ unsafeFreezeSTArray pathsLazy
+    let !paths = pathsLazy' `deepseq` pathsLazy'
+    after <- getPOSIXTime
+    putStrLn $ "Etage search time for shortest paths: " ++ show (after - before - fromRational (fromIntegral collectTimeout % 1000000)) ++ " (" ++ printf "%fs" ((fromIntegral collectTimeout :: Double) / 1000000) ++ " timeout)" -- we correct for the last timeout
+    let paths'      = M.fromList $ assocs paths
+        knownPaths' = M.fromList $ assocs knownPaths
+        shortest    = (fromIntegral . sum . map fromEnum . M.elems $ M.intersectionWith (\(l, p) (l', p') -> l == l' && p == p') knownPaths' paths') / fromIntegral (M.size knownPaths') :: Float
+    putStrLn $ "Found " ++ printf "%.2f %%" (shortest * 100) ++ " shortest paths."
+    dissolving ()
+      where collectPaths :: Int -> STArray RealWorld (Node, Node) (b, [Node]) -> IO Int
+            collectPaths collectTimeout arr = do
+              before <- getPOSIXTime
+              impulse <- timeout collectTimeout $ getForNeuron nerve
+              case impulse of
+                Nothing -> return collectTimeout
+                Just i  -> do
+                  let timestamp       = impulseTimestamp i
+                      -- TODO: Improve timeout handling. Timeout only after the first TopologyChange?
+                      collectTimeout' = max ((collectTimeout + round ((timestamp - before) * 2 * 1000000)) `div` 2) minCollectTimeout
+                  case i of
+                    TopologyChange {}                                                 -> collectPaths collectTimeout' arr
+                    AddOutEdges {}                                                    -> collectPaths collectTimeout' arr
+                    TopologyUpdate { destination = (node, _), path = (LP path, len) } -> do
+                      let sourceNode = fst . head $ path
+                      stToIO $ writeArray arr (sourceNode, node) (len, map fst path)
+                      collectPaths collectTimeout' arr
+
+main :: IO ()
+main = do
+  prepareEnvironment
+  
+  args <- getArgs
+  opts <- case getOpt Permute options args of
+            (o, [], [])  -> return o
+            (_, p:_, []) -> throwIO $ ErrorCall $ "unrecognized option `" ++ p ++ "'"
+            (_, _, errs) -> throwIO $ ErrorCall $ head $ lines $ head errs
+
+  when (Help `elem` opts) $ do
+    programName <- getProgName
+    putStrLn $ "Usage:\n" ++ usageInfo (programName ++ " [option ...]" ++ "\n\nOptions:") options
+    exitWith ExitSuccess
+  
+  (graph, graphSize) <- case find isInputGraph opts of
+                          Just (InputGraph inputGraph) -> do
+                            when (any isGraphSize opts) $ throwIO $ ErrorCall $ "conflicting options `" ++ "--graph" ++ "' and `" ++ "--size" ++ "'"
+                            putStrLn $ "Reading graph from \"" ++ inputGraph ++ "\"."
+                            [line1, line2] <- lines <$> readFile inputGraph
+                            let g = mkGraph (read line1) (read line2)
+                            forceStrictGraph g
+                            return (g, noNodes g)
+                          _                            -> do
+                            let GraphSize s = fromMaybe (GraphSize defaultGraphSize) $ find isGraphSize opts
+                            putStrLn $ "Generating a random graph of size " ++ show s ++ "."
+                            g <- generateGraph s
+                            forceStrictGraph g
+                            return (g, s)
+
+  case find isOutputGraph opts of
+    Just (OutputGraph outputGraph) -> do
+      putStrLn $ "Writing graph to \"" ++ outputGraph ++ "\"."
+      writeFile outputGraph $ (show . labNodes $ graph) ++ "\n" ++ (show . labEdges $ graph) ++ "\n"
+    _                              -> return ()
+
+  case find isOutputDot opts of
+    Just (OutputDot outputDot) -> do
+      putStrLn $ "Writing graph in dot format to \"" ++ outputDot ++ "\"."
+      writeFile outputDot $ graphviz graph "Etage" (8.27, 11.69) (1, 1) Landscape
+    _                          -> return ()
+  
+  putStrLn $ "Graph contains " ++ show graphSize ++ " nodes."
+  
+  before <- getPOSIXTime
+  let lazyPaths = dijkstraShortestPaths graph graphSize
+      !paths    = lazyPaths `deepseq` lazyPaths
+  after <- getPOSIXTime
+  putStrLn $ "Dijkstra search time for shortest paths: " ++ show (after - before)
+
+  incubate $ do
+    nerveTest <- (growNeuron :: NerveOnlyFor (TestNeuron String Double)) (\o -> o { graphSize, knownPaths = paths })
+    pathsNerves <- shortestPaths graph
+    
+    mapM_ (`attachTo` [TranslatableFor nerveTest]) $ M.elems pathsNerves
+    
+    sendTopologyChange pathsNerves
+
+forceStrictGraph :: (NFData a, NFData b, Graph gr) => gr a b -> IO ()
+forceStrictGraph g = labNodes g `deepseq` labEdges g `deepseq` return ()
+
+dijkstraShortestPaths :: forall gr a b. (Graph gr, Bounded b, Real b) => gr a b -> Int -> Array (Node, Node) (b, [Node])
+dijkstraShortestPaths graph graphSize = runSTArray buildPaths
+  where buildPaths :: ST s (STArray s (Node, Node) (b, [Node]))
+        buildPaths = do
+          arr <- newArray ((1, 1), (graphSize, graphSize)) (maxBound, [])
+          forM_ (nodes graph) $ \sourceNode ->
+            forM_ (spTree sourceNode graph) $ \(LP (n@(node, len):ns)) ->
+              writeArray arr (sourceNode, node) (len, reverse . map fst $ n:ns)
+          return arr
