diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2008-2016, Balazs Komuves
+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 names of the copyright holders nor the names of the 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 THE COPYRIGHT OWNER 
+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.
+
diff --git a/Math/Combinat.hs b/Math/Combinat.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat.hs
@@ -0,0 +1,76 @@
+
+-- | A collection of functions to generate, manipulate,
+-- visualize and count combinatorial objects like partitions, 
+-- compositions, permutations, braids, Young tableaux, 
+-- lattice paths, various tree structures, etc etc.
+--
+-- 
+-- See also the @combinat-diagrams@ library for generating
+-- graphical representations of (some of) these structure using 
+-- the @diagrams@ library (<http://projects.haskell.org/diagrams>).
+--
+--
+-- The long-term goals are 
+--
+--  (1) generate most of the standard structures;
+-- 
+--  (2) manipulate these structures;
+--
+--  (3) visualize these structures;
+--
+--  (4) the generation should be efficient; 
+--
+--  (5) to be able to enumerate the structures 
+--      with constant memory usage;
+--
+--  (6) to be able to randomly sample from them;
+--   
+--  (7) finally, to be a repository of algorithms.
+--
+--
+-- The short-term goal is simply to generate 
+-- and manipulate many interesting structures.
+--
+--
+-- Naming conventions (subject to change): 
+--
+--  * prime suffix: additional constrains, typically more general;
+--
+--  * underscore prefix: use plain lists instead of other types with 
+--    enforced invariants;
+--
+--  * \"random\" prefix: generates random objects 
+--    (typically with uniform distribution); 
+--
+--  * \"count\" prefix: counting functions.
+--
+--
+-- This module re-exports the most commonly used modules.
+--
+
+module Math.Combinat 
+  ( module Math.Combinat.Numbers
+  , module Math.Combinat.Sign
+  , module Math.Combinat.Sets
+  , module Math.Combinat.Tuples
+  , module Math.Combinat.Compositions
+  , module Math.Combinat.Partitions
+  , module Math.Combinat.Permutations
+  , module Math.Combinat.Tableaux
+  , module Math.Combinat.Trees
+  , module Math.Combinat.LatticePaths
+  , module Math.Combinat.ASCII
+  ) 
+  where
+
+import Math.Combinat.Numbers
+import Math.Combinat.Sign
+import Math.Combinat.Sets
+import Math.Combinat.Tuples
+import Math.Combinat.Compositions
+import Math.Combinat.Partitions
+import Math.Combinat.Permutations
+import Math.Combinat.Tableaux
+import Math.Combinat.Trees
+import Math.Combinat.LatticePaths
+import Math.Combinat.ASCII
diff --git a/Math/Combinat/ASCII.hs b/Math/Combinat/ASCII.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/ASCII.hs
@@ -0,0 +1,438 @@
+
+-- | A mini-DSL for ASCII drawing of structures.
+--
+--
+-- From some structures there is also Graphviz and\/or @diagrams@ 
+-- (<http://projects.haskell.org/diagrams>) visualization support 
+-- (the latter in the separate libray @combinat-diagrams@).
+--
+
+module Math.Combinat.ASCII where
+
+--------------------------------------------------------------------------------
+
+import Data.Char ( isSpace )
+import Data.List ( transpose , intercalate )
+
+import Math.Combinat.Helper
+
+--------------------------------------------------------------------------------
+-- * The basic ASCII type
+
+-- | The type of a (rectangular) ASCII figure. 
+-- Internally it is a list of lines of the same length plus the size.
+--
+-- Note: The Show instance is pretty-printing, so that it\'s convenient in ghci.
+--
+data ASCII = ASCII 
+  { asciiSize  :: (Int,Int) 
+  , asciiLines :: [String]
+  }
+
+-- | A type class to have a simple way to draw things 
+class DrawASCII a where
+  ascii :: a -> ASCII
+
+instance Show ASCII where
+  show = asciiString
+
+-- | An empty (0x0) rectangle
+emptyRect :: ASCII
+emptyRect = ASCII (0,0) []
+
+asciiXSize, asciiYSize :: ASCII -> Int
+asciiXSize = fst . asciiSize
+asciiYSize = snd . asciiSize
+
+asciiString :: ASCII -> String
+asciiString (ASCII sz ls) = unlines ls
+
+printASCII :: ASCII -> IO ()
+printASCII = putStrLn . asciiString
+
+asciiFromLines :: [String] -> ASCII
+asciiFromLines ls = ASCII (x,y) (map f ls) where
+  y   = length ls
+  x   = maximum (map length ls)
+  f l = l ++ replicate (x - length l) ' '
+
+asciiFromString :: String -> ASCII
+asciiFromString = asciiFromLines . lines
+
+--------------------------------------------------------------------------------
+-- * Alignment
+
+-- | Horizontal alignment
+data HAlign 
+  = HLeft 
+  | HCenter 
+  | HRight 
+  deriving (Eq,Show)
+
+-- | Vertical alignment
+data VAlign 
+  = VTop 
+  | VCenter 
+  | VBottom 
+  deriving (Eq,Show)
+
+data Alignment = Align HAlign VAlign
+
+--------------------------------------------------------------------------------
+-- * Separators
+
+-- | Horizontal separator
+data HSep 
+  = HSepEmpty           -- ^ empty separator
+  | HSepSpaces Int      -- ^ @n@ spaces
+  | HSepString String   -- ^ some custom string, eg. @\" | \"@
+  deriving Show
+
+hSepSize :: HSep -> Int
+hSepSize hsep = case hsep of
+  HSepEmpty    -> 0
+  HSepSpaces k -> k
+  HSepString s -> length s
+
+hSepString :: HSep -> String
+hSepString hsep = case hsep of
+  HSepEmpty    -> ""
+  HSepSpaces k -> replicate k ' '
+  HSepString s -> s
+
+-- | Vertical separator
+data VSep 
+  = VSepEmpty           -- ^ empty separator
+  | VSepSpaces Int      -- ^ @n@ spaces
+  | VSepString [Char]   -- ^ some custom list of characters, eg. @\" - \"@ (the characters are interpreted as below each other)
+  deriving Show
+
+vSepSize :: VSep -> Int
+vSepSize vsep = case vsep of
+  VSepEmpty    -> 0
+  VSepSpaces k -> k
+  VSepString s -> length s
+
+vSepString :: VSep -> [Char]
+vSepString vsep = case vsep of
+  VSepEmpty    -> []
+  VSepSpaces k -> replicate k ' '
+  VSepString s -> s
+                                        
+--------------------------------------------------------------------------------
+-- * Concatenation
+
+-- | Horizontal append, centrally aligned, no separation.
+(|||) :: ASCII -> ASCII -> ASCII
+(|||) p q = hCatWith VCenter HSepEmpty [p,q]
+
+-- | Vertical append, centrally aligned, no separation.
+(===) :: ASCII -> ASCII -> ASCII
+(===) p q = vCatWith HCenter VSepEmpty [p,q]
+
+-- | Horizontal concatenation, top-aligned, no separation
+hCatTop :: [ASCII] -> ASCII
+hCatTop = hCatWith VTop HSepEmpty
+
+-- | Horizontal concatenation, bottom-aligned, no separation
+hCatBot :: [ASCII] -> ASCII
+hCatBot = hCatWith VBottom HSepEmpty
+
+-- | Vertical concatenation, left-aligned, no separation
+vCatLeft :: [ASCII] -> ASCII
+vCatLeft = vCatWith HLeft VSepEmpty
+
+-- | Vertical concatenation, right-aligned, no separation
+vCatRight :: [ASCII] -> ASCII
+vCatRight = vCatWith HRight VSepEmpty
+
+-- | General horizontal concatenation
+hCatWith :: VAlign -> HSep -> [ASCII] -> ASCII
+hCatWith valign hsep rects = ASCII (x',maxy) final where
+  n    = length rects
+  maxy = maximum [ y | ASCII (_,y) _ <- rects ]
+  xsz  =         [ x | ASCII (x,_) _ <- rects ]
+  sep   = hSepString hsep
+  sepx  = length sep
+  rects1 = map (vExtendTo valign maxy) rects
+  x' = sum' xsz + (n-1)*sepx
+  final = map (intercalate sep) $ transpose (map asciiLines rects1)
+
+-- | General vertical concatenation
+vCatWith :: HAlign -> VSep -> [ASCII] -> ASCII
+vCatWith halign vsep rects = ASCII (maxx,y') final where
+  n    = length rects
+  maxx = maximum [ x | ASCII (x,_) _ <- rects ]
+  ysz  =         [ y | ASCII (_,y) _ <- rects ]
+  sepy    = vSepSize vsep
+  fullsep = transpose (replicate maxx $ vSepString vsep) :: [String]
+  rects1  = map (hExtendTo halign maxx) rects
+  y'    = sum' ysz + (n-1)*sepy
+  final = intercalate fullsep $ map asciiLines rects1
+
+--------------------------------------------------------------------------------
+-- * Padding
+
+-- | Horizontally pads with the given number of spaces, on both sides
+hPad :: Int -> ASCII -> ASCII
+hPad k (ASCII (x,y) ls) = ASCII (x+2*k,y) (map f ls) where
+  f l = pad ++ l ++ pad 
+  pad = replicate k ' '
+
+-- | Vertically pads with the given number of empty lines, on both sides
+vPad :: Int -> ASCII -> ASCII
+vPad k (ASCII (x,y) ls) = ASCII (x,y+2*k) (pad ++ ls ++ pad) where
+  pad = replicate k (replicate x ' ')
+
+-- | Pads by single empty lines vertically and two spaces horizontally
+pad :: ASCII -> ASCII
+pad = vPad 1 . hPad 2 
+
+--------------------------------------------------------------------------------
+-- * Extension
+
+-- | Extends an ASCII figure with spaces horizontally to the given width.
+-- Note: the alignment is the alignment of the original picture in the new bigger picture!
+hExtendTo :: HAlign -> Int -> ASCII -> ASCII
+hExtendTo halign n0 rect@(ASCII (x,y) ls) = hExtendWith halign (max n0 x - x) rect
+  
+-- | Extends an ASCII figure with spaces vertically to the given height.
+-- Note: the alignment is the alignment of the original picture in the new bigger picture!
+vExtendTo :: VAlign -> Int -> ASCII -> ASCII
+vExtendTo valign n0 rect@(ASCII (x,y) ls) = vExtendWith valign (max n0 y - y) rect
+
+-- | Extend horizontally with the given number of spaces.
+hExtendWith :: HAlign -> Int -> ASCII -> ASCII
+hExtendWith alignment d (ASCII (x,y) ls) = ASCII (x+d,y) (map f ls) where
+  f l = case alignment of
+    HLeft   -> l ++ replicate d ' '   
+    HRight  -> replicate d ' ' ++ l
+    HCenter -> replicate a ' ' ++ l ++ replicate (d-a) ' ' 
+  a = div d 2
+
+-- | Extend vertically with the given number of empty lines.
+vExtendWith :: VAlign -> Int -> ASCII -> ASCII
+vExtendWith valign d (ASCII (x,y) ls) = ASCII (x,y+d) (f ls) where
+  f ls = case valign of
+    VTop     -> ls ++ replicate d emptyline   
+    VBottom  -> replicate d emptyline ++ ls
+    VCenter  -> replicate a emptyline ++ ls ++ replicate (d-a) emptyline
+  a = div d 2
+  emptyline = replicate x ' '
+
+-- | Horizontal indentation
+hIndent :: Int -> ASCII -> ASCII
+hIndent d = hExtendWith HRight d
+
+-- | Vertical indentation
+vIndent :: Int -> ASCII -> ASCII
+vIndent d = vExtendWith VBottom d
+
+--------------------------------------------------------------------------------
+-- * Cutting
+
+-- | Cuts the given number of columns from the picture. 
+-- The alignment is the alignment of the /picture/, not the cuts.
+--
+-- This should be the (left) inverse of 'hExtendWith'.
+hCut :: HAlign -> Int -> ASCII -> ASCII
+hCut halign k (ASCII (x,y) ls) = ASCII (x',y) (map f ls) where
+  x' = max 0 (x-k)
+  f  = case halign of
+    HLeft   -> reverse . drop  k    . reverse
+    HCenter -> reverse . drop (k-a) . reverse . drop a
+    HRight  -> drop k 
+  a = div k 2
+
+-- | Cuts the given number of rows from the picture. 
+-- The alignment is the alignment of the /picture/, not the cuts.
+--
+-- This should be the (left) inverse of 'vExtendWith'.
+vCut :: VAlign -> Int -> ASCII -> ASCII
+vCut valign k (ASCII (x,y) ls) = ASCII (x,y') (g ls) where
+  y' = max 0 (y-k)
+  g  = case valign of
+    VTop    -> reverse . drop  k    . reverse
+    VCenter -> reverse . drop (k-a) . reverse . drop a
+    VBottom -> drop k 
+  a = div k 2
+
+--------------------------------------------------------------------------------
+-- * Pasting
+
+-- | Pastes the first ASCII graphics onto the second, keeping the second one's dimension
+-- (that is, overlapping parts of the first one are ignored). 
+-- The offset is relative to the top-left corner of the second picture.
+-- Spaces at treated as transparent.
+--
+-- Example:
+--
+-- > tabulate (HCenter,VCenter) (HSepSpaces 2, VSepSpaces 1)
+-- >  [ [ caption (show (x,y)) $
+-- >      pasteOnto (x,y) (filledBox '@' (4,3)) (asciiBox (7,5))
+-- >    | x <- [-4..7] ] 
+-- >  | y <- [-3..5] ]
+--
+pasteOnto :: (Int,Int) -> ASCII -> ASCII -> ASCII
+pasteOnto = pasteOnto' isSpace 
+
+-- | Pastes the first ASCII graphics onto the second, keeping the second one's dimension.
+-- The first argument specifies the transparency condition (on the first picture).
+-- The offset is relative to the top-left corner of the second picture.
+-- 
+pasteOnto' 
+  :: (Char -> Bool)      -- ^ transparency condition
+  -> (Int,Int)           -- ^ offset relative to the top-left corner of the second picture
+  -> ASCII               -- ^ picture to paste
+  -> ASCII               -- ^ picture to paste onto
+  -> ASCII
+pasteOnto' transparent (xpos,ypos) small big = new where
+  new = ASCII (xbig,ybig) lines'
+  (xbig,ybig) = asciiSize  big
+  bigLines    = asciiLines big
+  small'      = (if (ypos>=0) then vExtendWith VBottom ypos else vCut VBottom (-ypos))
+              $ (if (xpos>=0) then hExtendWith HRight  xpos else hCut HRight  (-xpos))
+              $ small
+  smallLines  = asciiLines small'
+  lines'  = zipWith f bigLines (smallLines ++ repeat "")
+  f bl sl = zipWith g bl (sl ++ repeat ' ')
+  g b  s  = if transparent s then b else s
+
+-- | A version of 'pasteOnto' where we can specify the corner of the second picture
+-- to which the offset is relative:
+--
+-- > pasteOntoRel (HLeft,VTop) == pasteOnto
+--
+pasteOntoRel :: (HAlign,VAlign) -> (Int,Int) -> ASCII -> ASCII -> ASCII
+pasteOntoRel = pasteOntoRel' isSpace
+
+pasteOntoRel' :: (Char -> Bool) -> (HAlign,VAlign) -> (Int,Int) -> ASCII -> ASCII -> ASCII
+pasteOntoRel' transparent (halign,valign) (xpos,ypos) small big = new where
+  new = pasteOnto' transparent (xpos',ypos') small big 
+  (xsize,ysize) = asciiSize big
+  xpos' = case halign of
+    HLeft   -> xpos
+    HCenter -> xpos + div xsize 2
+    HRight  -> xpos +     xsize
+  ypos' = case valign of
+    VTop    -> ypos
+    VCenter -> ypos + div ysize 2
+    VBottom -> ypos +     ysize
+
+--------------------------------------------------------------------------------
+-- * Tabulate
+
+-- | Tabulates the given matrix of pictures. Example:
+--
+-- > tabulate (HCenter, VCenter) (HSepSpaces 2, VSepSpaces 1)
+-- >   [ [ asciiFromLines [ "x=" ++ show x , "y=" ++ show y ] | x<-[7..13] ] 
+-- >   | y<-[98..102] ]
+--
+tabulate :: (HAlign,VAlign) -> (HSep,VSep) -> [[ASCII]] -> ASCII
+tabulate (halign,valign) (hsep,vsep) rects0 = final where
+  n = length rects0
+  m = maximum (map length rects0)
+  rects1 = map (\rs -> rs ++ replicate (m - length rs) emptyRect) rects0
+  ys = map (\rs -> maximum (map asciiYSize rs)) rects1
+  xs = map (\rs -> maximum (map asciiXSize rs)) (transpose rects1)
+  rects2 = map (\rs -> [      hExtendTo halign x  r  | (x,r ) <- zip xs rs     ]) rects1
+  rects3 =             [ map (vExtendTo valign y) rs | (y,rs) <- zip ys rects2 ]  
+  final  = vCatWith HLeft vsep 
+         $ map (hCatWith VTop hsep) rects3
+
+-- | Order of elements in a matrix
+data MatrixOrder 
+  = RowMajor
+  | ColMajor
+  deriving (Eq,Ord,Show,Read)
+
+-- | Automatically tabulates ASCII rectangles.
+--
+autoTabulate 
+  :: MatrixOrder      -- ^ whether to use row-major or column-major ordering of the elements
+  -> Either Int Int   -- ^ @(Right x)@ creates x columns, while @(Left y)@ creates y rows
+  -> [ASCII]          -- ^ list of ASCII rectangles
+  -> ASCII
+autoTabulate mtxorder ei list = final where
+  
+  final = tabulate (HLeft,VBottom) (HSepSpaces 2,VSepSpaces 1) rects 
+
+  n = length list
+
+  rects = case ei of
+
+    Left y  -> case mtxorder of
+                 ColMajor -> transpose (parts y list)
+                 RowMajor -> invparts y list
+
+    Right x -> case mtxorder of
+                 ColMajor -> transpose (invparts x list)
+                 RowMajor -> parts x list
+
+  transposeIf b = if b then transpose else id
+
+  -- chops into parts (the last one can be smaller)
+  parts d = go where
+    go [] = []
+    go xs = take d xs : go (drop d xs)
+
+  invparts d xs = parts' ds xs where
+    (q,r) = divMod n d
+    ds = replicate r (q+1) ++ replicate (d-r) q
+
+  parts' ds xs = go ds xs where
+    go _  [] = []                                      
+    go [] _  = []
+    go (d:ds) xs = take d xs : go ds (drop d xs)
+
+--------------------------------------------------------------------------------
+-- * Captions
+
+-- | Adds a caption to the bottom, with default settings.
+caption :: String -> ASCII -> ASCII
+caption = caption' False HLeft
+
+-- | Adds a caption to the bottom. The @Bool@ flag specifies whether to add an empty between 
+-- the caption and the figure
+caption' :: Bool -> HAlign -> String -> ASCII -> ASCII
+caption' emptyline halign str rect = vCatWith halign sep [rect,capt] where
+  sep  = if emptyline then VSepSpaces 1 else VSepEmpty 
+  capt = asciiFromString str
+
+--------------------------------------------------------------------------------
+-- * Ready-made boxes
+
+-- | An ASCII border box of the given size 
+asciiBox :: (Int,Int) -> ASCII
+asciiBox (x,y) = ASCII (max x 2, max y 2) (h : replicate (y-2) m ++ [h]) where
+  h = "+" ++ replicate (x-2) '-' ++ "+"
+  m = "|" ++ replicate (x-2) ' ' ++ "|"
+
+-- | An \"rounded\" ASCII border box of the given size
+roundedAsciiBox :: (Int,Int) -> ASCII
+roundedAsciiBox (x,y) = ASCII (max x 2, max y 2) (a : replicate (y-2) m ++ [b]) where
+  a = "/"  ++ replicate (x-2) '-' ++ "\\"
+  m = "|"  ++ replicate (x-2) ' ' ++ "|"
+  b = "\\" ++ replicate (x-2) '-' ++ "/"
+
+-- | A box simply filled with the given character
+filledBox :: Char -> (Int,Int) -> ASCII
+filledBox c (x0,y0) = asciiFromLines $ replicate y (replicate x c) where
+  x = max 0 x0
+  y = max 0 y0
+
+-- | A box of spaces
+transparentBox :: (Int,Int) -> ASCII
+transparentBox = filledBox ' '
+
+--------------------------------------------------------------------------------
+-- * Testing \/ miscellanea
+
+-- | An integer
+asciiNumber :: Int -> ASCII
+asciiNumber = asciiShow
+
+asciiShow :: Show a => a -> ASCII
+asciiShow = asciiFromLines . (:[]) . show
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Classes.hs b/Math/Combinat/Classes.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Classes.hs
@@ -0,0 +1,66 @@
+
+-- | Type classes for some common properties shared by different objects
+
+{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
+module Math.Combinat.Classes where
+
+--------------------------------------------------------------------------------
+
+-- | Emptyness
+class CanBeEmpty a where
+  isEmpty :: a -> Bool
+  empty   :: a
+
+--------------------------------------------------------------------------------
+-- * Partitions
+
+-- | Number of parts
+class HasNumberOfParts a where
+  numberOfParts :: a -> Int
+
+--------------------------------------------------------------------------------
+
+class HasWidth a where
+  width :: a -> Int
+
+class HasHeight a where
+  height :: a -> Int
+
+--------------------------------------------------------------------------------
+
+-- | Weight (of partitions, tableaux, etc)
+class HasWeight a where
+  weight :: a -> Int
+
+--------------------------------------------------------------------------------
+
+-- | Duality (of partitions, tableaux, etc)
+class HasDuality a where
+  dual :: a -> a
+
+--------------------------------------------------------------------------------
+-- * Tableau
+
+-- | Shape (of tableaux, skew tableaux)
+class HasShape a s | a -> s where
+  shape :: a -> s
+
+--------------------------------------------------------------------------------
+-- * Trees
+
+-- | Number of nodes (of trees)
+class HasNumberOfNodes t where
+  numberOfNodes :: t -> Int
+
+-- | Number of leaves (of trees)
+class HasNumberOfLeaves t where
+  numberOfLeaves :: t -> Int
+
+--------------------------------------------------------------------------------
+-- * Permutations
+
+-- | Number of cycles (of partitions)
+class HasNumberOfCycles p where
+  numberOfCycles :: p -> Int
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Compositions.hs b/Math/Combinat/Compositions.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Compositions.hs
@@ -0,0 +1,109 @@
+
+-- | Compositions. 
+--
+-- See eg. <http://en.wikipedia.org/wiki/Composition_%28combinatorics%29>
+--
+
+module Math.Combinat.Compositions where
+
+--------------------------------------------------------------------------------
+
+import System.Random
+
+import Math.Combinat.Sets    ( randomChoice )
+import Math.Combinat.Numbers ( factorial , binomial )
+import Math.Combinat.Helper
+
+--------------------------------------------------------------------------------
+-- * generating all compositions
+
+-- | A /composition/ of an integer @n@ into @k@ parts is an ordered @k@-tuple of nonnegative (sometimes positive) integers
+-- whose sum is @n@.
+type Composition = [Int]
+
+-- | Compositions fitting into a given shape and having a given degree.
+--   The order is lexicographic, that is, 
+--
+-- > sort cs == cs where cs = compositions' shape k
+--
+compositions'  
+  :: [Int]         -- ^ shape
+  -> Int           -- ^ sum
+  -> [[Int]]
+compositions' [] 0 = [[]]
+compositions' [] _ = []
+compositions' shape@(s:ss) n = 
+  [ x:xs | x <- [0..min s n] , xs <- compositions' ss (n-x) ] 
+
+countCompositions' :: [Int] -> Int -> Integer
+countCompositions' [] 0 = 1
+countCompositions' [] _ = 0
+countCompositions' shape@(s:ss) n = sum 
+  [ countCompositions' ss (n-x) | x <- [0..min s n] ] 
+
+-- | All positive compositions of a given number (filtrated by the length). 
+-- Total number of these is @2^(n-1)@
+allCompositions1 :: Int -> [[Composition]]
+allCompositions1 n = map (\d -> compositions1 d n) [1..n] 
+
+-- | All compositions fitting into a given shape.
+allCompositions' :: [Int] -> [[Composition]]
+allCompositions' shape = map (compositions' shape) [0..d] where d = sum shape
+
+-- | Nonnegative compositions of a given length.
+compositions 
+  :: Integral a 
+  => a       -- ^ length
+  -> a       -- ^ sum
+  -> [[Int]]
+compositions len' d' = compositions' (replicate len d) d where
+  len = fromIntegral len'
+  d   = fromIntegral d'
+
+-- | # = \\binom { len+d-1 } { len-1 }
+countCompositions :: Integral a => a -> a -> Integer
+countCompositions len d = binomial (len+d-1) (len-1)
+
+-- | Positive compositions of a given length.
+compositions1  
+  :: Integral a 
+  => a       -- ^ length
+  -> a       -- ^ sum
+  -> [[Int]]
+compositions1 len d 
+  | len > d   = []
+  | otherwise = map plus1 $ compositions len (d-len)
+  where
+    plus1 = map (+1)
+    -- len = fromIntegral len'
+    -- d   = fromIntegral d'
+
+countCompositions1 :: Integral a => a -> a -> Integer
+countCompositions1 len d = countCompositions len (d-len)
+
+--------------------------------------------------------------------------------
+-- * random compositions
+
+-- | @randomComposition k n@ returns a uniformly random composition 
+-- of the number @n@ as an (ordered) sum of @k@ /nonnegative/ numbers
+randomComposition :: RandomGen g => Int -> Int -> g -> ([Int],g)
+randomComposition k n g0 = 
+  if k<1 || n<0 
+    then error "randomComposition: k should be positive, and n should be nonnegative" 
+    else (comp, g1) 
+  where
+    (cs,g1) = randomChoice (k-1) (n+k-1) g0
+    comp = pairsWith (\x y -> y-x-1) (0 : cs ++ [n+k])
+  
+-- | @randomComposition1 k n@ returns a uniformly random composition 
+-- of the number @n@ as an (ordered) sum of @k@ /positive/ numbers
+randomComposition1 :: RandomGen g => Int -> Int -> g -> ([Int],g)
+randomComposition1 k n g0 = 
+  if k<1 || n<k 
+    then error "randomComposition1: we require 0 < k <= n" 
+    else (comp, g1) 
+  where
+    (cs,g1) = randomComposition k (n-k) g0 
+    comp = map (+1) cs
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Groups/Braid.hs b/Math/Combinat/Groups/Braid.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Groups/Braid.hs
@@ -0,0 +1,744 @@
+
+-- | Braids. See eg. <https://en.wikipedia.org/wiki/Braid_group>
+--
+--
+-- Based on: 
+--
+--  * Joan S. Birman, Tara E. Brendle: BRAIDS - A SURVEY
+--    <https://www.math.columbia.edu/~jb/Handbook-21.pdf>
+--
+--
+-- Note: This module GHC 7.8, since we use type-level naturals
+-- to parametrize the 'Braid' type.
+--
+
+
+{-# LANGUAGE 
+      CPP, BangPatterns, 
+      ScopedTypeVariables, ExistentialQuantification,
+      DataKinds, KindSignatures, Rank2Types,
+      TypeOperators, TypeFamilies,
+      StandaloneDeriving #-}
+
+module Math.Combinat.Groups.Braid where
+
+--------------------------------------------------------------------------------
+
+import Data.Proxy
+import GHC.TypeLits
+
+import Control.Monad
+
+import Data.List ( mapAccumL , foldl' )
+
+import Data.Array.Unboxed
+import Data.Array.ST
+import Data.Array.IArray
+import Data.Array.MArray
+import Data.Array.Unsafe
+import Data.Array.Base
+
+import Control.Monad.ST
+
+import System.Random
+
+import Math.Combinat.ASCII
+import Math.Combinat.Sign
+import Math.Combinat.Helper
+import Math.Combinat.TypeLevel
+import Math.Combinat.Numbers.Series
+
+import Math.Combinat.Permutations ( Permutation(..) )
+import qualified Math.Combinat.Permutations as P
+
+--------------------------------------------------------------------------------
+-- * Artin generators
+
+-- | A standard Artin generator of a braid: @Sigma i@ represents twisting 
+-- the neighbour strands @i@ and @(i+1)@, such that strand @i@ goes /under/ strand @(i+1)@.
+--
+-- Note: The strands are numbered @1..n@.
+data BrGen
+  = Sigma    !Int         -- ^ @i@ goes under @(i+1)@
+  | SigmaInv !Int         -- ^ @i@ goes above @(i+1)@
+  deriving (Eq,Ord,Show)
+ 
+-- | The strand (more precisely, the first of the two strands) the generator twistes
+brGenIdx :: BrGen -> Int
+brGenIdx g = case g of
+  Sigma    i -> i
+  SigmaInv i -> i
+
+brGenSign :: BrGen -> Sign
+brGenSign g = case g of
+  Sigma    _ -> Plus
+  SigmaInv _ -> Minus
+
+brGenSignIdx :: BrGen -> (Sign,Int)        
+brGenSignIdx g = case g of
+  Sigma    i -> (Plus ,i)
+  SigmaInv i -> (Minus,i) 
+
+-- | The inverse of a braid generator
+invBrGen :: BrGen -> BrGen
+invBrGen  g = case g of
+  Sigma    i -> SigmaInv i
+  SigmaInv i -> Sigma    i
+
+--------------------------------------------------------------------------------
+-- * The braid type
+  
+-- | The braid group @B_n@ on @n@ strands.
+-- The number @n@ is encoded as a type level natural in the type parameter.
+--
+-- Braids are represented as words in the standard generators and their
+-- inverses.
+newtype Braid (n :: Nat) = Braid [BrGen] deriving (Show)
+
+-- | The number of strands in the braid
+numberOfStrands :: KnownNat n => Braid n -> Int
+numberOfStrands = fromInteger . natVal . braidProxy where                                                         
+  braidProxy :: Braid n -> Proxy n
+  braidProxy _ = Proxy
+
+-- | Sometimes we want to hide the type-level parameter @n@, for example when
+-- dynamically creating braids whose size is known only at runtime.
+data SomeBraid = forall n. KnownNat n => SomeBraid (Braid n)
+
+someBraid :: Int -> (forall (n :: Nat). KnownNat n => Braid n) -> SomeBraid
+someBraid n polyBraid = 
+  case snat of    
+    SomeNat pxy -> SomeBraid (asProxyTypeOf1 polyBraid pxy)
+  where
+    snat = case someNatVal (fromIntegral n :: Integer) of
+      Just sn -> sn
+      Nothing -> error "someBraid: input is not a natural number"
+
+withSomeBraid :: SomeBraid -> (forall n. KnownNat n => Braid n -> a) -> a
+withSomeBraid sbraid f = case sbraid of SomeBraid braid -> f braid
+
+mkBraid :: (forall n. KnownNat n => Braid n -> a) -> Int -> [BrGen] -> a
+mkBraid f n w = y where
+  sb = someBraid n (Braid w)
+  y  = withSomeBraid sb f
+
+withBraid 
+  :: Int
+  -> (forall (n :: Nat). KnownNat n => Braid n)
+  -> (forall (n :: Nat). KnownNat n => Braid n -> a) 
+  -> a
+withBraid n polyBraid f = 
+  case snat of    
+    SomeNat pxy -> f (asProxyTypeOf1 polyBraid pxy)
+  where
+    snat = case someNatVal (fromIntegral n :: Integer) of
+      Just sn -> sn
+      Nothing -> error "withBraid: input is not a natural number"
+
+--------------------------------------------------------------------------------
+
+braidWord :: Braid n -> [BrGen]
+braidWord (Braid gs) = gs
+
+braidWordLength :: Braid n -> Int
+braidWordLength (Braid gs) = length gs
+
+-- | Embeds a smaller braid group into a bigger braid group    
+extend :: (n1 <= n2) => Braid n1 -> Braid n2
+extend (Braid gs) = Braid gs
+
+-- | Apply \"free reduction\" to the word, that is, iteratively remove @sigma_i sigma_i^-1@ pairs.
+-- The resulting braid is clearly equivalent to the original.
+freeReduceBraidWord :: Braid n -> Braid n
+freeReduceBraidWord (Braid orig) = Braid (loop orig) where
+
+  loop w = case reduceStep w of
+    Nothing -> w
+    Just w' -> loop w'
+  
+  reduceStep :: [BrGen] -> Maybe [BrGen]
+  reduceStep = go False where    
+    go !changed w = case w of
+      (Sigma    x : SigmaInv y : rest) | x==y   -> go True rest
+      (SigmaInv x : Sigma    y : rest) | x==y   -> go True rest
+      (this : rest)                             -> liftM (this:) $ go changed rest
+      _                                         -> if changed then Just w else Nothing
+
+--------------------------------------------------------------------------------
+-- * Some specific braids
+
+-- | The braid generator @sigma_i@ as a braid
+sigma :: KnownNat n => Int -> Braid (n :: Nat)
+sigma k = braid where
+  braid = if k > 0 && k < numberOfStrands braid
+    then Braid [Sigma k]
+    else error "sigma: braid generator index out of range"
+
+-- | The braid generator @sigma_i^(-1)@ as a braid
+sigmaInv :: KnownNat n => Int -> Braid (n :: Nat)
+sigmaInv k = braid where
+  braid = if k > 0 && k < numberOfStrands braid
+    then Braid [SigmaInv k]
+    else error "sigma: braid generator index out of range"
+
+-- | @doubleSigma s t@ (for s<t)is the generator @sigma_{s,t}@ in Birman-Ko-Lee's
+-- \"new presentation\". It twistes the strands @s@ and @t@ while going over all
+-- other strands. For @t==s+1@ we get back @sigma s@
+-- 
+doubleSigma :: KnownNat n => Int -> Int -> Braid (n :: Nat)
+doubleSigma s t = braid where
+  n = numberOfStrands braid
+  braid
+    | s < 1 || s > n   = error "doubleSigma: s index out of range"
+    | t < 1 || t > n   = error "doubleSigma: t index out of range"
+    | s >= t           = error "doubleSigma: s >= t"
+    | otherwise        = Braid $
+       [ Sigma i | i<-[t-1,t-2..s] ] ++ [ SigmaInv i | i<-[s+1..t-1] ]
+
+-- | @positiveWord [2,5,1]@ is shorthand for the word @sigma_2*sigma_5*sigma_1@.
+positiveWord :: KnownNat n => [Int] -> Braid (n :: Nat)
+positiveWord idxs = braid where
+  braid = Braid (map gen idxs) 
+  n     = numberOfStrands braid
+  gen i = if i>0 && i<n then Sigma i else error "positiveWord: index out of range"
+       
+-- | The (positive) half-twist of all the braid strands, usually denoted by @Delta@.
+halfTwist :: KnownNat n => Braid n
+halfTwist = braid where
+  braid = Braid $ map Sigma $ _halfTwist n 
+  n     = numberOfStrands braid
+
+-- | The untyped version of 'halfTwist'
+_halfTwist :: Int -> [Int]
+_halfTwist n = gens where
+  gens  = concat [ sub k | k<-[1..n-1] ]
+  sub k = [ j | j<-[n-1,n-2..k] ]
+  
+-- | Synonym for 'halfTwist'
+theGarsideBraid :: KnownNat n => Braid n
+theGarsideBraid = halfTwist 
+
+-- | The inner automorphism defined by @tau(X) = Delta^-1 X Delta@, 
+-- where @Delta@ is the positive half-twist.
+-- 
+-- This sends each generator @sigma_j@ to @sigma_(n-j)@.
+--
+tau :: KnownNat n => Braid n -> Braid n
+tau braid@(Braid gens) = Braid (map f gens) where
+  n = numberOfStrands braid
+  f (Sigma    i) = Sigma    (n-i)
+  f (SigmaInv i) = SigmaInv (n-i)
+
+
+-- | The involution @tau@ on permutations (permutation braids)
+--
+tauPerm :: Permutation -> Permutation
+tauPerm (Permutation arr) = Permutation $ listArray (1,n) [ (n+1) - arr!(n-i) | i<-[0..n-1] ] where
+  (1,n) = bounds arr
+
+--------------------------------------------------------------------------------
+-- * Group operations
+
+-- | The trivial braid
+identity :: Braid n
+identity = Braid []
+
+-- | The inverse of a braid. Note: we do not perform reduction here,
+-- as a word is reduced if and only if its inverse is reduced.
+inverse :: Braid n -> Braid n
+inverse = Braid . reverse . map invBrGen . braidWord
+
+-- | Composes two braids, doing free reduction on the result 
+-- (that is, removing @(sigma_k * sigma_k^-1)@ pairs@)
+compose :: Braid n -> Braid n -> Braid n
+compose (Braid gs) (Braid hs) = freeReduceBraidWord $ Braid (gs++hs)
+
+composeMany :: [Braid n] -> Braid n
+composeMany = freeReduceBraidWord . Braid . concat . map braidWord 
+
+-- | Composes two braids without doing any reduction.
+composeDontReduce :: Braid n -> Braid n -> Braid n
+composeDontReduce (Braid gs) (Braid hs) = Braid (gs++hs)
+
+--------------------------------------------------------------------------------
+-- * Braid permutations
+
+-- | A braid is pure if its permutation is trivial
+isPureBraid :: KnownNat n => Braid n -> Bool
+isPureBraid braid = (braidPermutation braid == P.identity n) where
+  n = numberOfStrands braid
+
+-- | Returns the left-to-right permutation associated to the braid. 
+-- We follow the strands /from the left to the right/ (or from the top to the 
+-- bottom), and return the permutation taking the left side to the right side.
+--
+-- This is compatible with /right/ (standard) action of the permutations:
+-- @permuteRight (braidPermutationRight b1)@ corresponds to the left-to-right
+-- permutation of the strands; also:
+--
+-- > (braidPermutation b1) `multiply` (braidPermutation b2) == braidPermutation (b1 `compose` b2)
+--
+-- Writing the right numbering of the strands below the left numbering,
+-- we got the two-line notation of the permutation.
+--
+braidPermutation :: KnownNat n => Braid n -> Permutation
+braidPermutation braid@ (Braid gens) = perm where
+  n    = numberOfStrands braid
+  perm = _braidPermutation n (map brGenIdx gens)
+
+-- | This is an untyped version of 'braidPermutation'
+_braidPermutation :: Int -> [Int] -> Permutation
+_braidPermutation n idxs = Permutation (runSTUArray action) where
+
+  action :: forall s. ST s (STUArray s Int Int) 
+  action = do 
+    arr <- newArray_ (1,n) 
+    forM_ [1..n] $ \i -> writeArray arr i i
+    worker arr idxs
+    return arr
+    
+  worker arr = go where
+    go []     = return arr 
+    go (i:is) = do
+      a <- readArray arr  i
+      b <- readArray arr (i+1)
+      writeArray arr  i    b
+      writeArray arr (i+1) a
+      go is
+
+--------------------------------------------------------------------------------
+-- * Permutation braids
+
+-- | A positive braid word contains only positive (@Sigma@) generators.
+isPositiveBraidWord :: KnownNat n => Braid n -> Bool
+isPositiveBraidWord (Braid gs) = all (isPlus . brGenSign) gs 
+
+-- | A /permutation braid/ is a positive braid where any two strands cross
+-- at most one, and /positively/. 
+--
+isPermutationBraid :: KnownNat n => Braid n -> Bool
+isPermutationBraid braid = isPositiveBraidWord braid && crosses where
+  crosses     = and [ check i j | i<-[1..n-1], j<-[i+1..n] ] 
+  check i j   = zeroOrOne (lkMatrix ! (i,j)) 
+  zeroOrOne a = (a==1 || a==0)
+  lkMatrix    = linkingMatrix   braid
+  n           = numberOfStrands braid
+
+-- | Untyped version of 'isPermutationBraid' for positive words.
+_isPermutationBraid :: Int -> [Int] -> Bool
+_isPermutationBraid n gens = crosses where
+  crosses     = and [ check i j | i<-[1..n-1], j<-[i+1..n] ] 
+  check i j   = zeroOrOne (lkMatrix ! (i,j)) 
+  zeroOrOne a = (a==1 || a==0)
+  lkMatrix    = _linkingMatrix n $ map Sigma gens
+
+-- | For any permutation this functions returns a /permutation braid/ realizing
+-- that permutation. Note that this is not unique, so we make an arbitrary choice
+-- (except for the permutation @[n,n-1..1]@ reversing the order, in which case 
+-- the result must be the half-twist braid).
+-- 
+-- The resulting braid word will have a length at most @choose n 2@ (and will have
+-- that length only for the permutation @[n,n-1..1]@)
+--
+-- > braidPermutationRight (permutationBraid perm) == perm
+-- > isPermutationBraid    (permutationBraid perm) == True
+--
+permutationBraid :: KnownNat n => Permutation -> Braid n
+permutationBraid perm = braid where
+  n1 = numberOfStrands braid
+  n2 = P.permutationSize perm
+  braid = if n1 == n2
+    then Braid (map Sigma $ _permutationBraid perm)
+    else error $ "permutationBraid: incompatible n: " ++ show n1 ++ " vs. " ++ show n2
+
+-- | Untyped version of 'permutationBraid'
+_permutationBraid :: Permutation -> [Int]
+_permutationBraid = concat . _permutationBraid'
+
+-- | Returns the individual \"phases\" of the a permutation braid realizing the
+-- given permutation.
+_permutationBraid' :: Permutation -> [[Int]]
+_permutationBraid' perm@(Permutation arr) = runST action where
+  (1,n) = bounds arr
+
+  action :: forall s. ST s [[Int]]
+  action = do
+
+    -- cfwd = the current state of strands    : cfwd!j = where is strand #j now?
+    -- cinv = the inverse of that permutation : cinv!i = which strand is on the #i position now?
+
+    cfwd <- newArray_ (1,n) :: ST s (STUArray s Int Int)
+    cinv <- newArray_ (1,n) :: ST s (STUArray s Int Int)
+    forM_ [1..n] $ \j -> do
+      writeArray cfwd j j
+      writeArray cinv j j
+
+    let doSwap i = do     
+          a <- readArray cinv  i
+          b <- readArray cinv (i+1)
+          writeArray cinv  i    b
+          writeArray cinv (i+1) a
+
+          u <- readArray cfwd a
+          v <- readArray cfwd b
+          writeArray cfwd a v
+          writeArray cfwd b u
+
+    -- at the k-th phase, we move the (inv!k)-th strand, which is the k-th strand /on the RHS/, to correct position.
+    let worker phase
+          | phase >= n  = return []
+          | otherwise   = do
+              let tgt = (arr ! phase)
+              src <- readArray cfwd tgt
+              let this = [src-1,src-2..phase]
+              mapM_ doSwap $ this 
+              rest <- worker (phase+1)
+              return (this:rest)
+
+    worker 1
+ 
+
+-- | We compute the linking numbers between all pairs of strands:
+--
+-- > linkingMatrix braid ! (i,j) == strandLinking braid i j 
+--
+linkingMatrix :: KnownNat n => Braid n -> UArray (Int,Int) Int
+linkingMatrix braid@(Braid gens) = _linkingMatrix (numberOfStrands braid) gens where
+
+-- | Untyped version of 'linkingMatrix'
+_linkingMatrix :: Int -> [BrGen] -> UArray (Int,Int) Int
+_linkingMatrix n gens = runSTUArray action where
+
+  action :: forall s. ST s (STUArray s (Int,Int) Int)
+  action = do
+    perm <- newArray_ (1,n) :: ST s (STUArray s Int Int)
+    forM_ [1..n] $ \i -> writeArray perm i i
+    let doSwap :: Int -> ST s ()
+        doSwap i = do
+          a <- readArray perm  i
+          b <- readArray perm (i+1)
+          writeArray perm  i    b
+          writeArray perm (i+1) a
+               
+    mat <- newArray ((1,1),(n,n)) 0 :: ST s (STUArray s (Int,Int) Int)
+    let doAdd :: Int -> Int -> Int -> ST s ()
+        doAdd i j pm1 = do
+          x <- readArray mat (i,j)
+          writeArray mat (i,j) (x+pm1) 
+          writeArray mat (j,i) (x+pm1)
+       
+    forM_ gens $ \g -> do
+      let (sgn,k) = brGenSignIdx g
+      u <- readArray perm  k 
+      v <- readArray perm (k+1)
+      doAdd u v (signValue sgn)
+      doSwap k 
+        
+    return mat
+    
+    
+-- | The linking number between two strands numbered @i@ and @j@ 
+-- (numbered such on the /left/ side).
+strandLinking :: KnownNat n => Braid n -> Int -> Int -> Int
+strandLinking braid@(Braid gens) i0 j0 
+  | i0 < 1 || i0 > n  = error $ "strandLinkingNumber: invalid strand index i: " ++ show i0
+  | j0 < 1 || j0 > n  = error $ "strandLinkingNumber: invalid strand index j: " ++ show j0
+  | i0 == j0          = 0
+  | otherwise         = go i0 j0 gens
+  where
+    n = numberOfStrands braid
+    
+    go !i !j []     = 0
+    go !i !j (g:gs)  
+      | i == k   && j == k+1  = s + go (i+1) (j-1) gs
+      | j == k   && i == k+1  = s + go (i-1) (j+1) gs
+      | i == k                =     go (i+1)  j    gs
+      |             i == k+1  =     go (i-1)  j    gs
+      | j == k                =     go  i    (j+1) gs
+      |             j == k+1  =     go  i    (j-1) gs
+      | otherwise             =     go  i     j    gs
+      where
+        (sgn,k) = brGenSignIdx g
+        s = signValue sgn
+
+--------------------------------------------------------------------------------
+-- * Growth 
+
+-- | Bronfman's recursive formula for the reciprocial of the growth function 
+-- of /positive/ braids. It was already known (by Deligne) that these generating functions 
+-- are reciprocials of polynomials; Bronfman [1] gave a recursive formula for them.
+--
+-- > let count n l = length $ nub $ [ braidNormalForm w | w <- allPositiveBraidWords n l ]
+-- > let convertPoly (1:cs) = zip (map negate cs) [1..]
+-- > pseries' (convertPoly $ bronfmanH n) == expandBronfmanH n == [ count n l | l <- [0..] ] 
+--
+-- * [1] Aaron Bronfman: Growth functions of a class of monoids. Preprint, 2001
+--
+bronfmanH :: Int -> [Int]
+bronfmanH n = bronfmanHsList !! n
+
+-- | An infinite list containing the Bronfman polynomials:
+--
+-- > bronfmanH n = bronfmanHsList !! n
+--
+bronfmanHsList :: [[Int]]
+bronfmanHsList = list where
+  list = map go [0..]
+  go 0 = [1]
+  go n = sumSeries [ sgn i $ replicate (choose2 i) 0 ++ list !! (n-i) | i<-[1..n] ]
+  sgn i = if odd i then id else map negate
+  choose2 k = div (k*(k-1)) 2
+
+-- | Expands the reciprocial of @H(n)@ into an infinite power series,
+-- giving the growth function of the positive braids on @n@ strands.
+expandBronfmanH :: Int -> [Int]
+expandBronfmanH n = pseries' (convertPoly $ bronfmanH n) where
+  convertPoly (1:cs) = zip (map negate cs) [1..]
+   
+--------------------------------------------------------------------------------
+-- * ASCII diagram
+
+instance KnownNat n => DrawASCII (Braid n) where
+  ascii = horizBraidASCII
+
+-- | Horizontal braid diagram, drawn from left to right,
+-- with strands numbered from the bottom to the top
+horizBraidASCII :: KnownNat n => Braid n -> ASCII
+horizBraidASCII = horizBraidASCII' True
+
+-- | Horizontal braid diagram, drawn from left to right.
+-- The boolean flag indicates whether to flip the strands
+-- vertically ('True' means bottom-to-top, 'False' means top-to-bottom) 
+horizBraidASCII' :: KnownNat n => Bool -> Braid n -> ASCII
+horizBraidASCII' flipped braid@(Braid gens) = final where
+
+  n = numberOfStrands braid
+ 
+  final        = vExtendWith VTop 1 $ hCatTop allBlocks
+  allBlocks    = prelude ++ middleBlocks ++ epilogue
+  prelude      = [ numberBlock   , spaceBlock , beginEndBlock ] 
+  epilogue     = [ beginEndBlock , spaceBlock , numberBlock'  ]
+  middleBlocks = map block gens 
+  
+  block g = case g of
+    Sigma    i -> block' i $ if flipped then over  else under
+    SigmaInv i -> block' i $ if flipped then under else over
+
+  block' i middle = asciiFromLines $ drop 2 $ concat 
+                  $ replicate a horiz ++ [space3, middle] ++ replicate b horiz
+    where 
+      (a,b) = if flipped then (n-i-1,i-1) else (i-1,n-i-1)
+
+  -- cycleN :: Int -> [a] -> [a]
+  -- cycleN n = concat . replicate n
+
+  spaceBlock    = transparentBox (1,n*3-2)
+  beginEndBlock = asciiFromLines $ drop 2 $ concat $ replicate n horiz
+  numberBlock   = mkNumbers [1..n]
+  numberBlock'  = mkNumbers $ P.fromPermutation $ braidPermutation braid
+
+  mkNumbers :: [Int] -> ASCII
+  mkNumbers list = vCatWith HRight (VSepSpaces 2) $ map asciiShow 
+                 $ (if flipped then reverse else id) $ list
+
+  under  = [ "\\ /" , " / "  , "/ \\" ]
+  over   = [ "\\ /" , " \\ " , "/ \\" ]
+  horiz  = [ "   "  , "   "  , "___"  ]
+  space3 = [ "   "  , "   "  , "   "  ]
+
+--------------------------------------------------------------------------------
+
+{- this is unusably ugly and vertically loooong
+
+-- | Vertical braid diagram, drawn from the top to the bottom.
+-- Strands are numbered from the left to the right.
+--
+-- Writing down the strand numbers from the top and and the bottom
+-- gives the two-line notation of the permutation realized by the braid.
+--
+verticalBraidASCII :: KnownNat n => Braid n -> ASCII
+verticalBraidASCII braid@(Braid gens) = final where
+
+  n = numberOfStrands braid
+ 
+  final        = hExtendWith HLeft 1 $ vCatLeft allBlocks
+  allBlocks    = prelude ++ middleBlocks ++ epilogue
+  prelude      = [ numberBlock   , spaceBlock , beginEndBlock ] 
+  epilogue     = [ beginEndBlock , spaceBlock , numberBlock'  ]
+  middleBlocks = map block gens 
+  
+  block g = case g of
+    Sigma    i -> block' i under
+    SigmaInv i -> block' i over
+
+  block' i middle = asciiFromLines (map f middle) where
+    f xs = drop 1 $ concat $ h (i-1) ++ ["   ",xs] ++ h (n-i-1)
+    h k  = replicate k "  |"
+
+  spaceBlock    = transparentBox (n*3-2,1)
+  beginEndBlock = asciiFromLines $ replicate 3 $ drop 1 $ concat (replicate n "  |")
+  numberBlock   = mkNumbers [1..n]
+  numberBlock'  = mkNumbers $ P.fromPermutation $ braidPermutation braid
+
+  mkNumbers :: [Int] -> ASCII
+  mkNumbers list = asciiFromString (drop 1 $ concatMap show3 list)
+  show3 k = let s = show k 
+            in  replicate (3-length s) ' ' ++ s
+
+  under  = [ "\\ /" , " / "  , "/ \\" ]
+  over   = [ "\\ /" , " \\ " , "/ \\" ]
+
+-}
+
+--------------------------------------------------------------------------------
+-- * List of all words
+
+-- | All positive braid words of the given length
+allPositiveBraidWords :: KnownNat n => Int -> [Braid n]
+allPositiveBraidWords l = braids where
+  n = numberOfStrands (head braids)
+  braids = map Braid $ _allPositiveBraidWords n l 
+
+-- | All braid words of the given length
+allBraidWords :: KnownNat n => Int -> [Braid n]
+allBraidWords l = braids where
+  n = numberOfStrands (head braids)
+  braids = map Braid $ _allBraidWords n l 
+
+-- | Untyped version of 'allPositiveBraidWords'
+_allPositiveBraidWords :: Int -> Int -> [[BrGen]]
+_allPositiveBraidWords n = go where
+  go 0 = [[]]
+  go k = [ Sigma i : rest | i<-[1..n-1] , rest <- go (k-1) ]
+
+-- | Untyped version of 'allBraidWords'
+_allBraidWords :: Int -> Int -> [[BrGen]]
+_allBraidWords n = go where
+  go 0 = [[]]
+  go k = [ gen : rest | gen <- gens , rest <- go (k-1) ]
+  gens = concat [ [ Sigma i , SigmaInv i ] | i<-[1..n-1] ]
+
+--------------------------------------------------------------------------------
+-- * Random braids  
+
+-- | Random braid word of the given length
+randomBraidWord :: (RandomGen g, KnownNat n) => Int -> g -> (Braid n, g)
+randomBraidWord len g = (braid, g') where
+  braid  = Braid w
+  n      = numberOfStrands braid
+  (w,g') = _randomBraidWord n len g
+
+-- | Random /positive/ braid word of the given length
+randomPositiveBraidWord :: (RandomGen g, KnownNat n) => Int -> g -> (Braid n, g)
+randomPositiveBraidWord len g = (braid, g') where
+  braid  = Braid w
+  n      = numberOfStrands braid
+  (w,g') = _randomPositiveBraidWord n len g
+
+--------------------------------------------------------------------------------
+
+-- | Given a braid word, we perturb it randomly @m@ times using the braid relations,
+-- so that the resulting new braid word is equivalent to the original.
+--
+-- Useful for testing.
+--
+randomPerturbBraidWord :: forall n g. (RandomGen g, KnownNat n) => Int -> Braid n -> g -> (Braid n, g)
+randomPerturbBraidWord m braid@(Braid xs) g = (Braid word' , g') where
+
+  (word',g') = go m (length xs) xs g 
+
+  n = numberOfStrands braid
+
+  -- | A random pair cancelling each other
+  rndE :: g -> ([BrGen],g)
+  rndE g = (e1,g'') where
+    (i , g'  ) = randomR (1,n-1) g 
+    (b , g'' ) = random          g'
+    e0 = [SigmaInv i, Sigma i] 
+    e1 = if b then reverse e0 else e0
+
+  brg    s i = case s of { Plus -> Sigma    i ; Minus -> SigmaInv i }
+  brginv s i = case s of { Plus -> SigmaInv i ; Minus -> Sigma    i }
+
+  go :: Int -> Int -> [BrGen] -> g -> ([BrGen], g)
+  go !cnt !len !word !g 
+
+    | cnt <= 0   = (word, g)
+
+    | len <  2   = let w' = if b1 then (e++word) else (word++e)        -- if it is short, we just add a trivial pair somewhere
+                   in  continue g4 (len+2) w'
+
+    | abs (i-j) >= 2            = continue g4  len    (as ++ v:u:bs)         -- they commute, so we just commute them
+
+    | i == j && s/=t            = continue g4 (len-2) (as ++ bs    )         -- they are inverse of each other, so we kill them
+
+    | abs (i-j) == 1 && s == t  = let mid = if b1 
+                                        then [ brg s j , brg s i , brg s j , brginv s i ]   -- insert pair and
+                                        else [ brginv s j , brg s i , brg s j , brg s i ]   -- apply ternary relation 
+                                  in  continue g4 (len+2) (as ++ mid ++ bs)
+
+    | otherwise                 = let mid = if b1
+                                        then (u : e ++ [v])
+                                        else if b2
+                                          then [u,v] ++ e
+                                          else e ++ [u,v]
+                                  in continue g4 (len+2) (as++(u:e)++[v]++bs)          -- otherwise we just insert an trivial pair         
+
+    where
+
+      (pos         , g1 ) = randomR (0,len-2) g
+      (b1 :: Bool  , g2 ) = random g1
+      (b2 :: Bool  , g3 ) = random g2
+      (e           , g4 ) = rndE   g3
+      (as,u:v:bs) = splitAt pos word
+      (s,i) = brGenSignIdx u
+      (t,j) = brGenSignIdx v
+  
+      continue g' len' word' = go (cnt-1) len' word' g'
+
+--------------------------------------------------------------------------------
+
+-- | This version of 'randomBraidWord' may be convenient to avoid the type level stuff
+withRandomBraidWord 
+  :: RandomGen g 
+  => (forall n. KnownNat n => Braid n -> a) 
+  -> Int                -- ^ number of strands
+  -> Int                -- ^ length of the random word
+  -> g -> (a, g)
+withRandomBraidWord f n len = runRand $ do
+  withSelectedM f (rand $ randomBraidWord len) n
+
+-- | This version of 'randomPositiveBraidWord' may be convenient to avoid the type level stuff
+withRandomPositiveBraidWord 
+  :: RandomGen g 
+  => (forall n. KnownNat n => Braid n -> a) 
+  -> Int                -- ^ number of strands
+  -> Int                -- ^ length of the random word
+  -> g -> (a, g)
+withRandomPositiveBraidWord f n len = runRand $ do
+  withSelectedM f (rand $ randomPositiveBraidWord len) n
+
+-- | Untyped version of 'randomBraidWord'
+_randomBraidWord 
+  :: (RandomGen g) 
+  => Int                -- ^ number of strands
+  -> Int                -- ^ length of the random word
+  -> g -> ([BrGen], g)
+_randomBraidWord n len = runRand $ replicateM len $ do
+  k <- randChoose (1,n-1)
+  s <- randRoll
+  return $ case s of
+    Plus  -> Sigma k
+    Minus -> SigmaInv k
+
+-- | Untyped version of 'randomPositiveBraidWord'
+_randomPositiveBraidWord 
+  :: (RandomGen g) 
+  => Int             -- ^ number of strands
+  -> Int             -- ^ length of the random word
+  -> g -> ([BrGen], g)
+_randomPositiveBraidWord n len = runRand $ replicateM len $ do
+  liftM Sigma $ randChoose (1,n-1)
+
+--------------------------------------------------------------------------------
+
diff --git a/Math/Combinat/Groups/Braid/NF.hs b/Math/Combinat/Groups/Braid/NF.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Groups/Braid/NF.hs
@@ -0,0 +1,534 @@
+
+-- | Normal form of braids, take 1.
+--
+-- We implement the Adyan-Thurston-ElRifai-Morton solution to the word problem in braid groups.
+--
+--
+-- Based on:
+--
+-- * [1] Joan S. Birman, Tara E. Brendle: BRAIDS - A SURVEY
+--   <https://www.math.columbia.edu/~jb/Handbook-21.pdf> (chapter 5.1)
+--
+-- * [2] Elsayed A. Elrifai, Hugh R. Morton: Algorithms for positive braids
+--
+
+{-# LANGUAGE 
+      CPP, BangPatterns, 
+      ScopedTypeVariables, ExistentialQuantification,
+      DataKinds, KindSignatures, Rank2Types #-}
+
+module Math.Combinat.Groups.Braid.NF  
+  ( -- * Normal form
+    BraidNF (..)
+  , nfReprWord
+  , braidNormalForm
+  , braidNormalForm'
+  , braidNormalFormNaive'
+    -- * Starting and finishing sets
+  , permWordStartingSet
+  , permWordFinishingSet    
+  , permutationStartingSet
+  , permutationFinishingSet    
+  )
+  where
+
+--------------------------------------------------------------------------------
+
+import Data.Proxy
+import GHC.TypeLits
+
+import Control.Monad
+
+import Data.List ( mapAccumL , foldl' , (\\) )
+
+import Data.Array.Unboxed
+import Data.Array.ST
+import Data.Array.IArray
+import Data.Array.MArray
+import Data.Array.Unsafe
+import Data.Array.Base
+
+import Control.Monad.ST
+
+import Math.Combinat.Helper
+import Math.Combinat.Sign
+
+import Math.Combinat.Permutations ( Permutation(..) , isIdentityPermutation , isReversePermutation )
+import qualified Math.Combinat.Permutations as P
+
+import Math.Combinat.Groups.Braid
+
+--------------------------------------------------------------------------------
+
+-- | A unique normal form for braids, called the /left-greedy normal form/.
+-- It looks like @Delta^i*P@, where @Delta@ is the positive half-twist, @i@ is an integer,
+-- and @P@ is a positive word, which can be further decomposed into non-@Delta@ /permutation words/; 
+-- these words themselves are not unique, but the permutations they realize /are/ unique.
+--
+-- This will solve the word problem relatively fast, 
+-- though it is not the fastest known algorithm.
+--
+data BraidNF (n :: Nat) = BraidNF
+  { _nfDeltaExp :: !Int              -- ^ the exponent of @Delta@
+  , _nfPerms    :: [Permutation]     -- ^ the permutations
+  }
+  deriving (Eq,Ord,Show)
+
+-- | A braid word representing the given normal form
+nfReprWord :: KnownNat n => BraidNF n -> Braid n
+nfReprWord (BraidNF k perms) = freeReduceBraidWord $ composeMany (deltas ++ rest) where
+
+  deltas 
+    | k > 0     = replicate   k           halfTwist
+    | k < 0     = replicate (-k) (inverse halfTwist)
+    | otherwise = []
+
+  rest = map permutationBraid perms
+
+--------------------------------------------------------------------------------
+
+-- | Computes the normal form of a braid. We apply free reduction first, it should be faster that way.
+braidNormalForm :: KnownNat n => Braid n -> BraidNF n
+braidNormalForm = braidNormalForm' . freeReduceBraidWord
+
+-- | This function does not apply free reduction before computing the normal form
+braidNormalForm' :: KnownNat n => Braid n -> BraidNF n
+braidNormalForm' braid@(Braid gens) = BraidNF (dexp+pexp) perms where
+  n = numberOfStrands braid
+  invless = replaceInverses n gens
+  (dexp,posxword) = moveDeltasLeft n invless
+  factors = leftGreedyFactors n $ expandPosXWord n posxword
+  (pexp,perms) = normalizePermFactors n $ map (_braidPermutation n) factors
+
+-- | This one uses the naive inverse replacement method. Probably somewhat slower than 'braidNormalForm''.
+braidNormalFormNaive' :: KnownNat n => Braid n -> BraidNF n
+braidNormalFormNaive' braid@(Braid gens) = BraidNF (dexp+pexp) perms where
+  n = numberOfStrands braid
+  invless = replaceInversesNaive gens
+  (dexp,posxword) = moveDeltasLeft n invless
+  factors = leftGreedyFactors n $ expandPosXWord n posxword
+  (pexp,perms) = normalizePermFactors n $ map (_braidPermutation n) factors
+
+--------------------------------------------------------------------------------
+
+-- | Replaces groups of @sigma_i^-1@ generators by @(Delta^-1 * P)@, 
+-- where @P@ is a positive word.
+--
+-- This should be more clever (resulting in shorter words) than the naive version below
+--
+replaceInverses :: Int -> [BrGen] -> [XGen]
+replaceInverses n gens = worker gens where
+
+  worker [] = []
+  worker xs = replaceNegs neg ++ map (XSigma . brGenIdx) pos ++ worker rest where 
+    (neg,tmp ) = span (isMinus . brGenSign) xs
+    (pos,rest) = span (isPlus  . brGenSign) tmp
+  
+  replaceNegs gs = concatMap replaceFac facs where
+    facs = leftGreedyFactors n $ map brGenIdx gs
+  
+  replaceFac idxs = XDelta (-1) : map XSigma (_permutationBraid perm) where
+    perm = (P.reversePermutation n) `P.multiply` (P.adjacentTranspositions n idxs)
+
+
+-- | Replaces @sigma_i^-1@ generators by @(Delta^-1 * L_i)@.
+replaceInversesNaive :: [BrGen] -> [XGen]
+replaceInversesNaive gens = concatMap f gens where 
+  f (Sigma    i) = [ XSigma i ]
+  f (SigmaInv i) = [ XDelta (-1) , XL i ]
+
+--------------------------------------------------------------------------------
+
+-- | Temporary data structure to be used during the normal form computation
+data XGen
+  = XDelta !Int   -- ^ @Delta^k@
+  | XSigma !Int   -- ^ @Sigma_j@
+  | XL     !Int   -- ^ @L_j = Delta * sigma_j^-1@
+  | XTauL  !Int   -- ^ @tau(L_j)@
+  deriving (Eq,Show)
+
+isXDelta :: XGen -> Bool
+isXDelta x = case x of { XDelta {} -> True ; _ -> False }
+
+-- | We move the all @Delta@'s to the left
+moveDeltasLeft :: Int -> [XGen] -> (Int,[XGen])
+moveDeltasLeft n input = (finalExp, finalPosWord) where
+  
+  (XDelta finalExp : finalPosWord) =  reverse $ worker 0 (reverse input) 
+
+  -- we start from the right end, and work towards the left end
+  worker  dexp [] = [ XDelta dexp ]
+  worker !dexp xs = this' ++ worker dexp' rest where 
+    (delta,notdelta) = span isXDelta xs
+    (this ,rest    ) = span (not . isXDelta) notdelta
+    dexp' = dexp + sumDeltas delta
+    this' = if even dexp' 
+      then this
+      else map xtau this
+
+  sumDeltas :: [XGen] -> Int
+  sumDeltas xs = foldl' (+) 0 [ k | XDelta k <- xs ]
+
+  -- | The @X -> Delta^-1 * X * Delta@ inner automorphism
+  xtau :: XGen -> XGen
+  xtau (XSigma j) = XSigma (n-j)
+  xtau (XDelta k) = XDelta k  
+  xtau (XL     k) = XTauL  k  
+  xtau (XTauL  k) = XL     k  
+
+--------------------------------------------------------------------------------
+
+-- | Expands a /positive/ \"X-word\" into a positive braid word
+expandPosXWord :: Int -> [XGen] -> [Int]
+expandPosXWord n = concatMap f where
+
+  posHalfTwist = _halfTwist n
+
+  jtau :: Int -> Int
+  jtau j = n-j
+
+  posLTable    = listArray (1,n-1) [ _permutationBraid (posLPerm n i) | i<-[1..n-1] ] :: Array Int [Int]
+  posTauLTable = amap (map jtau) posLTable
+
+  -- posRTable = listArray (1,n-1) [ _permutationBraid (posRPerm n i) | i<-[1..n-1] ] :: Array Int [Int]
+
+  f x = case x of
+    XSigma i -> [i]
+    XL     i -> posLTable    ! i
+    XTauL  i -> posTauLTable ! i
+    XDelta i 
+      | i > 0     -> concat (replicate i posHalfTwist)
+      | i < 0     -> error "expandPosXWord: negative delta power"
+      | otherwise -> []
+
+  -- word :: Braid n -> [Int]
+  -- word (Braid gens) = map brGenIdx gens
+
+
+-- | Expands an \"X-word\" into a braid word. Useful for debugging.
+expandAnyXWord :: forall n. KnownNat n => [XGen] -> Braid n
+expandAnyXWord xgens = braid where
+  n = numberOfStrands braid
+
+  braid = composeMany (map f xgens)
+
+  posHalfTwist = halfTwist            :: Braid n
+  negHalfTwist = inverse posHalfTwist :: Braid n
+
+  posLTable    = listArray (1,n-1) [ permutationBraid (posLPerm n i) | i<-[1..n-1] ] :: Array Int (Braid n)
+  posTauLTable = amap tau posLTable
+
+  -- posRTable = listArray (1,n-1) [ permutationBraid (posRPerm n i) | i<-[1..n-1] ] :: Array Int (Braid n)
+
+  f :: XGen -> Braid n
+  f x = case x of
+    XSigma i -> sigma i
+    XL     i -> posLTable    ! i
+    XTauL  i -> posTauLTable ! i
+    XDelta i 
+      | i > 0     -> composeMany (replicate   i  posHalfTwist)
+      | i < 0     -> composeMany (replicate (-i) negHalfTwist)
+      | otherwise -> identity
+
+--------------------------------------------------------------------------------
+
+-- | @posL k@ (denoted as @L_k@) is a /positive word/ which 
+-- satisfies @Delta = L_k * sigma_k@, or:
+-- 
+-- > (inverse halfTwist) `compose` (posL k) ~=~ sigmaInv k@
+-- 
+-- Thus we can replace any word with a positive word plus some @Delta^-1@\'s
+--
+posL :: KnownNat n => Int -> Braid n
+posL k = braid where
+  n = numberOfStrands braid
+  braid = permutationBraid (posLPerm n k)
+
+-- | @posR k n@ (denoted as @R_k@) is a /permutation braid/ which 
+-- satisfies @Delta = sigma_k * R_k@
+-- 
+-- > (posR k) `compose` (inverse halfTwist) ~=~ sigmaInv k@
+-- 
+-- Thus we can replace any word with a positive word plus some @Delta^-1@'s
+--
+posR :: KnownNat n => Int -> Braid n
+posR k = braid where
+  n = numberOfStrands braid
+  braid = permutationBraid (posRPerm n k)
+
+-- | The permutation @posL k :: Braid n@ is realizing
+posLPerm :: Int -> Int -> Permutation
+posLPerm n k 
+  | k>0 && k<n  = (P.reversePermutation n `P.multiply` P.adjacentTransposition n k)
+  | otherwise   = error "posLPerm: index out of range"
+
+-- | The permutation @posR k :: Braid n@ is realizing
+posRPerm :: Int -> Int -> Permutation
+posRPerm n k 
+  | k>0 && k<n  = (P.adjacentTransposition n k `P.multiply` P.reversePermutation n )
+  | otherwise   = error "posRPerm: index out of range"
+
+--------------------------------------------------------------------------------
+
+-- | We recognize left-greedy factors which are @Delta@-s (easy, since they are the only ones
+-- with length @(n choose 2)@), and move them to the left, returning their summed exponent
+-- and the filtered new factors. We also filter trivial permutations (which should only happen 
+-- for the trivial braid, but it happens there?)
+--
+filterDeltaFactors :: Int -> [[Int]] -> (Int, [[Int]])
+filterDeltaFactors n facs = (exp',facs'') where
+
+  (exp',facs') = go 0 (reverse facs)
+
+  jtau j = n-j
+  facs'' = reverse facs'
+  maxlen = div (n*(n-1)) 2
+
+  go !e []       = (e,[])
+  go !e (xs:xxs)  
+    | null xs             = go e xxs
+    | length xs == maxlen = go (e+1) xxs
+    | otherwise           =  
+        if even e
+          then let (e',yys) = go e xxs in (e' ,          xs : yys) 
+          else let (e',yys) = go e xxs in (e' , map jtau xs : yys)  
+
+-------------------------------------------------------------------------------- 
+
+-- | The /starting set/ of a positive braid P is the subset of @[1..n-1]@ defined by
+-- 
+-- > S(P) = [ i | P = sigma_i * Q , Q is positive ] = [ i | (sigma_i^-1 * P) is positive ] 
+--
+-- This function returns the starting set a positive word, assuming it 
+-- is a /permutation braid/ (see Lemma 2.4 in [2])
+--
+permWordStartingSet :: Int -> [Int] -> [Int]
+permWordStartingSet n xs = permWordFinishingSet n (reverse xs)
+
+-- | The /finishing set/ of a positive braid P is the subset of @[1..n-1]@ defined by
+-- 
+-- > F(P) = [ i | P = Q * sigma_i , Q is positive ] = [ i | (P * sigma_i^-1) is positive ] 
+--
+-- This function returns the finishing set, assuming the input is a /permutation braid/
+--
+permWordFinishingSet :: Int -> [Int] -> [Int]
+permWordFinishingSet n input = runST action where
+
+  action :: forall s. ST s [Int]
+  action = do
+    perm <- newArray_ (1,n) :: ST s (STUArray s Int Int)
+    forM_ [1..n] $ \i -> writeArray perm i i
+    forM_ input $ \i -> do
+      a <- readArray perm  i
+      b <- readArray perm (i+1)
+      writeArray perm  i    b
+      writeArray perm (i+1) a
+    flip filterM [1..n-1] $ \i -> do
+      a <- readArray perm  i
+      b <- readArray perm (i+1) 
+      return (b<a)                    -- Lemma 2.4 in [2]
+
+-- | This satisfies
+-- 
+-- > permutationStartingSet p == permWordStartingSet n (_permutationBraid p)
+--
+permutationStartingSet :: Permutation -> [Int]
+permutationStartingSet = permutationFinishingSet . P.inverse
+
+-- | This satisfies
+-- 
+-- > permutationFinishingSet p == permWordFinishingSet n (_permutationBraid p)
+--
+permutationFinishingSet :: Permutation -> [Int]
+permutationFinishingSet (Permutation arr) 
+  = [ i | i<-[1..n-1] , arr ! i > arr ! (i+1) ] where (1,n) = bounds arr
+
+-- | Returns the list of permutations failing Lemma 2.5 in [2] 
+-- (so an empty list means the implementaton is correct)
+fails_lemmma_2_5 :: Int -> [Permutation]
+fails_lemmma_2_5 n = [ p | p <- P.permutations n , not (test p) ] where
+  test p = and [ check i | i<-[1..n-1] ] where
+    w = _permutationBraid p
+    s = permWordStartingSet n w
+    check i = _isPermutationBraid n (i:w) == (not $ elem i s)
+
+-------------------------------------------------------------------------------- 
+                    
+-- | Given factors defined as permutation braids, we normalize them
+-- to /left-canonical form/ by ensuring that
+--
+-- * for each consecutive pair @(P,Q)@ the finishing set F(P) contains the starting set S(Q)
+--
+-- * all @Delta@-s (corresponding to the reverse permutation) are moved to the left
+--
+-- * all trivial factors are filtered out
+--
+-- Unfortunately, it seems that we may need multiple sweeps to do that...
+--
+normalizePermFactors :: Int -> [Permutation] -> (Int,[Permutation])
+normalizePermFactors n = go 0 where
+  go !acc input = 
+    if (exp==0 && input == output) 
+      then (acc,input) 
+      else go (acc+exp) output 
+    where 
+      (exp,output) = normalizePermFactors1 n input
+
+-- | Does 1 sweep of the above normalization process.
+-- Unfortunately, it seems that we may need to do this multiple times...
+--
+normalizePermFactors1 :: Int -> [Permutation] -> (Int,[Permutation])
+normalizePermFactors1 n input = (exp, reverse output) where
+  (exp, output) = worker 0 (reverse input)
+
+  -- Notes: We work in reverse order, from the right to the left.
+  -- We maintain the number of Delta-s pushed through; the tau involutions
+  -- are implicit in the parity of this number
+  --
+  worker :: Int -> [Permutation] -> (Int,[Permutation])
+  worker = worker' 0 0
+  
+  -- We also maintain additional 0/1 flip flags for the first two permutations
+  -- this is a little bit of hack but it should work nicely
+  --
+  worker' :: Int -> Int -> Int -> [Permutation] -> (Int,[Permutation])
+  worker' !ep !eq !e (!p : rest@(!q : rest')) 
+
+    -- check if the very first element is identity or Delta 
+    -- (note: these are tau-invariants)
+
+    | isIdentityPermutation p  = worker'  eq  0  e    rest
+    | isReversePermutation  p  = worker'  eq  0 (e+1) rest
+
+    -- check if the second element is identity or Delta 
+    -- this is necessary since we "fatten" the second element and it can possibly
+    -- become Delta after a while (?)
+
+    | isIdentityPermutation q  = worker'  ep    0  e    (p : rest')
+    | isReversePermutation  q  = worker' (ep-1) 0 (e+1) (p : rest')    
+
+    -- ok so we have something like "... : Q : P"
+    -- if F(Q) contains S(P) then we can move on; 
+    -- otherwise there is an element j in S(P) \\ F(Q), so we can 
+    -- replace it by "... : Qj : jP"
+
+    | otherwise = 
+        case permutationStartingSet preal \\ permutationFinishingSet qreal of  
+          []    -> let (e',rs) = worker' eq 0 e rest in (e', preal : rs)
+          (j:_) -> worker' (-e) (-e) e (p':q':rest') where 
+                     s  = P.adjacentTransposition n j
+                     p' = P.multiply s preal
+                     q' = P.multiply qreal s
+        where
+          preal = oddTau (e+ep) p       -- the "real" p
+          qreal = oddTau (e+eq) q       -- the "real" q
+
+  worker' _   _  !e [ ] = (e,[])
+  worker' !ep _  !e [p] 
+    | isIdentityPermutation p  = (e   , [])
+    | isReversePermutation  p  = (e+1 , [])
+    | otherwise                = (e   , [oddTau (e+ep) p] )
+
+  oddTau :: Int -> Permutation -> Permutation
+  oddTau !e p = if even e then p else tauPerm p
+
+{-
+  checkDelta :: Int -> Permutation -> [Permutation] -> (Int,[Permutation])
+  checkDelta !e !p !rest 
+    | P.isIdentityPermutation p  = worker  e    rest
+    | isReversePermutation    p  = worker (e+1) rest
+    | otherwise                  = let (e',rs) = worker e rest in (e', oddTau e p : rs)
+-}        
+
+-------------------------------------------------------------------------------- 
+
+-- | Given a /positive/ word, we apply left-greedy factorization of
+-- that word into subwords representing /permutation braids/.
+--
+-- Example 5.1 from the above handbook:
+--
+-- > leftGreedyFactors 7 [1,3,2,2,1,3,3,2,3,2] == [[1,3,2],[2,1,3],[3,2,3],[2]]
+--
+leftGreedyFactors :: Int -> [Int] -> [[Int]]
+leftGreedyFactors n input = filter (not . null) $ runST (action input) where
+
+  action :: forall s. [Int] -> ST s [[Int]]
+  action input = do
+
+    perm <- newArray_ (1,n) :: ST s (STUArray s Int Int)
+    forM_ [1..n] $ \i -> writeArray perm i i
+    let doSwap :: Int -> ST s ()
+        doSwap i = do
+          a <- readArray perm  i
+          b <- readArray perm (i+1)
+          writeArray perm  i    b
+          writeArray perm (i+1) a
+               
+    mat <- newArray ((1,1),(n,n)) 0 :: ST s (STUArray s (Int,Int) Int)
+    let clearMat = forM_ [1..n] $ \i -> 
+          forM_ [1..n] $ \j -> writeArray mat (i,j) 0
+          
+    let doAdd1 :: Int -> Int -> ST s Int
+        doAdd1 i j = do
+          x <- readArray mat (i,j)
+          let y = x+1
+          writeArray mat (i,j) y 
+          writeArray mat (j,i) y
+          return y
+           
+    let worker :: [Int] -> ST s [[Int]]
+        worker []     = return [[]]
+        worker (p:ps) = do
+          u <- readArray perm  p 
+          v <- readArray perm (p+1)
+          c <- doAdd1 u v 
+          doSwap p
+          if c<=1
+            then do
+              (f:fs) <- worker ps
+              return ((p:f):fs)
+            else do
+              clearMat
+              fs <- worker (p:ps)
+              return ([]:fs)
+              
+    worker input
+
+--------------------------------------------------------------------------------
+
+{-
+
+-- | Finds ternary braid relations, and returns them as a list of indices, decorated
+-- with a flag specifying which side of the relation we found, a sign specifying
+-- whether it is a relation between positive or negative generators.
+--
+findTernaryBraidRelations :: Braid n -> [(Int,Bool,Sign)]
+findTernaryBraidRelations (Braid gens) = go 0 gens where
+  go !k (Sigma a : rest@(Sigma b : Sigma c : _))  
+    | a==c && b==a+1 = (k,True ,Plus) : go (k+1) rest
+    | a==c && b==a-1 = (k,False,Plus) : go (k+1) rest
+    | otherwise      =                  go (k+1) rest
+  go !k (SigmaInv a : rest@(SigmaInv b : SigmaInv c : _))  
+    | a==c && b==a+1 = (k,True ,Minus) : go (k+1) rest
+    | a==c && b==a-1 = (k,False,Minus) : go (k+1) rest
+    | otherwise      =                   go (k+1) rest
+  go !k (x:xs) = go (k+1) xs
+  go _  []     = []
+
+-- | Finds subsequences like @(i,i+1,i)@ and @(i+1,i,i+1)@, and returns them
+-- and a list of indices, plus a flag specifying which one we found (the first 
+-- one is 'True', second one is 'False')
+--
+_findTernaryBraidRelations :: [Int] -> [(Int,Bool)]
+_findTernaryBraidRelations = go 0 where
+  go !k (a:rest@(b:c:_))  
+    | a==c && b==a+1 = (k,True ) : go (k+1) rest
+    | a==c && b==a-1 = (k,False) : go (k+1) rest
+    | otherwise      =             go (k+1) rest
+  go !k (x:xs) = go (k+1) xs
+  go _  []     = []
+
+-}
+
+--------------------------------------------------------------------------------
+
diff --git a/Math/Combinat/Groups/Free.hs b/Math/Combinat/Groups/Free.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Groups/Free.hs
@@ -0,0 +1,523 @@
+
+-- | Words in free groups (and free powers of cyclic groups).
+--
+-- This module is not re-exported by "Math.Combinat"
+--
+{-# LANGUAGE CPP, BangPatterns, PatternGuards #-}
+module Math.Combinat.Groups.Free where
+
+--------------------------------------------------------------------------------
+
+-- new Base exports "Word" from Data.Word...
+#ifdef MIN_VERSION_base
+#if MIN_VERSION_base(4,7,1)
+import Prelude hiding ( Word )
+#endif
+#elif __GLASGOW_HASKELL__ >= 709
+import Prelude hiding ( Word )
+#endif
+
+import Data.Char     ( chr )
+import Data.List     ( mapAccumL , groupBy )
+
+import Control.Monad ( liftM )
+import System.Random
+
+import Math.Combinat.Numbers
+import Math.Combinat.Sign
+import Math.Combinat.Helper
+
+--------------------------------------------------------------------------------
+-- * Words
+
+-- | A generator of a (free) group, indexed by which \"copy\" of the group we are dealing with.
+data Generator idx
+  = Gen !idx          -- @a@
+  | Inv !idx          -- @a^(-1)@
+  deriving (Eq,Ord,Show,Read)
+
+-- | The index of a generator
+genIdx :: Generator idx -> idx
+genIdx g = case g of
+  Gen x -> x
+  Inv x -> x
+
+-- | The sign of the (exponent of the) generator (that is, the generator is 'Plus', the inverse is 'Minus')
+genSign :: Generator idx -> Sign
+genSign g = case g of { Gen _ -> Plus ; Inv _ -> Minus }  
+
+genSignValue :: Generator idx -> Int
+genSignValue g = case g of { Gen _ -> (1::Int) ; Inv _ -> (-1::Int) } 
+
+-- | keep the index, but return always the 'Gen' one.
+absGen :: Generator idx -> Generator idx 
+absGen g = case g of
+  Gen x -> Gen x
+  Inv x -> Gen x
+
+-- | A /word/, describing (non-uniquely) an element of a group.
+-- The identity element is represented (among others) by the empty word.
+type Word idx = [Generator idx] 
+
+--------------------------------------------------------------------------------
+
+-- | Generators are shown as small letters: @a@, @b@, @c@, ...
+-- and their inverses are shown as capital letters, so @A=a^-1@, @B=b^-1@, etc.
+showGen :: Generator Int -> Char
+showGen (Gen i) = chr (96+i)
+showGen (Inv i) = chr (64+i)
+
+showWord :: Word Int -> String
+showWord = map showGen
+
+--------------------------------------------------------------------------------
+  
+instance Functor Generator where
+  fmap f g = case g of 
+    Gen x -> Gen (f x) 
+    Inv y -> Inv (f y)
+    
+--------------------------------------------------------------------------------
+
+-- | The inverse of a generator
+inverseGen :: Generator a -> Generator a
+inverseGen g = case g of
+  Gen x -> Inv x
+  Inv x -> Gen x
+
+-- | The inverse of a word
+inverseWord :: Word a -> Word a
+inverseWord = map inverseGen . reverse
+
+-- | Lists all words of the given length (total number will be @(2g)^n@).
+-- The numbering of the generators is @[1..g]@.
+allWords 
+  :: Int         -- ^ @g@ = number of generators 
+  -> Int         -- ^ @n@ = length of the word
+  -> [Word Int]
+allWords g = go where
+  go !0 = [[]]
+  go !n = [ x:xs | xs <- go (n-1) , x <- elems ]
+  elems =  [ Gen a | a<-[1..g] ]
+        ++ [ Inv a | a<-[1..g] ]
+
+-- | Lists all words of the given length which do not contain inverse generators
+-- (total number will be @g^n@).
+-- The numbering of the generators is @[1..g]@.
+allWordsNoInv 
+  :: Int         -- ^ @g@ = number of generators 
+  -> Int         -- ^ @n@ = length of the word
+  -> [Word Int]
+allWordsNoInv g = go where
+  go !0 = [[]]
+  go !n = [ x:xs | xs <- go (n-1) , x <- elems ]
+  elems = [ Gen a | a<-[1..g] ]
+
+--------------------------------------------------------------------------------
+-- * Random words
+
+-- | A random group generator (or its inverse) between @1@ and @g@
+randomGenerator
+  :: RandomGen g
+  => Int         -- ^ @g@ = number of generators 
+  -> g -> (Generator Int, g)
+randomGenerator !d !g0 = (gen, g2) where
+  (b, !g1) = random        g0
+  (k, !g2) = randomR (1,d) g1
+  gen = if b then Gen k else Inv k
+
+-- | A random group generator (but never its inverse) between @1@ and @g@
+randomGeneratorNoInv
+  :: RandomGen g
+  => Int         -- ^ @g@ = number of generators 
+  -> g -> (Generator Int, g)
+randomGeneratorNoInv !d !g0 = (Gen k, g1) where
+  (!k, !g1) = randomR (1,d) g0
+
+-- | A random word of length @n@ using @g@ generators (or their inverses)
+randomWord 
+  :: RandomGen g
+  => Int         -- ^ @g@ = number of generators 
+  -> Int         -- ^ @n@ = length of the word
+  -> g -> (Word Int, g)
+randomWord !d !n !g0 = (word,g1) where
+  (g1,word) = mapAccumL (\g _ -> swap (randomGenerator d g)) g0 [1..n]   
+
+-- | A random word of length @n@ using @g@ generators (but not their inverses)
+randomWordNoInv
+  :: RandomGen g
+  => Int         -- ^ @g@ = number of generators 
+  -> Int         -- ^ @n@ = length of the word
+  -> g -> (Word Int, g)
+randomWordNoInv !d !n !g0 = (word,g1) where
+  (g1,word) = mapAccumL (\g _ -> swap (randomGeneratorNoInv d g)) g0 [1..n]   
+  
+--------------------------------------------------------------------------------
+-- * The free group on @g@ generators
+
+{-# SPECIALIZE multiplyFree        :: Word Int -> Word Int -> Word Int #-}
+{-# SPECIALIZE equivalentFree      :: Word Int -> Word Int -> Bool     #-}
+{-# SPECIALIZE reduceWordFree      :: Word Int -> Word Int #-}
+{-# SPECIALIZE reduceWordFreeNaive :: Word Int -> Word Int #-}
+
+-- | Multiplication of the free group (returns the reduced result). It is true
+-- for any two words w1 and w2 that
+--
+-- > multiplyFree (reduceWordFree w1) (reduceWord w2) = multiplyFree w1 w2
+--
+multiplyFree :: Eq idx => Word idx -> Word idx -> Word idx
+multiplyFree w1 w2 = reduceWordFree (w1 ++ w2)
+
+-- | Decides whether two words represent the same group element in the free group
+equivalentFree :: Eq idx => Word idx -> Word idx -> Bool
+equivalentFree w1 w2 = null $ reduceWordFree $ w1 ++ inverseWord w2
+
+-- | Reduces a word in a free group by repeatedly removing @x*x^(-1)@ and
+-- @x^(-1)*x@ pairs. The set of /reduced words/ forms the free group; the
+-- multiplication is obtained by concatenation followed by reduction.
+--
+reduceWordFree :: Eq idx => Word idx -> Word idx
+reduceWordFree = loop where
+
+  loop w = case reduceStep w of
+    Nothing -> w
+    Just w' -> loop w'
+  
+  reduceStep :: Eq a => Word a -> Maybe (Word a)
+  reduceStep = go False where    
+    go !changed w = case w of
+      (Gen x : Inv y : rest) | x==y   -> go True rest
+      (Inv x : Gen y : rest) | x==y   -> go True rest
+      (this : rest)                   -> liftM (this:) $ go changed rest
+      _                               -> if changed then Just w else Nothing
+
+
+-- | Naive (but canonical) reduction algorithm for the free groups
+reduceWordFreeNaive :: Eq idx => Word idx -> Word idx
+reduceWordFreeNaive = loop where
+  loop w = let w' = step w in if w/=w' then loop w' else w
+  step   = concatMap worker . groupBy (equating genIdx) where
+  worker gs 
+    | s>0       = replicate      s  (Gen i)
+    | s<0       = replicate (abs s) (Inv i)
+    | otherwise = []
+    where 
+      i = genIdx (head gs)
+      s = sum' (map genSignValue gs)
+
+--------------------------------------------------------------------------------
+
+-- | Counts the number of words of length @n@ which reduce to the identity element.
+--
+-- Generating function is @Gf_g(u) = \\frac {2g-1} { g-1 + g \\sqrt{ 1 - (8g-4)u^2 } }@
+--
+countIdentityWordsFree
+  :: Int   -- ^ g = number of generators in the free group
+  -> Int   -- ^ n = length of the unreduced word
+  -> Integer
+countIdentityWordsFree g n = countWordReductionsFree g n 0
+  
+-- | Counts the number of words of length @n@ whose reduced form has length @k@
+-- (clearly @n@ and @k@ must have the same parity for this to be nonzero):
+--
+-- > countWordReductionsFree g n k == sum [ 1 | w <- allWords g n, k == length (reduceWordFree w) ]
+--
+countWordReductionsFree 
+  :: Int   -- ^ g = number of generators in the free group
+  -> Int   -- ^ n = length of the unreduced word
+  -> Int   -- ^ k = length of the reduced word
+  -> Integer
+countWordReductionsFree gens_ nn_ kk_
+  | nn==0              = if k==0 then 1 else 0
+  | even nn && kk == 0 = sum [ ( binomial (nn-i) (n  -i) * gg^(i  ) * (gg-1)^(n  -i  ) * (   i) ) `div` (nn-i) | i<-[0..n  ] ]
+  | even nn && even kk = sum [ ( binomial (nn-i) (n-k-i) * gg^(i+1) * (gg-1)^(n+k-i-1) * (kk+i) ) `div` (nn-i) | i<-[0..n-k] ] 
+  | odd  nn && odd  kk = sum [ ( binomial (nn-i) (n-k-i) * gg^(i+1) * (gg-1)^(n+k-i  ) * (kk+i) ) `div` (nn-i) | i<-[0..n-k] ]
+  | otherwise          = 0  
+  where
+    g  = fromIntegral gens_ :: Integer
+    nn = fromIntegral nn_   :: Integer
+    kk = fromIntegral kk_   :: Integer
+    
+    gg = 2*g
+    n = div nn 2
+    k = div kk 2
+    
+--------------------------------------------------------------------------------
+-- * Free powers of cyclic groups
+
+{-# SPECIALIZE multiplyZ2 ::        Word Int -> Word Int -> Word Int #-}
+{-# SPECIALIZE multiplyZ3 ::        Word Int -> Word Int -> Word Int #-}
+{-# SPECIALIZE multiplyZm :: Int -> Word Int -> Word Int -> Word Int #-}
+
+-- | Multiplication in free products of Z2's
+multiplyZ2 :: Eq idx => Word idx -> Word idx -> Word idx
+multiplyZ2 w1 w2 = reduceWordZ2 (w1 ++ w2)
+
+-- | Multiplication in free products of Z3's
+multiplyZ3 :: Eq idx => Word idx -> Word idx -> Word idx
+multiplyZ3 w1 w2 = reduceWordZ3 (w1 ++ w2)
+
+-- | Multiplication in free products of Zm's
+multiplyZm :: Eq idx => Int -> Word idx -> Word idx -> Word idx
+multiplyZm k w1 w2 = reduceWordZm k (w1 ++ w2)
+
+--------------------------------------------------------------------------------
+
+{-# SPECIALIZE equivalentZ2 ::        Word Int -> Word Int -> Bool #-}
+{-# SPECIALIZE equivalentZ3 ::        Word Int -> Word Int -> Bool #-}
+{-# SPECIALIZE equivalentZm :: Int -> Word Int -> Word Int -> Bool #-}
+
+-- | Decides whether two words represent the same group element in free products of Z2
+equivalentZ2 :: Eq idx => Word idx -> Word idx -> Bool
+equivalentZ2 w1 w2 = null $ reduceWordZ2 $ w1 ++ inverseWord w2
+
+-- | Decides whether two words represent the same group element in free products of Z3
+equivalentZ3 :: Eq idx => Word idx -> Word idx -> Bool
+equivalentZ3 w1 w2 = null $ reduceWordZ3 $ w1 ++ inverseWord w2
+
+-- | Decides whether two words represent the same group element in free products of Zm
+equivalentZm :: Eq idx => Int -> Word idx -> Word idx -> Bool
+equivalentZm m w1 w2 = null $ reduceWordZm m $ w1 ++ inverseWord w2
+
+--------------------------------------------------------------------------------
+
+{-# SPECIALIZE reduceWordZ2 ::        Word Int -> Word Int #-}
+{-# SPECIALIZE reduceWordZ3 ::        Word Int -> Word Int #-}
+{-# SPECIALIZE reduceWordZm :: Int -> Word Int -> Word Int #-}
+
+--------------------------------------------------------------------------------
+
+-- | Reduces a word, where each generator @x@ satisfies the additional relation @x^2=1@
+-- (that is, free products of Z2's)
+reduceWordZ2 :: Eq idx => Word idx -> Word idx
+reduceWordZ2 = loop where
+  loop w = case reduceStep w of
+    Nothing -> w
+    Just w' -> loop w'
+ 
+  reduceStep :: Eq a => Word a -> Maybe (Word a)
+  reduceStep = go False where   
+    go !changed w = case w of
+      (Gen x : Gen y : rest) | x==y   -> go True rest
+      (Gen x : Inv y : rest) | x==y   -> go True rest
+      (Inv x : Gen y : rest) | x==y   -> go True rest
+      (Inv x : Inv y : rest) | x==y   -> go True rest
+      (this : rest)                   -> liftM (absGen this:) $ go changed rest
+      _                               -> if changed then Just w else Nothing
+
+-- | Reduces a word, where each generator @x@ satisfies the additional relation @x^3=1@
+-- (that is, free products of Z3's)
+reduceWordZ3 :: Eq idx => Word idx -> Word idx
+reduceWordZ3 = loop where
+  loop w = case reduceStep w of
+    Nothing -> w
+    Just w' -> loop w'
+ 
+  reduceStep :: Eq a => Word a -> Maybe (Word a)
+  reduceStep = go False where   
+    go !changed w = case w of
+      (Gen x : Inv y : rest)         | x==y           -> go True rest
+      (Inv x : Gen y : rest)         | x==y           -> go True rest
+      (Gen x : Gen y : Gen z : rest) | x==y && y==z   -> go True rest
+      (Inv x : Inv y : Inv z : rest) | x==y && y==z   -> go True rest
+      (Gen x : Gen y : rest)         | x==y           -> go True (Inv x : rest)       -- !!!
+      (Inv x : Inv y : rest)         | x==y           -> go True (Gen x : rest)
+      (this : rest)                                   -> liftM (this:) $ go changed rest
+      _                                               -> if changed then Just w else Nothing
+      
+-- | Reduces a word, where each generator @x@ satisfies the additional relation @x^m=1@
+-- (that is, free products of Zm's)
+reduceWordZm :: Eq idx => Int -> Word idx -> Word idx
+reduceWordZm m = loop where
+
+  loop w = case reduceStep w of
+    Nothing -> w
+    Just w' -> loop w'
+
+  halfm = div m 2  -- if we encounter strictly more than m/2 equal elements in a row, we replace them by the inverses
+ 
+  -- reduceStep :: Eq a => Word a -> Maybe (Word a)
+  reduceStep = go False where   
+    go !changed w = case w of
+      (Gen x : Inv y : rest) | x==y                        -> go True rest
+      (Inv x : Gen y : rest) | x==y                        -> go True rest
+      something | Just (k,rest) <- dropIfMoreThanHalf w    -> go True (replicate (m-k) (inverseGen (head w)) ++ rest)
+      (this : rest)                                        -> liftM (this:) $ go changed rest
+      _                                                    -> if changed then Just w else Nothing
+  
+  -- dropIfMoreThanHalf :: Eq a => Word a -> Maybe (Int, Word a)
+  dropIfMoreThanHalf w = 
+    let (!k,rest) = dropWhileEqual w 
+    in  if k > halfm then Just (k,rest)
+                     else Nothing
+                     
+  -- dropWhileEqual :: Eq a => Word a -> (Int, Word a) 
+  dropWhileEqual []     = (0,[])
+  dropWhileEqual (x0:rest) = go 1 rest where
+    go !k []         = (k,[])
+    go !k xxs@(x:xs) = if k==m then (m,xxs) 
+                               else if x==x0 then go (k+1) xs 
+                                             else (k,xxs)
+
+{-  
+  dropm :: Eq a => Word a -> Maybe (Word a)    
+  dropm []     = Nothing
+  dropm (x:xs) = go (m-1) xs where
+    go 0 rest    = Just rest
+    go j (y:ys)  = if y==x 
+      then go (j-1) ys
+      else Nothing 
+    go j []      = Nothing
+-}
+
+--------------------------------------------------------------------------------
+
+{-# SPECIALIZE reduceWordZ2Naive ::        Word Int -> Word Int #-}
+{-# SPECIALIZE reduceWordZ3Naive ::        Word Int -> Word Int #-}
+{-# SPECIALIZE reduceWordZmNaive :: Int -> Word Int -> Word Int #-}
+
+-- | Reduces a word, where each generator @x@ satisfies the additional relation @x^2=1@
+-- (that is, free products of Z2's). Naive (but canonical) algorithm.
+reduceWordZ2Naive :: Eq idx => Word idx -> Word idx
+reduceWordZ2Naive = loop where
+  loop w = let w' = step w in if w/=w' then loop w' else w
+  step   = concatMap worker . groupBy (equating genIdx) where
+  worker gs = 
+    case mod s 2 of
+      1 -> [Gen i]
+      0 -> []
+      _ -> error "reduceWordZ2: fatal error, shouldn't happen"
+    where 
+      i = genIdx (head gs)
+      s = sum' (map genSignValue gs)
+
+-- | Reduces a word, where each generator @x@ satisfies the additional relation @x^3=1@
+-- (that is, free products of Z3's). Naive (but canonical) algorithm.
+reduceWordZ3Naive :: Eq idx => Word idx -> Word idx
+reduceWordZ3Naive = loop where
+  loop w = let w' = step w in if w/=w' then loop w' else w
+  step   = concatMap worker . groupBy (equating genIdx) where
+  worker gs = 
+    case mod s 3 of
+      0 -> []
+      1 -> [Gen i]
+      2 -> [Inv i]
+      _ -> error "reduceWordZ3: fatal error, shouldn't happen"
+    where 
+      i = genIdx (head gs)
+      s = sum' (map genSignValue gs)
+
+-- | Reduces a word, where each generator @x@ satisfies the additional relation @x^m=1@
+-- (that is, free products of Zm's). Naive (but canonical) algorithm.
+reduceWordZmNaive :: Eq idx => Int -> Word idx -> Word idx
+reduceWordZmNaive m = loop where
+  loop w = let w' = step w in if w/=w' then loop w' else w
+  step   = concatMap worker . groupBy (equating genIdx) where
+  halfm1 = div (m+1) 2
+  worker gs 
+    | mods <= halfm1  = replicate    mods  (Gen i)
+    | otherwise       = replicate (m-mods) (Inv i)
+    where 
+      i = genIdx (head gs)
+      s = sum' (map genSignValue gs)
+      mods = mod s m
+
+--------------------------------------------------------------------------------
+
+-- | Counts the number of words (without inverse generators) of length @n@ 
+-- which reduce to the identity element, using the relations @x^2=1@.
+--
+-- Generating function is @Gf_g(u) = \\frac {2g-2} { g-2 + g \\sqrt{ 1 - (4g-4)u^2 } }@
+--
+-- The first few @g@ cases:
+--
+-- > A000984 = [ countIdentityWordsZ2 2 (2*n) | n<-[0..] ] = [1,2,6,20,70,252,924,3432,12870,48620,184756...]
+-- > A089022 = [ countIdentityWordsZ2 3 (2*n) | n<-[0..] ] = [1,3,15,87,543,3543,23823,163719,1143999,8099511,57959535...]
+-- > A035610 = [ countIdentityWordsZ2 4 (2*n) | n<-[0..] ] = [1,4,28,232,2092,19864,195352,1970896,20275660,211823800,2240795848...]
+-- > A130976 = [ countIdentityWordsZ2 5 (2*n) | n<-[0..] ] = [1,5,45,485,5725,71445,925965,12335685,167817405,2321105525,32536755565...]
+--
+countIdentityWordsZ2
+  :: Int   -- ^ g = number of generators in the free group
+  -> Int   -- ^ n = length of the unreduced word
+  -> Integer
+countIdentityWordsZ2 g n = countWordReductionsZ2 g n 0
+
+-- | Counts the number of words (without inverse generators) of length @n@ whose 
+-- reduced form in the product of Z2-s (that is, for each generator @x@ we have @x^2=1@) 
+-- has length @k@
+-- (clearly @n@ and @k@ must have the same parity for this to be nonzero):
+--
+-- > countWordReductionsZ2 g n k == sum [ 1 | w <- allWordsNoInv g n, k == length (reduceWordZ2 w) ]
+--
+countWordReductionsZ2 
+  :: Int   -- ^ g = number of generators in the free group
+  -> Int   -- ^ n = length of the unreduced word
+  -> Int   -- ^ k = length of the reduced word
+  -> Integer
+countWordReductionsZ2 gens_ nn_ kk_
+  | nn==0              = if k==0 then 1 else 0
+  | even nn && kk == 0 = sum [ ( binomial (nn-i) (n  -i) * g^(i  ) * (g-1)^(n  -i  ) * (   i) ) `div` (nn-i) | i<-[0..n  ] ]
+  | even nn && even kk = sum [ ( binomial (nn-i) (n-k-i) * g^(i+1) * (g-1)^(n+k-i-1) * (kk+i) ) `div` (nn-i) | i<-[0..n-k] ] 
+  | odd  nn && odd  kk = sum [ ( binomial (nn-i) (n-k-i) * g^(i+1) * (g-1)^(n+k-i  ) * (kk+i) ) `div` (nn-i) | i<-[0..n-k] ]
+  | otherwise          = 0  
+  where
+    g  = fromIntegral gens_ :: Integer
+    nn = fromIntegral nn_   :: Integer
+    kk = fromIntegral kk_   :: Integer
+    
+    n = div nn 2
+    k = div kk 2
+
+-- | Counts the number of words (without inverse generators) of length @n@ 
+-- which reduce to the identity element, using the relations @x^3=1@.
+--
+-- > countIdentityWordsZ3NoInv g n == sum [ 1 | w <- allWordsNoInv g n, 0 == length (reduceWordZ2 w) ]
+--
+-- In mathematica, the formula is: @Sum[ g^k * (g-1)^(n-k) * k/n * Binomial[3*n-k-1, n-k] , {k, 1,n} ]@
+--
+countIdentityWordsZ3NoInv
+  :: Int   -- ^ g = number of generators in the free group
+  -> Int   -- ^ n = length of the unreduced word
+  -> Integer
+countIdentityWordsZ3NoInv gens_ nn_ 
+  | nn==0           = 1
+  | mod nn 3 == 0   = sum [ ( binomial (3*n-i-1) (n-i) * g^i * (g-1)^(n-i) * i ) `div` n | i<-[1..n] ]
+  | otherwise       = 0
+  where
+    g  = fromIntegral gens_ :: Integer
+    nn = fromIntegral nn_   :: Integer
+    
+    n = div nn 3
+  
+--------------------------------------------------------------------------------
+      
+{-
+
+-- some basic testing. TODO: real tests
+
+import Math.Combinat.Helper
+import Math.Combinat.Groups.Free
+
+g    = 3 :: Int
+maxn = 8 :: Int
+
+bad_free = [ w | n<-[0..maxn] , w <- allWords g n , not (reduceWordFree w `equivalentFree` reduceWordFreeNaive w) ]
+bad_z2   = [ w | n<-[0..maxn] , w <- allWords g n , not (reduceWordZ2   w `equivalentZ2`   reduceWordZ2Naive   w) ]
+bad_z3   = [ w | n<-[0..maxn] , w <- allWords g n , not (reduceWordZ3   w `equivalentZ3`   reduceWordZ3Naive   w) ]
+bad_zm m = [ w | n<-[0..maxn] , w <- allWords g n , not (equivalentZm m (reduceWordZm m w) (reduceWordZmNaive m w)) ]
+
+speed_free = sum' [ length (reduceWordFree w) | n<-[0..maxn] , w <- allWords g n ]
+speed_z2   = sum' [ length (reduceWordZ2   w) | n<-[0..maxn] , w <- allWords g n ]
+speed_z3   = sum' [ length (reduceWordZ3   w) | n<-[0..maxn] , w <- allWords g n ]
+speed_zm m = sum' [ length (reduceWordZm m w) | n<-[0..maxn] , w <- allWords g n ]
+
+naive_speed_free = sum' [ length (reduceWordFreeNaive w) | n<-[0..maxn] , w <- allWords g n ]
+naive_speed_z2   = sum' [ length (reduceWordZ2Naive   w) | n<-[0..maxn] , w <- allWords g n ]
+naive_speed_z3   = sum' [ length (reduceWordZ3Naive   w) | n<-[0..maxn] , w <- allWords g n ]
+naive_speed_zm m = sum' [ length (reduceWordZmNaive m w) | n<-[0..maxn] , w <- allWords g n ]
+
+-}
+
+--------------------------------------------------------------------------------
+
+
diff --git a/Math/Combinat/Groups/Thompson/F.hs b/Math/Combinat/Groups/Thompson/F.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Groups/Thompson/F.hs
@@ -0,0 +1,404 @@
+
+-- | Thompson's group F.
+--
+-- See eg. <https://en.wikipedia.org/wiki/Thompson_groups>
+--
+-- Based mainly on James Michael Belk's PhD thesis \"THOMPSON'S GROUP F\";
+-- see <http://www.math.u-psud.fr/~breuilla/Belk.pdf>
+--
+
+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, BangPatterns, PatternSynonyms, DeriveFunctor #-}
+module Math.Combinat.Groups.Thompson.F where
+
+--------------------------------------------------------------------------------
+
+import Data.List
+
+import Math.Combinat.Classes
+import Math.Combinat.ASCII
+
+import Math.Combinat.Trees.Binary ( BinTree )
+import qualified Math.Combinat.Trees.Binary as B
+
+--------------------------------------------------------------------------------
+-- * Tree diagrams
+
+-- | A tree diagram, consisting of two binary trees with the same number of leaves, 
+-- representing an element of the group F.
+data TDiag = TDiag 
+  { _width  :: !Int      -- ^ the width is the number of leaves, minus 1, of both diagrams
+  , _domain :: !T        -- ^ the top diagram correspond to the /domain/
+  , _range  :: !T        -- ^ the bottom diagram corresponds to the /range/
+  }
+  deriving (Eq,Ord,Show)
+
+instance DrawASCII TDiag where
+  ascii = asciiTDiag
+
+instance HasWidth TDiag where
+  width = _width
+
+-- | Creates a tree diagram from two trees
+mkTDiag :: T -> T -> TDiag 
+mkTDiag d1 d2 = reduce $ mkTDiagDontReduce d1 d2
+
+-- | Creates a tree diagram, but does not reduce it.
+mkTDiagDontReduce :: T -> T -> TDiag 
+mkTDiagDontReduce top bot = 
+  if w1 == w2 
+    then TDiag w1 top bot 
+    else error "mkTDiag: widths do not match"
+  where
+    w1 = treeWidth top 
+    w2 = treeWidth bot
+
+
+isValidTDiag :: TDiag -> Bool
+isValidTDiag (TDiag w top bot) = (treeWidth top == w && treeWidth bot == w)
+
+isPositive :: TDiag -> Bool
+isPositive (TDiag w top bot) = (bot == rightVine w)
+
+isReduced :: TDiag -> Bool
+isReduced diag = (reduce diag == diag)
+
+-- | The generator x0
+x0 :: TDiag
+x0 = TDiag 2 top bot where
+  top = branch caret leaf
+  bot = branch leaf  caret
+
+-- | The generator x1
+x1 :: TDiag
+x1 = xk 1
+
+-- | The generators x0, x1, x2 ...
+xk :: Int -> TDiag
+xk = go where
+  go k | k< 0      = error "xk: negative indexed generator"
+       | k==0      = x0
+       | otherwise = let TDiag _ t b = go (k-1) 
+                     in  TDiag (k+2) (branch leaf t) (branch leaf b)
+
+-- | The identity element in the group F                     
+identity :: TDiag
+identity = TDiag 0 Lf Lf
+
+-- | A /positive diagram/ is a diagram whose bottom tree (the range) is a right vine.
+positive :: T -> TDiag
+positive t = TDiag w t (rightVine w) where w = treeWidth t
+
+-- | Swaps the top and bottom of a tree diagram. This is the inverse in the group F.
+-- (Note: we don't do reduction here, as this operation keeps the reducedness)
+inverse :: TDiag -> TDiag
+inverse (TDiag w top bot) = TDiag w bot top
+
+-- | Decides whether two (possibly unreduced) tree diagrams represents the same group element in F.
+equivalent :: TDiag -> TDiag -> Bool
+equivalent diag1 diag2 = (identity == reduce (compose diag1 (inverse diag2)))
+
+--------------------------------------------------------------------------------
+-- * Reduction of tree diagrams
+
+-- | Reduces a diagram. The result is a normal form of an element in the group F.
+reduce :: TDiag -> TDiag
+reduce = worker where
+
+  worker :: TDiag -> TDiag
+  worker diag = case step diag of
+    Nothing    -> diag
+    Just diag' -> worker diag'
+
+  step :: TDiag -> Maybe TDiag
+  step (TDiag w top bot) = 
+    if null idxs 
+      then Nothing
+      else Just $ TDiag w' top' bot'
+    where
+      cs1  = treeCaretList top
+      cs2  = treeCaretList bot
+      idxs = sortedIntersect cs1 cs2
+      w'   = w - length idxs
+      top' = removeCarets idxs top
+      bot' = removeCarets idxs bot
+
+  -- | Intersects sorted lists      
+  sortedIntersect :: [Int] -> [Int] -> [Int]
+  sortedIntersect = go where
+    go [] _  = []
+    go _  [] = []
+    go xxs@(x:xs) yys@(y:ys) = case compare x y of
+      LT ->     go  xs yys
+      EQ -> x : go  xs  ys
+      GT ->     go xxs  ys
+
+-- | List of carets at the bottom of the tree, indexed by their left edge position
+treeCaretList :: T -> [Int]
+treeCaretList = snd . go 0 where
+  go !x t = case t of 
+    Lf        ->  (x+1 , []  )
+    Ct        ->  (x+2 , [x] )
+    Br t1 t2  ->  (x2  , cs1++cs2) where
+      (x1 , cs1) = go x  t1
+      (x2 , cs2) = go x1 t2
+
+-- | Remove the carets with the given indices 
+-- (throws an error if there is no caret at the given index)
+removeCarets :: [Int] -> T -> T
+removeCarets idxs tree = if null rem then final else error ("removeCarets: some stuff remained: " ++ show rem) where
+
+  (_,rem,final) =  go 0 idxs tree where
+
+  go :: Int -> [Int] -> T -> (Int,[Int],T)
+  go !x []         t  = (x + treeWidth t , [] , t)
+  go !x iis@(i:is) t  = case t of
+    Lf        ->  (x+1 , iis , t)
+    Ct        ->  if x==i then (x+2 , is , Lf) else (x+2 , iis , Ct)
+    Br t1 t2  ->  (x2  , iis2 , Br t1' t2') where
+      (x1 , iis1 , t1') = go x  iis  t1
+      (x2 , iis2 , t2') = go x1 iis1 t2
+      
+--------------------------------------------------------------------------------
+-- * Composition of tree diagrams
+
+-- | If @diag1@ corresponds to the PL function @f@, and @diag2@ to @g@, then @compose diag1 diag2@ 
+-- will correspond to @(g.f)@ (note that the order is opposite than normal function composition!)
+--
+-- This is the multiplication in the group F.
+--
+compose :: TDiag -> TDiag -> TDiag
+compose d1 d2 = reduce (composeDontReduce d1 d2)
+
+-- | Compose two tree diagrams without reducing the result
+composeDontReduce :: TDiag -> TDiag -> TDiag
+composeDontReduce (TDiag w1 top1 bot1) (TDiag w2 top2 bot2) = new where
+  new = mkTDiagDontReduce top' bot' 
+  (list1,list2) = extensionToCommonTree bot1 top2
+  top' = listGraft list1 top1
+  bot' = listGraft list2 bot2
+
+-- | Given two binary trees, we return a pair of list of subtrees which, grafted the to leaves of
+-- the first (resp. the second) tree, results in the same extended tree.
+extensionToCommonTree :: T -> T -> ([T],[T])
+extensionToCommonTree t1 t2 = snd $ go (0,0) (t1,t2) where
+  go (!x1,!x2) (!t1,!t2) = 
+    case (t1,t2) of
+      ( Lf       , Lf       ) -> ( (x1+n1 , x2+n2 ) , (             [Lf] ,             [Lf] ) )
+      ( Lf       , Br _  _  ) -> ( (x1+n1 , x2+n2 ) , (             [t2] , replicate n2 Lf  ) )
+      ( Br _  _  , Lf       ) -> ( (x1+n1 , x2+n2 ) , ( replicate n1 Lf  ,             [t1] ) )
+      ( Br l1 r1 , Br l2 r2 ) 
+        -> let ( (x1' ,x2' ) , (ps1,ps2) ) = go (x1 ,x2 ) (l1,l2)
+               ( (x1'',x2'') , (qs1,qs2) ) = go (x1',x2') (r1,r2)
+           in  ( (x1'',x2'') , (ps1++qs1, ps2++qs2) )
+    where
+      n1 = numberOfLeaves t1
+      n2 = numberOfLeaves t2
+
+--------------------------------------------------------------------------------
+-- * Subdivions
+
+-- | Returns the list of dyadic subdivision points
+subdivision1 :: T -> [Rational]
+subdivision1 = go 0 1 where
+  go !a !b t = case t of
+    Leaf   _   -> [a,b]
+    Branch l r -> go a c l ++ tail (go c b r) where c = (a+b)/2
+
+-- | Returns the list of dyadic intervals
+subdivision2 :: T -> [(Rational,Rational)]
+subdivision2 = go 0 1 where
+  go !a !b t = case t of
+    Leaf   _   -> [(a,b)]
+    Branch l r -> go a c l ++ go c b r where c = (a+b)/2
+
+
+--------------------------------------------------------------------------------
+-- * Binary trees
+
+-- | A (strict) binary tree with labelled leaves (but unlabelled nodes)
+data Tree a
+  = Branch !(Tree a) !(Tree a)
+  | Leaf   !a
+  deriving (Eq,Ord,Show,Functor)
+
+-- | The monadic join operation of binary trees
+graft :: Tree (Tree a) -> Tree a
+graft = go where
+  go (Branch l r) = Branch (go l) (go r)
+  go (Leaf   t  ) = t 
+
+-- | A list version of 'graft'
+listGraft :: [Tree a] -> Tree b -> Tree a
+listGraft subs big = snd $ go subs big where  
+  go ggs@(g:gs) t = case t of
+    Leaf   _   -> (gs,g)
+    Branch l r -> (gs2, Branch l' r') where
+                    (gs1,l') = go ggs l
+                    (gs2,r') = go gs1 r
+
+-- | A completely unlabelled binary tree
+type T = Tree ()
+
+instance DrawASCII T where
+  ascii = asciiT 
+
+instance HasNumberOfLeaves (Tree a) where
+  numberOfLeaves = treeNumberOfLeaves
+
+instance HasWidth (Tree a) where
+  width = treeWidth
+
+leaf :: T
+leaf = Leaf ()
+
+branch :: T -> T -> T
+branch = Branch
+
+caret :: T
+caret = branch leaf leaf
+
+treeNumberOfLeaves :: Tree a -> Int
+treeNumberOfLeaves = go where
+  go (Branch l r) = go l + go r
+  go (Leaf   _  ) = 1  
+
+-- | The width of the tree is the number of leaves minus 1.
+treeWidth :: Tree a -> Int
+treeWidth t = numberOfLeaves t - 1
+
+-- | Enumerates the leaves a tree, starting from 0
+enumerate_ :: Tree a -> Tree Int
+enumerate_ = snd . enumerate
+
+-- | Enumerates the leaves a tree, and also returns the number of leaves
+enumerate :: Tree a -> (Int, Tree Int)
+enumerate = go 0 where
+  go !k t = case t of
+    Leaf   _   -> (k+1 , Leaf k)
+    Branch l r -> let (k' ,l') = go k  l
+                      (k'',r') = go k' r
+                  in (k'', Branch l' r') 
+
+-- | \"Right vine\" of the given width 
+rightVine :: Int -> T
+rightVine k 
+  | k< 0      = error "rightVine: negative width"
+  | k==0      = leaf
+  | otherwise = branch leaf (rightVine (k-1))
+
+-- | \"Left vine\" of the given width 
+leftVine :: Int -> T
+leftVine k 
+  | k< 0      = error "leftVine: negative width"
+  | k==0      = leaf
+  | otherwise = branch (leftVine (k-1)) leaf 
+
+-- | Flips each node of a binary tree
+flipTree :: Tree a -> Tree a
+flipTree = go where
+  go t = case t of
+    Leaf   _   -> t
+    Branch l r -> Branch (go r) (go l)
+
+--------------------------------------------------------------------------------
+-- * Conversion to\/from BinTree
+
+-- | 'Tree' and 'BinTree' are the same type, except that 'Tree' is strict.
+--
+-- TODO: maybe unify these two types? Until that, you can convert between the two
+-- with these functions if necessary.
+--
+toBinTree :: Tree a -> B.BinTree a
+toBinTree = go where
+  go (Branch l r) = B.Branch (go l) (go r)
+  go (Leaf   y  ) = B.Leaf   y
+
+fromBinTree :: B.BinTree a -> Tree a 
+fromBinTree = go where
+  go (B.Branch l r) = Branch (go l) (go r)
+  go (B.Leaf   y  ) = Leaf   y
+    
+--------------------------------------------------------------------------------
+-- * Pattern synonyms
+
+pattern Lf     = Leaf ()
+pattern Br l r = Branch l r
+pattern Ct     = Br Lf Lf
+pattern X0     = TDiag 2        (Br Ct Lf)         (Br Lf Ct)
+pattern X1     = TDiag 3 (Br Lf (Br Ct Lf)) (Br Lf (Br Lf Ct))
+
+--------------------------------------------------------------------------------
+-- * ASCII
+
+-- | Draws a binary tree, with all leaves at the same (bottom) row
+asciiT :: T -> ASCII
+asciiT = asciiT' False
+
+-- | Draws a binary tree; when the boolean flag is @True@, we draw upside down
+asciiT' :: Bool -> T -> ASCII
+asciiT' inv = go where
+
+  go t = case t of
+    Leaf _                   -> emptyRect 
+    Branch l r -> 
+      if yl >= yr
+        then pasteOnto (yl+yr+1,if inv then yr else 0) (rs $ yl+1) $ 
+               vcat HCenter 
+                 (bc $ yr+1) 
+                 (hcat bot al ar)
+        else pasteOnto (yl, if inv then yl else 0) (ls $ yr+1) $
+               vcat HCenter 
+                 (bc $ yl+1) 
+                 (hcat bot al ar)
+      where
+        al = go l
+        ar = go r
+        yl = asciiYSize al 
+        yr = asciiYSize ar 
+
+  bot = if inv then VTop else VBottom
+  hcat align p q = hCatWith align (HSepString "  ") [p,q]
+  vcat align p q = vCatWith align VSepEmpty $ if inv then [q,p] else [p,q]
+  bc = if inv then asciiBigInvCaret   else asciiBigCaret
+  ls = if inv then asciiBigRightSlope else asciiBigLeftSlope
+  rs = if inv then asciiBigLeftSlope  else asciiBigRightSlope
+
+  asciiBigCaret :: Int -> ASCII
+  asciiBigCaret k = hCatWith VTop HSepEmpty [ asciiBigLeftSlope k , asciiBigRightSlope k ]
+
+  asciiBigInvCaret :: Int -> ASCII
+  asciiBigInvCaret k = hCatWith VTop HSepEmpty [ asciiBigRightSlope k , asciiBigLeftSlope k ]
+
+  asciiBigLeftSlope :: Int -> ASCII  
+  asciiBigLeftSlope k = if k>0 
+    then asciiFromLines [ replicate l ' ' ++ "/" | l<-[k-1,k-2..0] ]
+    else emptyRect
+
+  asciiBigRightSlope :: Int -> ASCII  
+  asciiBigRightSlope k = if k>0 
+    then asciiFromLines [ replicate l ' ' ++ "\\" | l<-[0..k-1] ]
+    else emptyRect
+  
+-- | Draws a binary tree, with all leaves at the same (bottom) row, and labelling
+-- the leaves starting with 0 (continuing with letters after 9)
+asciiTLabels :: T -> ASCII
+asciiTLabels = asciiTLabels' False
+
+-- | When the flag is true, we draw upside down
+asciiTLabels' :: Bool -> T -> ASCII
+asciiTLabels' inv t = 
+  if inv 
+    then vCatWith HLeft VSepEmpty [ labels , asciiT' inv t ]
+    else vCatWith HLeft VSepEmpty [ asciiT' inv t , labels ]
+  where
+    w = treeWidth t
+    labels = asciiFromString $ intersperse ' ' $ take (w+1) allLabels
+    allLabels = ['0'..'9'] ++ ['a'..'z']
+    
+-- | Draws a tree diagram
+asciiTDiag :: TDiag -> ASCII
+asciiTDiag (TDiag _ top bot) = vCatWith HLeft (VSepString " ") [asciiT' False top , asciiT' True bot]
+
+--------------------------------------------------------------------------------
+
+
diff --git a/Math/Combinat/Helper.hs b/Math/Combinat/Helper.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Helper.hs
@@ -0,0 +1,280 @@
+
+-- | Miscellaneous helper functions
+
+{-# LANGUAGE BangPatterns, PolyKinds, GeneralizedNewtypeDeriving #-}
+module Math.Combinat.Helper where
+
+--------------------------------------------------------------------------------
+
+import Control.Monad
+import Control.Applicative ( Applicative(..) )    -- required before AMP (before GHC 7.10)
+import Data.Functor.Identity
+
+import Data.List
+import Data.Ord
+import Data.Proxy
+
+import Data.Set (Set) ; import qualified Data.Set as Set
+import Data.Map (Map) ; import qualified Data.Map as Map
+
+import Debug.Trace
+
+import System.Random
+import Control.Monad.Trans.State
+
+--------------------------------------------------------------------------------
+-- * debugging
+
+debug :: Show a => a -> b -> b
+debug x y = trace ("-- " ++ show x ++ "\n") y
+
+--------------------------------------------------------------------------------
+-- * pairs
+
+swap :: (a,b) -> (b,a)
+swap (x,y) = (y,x)
+
+pairs :: [a] -> [(a,a)]
+pairs = go where
+  go (x:xs@(y:_)) = (x,y) : go xs
+  go _            = []
+
+pairsWith :: (a -> a -> b) -> [a] -> [b]
+pairsWith f = go where
+  go (x:xs@(y:_)) = f x y : go xs
+  go _            = []
+
+--------------------------------------------------------------------------------
+-- * lists
+
+{-# SPECIALIZE sum' :: [Int]     -> Int     #-}
+{-# SPECIALIZE sum' :: [Integer] -> Integer #-}
+sum' :: Num a => [a] -> a
+sum' = foldl' (+) 0
+
+--------------------------------------------------------------------------------
+-- * equality and ordering 
+
+equating :: Eq b => (a -> b) -> a -> a -> Bool
+equating f x y = (f x == f y)
+
+reverseOrdering :: Ordering -> Ordering
+reverseOrdering LT = GT
+reverseOrdering GT = LT
+reverseOrdering EQ = EQ
+
+reverseCompare :: Ord a => a -> a -> Ordering
+reverseCompare x y = reverseOrdering $ compare x y
+
+reverseSort :: Ord a => [a] -> [a]
+reverseSort = sortBy reverseCompare
+
+groupSortBy :: (Eq b, Ord b) => (a -> b) -> [a] -> [[a]]
+groupSortBy f = groupBy (equating f) . sortBy (comparing f) 
+
+nubOrd :: Ord a => [a] -> [a]
+nubOrd = worker Set.empty where
+  worker _ [] = []
+  worker s (x:xs) 
+    | Set.member x s = worker s xs
+    | otherwise      = x : worker (Set.insert x s) xs
+
+--------------------------------------------------------------------------------
+-- * increasing \/ decreasing sequences
+
+{-# SPECIALIZE isWeaklyIncreasing :: [Int] -> Bool #-}
+isWeaklyIncreasing :: Ord a => [a] -> Bool
+isWeaklyIncreasing = go where
+  go xs = case xs of 
+    (a:rest@(b:_)) -> a <= b && go rest
+    [_]            -> True
+    []             -> True
+
+{-# SPECIALIZE isStrictlyIncreasing :: [Int] -> Bool #-}
+isStrictlyIncreasing :: Ord a => [a] -> Bool
+isStrictlyIncreasing = go where
+  go xs = case xs of 
+    (a:rest@(b:_)) -> a < b && go rest
+    [_]            -> True
+    []             -> True
+
+{-# SPECIALIZE isWeaklyDecreasing :: [Int] -> Bool #-}
+isWeaklyDecreasing :: Ord a => [a] -> Bool
+isWeaklyDecreasing = go where
+  go xs = case xs of 
+    (a:rest@(b:_)) -> a >= b && go rest
+    [_]            -> True
+    []             -> True
+
+{-# SPECIALIZE isStrictlyDecreasing :: [Int] -> Bool #-}
+isStrictlyDecreasing :: Ord a => [a] -> Bool
+isStrictlyDecreasing = go where
+  go xs = case xs of 
+    (a:rest@(b:_)) -> a > b && go rest
+    [_]            -> True
+    []             -> True
+
+--------------------------------------------------------------------------------
+-- * first \/ last 
+
+-- | The boolean argument will @True@ only for the last element
+mapWithLast :: (Bool -> a -> b) -> [a] -> [b]
+mapWithLast f = go where
+  go (x : []) = f True  x : []
+  go (x : xs) = f False x : go xs
+
+mapWithFirst :: (Bool -> a -> b) -> [a] -> [b]
+mapWithFirst f = go True where
+  go b (x:xs) = f b x : go False xs 
+  
+mapWithFirstLast :: (Bool -> Bool -> a -> b) -> [a] -> [b]
+mapWithFirstLast f = go True where
+  go b (x : []) = f b True  x : []
+  go b (x : xs) = f b False x : go False xs
+
+--------------------------------------------------------------------------------
+-- * older helpers for ASCII drawing
+
+-- | extend lines with spaces so that they have the same line
+mkLinesUniformWidth :: [String] -> [String]
+mkLinesUniformWidth old = zipWith worker ls old where
+  ls = map length old
+  m  = maximum ls
+  worker l s = s ++ replicate (m-l) ' '
+
+mkBlocksUniformHeight :: [[String]] -> [[String]]
+mkBlocksUniformHeight old = zipWith worker ls old where
+  ls = map length old
+  m  = maximum ls
+  worker l s = s ++ replicate (m-l) ""
+    
+mkUniformBlocks :: [[String]] -> [[String]] 
+mkUniformBlocks = map mkLinesUniformWidth . mkBlocksUniformHeight
+    
+hConcatLines :: [[String]] -> [String]
+hConcatLines = map concat . transpose . mkUniformBlocks
+
+vConcatLines :: [[String]] -> [String]  
+vConcatLines = concat
+
+--------------------------------------------------------------------------------
+-- * counting
+
+-- helps testing the random rutines 
+count :: Eq a => a -> [a] -> Int
+count x xs = length $ filter (==x) xs
+
+histogram :: (Eq a, Ord a) => [a] -> [(a,Int)]
+histogram xs = Map.toList table where
+  table = Map.fromListWith (+) [ (x,1) | x<-xs ] 
+
+--------------------------------------------------------------------------------
+-- * maybe
+
+fromJust :: Maybe a -> a
+fromJust (Just x) = x
+fromJust Nothing = error "fromJust: Nothing"
+
+--------------------------------------------------------------------------------
+-- * bool
+
+intToBool :: Int -> Bool
+intToBool 0 = False
+intToBool 1 = True
+intToBool _ = error "intToBool"
+
+boolToInt :: Bool -> Int 
+boolToInt False = 0
+boolToInt True  = 1
+
+--------------------------------------------------------------------------------
+-- * iteration
+    
+-- iterated function application
+nest :: Int -> (a -> a) -> a -> a
+nest !0 _ x = x
+nest !n f x = nest (n-1) f (f x)
+
+unfold1 :: (a -> Maybe a) -> a -> [a]
+unfold1 f x = case f x of 
+  Nothing -> [x] 
+  Just y  -> x : unfold1 f y 
+  
+unfold :: (b -> (a,Maybe b)) -> b -> [a]
+unfold f y = let (x,m) = f y in case m of 
+  Nothing -> [x]
+  Just y' -> x : unfold f y'
+
+unfoldEither :: (b -> Either c (b,a)) -> b -> (c,[a])
+unfoldEither f y = case f y of
+  Left z -> (z,[])
+  Right (y,x) -> let (z,xs) = unfoldEither f y in (z,x:xs)
+  
+unfoldM :: Monad m => (b -> m (a,Maybe b)) -> b -> m [a]
+unfoldM f y = do
+  (x,m) <- f y
+  case m of
+    Nothing -> return [x]
+    Just y' -> do
+      xs <- unfoldM f y'
+      return (x:xs)
+
+mapAccumM :: Monad m => (acc -> x -> m (acc, y)) -> acc -> [x] -> m (acc, [y])
+mapAccumM _ s [] = return (s, [])
+mapAccumM f s (x:xs) = do
+  (s1,y) <- f s x
+  (s2,ys) <- mapAccumM f s1 xs
+  return (s2, y:ys)
+
+--------------------------------------------------------------------------------
+-- * long zipwith    
+
+longZipWith :: a -> b -> (a -> b -> c) -> [a] -> [b] -> [c]
+longZipWith a0 b0 f = go where
+  go (x:xs) (y:ys)  =   f x  y : go xs ys
+  go []     ys      = [ f a0 y | y<-ys ]
+  go xs     []      = [ f x b0 | x<-xs ]
+
+{-
+longZipWithZero :: (Num a, Num b) => (a -> b -> c) -> [a] -> [b] -> [c]
+longZipWithZero = longZipWith 0 0 
+-}
+
+--------------------------------------------------------------------------------
+-- * random
+
+-- | A simple random monad to make life suck less
+type Rand g = RandT g Identity
+
+runRand :: Rand g a -> g -> (a,g)
+runRand action g = runIdentity (runRandT action g)
+
+flipRunRand :: Rand s a -> s -> (s,a)
+flipRunRand action g = runIdentity (flipRunRandT action g)
+
+
+-- | The Rand monad transformer
+newtype RandT g m a = RandT (StateT g m a) deriving (Functor,Applicative,Monad)
+
+runRandT :: RandT g m a -> g -> m (a,g)
+runRandT (RandT stuff) = runStateT stuff
+
+-- | This may be occasionally useful
+flipRunRandT :: Monad m => RandT s m a -> s -> m (s,a)
+flipRunRandT action ini = liftM swap $ runRandT action ini
+
+
+-- | Puts a standard-conforming random function into the monad
+rand :: (g -> (a,g)) -> Rand g a
+rand user = RandT (state user)
+
+randRoll :: (RandomGen g, Random a) => Rand g a
+randRoll = rand random
+
+randChoose :: (RandomGen g, Random a) => (a,a) -> Rand g a
+randChoose uv = rand (randomR uv)
+
+randProxy1 :: Rand g (f n) -> Proxy n -> Rand g (f n)
+randProxy1 action _ = action
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/LatticePaths.hs b/Math/Combinat/LatticePaths.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/LatticePaths.hs
@@ -0,0 +1,386 @@
+
+-- | Dyck paths, lattice paths, etc
+--
+-- For example, the following figure represents a Dyck path of height 5 with 3 zero-touches (not counting the starting point,
+-- but counting the endpoint) and 7 peaks:
+--
+-- <<svg/dyck_path.svg>>
+--
+
+{-# LANGUAGE BangPatterns, FlexibleInstances, TypeSynonymInstances #-}
+module Math.Combinat.LatticePaths where
+
+--------------------------------------------------------------------------------
+
+import Data.List
+import System.Random
+
+import Math.Combinat.Classes
+import Math.Combinat.Numbers
+import Math.Combinat.Trees.Binary
+import Math.Combinat.ASCII as ASCII
+
+--------------------------------------------------------------------------------
+-- * Types
+
+-- | A step in a lattice path
+data Step 
+  = UpStep         -- ^ the step @(1,1)@
+  | DownStep       -- ^ the step @(1,-1)@
+  deriving (Eq,Ord,Show)
+
+-- | A lattice path is a path using only the allowed steps, never going below the zero level line @y=0@. 
+--
+-- Note that if you rotate such a path by 45 degrees counterclockwise,
+-- you get a path which uses only the steps @(1,0)@ and @(0,1)@, and stays
+-- above the main diagonal (hence the name, we just use a different convention).
+--
+type LatticePath = [Step]
+
+--------------------------------------------------------------------------------
+-- * ascii drawing of paths
+
+-- | Draws the path into a list of lines. For example try:
+--
+-- > autotabulate RowMajor (Right 5) (map asciiPath $ dyckPaths 4)
+--
+asciiPath :: LatticePath -> ASCII
+asciiPath p = asciiFromLines $ transpose (go 0 p) where
+
+  go !h [] = []
+  go !h (x:xs) = case x of
+    UpStep   -> ee  h    x : go (h+1) xs
+    DownStep -> ee (h-1) x : go (h-1) xs
+
+  maxh   = pathHeight p
+
+  ee h x = replicate (maxh-h-1) ' ' ++ [ch x] ++ replicate h ' '
+  ch x   = case x of 
+    UpStep   -> '/' 
+    DownStep -> '\\' 
+
+instance DrawASCII LatticePath where 
+  ascii = asciiPath
+
+--------------------------------------------------------------------------------
+-- * elementary queries
+
+-- | A lattice path is called \"valid\", if it never goes below the @y=0@ line.
+isValidPath :: LatticePath -> Bool
+isValidPath = go 0 where
+  go :: Int -> LatticePath -> Bool
+  go !y []     = y>=0
+  go !y (t:ts) = let y' = case t of { UpStep -> y+1 ; DownStep -> y-1 }
+                 in  if y'<0 then False 
+                             else go y' ts
+
+-- | A Dyck path is a lattice path whose last point lies on the @y=0@ line
+isDyckPath :: LatticePath -> Bool
+isDyckPath = go 0 where
+  go :: Int -> LatticePath -> Bool
+  go !y []     = y==0
+  go !y (t:ts) = let y' = case t of { UpStep -> y+1 ; DownStep -> y-1 }
+                 in  if y'<0 then False 
+                             else go y' ts
+
+-- | Maximal height of a lattice path
+pathHeight :: LatticePath -> Int
+pathHeight = go 0 0 where
+  go :: Int -> Int -> LatticePath -> Int
+  go !h !y []     = h
+  go !h !y (t:ts) = case t of
+    UpStep   -> go (max h (y+1)) (y+1) ts
+    DownStep -> go      h        (y-1) ts
+
+instance HasHeight LatticePath where
+  height = pathHeight
+
+instance HasWidth LatticePath where
+  width = length
+
+-- | Endpoint of a lattice path, which starts from @(0,0)@.
+pathEndpoint :: LatticePath -> (Int,Int)
+pathEndpoint = go 0 0 where
+  go :: Int -> Int -> LatticePath -> (Int,Int)
+  go !x !y []     = (x,y)
+  go !x !y (t:ts) = case t of                         
+    UpStep   -> go (x+1) (y+1) ts
+    DownStep -> go (x+1) (y-1) ts
+
+-- | Returns the coordinates of the path (excluding the starting point @(0,0)@, but including
+-- the endpoint)
+pathCoordinates :: LatticePath -> [(Int,Int)]
+pathCoordinates = go 0 0 where
+  go :: Int -> Int -> LatticePath -> [(Int,Int)]
+  go _  _  []     = []
+  go !x !y (t:ts) = let x' = x + 1
+                        y' = case t of { UpStep -> y+1 ; DownStep -> y-1 }
+                    in  (x',y') : go x' y' ts
+
+-- | Counts the up-steps
+pathNumberOfUpSteps :: LatticePath -> Int
+pathNumberOfUpSteps   = fst . pathNumberOfUpDownSteps
+
+-- | Counts the down-steps
+pathNumberOfDownSteps :: LatticePath -> Int
+pathNumberOfDownSteps = snd . pathNumberOfUpDownSteps
+
+-- | Counts both the up-steps and down-steps
+pathNumberOfUpDownSteps :: LatticePath -> (Int,Int)
+pathNumberOfUpDownSteps = go 0 0 where 
+  go :: Int -> Int -> LatticePath -> (Int,Int)
+  go !u !d (p:ps) = case p of 
+    UpStep   -> go (u+1)  d    ps  
+    DownStep -> go  u    (d+1) ps    
+  go !u !d []     = (u,d)
+
+--------------------------------------------------------------------------------
+-- * path-specific queries
+
+-- | Number of peaks of a path (excluding the endpoint)
+pathNumberOfPeaks :: LatticePath -> Int
+pathNumberOfPeaks = go 0 where
+  go :: Int -> LatticePath -> Int
+  go !k (x:xs@(y:_)) = go (if x==UpStep && y==DownStep then k+1 else k) xs
+  go !k [x] = k
+  go !k [ ] = k
+
+-- | Number of points on the path which touch the @y=0@ zero level line
+-- (excluding the starting point @(0,0)@, but including the endpoint; that is, for Dyck paths it this is always positive!).
+pathNumberOfZeroTouches :: LatticePath -> Int
+pathNumberOfZeroTouches = pathNumberOfTouches' 0
+
+-- | Number of points on the path which touch the level line at height @h@
+-- (excluding the starting point @(0,0)@, but including the endpoint).
+pathNumberOfTouches' 
+  :: Int       -- ^ @h@ = the touch level
+  -> LatticePath -> Int
+pathNumberOfTouches' h = go 0 0 0 where
+  go :: Int -> Int -> Int -> LatticePath -> Int
+  go !cnt _  _  []     = cnt
+  go !cnt !x !y (t:ts) = let y'   = case t of { UpStep -> y+1 ; DownStep -> y-1 }
+                             cnt' = if y'==h then cnt+1 else cnt
+                         in  go cnt' (x+1) y' ts
+
+--------------------------------------------------------------------------------
+-- * Dyck paths
+
+-- | @dyckPaths m@ lists all Dyck paths from @(0,0)@ to @(2m,0)@. 
+-- 
+-- Remark: Dyck paths are obviously in bijection with nested parentheses, and thus
+-- also with binary trees.
+--
+-- Order is reverse lexicographical:
+--
+-- > sort (dyckPaths m) == reverse (dyckPaths m)
+-- 
+dyckPaths :: Int -> [LatticePath]
+dyckPaths = map nestedParensToDyckPath . nestedParentheses 
+
+-- | @dyckPaths m@ lists all Dyck paths from @(0,0)@ to @(2m,0)@. 
+--
+-- > sort (dyckPathsNaive m) == sort (dyckPaths m) 
+--  
+-- Naive recursive algorithm, order is ad-hoc
+--
+dyckPathsNaive :: Int -> [LatticePath]
+dyckPathsNaive = worker where
+  worker  0 = [[]]
+  worker  m = as ++ bs where
+    as = [ bracket p      | p <- worker (m-1) ] 
+    bs = [ bracket p ++ q | k <- [1..m-1] , p <- worker (k-1) , q <- worker (m-k) ]
+  bracket p = UpStep : p ++ [DownStep]
+
+-- | The number of Dyck paths from @(0,0)@ to @(2m,0)@ is simply the m\'th Catalan number.
+countDyckPaths :: Int -> Integer
+countDyckPaths m = catalan m
+
+-- | The trivial bijection
+nestedParensToDyckPath :: [Paren] -> LatticePath
+nestedParensToDyckPath = map f where
+  f p = case p of { LeftParen -> UpStep ; RightParen -> DownStep }
+
+-- | The trivial bijection in the other direction
+dyckPathToNestedParens :: LatticePath -> [Paren]
+dyckPathToNestedParens = map g where
+  g s = case s of { UpStep -> LeftParen ; DownStep -> RightParen }
+
+--------------------------------------------------------------------------------
+-- * Bounded Dyck paths
+
+-- | @boundedDyckPaths h m@ lists all Dyck paths from @(0,0)@ to @(2m,0)@ whose height is at most @h@.
+-- Synonym for 'boundedDyckPathsNaive'.
+--
+boundedDyckPaths
+  :: Int   -- ^ @h@ = maximum height
+  -> Int   -- ^ @m@ = half-length
+  -> [LatticePath]
+boundedDyckPaths = boundedDyckPathsNaive 
+
+-- | @boundedDyckPathsNaive h m@ lists all Dyck paths from @(0,0)@ to @(2m,0)@ whose height is at most @h@.
+--
+-- > sort (boundedDyckPaths h m) == sort [ p | p <- dyckPaths m , pathHeight p <= h ]
+-- > sort (boundedDyckPaths m m) == sort (dyckPaths m) 
+--
+-- Naive recursive algorithm, resulting order is pretty ad-hoc.
+--
+boundedDyckPathsNaive
+  :: Int   -- ^ @h@ = maximum height
+  -> Int   -- ^ @m@ = half-length
+  -> [LatticePath]
+boundedDyckPathsNaive = worker where
+  worker !h !m 
+    | h<0        = []
+    | m<0        = []
+    | m==0       = [[]]
+    | h<=0       = []
+    | otherwise  = as ++ bs 
+    where
+      bracket p = UpStep : p ++ [DownStep]
+      as = [ bracket p      |                 p <- boundedDyckPaths (h-1) (m-1)                                 ]
+      bs = [ bracket p ++ q | k <- [1..m-1] , p <- boundedDyckPaths (h-1) (k-1) , q <- boundedDyckPaths h (m-k) ]
+
+--------------------------------------------------------------------------------
+-- * More general lattice paths
+
+-- | All lattice paths from @(0,0)@ to @(x,y)@. Clearly empty unless @x-y@ is even.
+-- Synonym for 'latticePathsNaive'
+--
+latticePaths :: (Int,Int) -> [LatticePath]
+latticePaths = latticePathsNaive
+
+-- | All lattice paths from @(0,0)@ to @(x,y)@. Clearly empty unless @x-y@ is even.
+--
+-- Note that
+--
+-- > sort (dyckPaths n) == sort (latticePaths (0,2*n))
+--
+-- Naive recursive algorithm, resulting order is pretty ad-hoc.
+--
+latticePathsNaive :: (Int,Int) -> [LatticePath]
+latticePathsNaive (x,y) = worker x y where
+  worker !x !y 
+    | odd (x-y)     = []
+    | x<0           = []
+    | y<0           = []
+    | y==0          = dyckPaths (div x 2)
+    | x==1 && y==1  = [[UpStep]]
+    | otherwise     = as ++ bs
+    where
+      bracket p = UpStep : p ++ [DownStep] 
+      as = [ UpStep : p     | p <- worker (x-1) (y-1) ]
+      bs = [ bracket p ++ q | k <- [1..(div x 2)] , p <- dyckPaths (k-1) , q <- worker (x-2*k) y ]
+
+-- | Lattice paths are counted by the numbers in the Catalan triangle.
+countLatticePaths :: (Int,Int) -> Integer
+countLatticePaths (x,y) 
+  | even (x+y)  = catalanTriangle (div (x+y) 2) (div (x-y) 2)
+  | otherwise   = 0
+
+--------------------------------------------------------------------------------
+-- * Zero-level touches
+
+-- | @touchingDyckPaths k m@ lists all Dyck paths from @(0,0)@ to @(2m,0)@ which touch the 
+-- zero level line @y=0@ exactly @k@ times (excluding the starting point, but including the endpoint;
+-- thus, @k@ should be positive). Synonym for 'touchingDyckPathsNaive'.
+touchingDyckPaths
+  :: Int   -- ^ @k@ = number of zero-touches
+  -> Int   -- ^ @m@ = half-length
+  -> [LatticePath]
+touchingDyckPaths = touchingDyckPathsNaive
+
+
+-- | @touchingDyckPathsNaive k m@ lists all Dyck paths from @(0,0)@ to @(2m,0)@ which touch the 
+-- zero level line @y=0@ exactly @k@ times (excluding the starting point, but including the endpoint;
+-- thus, @k@ should be positive).
+--
+-- > sort (touchingDyckPathsNaive k m) == sort [ p | p <- dyckPaths m , pathNumberOfZeroTouches p == k ]
+-- 
+-- Naive recursive algorithm, resulting order is pretty ad-hoc.
+--
+touchingDyckPathsNaive
+  :: Int   -- ^ @k@ = number of zero-touches
+  -> Int   -- ^ @m@ = half-length
+  -> [LatticePath]
+touchingDyckPathsNaive = worker where
+  worker !k !m 
+    | m == 0    = if k==0 then [[]] else []
+    | k <= 0    = []
+    | m <  0    = []
+    | k == 1    = [ bracket p      |                 p <- dyckPaths (m-1)                           ]
+    | otherwise = [ bracket p ++ q | l <- [1..m-1] , p <- dyckPaths (l-1) , q <- worker (k-1) (m-l) ]
+    where
+      bracket p = UpStep : p ++ [DownStep] 
+
+
+-- | There is a bijection from the set of non-empty Dyck paths of length @2n@ which touch the zero lines @t@ times,
+-- to lattice paths from @(0,0)@ to @(2n-t-1,t-1)@ (just remove all the down-steps just before touching
+-- the zero line, and also the very first up-step). This gives us a counting formula.
+countTouchingDyckPaths 
+  :: Int   -- ^ @k@ = number of zero-touches
+  -> Int   -- ^ @m@ = half-length
+  -> Integer
+countTouchingDyckPaths t n
+  | t==0 && n==0   = 1
+  | otherwise      = countLatticePaths (2*n-t-1,t-1)
+
+--------------------------------------------------------------------------------
+-- * Dyck paths with given number of peaks
+
+-- | @peakingDyckPaths k m@ lists all Dyck paths from @(0,0)@ to @(2m,0)@ with exactly @k@ peaks.
+--
+-- Synonym for 'peakingDyckPathsNaive'
+--
+peakingDyckPaths
+  :: Int      -- ^ @k@ = number of peaks
+  -> Int      -- ^ @m@ = half-length
+  -> [LatticePath]
+peakingDyckPaths = peakingDyckPathsNaive 
+
+-- | @peakingDyckPathsNaive k m@ lists all Dyck paths from @(0,0)@ to @(2m,0)@ with exactly @k@ peaks.
+--
+-- > sort (peakingDyckPathsNaive k m) = sort [ p | p <- dyckPaths m , pathNumberOfPeaks p == k ]
+--  
+-- Naive recursive algorithm, resulting order is pretty ad-hoc.
+--
+peakingDyckPathsNaive 
+  :: Int      -- ^ @k@ = number of peaks
+  -> Int      -- ^ @m@ = half-length
+  -> [LatticePath]
+peakingDyckPathsNaive = worker where
+  worker !k !m
+    | m == 0    = if k==0 then [[]] else []       
+    | k <= 0    = []
+    | m <  0    = []
+    | k == 1    = [ singlePeak m ] 
+    | otherwise = as ++ bs ++ cs
+    where
+      as = [ bracket p      |                                 p <- worker k (m-1)                           ]
+      bs = [ smallHill ++ q |                                                       q <- worker (k-1) (m-1) ]
+      cs = [ bracket p ++ q | l <- [2..m-1] , a <- [1..k-1] , p <- worker a (l-1) , q <- worker (k-a) (m-l) ]
+      smallHill     = [ UpStep , DownStep ]
+      singlePeak !m = replicate m UpStep ++ replicate m DownStep 
+      bracket p = UpStep : p ++ [DownStep] 
+
+-- | Dyck paths of length @2m@ with @k@ peaks are counted by the Narayana numbers @N(m,k) = \binom{m}{k} \binom{m}{k-1} / m@
+countPeakingDyckPaths
+  :: Int      -- ^ @k@ = number of peaks
+  -> Int      -- ^ @m@ = half-length
+  -> Integer
+countPeakingDyckPaths k m 
+  | m == 0    = if k==0 then 1 else 0
+  | k <= 0    = 0
+  | m <  0    = 0
+  | k == 1    = 1
+  | otherwise = div (binomial m k * binomial m (k-1)) (fromIntegral m)
+
+--------------------------------------------------------------------------------
+-- * Random lattice paths
+
+-- | A uniformly random Dyck path of length @2m@
+randomDyckPath :: RandomGen g => Int -> g -> (LatticePath,g)
+randomDyckPath m g0 = (nestedParensToDyckPath parens, g1) where
+  (parens,g1) = randomNestedParentheses m g0
+
+--------------------------------------------------------------------------------
+
diff --git a/Math/Combinat/Numbers.hs b/Math/Combinat/Numbers.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Numbers.hs
@@ -0,0 +1,194 @@
+
+-- | A few important number sequences. 
+--  
+-- See the \"On-Line Encyclopedia of Integer Sequences\",
+-- <https://oeis.org> .
+
+module Math.Combinat.Numbers where
+
+--------------------------------------------------------------------------------
+
+import Data.Array
+
+import Math.Combinat.Helper ( sum' )
+import Math.Combinat.Sign
+
+--------------------------------------------------------------------------------
+
+-- | A000142.
+factorial :: Integral a => a -> Integer
+factorial n
+  | n <  0    = error "factorial: input should be nonnegative"
+  | n == 0    = 1
+  | otherwise = product [1..fromIntegral n]
+
+-- | A006882.
+doubleFactorial :: Integral a => a -> Integer
+doubleFactorial n
+  | n <  0    = error "doubleFactorial: input should be nonnegative"
+  | n == 0    = 1
+  | odd n     = product [1,3..fromIntegral n]
+  | otherwise = product [2,4..fromIntegral n]
+
+-- | A007318. Note: This is zero for @n<0@ or @k<0@; see also 'signedBinomial' below.
+binomial :: Integral a => a -> a -> Integer
+binomial n k 
+  | k > n = 0
+  | k < 0 = 0
+  | k > (n `div` 2) = binomial n (n-k)
+  | otherwise = (product [n'-k'+1 .. n']) `div` (product [1..k'])
+  where 
+    k' = fromIntegral k
+    n' = fromIntegral n
+
+-- | The extension of the binomial function to negative inputs. This should satisfy the following properties:
+--
+-- > for n,k >=0 : signedBinomial n k == binomial n k
+-- > for any n,k : signedBinomial n k == signedBinomial n (n-k) 
+-- > for k >= 0  : signedBinomial (-n) k == (-1)^k * signedBinomial (n+k-1) k
+--
+-- Note: This is compatible with Mathematica's @Binomial@ function.
+--
+signedBinomial :: Int -> Int -> Integer
+signedBinomial n k
+  | n >= 0     = binomial n k
+  | k >= 0     = negateIfOdd    k  $ binomial (k-n-1)   k  
+  | otherwise  = negateIfOdd (n+k) $ binomial (-k-1) (-n-1)
+
+{-
+test_signed_0 = [ signedBinomial ( n) k == signedBinomial ( n) ( n-k)                | n<-[-30..40] , k<-[-30..40] ]
+test_signed_1 = [ signedBinomial (-n) k == signedBinomial (-n) (-n-k)                | n<-[-30..40] , k<-[-30..40] ]
+test_signed_2 = [ signedBinomial (-n) k == negateIfOdd k $ signedBinomial (n+k-1) k  | n<-[-30..40] , k<-[0..30] ]
+-}
+
+-- | A given row of the Pascal triangle; equivalent to a sequence of binomial 
+-- numbers, but much more efficient. You can also left-fold over it.
+--
+-- > pascalRow n == [ binomial n k | k<-[0..n] ]
+pascalRow :: Integral a => a -> [Integer]
+pascalRow n' = worker 0 1 where
+  n = fromIntegral n'
+  worker j x
+    | j>n   = [] 
+    | True  = let j'=j+1 in x : worker j' (div (x*(n-j)) j') 
+
+multinomial :: Integral a => [a] -> Integer
+multinomial xs = div
+  (factorial (sum xs))
+  (product [ factorial x | x<-xs ])  
+  
+--------------------------------------------------------------------------------
+-- * Catalan numbers
+
+-- | Catalan numbers. OEIS:A000108.
+catalan :: Integral a => a -> Integer
+catalan n 
+  | n < 0     = 0
+  | otherwise = binomial (n+n) n `div` fromIntegral (n+1)
+
+-- | Catalan's triangle. OEIS:A009766.
+-- Note:
+--
+-- > catalanTriangle n n == catalan n
+-- > catalanTriangle n k == countStandardYoungTableaux (toPartition [n,k])
+--
+catalanTriangle :: Integral a => a -> a -> Integer
+catalanTriangle n k
+  | k > n     = 0
+  | k < 0     = 0
+  | otherwise = (binomial (n+k) n * fromIntegral (n-k+1)) `div` fromIntegral (n+1)
+
+--------------------------------------------------------------------------------
+-- * Stirling numbers
+
+-- | Rows of (signed) Stirling numbers of the first kind. OEIS:A008275.
+-- Coefficients of the polinomial @(x-1)*(x-2)*...*(x-n+1)@.
+-- This function uses the recursion formula.
+signedStirling1stArray :: Integral a => a -> Array Int Integer
+signedStirling1stArray n
+  | n <  1    = error "stirling1stArray: n should be at least 1"
+  | n == 1    = listArray (1,1 ) [1]
+  | otherwise = listArray (1,n') [ lkp (k-1) - fromIntegral (n-1) * lkp k | k<-[1..n'] ] 
+  where
+    prev = signedStirling1stArray (n-1)
+    n' = fromIntegral n :: Int
+    lkp j | j <  1    = 0
+          | j >= n'   = 0
+          | otherwise = prev ! j 
+        
+-- | (Signed) Stirling numbers of the first kind. OEIS:A008275.
+-- This function uses 'signedStirling1stArray', so it shouldn't be used
+-- to compute /many/ Stirling numbers.
+--
+-- Argument order: @signedStirling1st n k@
+--
+signedStirling1st :: Integral a => a -> a -> Integer
+signedStirling1st n k 
+  | k==0 && n==0 = 1
+  | k < 1        = 0
+  | k > n        = 0
+  | otherwise    = signedStirling1stArray n ! (fromIntegral k)
+
+-- | (Unsigned) Stirling numbers of the first kind. See 'signedStirling1st'.
+unsignedStirling1st :: Integral a => a -> a -> Integer
+unsignedStirling1st n k = abs (signedStirling1st n k)
+
+-- | Stirling numbers of the second kind. OEIS:A008277.
+-- This function uses an explicit formula.
+-- 
+-- Argument order: @stirling2nd n k@
+--
+stirling2nd :: Integral a => a -> a -> Integer
+stirling2nd n k 
+  | k==0 && n==0 = 1
+  | k < 1        = 0
+  | k > n        = 0
+  | otherwise = sum xs `div` factorial k where
+      xs = [ negateIfOdd (k-i) $ binomial k i * (fromIntegral i)^n | i<-[0..k] ]
+
+--------------------------------------------------------------------------------
+-- * Bernoulli numbers
+
+-- | Bernoulli numbers. @bernoulli 1 == -1%2@ and @bernoulli k == 0@ for
+-- k>2 and /odd/. This function uses the formula involving Stirling numbers
+-- of the second kind. Numerators: A027641, denominators: A027642.
+bernoulli :: Integral a => a -> Rational
+bernoulli n 
+  | n <  0    = error "bernoulli: n should be nonnegative"
+  | n == 0    = 1
+  | n == 1    = -1/2
+  | otherwise = sum [ f k | k<-[1..n] ] 
+  where
+    f k = toRational (negateIfOdd (n+k) $ factorial k * stirling2nd n k) 
+        / toRational (k+1)
+
+--------------------------------------------------------------------------------
+-- * Bell numbers
+
+-- | Bell numbers (Sloane's A000110) from B(0) up to B(n). B(0)=B(1)=1, B(2)=2, etc. 
+--
+-- The Bell numbers count the number of /set partitions/ of a set of size @n@
+-- 
+-- See <http://en.wikipedia.org/wiki/Bell_number>
+--
+bellNumbersArray :: Integral a => a -> Array Int Integer
+bellNumbersArray nn = arr where
+  arr = array (0::Int,n) kvs 
+  n = fromIntegral nn :: Int
+  kvs = (0,1) : [ (k, f k) | k<-[1..n] ] 
+  f n = sum' [ binomial (n-1) k * arr ! k | k<-[0..n-1] ]
+
+-- | The n-th Bell number B(n), using the Stirling numbers of the second kind.
+-- This may be slower than using 'bellNumbersArray'.
+bellNumber :: Integral a => a -> Integer
+bellNumber nn
+  | n <  0     = error "bellNumber: expecting a nonnegative index"
+  | n == 0     = 1
+  | otherwise  = sum' [ stirling2nd n k | k<-[1..n] ] 
+  where
+    n = fromIntegral nn :: Int
+
+--------------------------------------------------------------------------------
+
+
+ 
diff --git a/Math/Combinat/Numbers/Primes.hs b/Math/Combinat/Numbers/Primes.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Numbers/Primes.hs
@@ -0,0 +1,354 @@
+
+-- | Prime numbers and related number theoretical stuff.
+
+module Math.Combinat.Numbers.Primes 
+  ( -- * List of prime numbers
+    primes
+  , primesSimple
+  , primesTMWE
+    -- * Prime factorization
+  , groupIntegerFactors
+  , integerFactorsTrialDivision
+    -- * Integer logarithm
+  , integerLog2
+  , ceilingLog2
+    -- * Integer square root
+  , isSquare
+  , integerSquareRoot
+  , ceilingSquareRoot
+  , integerSquareRoot' 
+  , integerSquareRootNewton'
+    -- * Modulo @m@ arithmetic
+  , powerMod
+    -- * Prime testing
+  , millerRabinPrimalityTest
+  , isProbablyPrime
+  , isVeryProbablyPrime
+  )
+  where
+
+--------------------------------------------------------------------------------
+
+-- import Math.Combinat.Numbers
+
+import Data.List ( group , sort )
+import Data.Bits
+
+import System.Random
+
+--------------------------------------------------------------------------------
+-- List of prime numbers 
+
+-- | Infinite list of primes, using the TMWE algorithm.
+primes :: [Integer]
+primes = primesTMWE
+
+-- | A relatively simple but still quite fast implementation of list of primes.
+-- By Will Ness <http://www.haskell.org/pipermail/haskell-cafe/2009-November/068441.html>
+primesSimple :: [Integer]
+primesSimple = 2 : 3 : sieve 0 primes' 5 where
+  primes' = tail primesSimple
+  sieve k (p:ps) x = noDivs k h ++ sieve (k+1) ps (t+2) where
+    t = p*p 
+    h = [x,x+2..t-2]
+  noDivs k = filter (\x -> all (\y -> rem x y /= 0) (take k primes'))
+  
+-- | List of primes, using tree merge with wheel. Code by Will Ness.
+primesTMWE :: [Integer]
+primesTMWE = 2:3:5:7: gaps 11 wheel (fold3t $ roll 11 wheel primes') where                                                             
+
+  primes' = 11: gaps 13 (tail wheel) (fold3t $ roll 11 wheel primes')
+  fold3t ((x:xs): ~(ys:zs:t)) 
+    = x : union xs (union ys zs) `union` fold3t (pairs t)            
+  pairs ((x:xs):ys:t) = (x : union xs ys) : pairs t 
+  wheel = 2:4:2:4:6:2:6:4:2:4:6:6:2:6:4:2:6:4:6:8:4:2:4:2:  
+          4:8:6:4:6:2:4:6:2:6:6:4:2:4:6:2:6:4:2:4:2:10:2:10:wheel 
+  gaps k ws@(w:t) cs@ ~(c:u) 
+    | k==c  = gaps (k+w) t u              
+    | True  = k : gaps (k+w) t cs  
+  roll k ws@(w:t) ps@ ~(p:u) 
+    | k==p  = scanl (\c d->c+p*d) (p*p) ws : roll (k+w) t u              
+    | True  = roll (k+w) t ps   
+
+  minus xxs@(x:xs) yys@(y:ys) = case compare x y of 
+    LT -> x : minus xs  yys
+    EQ ->     minus xs  ys 
+    GT ->     minus xxs ys
+  minus xs [] = xs
+  minus [] _  = []
+  
+  union xxs@(x:xs) yys@(y:ys) = case compare x y of 
+    LT -> x : union xs  yys
+    EQ -> x : union xs  ys 
+    GT -> y : union xxs ys
+  union xs [] = xs
+  union [] ys =ys
+
+--------------------------------------------------------------------------------
+-- Prime factorization
+
+-- | Groups integer factors. Example: from [2,2,2,3,3,5] we produce [(2,3),(3,2),(5,1)]  
+groupIntegerFactors :: [Integer] -> [(Integer,Int)]
+groupIntegerFactors = map f . group . sort where
+  f xs = (head xs, length xs)
+
+-- | The naive trial division algorithm.
+integerFactorsTrialDivision :: Integer -> [Integer]
+integerFactorsTrialDivision n 
+  | n<1 = error "integerFactorsTrialDivision: n should be at least 1"
+  | otherwise = go primes n 
+  where
+    go _  1 = []
+    go rs k = sub ps k where
+      sub [] k = [k]
+      sub qqs@(q:qs) k = case mod k q of
+        0 -> q : go qqs (div k q)
+        _ -> sub qs k
+      ps = takeWhile (\p -> p*p <= k) rs  
+{-
+    go 1 = []
+    go k = sub ps k where
+      sub [] k = [k]
+      sub (q:qs) k = case mod k q of
+        0 -> q : go (div k q)
+        _ -> sub qs k
+      ps = takeWhile (\p -> p*p <= k) primes
+-}
+
+{-    
+-- brute force testing of factors
+ifactorsTest :: (Integer -> [Integer]) -> Integer -> Bool
+ifactorsTest alg n = and [ product (alg k) == k | k<-[1..n] ]   
+-}
+
+--------------------------------------------------------------------------------
+-- Integer logarithm
+
+-- | Largest integer @k@ such that @2^k@ is smaller or equal to @n@
+integerLog2 :: Integer -> Integer
+integerLog2 n = go n where
+  go 0 = -1
+  go k = 1 + go (shiftR k 1)
+
+-- | Smallest integer @k@ such that @2^k@ is larger or equal to @n@
+ceilingLog2 :: Integer -> Integer
+ceilingLog2 0 = 0
+ceilingLog2 n = 1 + go (n-1) where
+  go 0 = -1
+  go k = 1 + go (shiftR k 1)
+  
+--------------------------------------------------------------------------------
+-- Integer square root
+
+isSquare :: Integer -> Bool
+isSquare n = 
+  if (fromIntegral $ mod n 32) `elem` rs 
+    then snd (integerSquareRoot' n) == 0
+    else False
+  where
+    rs = [0,1,4,9,16,17,25] :: [Int]
+    
+-- | Integer square root (largest integer whose square is smaller or equal to the input)
+-- using Newton's method, with a faster (for large numbers) inital guess based on bit shifts.
+integerSquareRoot :: Integer -> Integer
+integerSquareRoot = fst . integerSquareRoot'
+
+-- | Smallest integer whose square is larger or equal to the input
+ceilingSquareRoot :: Integer -> Integer
+ceilingSquareRoot n = (if r>0 then u+1 else u) where (u,r) = integerSquareRoot' n 
+
+-- | We also return the excess residue; that is
+--
+-- > (a,r) = integerSquareRoot' n
+-- 
+-- means that
+--
+-- > a*a + r = n
+-- > a*a <= n < (a+1)*(a+1)
+integerSquareRoot' :: Integer -> (Integer,Integer)
+integerSquareRoot' n
+  | n<0 = error "integerSquareRoot: negative input"
+  | n<2 = (n,0)
+  | otherwise = go firstGuess 
+  where
+    k = integerLog2 n
+    firstGuess = 2^(div (k+2) 2) -- !! note that (div (k+1) 2) is NOT enough !!
+    go a = 
+      if m < a
+        then go a' 
+        else (a, r + a*(m-a))
+      where
+        (m,r) = divMod n a
+        a' = div (m + a) 2
+
+-- | Newton's method without an initial guess. For very small numbers (<10^10) it
+-- is somewhat faster than the above version.
+integerSquareRootNewton' :: Integer -> (Integer,Integer)
+integerSquareRootNewton' n
+  | n<0 = error "integerSquareRootNewton: negative input"
+  | n<2 = (n,0)
+  | otherwise = go (div n 2) 
+  where
+    go a = 
+      if m < a
+        then go a' 
+        else (a, r + a*(m-a))
+      where
+        (m,r) = divMod n a
+        a' = div (m + a) 2
+
+{-
+-- brute force test of integer square root
+isqrt_test n1 n2 = 
+  [ k 
+  | k<-[n1..n2] 
+  , let (a,r) = integerSquareRoot' k
+  , (a*a+r/=k) || (a*a>k) || (a+1)*(a+1)<=k 
+  ]
+-}
+
+--------------------------------------------------------------------------------
+-- Modulo @m@ arithmetic
+
+-- | Efficient powers modulo m.
+-- 
+-- > powerMod a k m == (a^k) `mod` m
+powerMod :: Integer -> Integer -> Integer -> Integer
+powerMod a' k m = {- debug bs $ -} go a bs where
+
+  bs = bin k
+
+  bin 0 = []
+  bin x = (x .&. 1 /= 0) : bin (shiftR x 1)
+
+  a = mod a' m
+
+  go _ [] = 1
+  go x (b:bs) = -- debug (x,b) $ 
+    if b 
+      then mod (x*rest) m
+      else rest
+    where 
+      rest = go (mod (x*x) m) bs 
+      
+--------------------------------------------------------------------------------
+-- Prime testing
+ 
+-- | Miller-Rabin Primality Test (taken from Haskell wiki). 
+-- We test the primality of the first argument @n@ by using the second argument @a@ as a candidate witness.
+-- If it returs @False@, then @n@ is composite. If it returns @True@, then @n@ is either prime or composite.
+--
+-- A random choice between @2@ and @(n-2)@ is a good choice for @a@.
+millerRabinPrimalityTest :: Integer -> Integer -> Bool
+millerRabinPrimalityTest n a
+  | a <= 1 || a >= n-1 = 
+      error $ "millerRabinPrimalityTest: a out of range (" ++ show a ++ " for "++ show n ++ ")" 
+  | n < 2 = False
+  | even n = False
+  | b0 == 1 || b0 == n' = True
+  | otherwise = iter (tail b)
+  where
+    n' = n-1
+    (k,m) = find2km n'
+    b0 = powMod n a m
+    b = take (fromIntegral k) $ iterate (squareMod n) b0
+    iter [] = False
+    iter (x:xs)
+      | x == 1 = False
+      | x == n' = True
+      | otherwise = iter xs
+
+
+{-# SPECIALIZE find2km :: Integer -> (Integer,Integer) #-}
+find2km :: Integral a => a -> (a,a)
+find2km n = f 0 n where 
+  f k m
+    | r == 1 = (k,m)
+    | otherwise = f (k+1) q
+    where (q,r) = quotRem m 2        
+ 
+{-# SPECIALIZE pow' :: (Integer -> Integer -> Integer) -> (Integer -> Integer) -> Integer -> Integer -> Integer #-}
+pow' :: (Num a, Integral b) => (a -> a -> a) -> (a -> a) -> a -> b -> a
+pow' _ _ _ 0 = 1
+pow' mul sq x' n' = f x' n' 1 where 
+  f x n y
+    | n == 1 = x `mul` y
+    | r == 0 = f x2 q y
+    | otherwise = f x2 q (x `mul` y)
+    where
+      (q,r) = quotRem n 2
+      x2 = sq x
+ 
+{-# SPECIALIZE mulMod :: Integer -> Integer -> Integer -> Integer #-}
+mulMod :: Integral a => a -> a -> a -> a
+mulMod a b c = (b * c) `mod` a
+
+{-# SPECIALIZE squareMod :: Integer -> Integer -> Integer #-}
+squareMod :: Integral a => a -> a -> a
+squareMod a b = (b * b) `rem` a
+
+{-# SPECIALIZE powMod :: Integer -> Integer -> Integer -> Integer #-}
+powMod :: Integral a => a -> a -> a -> a
+powMod m = pow' (mulMod m) (squareMod m)
+
+--------------------------------------------------------------------------------
+
+-- | For very small numbers, we use trial division, for larger numbers, we apply the 
+-- Miller-Rabin primality test @log4(n)@ times, with candidate witnesses derived 
+-- deterministically from @n@ using a pseudo-random sequence 
+-- (which /should be/ based on a cryptographic hash function, but isn\'t, yet). 
+--
+-- Thus the candidate witnesses should behave essentially like random, but the 
+-- resulting function is still a deterministic, pure function.
+--
+-- TODO: implement the hash sequence, at the moment we use 'System.Random' instead...
+--
+isProbablyPrime :: Integer -> Bool
+isProbablyPrime n 
+  | n < 2      = False
+  | even n     = (n==2)
+  | n < 1000   = length (integerFactorsTrialDivision n) == 1
+  | otherwise  = and [ millerRabinPrimalityTest n a | a <- witnessList ]
+  where
+    log2n       = integerLog2 n 
+    nchecks     = 1 + fromInteger (div log2n 2) :: Int
+    witnessList = take nchecks pseudoRnds
+    pseudoRnds  = 2 : [ a | a <- integerRndSequence n , a > 1 && a < (n-1) ]
+
+-- | A more exhaustive version of 'isProbablyPrime', this one tests candidate
+-- witnesses both the first log4(n) prime numbers and then log4(n) pseudo-random
+-- numbers
+isVeryProbablyPrime :: Integer -> Bool
+isVeryProbablyPrime n
+  | n < 2      = False
+  | even n     = (n==2)
+  | n < 1000   = length (integerFactorsTrialDivision n) == 1
+  | otherwise  = and [ millerRabinPrimalityTest n a | a <- witnessList ]
+  where
+    log2n       = integerLog2 n 
+    nchecks     = 1 + fromInteger (div log2n 2) :: Int
+    witnessList = take nchecks primes ++ take nchecks pseudoRnds
+    pseudoRnds  = [ a | a <- integerRndSequence (n+3) , a > 1 && a < (n-1) ]
+
+--------------------------------------------------------------------------------
+
+{-
+-- | Given an integer @n@, we return an infinite sequence of pseudo-random integers 
+-- between @0..n-1@, generated using a crypographic hash function.
+--
+integerHashSequence :: Integer -> [Integer]
+integerHashSequence = error "integerHashSequence: not implemented yet"
+-}
+
+-- | Given an integer @n@, we initialize a system random generator with using a 
+-- seed derived from @n@ (note that this uses at most 32 or 64 bits), and generate 
+-- an infinite sequence of pseudo-random integers between @0..n-1@, generated by 
+-- that random generator. 
+--
+-- Note that this is not really a preferred way of generating such sequences!
+-- 
+integerRndSequence :: Integer -> [Integer]
+integerRndSequence n = randomRs (0,n-1) gen where
+  gen = mkStdGen $ fromInteger (n + 17 * integerLog2 n)
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Numbers/Series.hs b/Math/Combinat/Numbers/Series.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Numbers/Series.hs
@@ -0,0 +1,376 @@
+
+-- | Some basic univariate power series expansions.
+-- This module is not re-exported by "Math.Combinat".
+--
+-- Note: the \"@convolveWithXXX@\" functions are much faster than the equivalent
+-- @(XXX \`convolve\`)@!
+-- 
+-- TODO: better names for these functions.
+--
+
+{-# LANGUAGE CPP, GeneralizedNewtypeDeriving #-}
+module Math.Combinat.Numbers.Series where
+
+--------------------------------------------------------------------------------
+
+import Data.List
+
+import Math.Combinat.Sign
+import Math.Combinat.Numbers
+import Math.Combinat.Partitions.Integer
+import Math.Combinat.Helper
+
+--------------------------------------------------------------------------------
+-- * Trivial series
+
+-- | The series [1,0,0,0,0,...], which is the neutral element for the convolution.
+{-# SPECIALIZE unitSeries :: [Integer] #-}
+unitSeries :: Num a => [a]
+unitSeries = 1 : repeat 0
+
+-- | Constant zero series
+zeroSeries :: Num a => [a]
+zeroSeries = repeat 0
+
+-- | Power series representing a constant function
+constSeries :: Num a => a -> [a]
+constSeries x = x : repeat 0
+
+-- | The power series representation of the identity function @x@
+idSeries :: Num a => [a]
+idSeries = 0 : 1 : repeat 0
+
+-- | The power series representation of @x^n@
+powerTerm :: Num a => Int -> [a]
+powerTerm n = replicate n 0 ++ (1 : repeat 0)
+
+--------------------------------------------------------------------------------
+-- * Basic operations on power series
+
+addSeries :: Num a => [a] -> [a] -> [a]
+addSeries xs ys = longZipWith 0 0 (+) xs ys
+
+sumSeries :: Num a => [[a]] -> [a]
+sumSeries [] = [0]
+sumSeries xs = foldl1' addSeries xs
+
+subSeries :: Num a => [a] -> [a] -> [a]
+subSeries xs ys = longZipWith 0 0 (-) xs ys
+
+negateSeries :: Num a => [a] -> [a]
+negateSeries = map negate
+
+scaleSeries :: Num a => a -> [a] -> [a]
+scaleSeries s = map (*s)
+
+mulSeries :: Num a => [a] -> [a] -> [a]
+mulSeries = convolve
+
+productOfSeries :: Num a => [[a]] -> [a]
+productOfSeries = convolveMany
+
+--------------------------------------------------------------------------------
+-- * Convolution (product)
+
+-- | Convolution of series (that is, multiplication of power series). 
+-- The result is always an infinite list. Warning: This is slow!
+convolve :: Num a => [a] -> [a] -> [a]
+convolve xs1 ys1 = res where
+  res = [ foldl' (+) 0 (zipWith (*) xs (reverse (take n ys)))
+        | n<-[1..] 
+        ]
+  xs = xs1 ++ repeat 0
+  ys = ys1 ++ repeat 0
+
+-- | Convolution (= product) of many series. Still slow!
+convolveMany :: Num a => [[a]] -> [a]
+convolveMany []  = 1 : repeat 0
+convolveMany xss = foldl1 convolve xss
+
+--------------------------------------------------------------------------------
+-- * Reciprocals of general power series
+
+-- | Given a power series, we iteratively compute its multiplicative inverse
+reciprocalSeries :: (Eq a, Fractional a) => [a] -> [a]
+reciprocalSeries series = case series of
+  [] -> error "reciprocalSeries: empty input series (const 0 function does not have an inverse)"
+  (a:as) -> case a of
+    0 -> error "reciprocalSeries: input series has constant term 0"
+    _ -> map (/a) $ integralReciprocalSeries $ map (/a) series
+
+-- | Given a power series starting with @1@, we can compute its multiplicative inverse
+-- without divisions.
+--
+{-# SPECIALIZE integralReciprocalSeries :: [Int]     -> [Int]     #-}
+{-# SPECIALIZE integralReciprocalSeries :: [Integer] -> [Integer] #-}
+integralReciprocalSeries :: (Eq a, Num a) => [a] -> [a]
+integralReciprocalSeries series = case series of 
+  [] -> error "integralReciprocalSeries: empty input series (const 0 function does not have an inverse)"
+  (a:as) -> case a of
+    1 -> 1 : worker [1]
+    _ -> error "integralReciprocalSeries: input series must start with 1"
+  where
+    worker bs = let b' = - sum (zipWith (*) (tail series) bs) 
+                in  b' : worker (b':bs)
+
+--------------------------------------------------------------------------------
+-- * Composition of formal power series
+
+-- | @g \`composeSeries\` f@ is the power series expansion of @g(f(x))@.
+-- This is a synonym for @flip substitute@.
+--
+-- We require that the constant term of @f@ is zero.
+composeSeries :: (Eq a, Num a) => [a] -> [a] -> [a]
+composeSeries g f = substitute f g
+
+-- | @substitute f g@ is the power series corresponding to @g(f(x))@. 
+-- Equivalently, this is the composition of univariate functions (in the \"wrong\" order).
+--
+-- Note: for this to be meaningful in general (not depending on convergence properties),
+-- we need that the constant term of @f@ is zero.
+substitute :: (Eq a, Num a) => [a] -> [a] -> [a]
+substitute as_ bs_ = 
+  case head as of
+    0 -> [ f n | n<-[0..] ]
+    _ -> error "PowerSeries/substitute: we expect the the constant term of the inner series to be zero"
+  where
+    as = as_ ++ repeat 0
+    bs = bs_ ++ repeat 0
+    a i = as !! i
+    b j = bs !! j
+    f n = sum
+            [ b m * product [ (a i)^j | (i,j)<-es ] * fromInteger (multinomial (map snd es))
+            | p <- partitions n 
+            , let es = toExponentialForm p
+            , let m  = partitionWidth    p
+            ]
+
+--------------------------------------------------------------------------------
+-- * Lagrange inversions
+
+-- | Coefficients of the Lagrange inversion
+lagrangeCoeff :: Partition -> Integer
+lagrangeCoeff p = div numer denom where
+  numer = (-1)^m * product (map fromIntegral [n+1..n+m])
+  denom = fromIntegral (n+1) * product (map (factorial . snd) es)
+  m  = partitionWidth    p
+  n  = partitionWeight   p
+  es = toExponentialForm p
+
+-- | We expect the input series to match @(0:1:_)@. The following is true for the result (at least with exact arithmetic):
+--
+-- > substitute f (integralLagrangeInversion f) == (0 : 1 : repeat 0)
+-- > substitute (integralLagrangeInversion f) f == (0 : 1 : repeat 0)
+--
+integralLagrangeInversion :: (Eq a, Num a) => [a] -> [a]
+integralLagrangeInversion series_ = 
+  case series of
+    (0:1:rest) -> 0 : 1 : [ f n | n<-[1..] ]
+    _ -> error "integralLagrangeInversion: the series should start with (0 + x + a2*x^2 + ...)"
+  where
+    series = series_ ++ repeat 0
+    as  = tail series 
+    a i = as !! i
+    f n = sum [ fromInteger (lagrangeCoeff p) * product [ (a i)^j | (i,j) <- toExponentialForm p ]
+              | p <- partitions n
+              ] 
+
+-- | We expect the input series to match @(0:a1:_)@. with a1 nonzero The following is true for the result (at least with exact arithmetic):
+--
+-- > substitute f (lagrangeInversion f) == (0 : 1 : repeat 0)
+-- > substitute (lagrangeInversion f) f == (0 : 1 : repeat 0)
+--
+lagrangeInversion :: (Eq a, Fractional a) => [a] -> [a]
+lagrangeInversion series_ = 
+  case series of
+    (0:a1:rest) -> if a1 ==0 
+      then err 
+      else 0 : (1/a1) : [ f n / a1^(n+1) | n<-[1..] ]
+    _ -> err
+  where
+    err    = error "lagrangeInversion: the series should start with (0 + a1*x + a2*x^2 + ...) where a1 is non-zero"
+    series = series_ ++ repeat 0
+    a1  = series !! 1
+    as  = map (/a1) (tail series)
+    a i = as !! i
+    f n = sum [ fromInteger (lagrangeCoeff p) * product [ (a i)^j | (i,j) <- toExponentialForm p ]
+              | p <- partitions n
+              ] 
+  
+--------------------------------------------------------------------------------
+-- * Power series expansions of elementary functions
+
+-- | Power series expansion of @exp(x)@
+expSeries :: Fractional a => [a]
+expSeries = go 0 1 where
+  go i e = e : go (i+1) (e / (i+1))
+
+-- | Power series expansion of @cos(x)@
+cosSeries :: Fractional a => [a]
+cosSeries = go 0 1 where
+  go i e = e : 0 : go (i+2) (-e / ((i+1)*(i+2)))
+
+-- | Power series expansion of @sin(x)@
+sinSeries :: Fractional a => [a]
+sinSeries = go 1 1 where
+  go i e = 0 : e : go (i+2) (-e / ((i+1)*(i+2)))
+
+-- | Power series expansion of @cosh(x)@
+coshSeries :: Fractional a => [a]
+coshSeries = go 0 1 where
+  go i e = e : 0 : go (i+2) (e / ((i+1)*(i+2)))
+
+-- | Power series expansion of @sinh(x)@
+sinhSeries :: Fractional a => [a]
+sinhSeries = go 1 1 where
+  go i e = 0 : e : go (i+2) (e / ((i+1)*(i+2)))
+
+-- | Power series expansion of @log(1+x)@
+log1Series :: Fractional a => [a]
+log1Series = 0 : go 1 1 where
+  go i e = (e/i) : go (i+1) (-e)
+
+-- | Power series expansion of @(1-Sqrt[1-4x])/(2x)@ (the coefficients are the Catalan numbers)
+dyckSeries :: Num a => [a]
+dyckSeries = [ fromInteger (catalan i) | i<-[(0::Int)..] ]
+
+--------------------------------------------------------------------------------
+-- * \"Coin\" series
+
+-- | Power series expansion of 
+-- 
+-- > 1 / ( (1-x^k_1) * (1-x^k_2) * ... * (1-x^k_n) )
+--
+-- Example:
+--
+-- @(coinSeries [2,3,5])!!k@ is the number of ways 
+-- to pay @k@ dollars with coins of two, three and five dollars.
+--
+-- TODO: better name?
+coinSeries :: [Int] -> [Integer]
+coinSeries [] = 1 : repeat 0
+coinSeries (k:ks) = xs where
+  xs = zipWith (+) (coinSeries ks) (replicate k 0 ++ xs) 
+
+-- | Generalization of the above to include coefficients: expansion of 
+--  
+-- > 1 / ( (1-a_1*x^k_1) * (1-a_2*x^k_2) * ... * (1-a_n*x^k_n) ) 
+-- 
+coinSeries' :: Num a => [(a,Int)] -> [a]
+coinSeries' [] = 1 : repeat 0
+coinSeries' ((a,k):aks) = xs where
+  xs = zipWith (+) (coinSeries' aks) (replicate k 0 ++ map (*a) xs) 
+
+convolveWithCoinSeries :: [Int] -> [Integer] -> [Integer]
+convolveWithCoinSeries ks series1 = worker ks where
+  series = series1 ++ repeat 0
+  worker [] = series
+  worker (k:ks) = xs where
+    xs = zipWith (+) (worker ks) (replicate k 0 ++ xs)
+
+convolveWithCoinSeries' :: Num a => [(a,Int)] -> [a] -> [a]
+convolveWithCoinSeries' ks series1 = worker ks where
+  series = series1 ++ repeat 0
+  worker [] = series
+  worker ((a,k):aks) = xs where
+    xs = zipWith (+) (worker aks) (replicate k 0 ++ map (*a) xs)
+
+--------------------------------------------------------------------------------
+-- * Reciprocals of products of polynomials
+
+-- | Convolution of many 'pseries', that is, the expansion of the reciprocal
+-- of a product of polynomials
+productPSeries :: [[Int]] -> [Integer]
+productPSeries = foldl (flip convolveWithPSeries) unitSeries
+
+-- | The same, with coefficients.
+productPSeries' :: Num a => [[(a,Int)]] -> [a]
+productPSeries' = foldl (flip convolveWithPSeries') unitSeries
+
+convolveWithProductPSeries :: [[Int]] -> [Integer] -> [Integer]
+convolveWithProductPSeries kss ser = foldl (flip convolveWithPSeries) ser kss
+
+-- | This is the most general function in this module; all the others
+-- are special cases of this one.  
+convolveWithProductPSeries' :: Num a => [[(a,Int)]] -> [a] -> [a] 
+convolveWithProductPSeries' akss ser = foldl (flip convolveWithPSeries') ser akss
+  
+--------------------------------------------------------------------------------
+-- * Reciprocals of polynomials
+
+-- Reciprocals of polynomials, without coefficients
+
+-- | The power series expansion of 
+--
+-- > 1 / (1 - x^k_1 - x^k_2 - x^k_3 - ... - x^k_n)
+--
+pseries :: [Int] -> [Integer]
+pseries ks = convolveWithPSeries ks unitSeries
+
+-- | Convolve with (the expansion of) 
+--
+-- > 1 / (1 - x^k_1 - x^k_2 - x^k_3 - ... - x^k_n)
+--
+convolveWithPSeries :: [Int] -> [Integer] -> [Integer]
+convolveWithPSeries ks series1 = ys where 
+  series = series1 ++ repeat 0 
+  ys = worker ks ys 
+  worker [] _ = series 
+  worker (k:ks) ys = xs where
+    xs = zipWith (+) (replicate k 0 ++ ys) (worker ks ys)
+
+--------------------------------------------------------------------------------
+--  Reciprocals of polynomials, with coefficients
+
+-- | The expansion of 
+--
+-- > 1 / (1 - a_1*x^k_1 - a_2*x^k_2 - a_3*x^k_3 - ... - a_n*x^k_n)
+--
+pseries' :: Num a => [(a,Int)] -> [a]
+pseries' aks = convolveWithPSeries' aks unitSeries
+
+-- | Convolve with (the expansion of) 
+--
+-- > 1 / (1 - a_1*x^k_1 - a_2*x^k_2 - a_3*x^k_3 - ... - a_n*x^k_n)
+--
+convolveWithPSeries' :: Num a => [(a,Int)] -> [a] -> [a]
+convolveWithPSeries' aks series1 = ys where 
+  series = series1 ++ repeat 0 
+  ys = worker aks ys 
+  worker [] _ = series
+  worker ((a,k):aks) ys = xs where
+    xs = zipWith (+) (replicate k 0 ++ map (*a) ys) (worker aks ys)
+
+{-
+data Sign = Plus | Minus deriving (Eq,Show)
+
+signValue :: Num a => Sign -> a
+signValue Plus  =  1
+signValue Minus = -1
+-}
+
+signedPSeries :: [(Sign,Int)] -> [Integer] 
+signedPSeries aks = convolveWithSignedPSeries aks unitSeries
+
+-- | Convolve with (the expansion of) 
+--
+-- > 1 / (1 +- x^k_1 +- x^k_2 +- x^k_3 +- ... +- x^k_n)
+--
+-- Should be faster than using `convolveWithPSeries'`.
+-- Note: 'Plus' corresponds to the coefficient @-1@ in `pseries'` (since
+-- there is a minus sign in the definition there)!
+convolveWithSignedPSeries :: [(Sign,Int)] -> [Integer] -> [Integer]
+convolveWithSignedPSeries aks series1 = ys where 
+  series = series1 ++ repeat 0 
+  ys = worker aks ys 
+  worker [] _ = series
+  worker ((a,k):aks) ys = xs where
+    xs = case a of
+      Minus -> zipWith (+) one two 
+      Plus  -> zipWith (-) one two
+    one = worker aks ys
+    two = replicate k 0 ++ ys
+     
+--------------------------------------------------------------------------------
+
+
diff --git a/Math/Combinat/Partitions.hs b/Math/Combinat/Partitions.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Partitions.hs
@@ -0,0 +1,22 @@
+
+-- | Partitions of integers and multisets. 
+-- Integer partitions are nonincreasing sequences of positive integers.
+--
+-- See:
+--
+--  * Donald E. Knuth: The Art of Computer Programming, vol 4, pre-fascicle 3B.
+--
+--  * <http://en.wikipedia.org/wiki/Partition_(number_theory)>
+--
+
+{-# LANGUAGE BangPatterns #-}
+module Math.Combinat.Partitions
+  ( module Math.Combinat.Partitions.Integer
+  )
+  where
+
+--------------------------------------------------------------------------------
+
+import Math.Combinat.Partitions.Integer
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Partitions/Integer.hs b/Math/Combinat/Partitions/Integer.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Partitions/Integer.hs
@@ -0,0 +1,708 @@
+
+-- | Partitions of integers.
+-- Integer partitions are nonincreasing sequences of positive integers.
+--
+-- See:
+--
+--  * Donald E. Knuth: The Art of Computer Programming, vol 4, pre-fascicle 3B.
+--
+--  * <http://en.wikipedia.org/wiki/Partition_(number_theory)>
+--
+-- For example the partition
+--
+-- > Partition [8,6,3,3,1]
+--
+-- can be represented by the (English notation) Ferrers diagram:
+--
+-- <<svg/ferrers.svg>>
+-- 
+
+{-# LANGUAGE CPP, BangPatterns, ScopedTypeVariables #-}
+module Math.Combinat.Partitions.Integer where
+
+--------------------------------------------------------------------------------
+
+import Data.List
+import Control.Monad ( liftM , replicateM )
+
+-- import Data.Map (Map)
+-- import qualified Data.Map as Map
+
+import Math.Combinat.Classes
+import Math.Combinat.ASCII as ASCII
+import Math.Combinat.Numbers (factorial,binomial,multinomial)
+import Math.Combinat.Helper
+
+import Data.Array
+import System.Random
+
+--------------------------------------------------------------------------------
+-- * Type and basic stuff
+
+-- | A partition of an integer. The additional invariant enforced here is that partitions 
+-- are monotone decreasing sequences of /positive/ integers. The @Ord@ instance is lexicographical.
+newtype Partition = Partition [Int] deriving (Eq,Ord,Show,Read)
+
+instance HasNumberOfParts Partition where
+  numberOfParts (Partition p) = length p
+
+---------------------------------------------------------------------------------
+  
+-- | Sorts the input, and cuts the nonpositive elements.
+mkPartition :: [Int] -> Partition
+mkPartition xs = Partition $ sortBy (reverseCompare) $ filter (>0) xs
+
+-- | Assumes that the input is decreasing.
+toPartitionUnsafe :: [Int] -> Partition
+toPartitionUnsafe = Partition
+
+-- | Checks whether the input is an integer partition. See the note at 'isPartition'!
+toPartition :: [Int] -> Partition
+toPartition xs = if isPartition xs
+  then toPartitionUnsafe xs
+  else error "toPartition: not a partition"
+  
+-- | This returns @True@ if the input is non-increasing sequence of 
+-- /positive/ integers (possibly empty); @False@ otherwise.
+--
+isPartition :: [Int] -> Bool
+isPartition []  = True
+isPartition [x] = x > 0
+isPartition (x:xs@(y:_)) = (x >= y) && isPartition xs
+
+isEmptyPartition :: Partition -> Bool
+isEmptyPartition (Partition p) = null p
+
+emptyPartition :: Partition
+emptyPartition = Partition []
+
+instance CanBeEmpty Partition where
+  empty   = emptyPartition
+  isEmpty = isEmptyPartition
+
+fromPartition :: Partition -> [Int]
+fromPartition (Partition part) = part
+
+-- | The first element of the sequence.
+partitionHeight :: Partition -> Int
+partitionHeight (Partition part) = case part of
+  (p:_) -> p
+  []    -> 0
+  
+-- | The length of the sequence (that is, the number of parts).
+partitionWidth :: Partition -> Int
+partitionWidth (Partition part) = length part
+
+instance HasHeight Partition where
+  height = partitionHeight
+ 
+instance HasWidth Partition where
+  width = partitionWidth
+
+heightWidth :: Partition -> (Int,Int)
+heightWidth part = (height part, width part)
+
+-- | The weight of the partition 
+--   (that is, the sum of the corresponding sequence).
+partitionWeight :: Partition -> Int
+partitionWeight (Partition part) = sum' part
+
+instance HasWeight Partition where 
+  weight = partitionWeight
+
+-- | The dual (or conjugate) partition.
+dualPartition :: Partition -> Partition
+dualPartition (Partition part) = Partition (_dualPartition part)
+
+instance HasDuality Partition where 
+  dual = dualPartition
+
+data Pair = Pair !Int !Int
+
+_dualPartition :: [Int] -> [Int]
+_dualPartition [] = []
+_dualPartition xs = go 0 (diffSequence xs) [] where
+  go !i (d:ds) acc = go (i+1) ds (d:acc)
+  go n  []     acc = finish n acc 
+  finish !j (k:ks) = replicate k j ++ finish (j-1) ks
+  finish _  []     = []
+
+{-
+-- more variations:
+
+_dualPartition_b :: [Int] -> [Int]
+_dualPartition_b [] = []
+_dualPartition_b xs = go 1 (diffSequence xs) [] where
+  go !i (d:ds) acc = go (i+1) ds ((d,i):acc)
+  go _  []     acc = concatMap (\(d,i) -> replicate d i) acc
+
+_dualPartition_c :: [Int] -> [Int]
+_dualPartition_c [] = []
+_dualPartition_c xs = reverse $ concat $ zipWith f [1..] (diffSequence xs) where
+  f _ 0 = []
+  f k d = replicate d k
+-}
+
+-- | A simpler, but bit slower (about twice?) implementation of dual partition
+_dualPartitionNaive :: [Int] -> [Int]
+_dualPartitionNaive [] = []
+_dualPartitionNaive xs@(k:_) = [ length $ filter (>=i) xs | i <- [1..k] ]
+
+-- | From a sequence @[a1,a2,..,an]@ computes the sequence of differences
+-- @[a1-a2,a2-a3,...,an-0]@
+diffSequence :: [Int] -> [Int]
+diffSequence = go where
+  go (x:ys@(y:_)) = (x-y) : go ys 
+  go [x] = [x]
+  go []  = []
+
+-- | Example:
+--
+-- > elements (toPartition [5,4,1]) ==
+-- >   [ (1,1), (1,2), (1,3), (1,4), (1,5)
+-- >   , (2,1), (2,2), (2,3), (2,4)
+-- >   , (3,1)
+-- >   ]
+--
+elements :: Partition -> [(Int,Int)]
+elements (Partition part) = _elements part
+
+_elements :: [Int] -> [(Int,Int)]
+_elements shape = [ (i,j) | (i,l) <- zip [1..] shape, j<-[1..l] ] 
+
+---------------------------------------------------------------------------------
+-- * Exponential form
+
+-- | We convert a partition to exponential form.
+-- @(i,e)@ mean @(i^e)@; for example @[(1,4),(2,3)]@ corresponds to @(1^4)(2^3) = [2,2,2,1,1,1,1]@. Another example:
+--
+-- > toExponentialForm (Partition [5,5,3,2,2,2,2,1,1]) == [(1,2),(2,4),(3,1),(5,2)]
+--
+toExponentialForm :: Partition -> [(Int,Int)]
+toExponentialForm = _toExponentialForm . fromPartition
+
+_toExponentialForm :: [Int] -> [(Int,Int)]
+_toExponentialForm = reverse . map (\xs -> (head xs,length xs)) . group
+
+fromExponentialFrom :: [(Int,Int)] -> Partition
+fromExponentialFrom = Partition . sortBy reverseCompare . go where
+  go ((j,e):rest) = replicate e j ++ go rest
+  go []           = []   
+
+---------------------------------------------------------------------------------
+-- * Automorphisms 
+
+-- | Computes the number of \"automorphisms\" of a given integer partition.
+countAutomorphisms :: Partition -> Integer  
+countAutomorphisms = _countAutomorphisms . fromPartition
+
+_countAutomorphisms :: [Int] -> Integer
+_countAutomorphisms = multinomial . map length . group
+
+---------------------------------------------------------------------------------
+-- * Generating partitions
+
+-- | Partitions of @d@.
+partitions :: Int -> [Partition]
+partitions = map Partition . _partitions
+
+-- | Partitions of @d@, as lists
+_partitions :: Int -> [[Int]]
+_partitions d = go d d where
+  go _  0  = [[]]
+  go !h !n = [ a:as | a<-[1..min n h], as <- go a (n-a) ]
+
+-- | Number of partitions of @n@
+countPartitions :: Int -> Integer
+countPartitions n = partitionCountList !! n
+
+-- | This uses 'countPartitions'', and thus is slow
+countPartitionsNaive :: Int -> Integer
+countPartitionsNaive d = countPartitions' (d,d) d
+
+--------------------------------------------------------------------------------
+
+-- | Infinite list of number of partitions of @0,1,2,...@
+--
+-- This uses the infinite product formula the generating function of partitions, recursively
+-- expanding it; it is quite fast.
+--
+-- > partitionCountList == map countPartitions [0..]
+--
+partitionCountList :: [Integer]
+partitionCountList = final where
+
+  final = go 1 (1:repeat 0) 
+
+  go !k (x:xs) = x : go (k+1) ys where
+    ys = zipWith (+) xs (take k final ++ ys)
+    -- explanation:
+    --   xs == drop k $ f (k-1)
+    --   ys == drop k $ f (k  )  
+
+{-
+
+Full explanation of 'partitionCountList':
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+let f k = productPSeries $ map (:[]) [1..k]
+
+f 0 = [1,0,0,0,0,0,0,0...]
+f 1 = [1,1,1,1,1,1,1,1...]
+f 2 = [1,1,2,2,3,3,4,4...]
+f 3 = [1,1,2,3,4,5,7,8...]
+
+observe: 
+
+* take (k+1) (f k) == take (k+1) partitionCountList
+* f (k+1) == zipWith (+) (f k) (replicate (k+1) 0 ++ f (k+1))
+
+now apply (drop (k+1)) to the second one : 
+
+* drop (k+1) (f (k+1)) == zipWith (+) (drop (k+1) $ f k) (f (k+1))
+* f (k+1) = take (k+1) final ++ drop (k+1) (f (k+1))
+
+-}
+
+--------------------------------------------------------------------------------
+
+-- | Naive infinite list of number of partitions of @0,1,2,...@
+--
+-- > partitionCountListNaive == map countPartitionsNaive [0..]
+--
+-- This is much slower than the power series expansion above.
+--
+partitionCountListNaive :: [Integer]
+partitionCountListNaive = map countPartitionsNaive [0..]
+
+-- | All integer partitions up to a given degree (that is, all integer partitions whose sum is less or equal to @d@)
+allPartitions :: Int -> [Partition]
+allPartitions d = concat [ partitions i | i <- [0..d] ]
+
+-- | All integer partitions up to a given degree (that is, all integer partitions whose sum is less or equal to @d@),
+-- grouped by weight
+allPartitionsGrouped :: Int -> [[Partition]]
+allPartitionsGrouped d = [ partitions i | i <- [0..d] ]
+
+-- | All integer partitions fitting into a given rectangle.
+allPartitions'  
+  :: (Int,Int)        -- ^ (height,width)
+  -> [Partition]
+allPartitions' (h,w) = concat [ partitions' (h,w) i | i <- [0..d] ] where d = h*w
+
+-- | All integer partitions fitting into a given rectangle, grouped by weight.
+allPartitionsGrouped'  
+  :: (Int,Int)        -- ^ (height,width)
+  -> [[Partition]]
+allPartitionsGrouped' (h,w) = [ partitions' (h,w) i | i <- [0..d] ] where d = h*w
+
+-- | # = \\binom { h+w } { h }
+countAllPartitions' :: (Int,Int) -> Integer
+countAllPartitions' (h,w) = 
+  binomial (h+w) (min h w)
+  --sum [ countPartitions' (h,w) i | i <- [0..d] ] where d = h*w
+
+countAllPartitions :: Int -> Integer
+countAllPartitions d = sum' [ countPartitions i | i <- [0..d] ]
+
+-- | Integer partitions of @d@, fitting into a given rectangle, as lists.
+_partitions' 
+  :: (Int,Int)     -- ^ (height,width)
+  -> Int           -- ^ d
+  -> [[Int]]        
+_partitions' _ 0 = [[]] 
+_partitions' ( 0 , _) d = if d==0 then [[]] else []
+_partitions' ( _ , 0) d = if d==0 then [[]] else []
+_partitions' (!h ,!w) d = 
+  [ i:xs | i <- [1..min d h] , xs <- _partitions' (i,w-1) (d-i) ]
+
+-- | Partitions of d, fitting into a given rectangle. The order is again lexicographic.
+partitions'  
+  :: (Int,Int)     -- ^ (height,width)
+  -> Int           -- ^ d
+  -> [Partition]
+partitions' hw d = map toPartitionUnsafe $ _partitions' hw d        
+
+countPartitions' :: (Int,Int) -> Int -> Integer
+countPartitions' _ 0 = 1
+countPartitions' (0,_) d = if d==0 then 1 else 0
+countPartitions' (_,0) d = if d==0 then 1 else 0
+countPartitions' (h,w) d = sum
+  [ countPartitions' (i,w-1) (d-i) | i <- [1..min d h] ] 
+
+
+---------------------------------------------------------------------------------
+-- * Random partitions
+
+-- | Uniformly random partition of the given weight. 
+--
+-- NOTE: This algorithm is effective for small @n@-s (say @n@ up to a few hundred \/ one thousand it should work nicely),
+-- and the first time it is executed may be slower (as it needs to build the table 'partitionCountList' first)
+--
+-- Algorithm of Nijenhuis and Wilf (1975); see
+--
+-- * Knuth Vol 4A, pre-fascicle 3B, exercise 47;
+--
+-- * Nijenhuis and Wilf: Combinatorial Algorithms for Computers and Calculators, chapter 10
+--
+randomPartition :: RandomGen g => Int -> g -> (Partition, g)
+randomPartition n g = (p, g') where
+  ([p], g') = randomPartitions 1 n g
+
+-- | Generates several uniformly random partitions of @n@ at the same time.
+-- Should be a little bit faster then generating them individually.
+--
+randomPartitions 
+  :: forall g. RandomGen g 
+  => Int   -- ^ number of partitions to generate
+  -> Int   -- ^ the weight of the partitions
+  -> g -> ([Partition], g)
+randomPartitions howmany n = runRand $ replicateM howmany (worker n []) where
+
+  table = listArray (0,n) $ take (n+1) partitionCountList :: Array Int Integer
+  cnt k = table ! k
+ 
+  finish :: [(Int,Int)] -> Partition
+  finish = mkPartition . concatMap f where f (j,d) = replicate j d
+
+  fi :: Int -> Integer 
+  fi = fromIntegral
+
+  find_jd :: Int -> Integer -> (Int,Int)
+  find_jd m capm = go 0 [ (j,d) | j<-[1..n], d<-[1..div m j] ] where
+    go :: Integer -> [(Int,Int)] -> (Int,Int)
+    go !s []   = (1,1)       -- ??
+    go !s [jd] = jd          -- ??
+    go !s (jd@(j,d):rest) = 
+      if s' > capm 
+        then jd 
+        else go s' rest
+      where
+        s' = s + fi d * cnt (m - j*d)
+
+  worker :: Int -> [(Int,Int)] -> Rand g Partition
+  worker  0 acc = return $ finish acc
+  worker !m acc = do
+    capm <- randChoose (0, (fi m) * cnt m - 1)
+    let jd@(!j,!d) = find_jd m capm
+    worker (m - j*d) (jd:acc)
+
+
+---------------------------------------------------------------------------------
+-- * Dominance order 
+
+-- | @q \`dominates\` p@ returns @True@ if @q >= p@ in the dominance order of partitions
+-- (this is partial ordering on the set of partitions of @n@).
+--
+-- See <http://en.wikipedia.org/wiki/Dominance_order>
+--
+dominates :: Partition -> Partition -> Bool
+dominates (Partition qs) (Partition ps) 
+  = and $ zipWith (>=) (sums (qs ++ repeat 0)) (sums ps)
+  where
+    sums = scanl (+) 0
+
+
+-- | Lists all partitions of the same weight as @lambda@ and also dominated by @lambda@
+-- (that is, all partial sums are less or equal):
+--
+-- > dominatedPartitions lam == [ mu | mu <- partitions (weight lam), lam `dominates` mu ]
+-- 
+dominatedPartitions :: Partition -> [Partition]    
+dominatedPartitions (Partition lambda) = map Partition (_dominatedPartitions lambda)
+
+_dominatedPartitions :: [Int] -> [[Int]]
+_dominatedPartitions []     = [[]]
+_dominatedPartitions lambda = go (head lambda) w dsums 0 where
+
+  n = length lambda
+  w = sum    lambda
+  dsums = scanl1 (+) (lambda ++ repeat 0)
+
+  go _   0 _       _  = [[]]
+  go !h !w (!d:ds) !e  
+    | w >  0  = [ (a:as) | a <- [1..min h (d-e)] , as <- go a (w-a) ds (e+a) ] 
+    | w == 0  = [[]]
+    | w <  0  = error "_dominatedPartitions: fatal error; shouldn't happen"
+
+-- | Lists all partitions of the sime weight as @mu@ and also dominating @mu@
+-- (that is, all partial sums are greater or equal):
+--
+-- > dominatingPartitions mu == [ lam | lam <- partitions (weight mu), lam `dominates` mu ]
+-- 
+dominatingPartitions :: Partition -> [Partition]    
+dominatingPartitions (Partition mu) = map Partition (_dominatingPartitions mu)
+
+_dominatingPartitions :: [Int] -> [[Int]]
+_dominatingPartitions []     = [[]]
+_dominatingPartitions mu     = go w w dsums 0 where
+
+  n = length mu
+  w = sum    mu
+  dsums = scanl1 (+) (mu ++ repeat 0)
+
+  go _   0 _       _  = [[]]
+  go !h !w (!d:ds) !e  
+    | w >  0  = [ (a:as) | a <- [max 0 (d-e)..min h w] , as <- go a (w-a) ds (e+a) ] 
+    | w == 0  = [[]]
+    | w <  0  = error "_dominatingPartitions: fatal error; shouldn't happen"
+
+--------------------------------------------------------------------------------
+-- * Partitions with given number of parts
+
+-- | Lists partitions of @n@ into @k@ parts.
+--
+-- > sort (partitionsWithKParts k n) == sort [ p | p <- partitions n , numberOfParts p == k ]
+--
+-- Naive recursive algorithm.
+--
+partitionsWithKParts 
+  :: Int    -- ^ @k@ = number of parts
+  -> Int    -- ^ @n@ = the integer we partition
+  -> [Partition]
+partitionsWithKParts k n = map Partition $ go n k n where
+{-
+  h = max height
+  k = number of parts
+  n = integer
+-}
+  go !h !k !n 
+    | k <  0     = []
+    | k == 0     = if h>=0 && n==0 then [[] ] else []
+    | k == 1     = if h>=n && n>=1 then [[n]] else []
+    | otherwise  = [ a:p | a <- [1..(min h (n-k+1))] , p <- go a (k-1) (n-a) ]
+
+countPartitionsWithKParts 
+  :: Int    -- ^ @k@ = number of parts
+  -> Int    -- ^ @n@ = the integer we partition
+  -> Integer
+countPartitionsWithKParts k n = go n k n where
+  go !h !k !n 
+    | k <  0     = 0
+    | k == 0     = if h>=0 && n==0 then 1 else 0
+    | k == 1     = if h>=n && n>=1 then 1 else 0
+    | otherwise  = sum' [ go a (k-1) (n-a) | a<-[1..(min h (n-k+1))] ]
+
+--------------------------------------------------------------------------------
+-- * Partitions with only odd\/distinct parts
+
+-- | Partitions of @n@ with only odd parts
+partitionsWithOddParts :: Int -> [Partition]
+partitionsWithOddParts d = map Partition (go d d) where
+  go _  0  = [[]]
+  go !h !n = [ a:as | a<-[1,3..min n h], as <- go a (n-a) ]
+
+{-
+-- | Partitions of @n@ with only even parts
+--
+-- Note: this is not very interesting, it's just @(map.map) (2*) $ _partitions (div n 2)@
+--
+partitionsWithEvenParts :: Int -> [Partition]
+partitionsWithEvenParts d = map Partition (go d d) where
+  go _  0  = [[]]
+  go !h !n = [ a:as | a<-[2,4..min n h], as <- go a (n-a) ]
+-}
+
+-- | Partitions of @n@ with distinct parts.
+-- 
+-- Note:
+--
+-- > length (partitionsWithDistinctParts d) == length (partitionsWithOddParts d)
+--
+partitionsWithDistinctParts :: Int -> [Partition]
+partitionsWithDistinctParts d = map Partition (go d d) where
+  go _  0  = [[]]
+  go !h !n = [ a:as | a<-[1..min n h], as <- go (a-1) (n-a) ]
+
+--------------------------------------------------------------------------------
+-- * Sub- and super-partitions of a given partition
+
+-- | Returns @True@ of the first partition is a subpartition (that is, fit inside) of the second.
+-- This includes equality
+isSubPartitionOf :: Partition -> Partition -> Bool
+isSubPartitionOf (Partition ps) (Partition qs) = and $ zipWith (<=) ps (qs ++ repeat 0)
+
+-- | This is provided for convenience\/completeness only, as:
+--
+-- > isSuperPartitionOf q p == isSubPartitionOf p q
+--
+isSuperPartitionOf :: Partition -> Partition -> Bool
+isSuperPartitionOf (Partition qs) (Partition ps) = and $ zipWith (<=) ps (qs ++ repeat 0)
+
+
+-- | Sub-partitions of a given partition with the given weight:
+--
+-- > sort (subPartitions d q) == sort [ p | p <- partitions d, isSubPartitionOf p q ]
+--
+subPartitions :: Int -> Partition -> [Partition]
+subPartitions d (Partition ps) = map Partition (_subPartitions d ps)
+
+_subPartitions :: Int -> [Int] -> [[Int]]
+_subPartitions d big
+  | null big       = if d==0 then [[]] else []
+  | d > sum' big   = []
+  | d < 0          = []
+  | otherwise      = go d (head big) big
+  where
+    go :: Int -> Int -> [Int] -> [[Int]]
+    go !k !h []      = if k==0 then [[]] else []
+    go !k !h (b:bs) 
+      | k<0 || h<0   = []
+      | k==0         = [[]]
+      | h==0         = []
+      | otherwise    = [ this:rest | this <- [1..min h b] , rest <- go (k-this) this bs ]
+
+----------------------------------------
+
+-- | All sub-partitions of a given partition
+allSubPartitions :: Partition -> [Partition]
+allSubPartitions (Partition ps) = map Partition (_allSubPartitions ps)
+
+_allSubPartitions :: [Int] -> [[Int]]
+_allSubPartitions big 
+  | null big   = [[]]
+  | otherwise  = go (head big) big
+  where
+    go _  [] = [[]]
+    go !h (b:bs) 
+      | h==0         = []
+      | otherwise    = [] : [ this:rest | this <- [1..min h b] , rest <- go this bs ]
+
+----------------------------------------
+
+-- | Super-partitions of a given partition with the given weight:
+--
+-- > sort (superPartitions d p) == sort [ q | q <- partitions d, isSubPartitionOf p q ]
+--
+superPartitions :: Int -> Partition -> [Partition]
+superPartitions d (Partition ps) = map Partition (_superPartitions d ps)
+
+_superPartitions :: Int -> [Int] -> [[Int]]
+_superPartitions dd small
+  | dd < w0     = []
+  | null small  = _partitions dd
+  | otherwise   = go dd w1 dd (small ++ repeat 0)
+  where
+    w0 = sum' small
+    w1 = w0 - head small
+    -- d = remaining weight of the outer partition we are constructing
+    -- w = remaining weight of the inner partition (we need to reserve at least this amount)
+    -- h = max height (decreasing)
+    go !d !w !h (!a:as@(b:_)) 
+      | d < 0     = []
+      | d == 0    = if a == 0 then [[]] else []
+      | otherwise = [ this:rest | this <- [max 1 a .. min h (d-w)] , rest <- go (d-this) (w-b) this as ]
+    
+--------------------------------------------------------------------------------
+-- * The Pieri rule
+
+-- | The Pieri rule computes @s[lambda]*h[n]@ as a sum of @s[mu]@-s (each with coefficient 1).
+--
+-- See for example <http://en.wikipedia.org/wiki/Pieri's_formula>
+--
+pieriRule :: Partition -> Int -> [Partition] 
+pieriRule (Partition lambda) n = map Partition (_pieriRule lambda n) where
+
+  -- | We assume here that @lambda@ is a partition (non-increasing sequence of /positive/ integers)! 
+  _pieriRule :: [Int] -> Int -> [[Int]] 
+  _pieriRule lambda n
+    | n == 0     = [lambda]
+    | n <  0     = [] 
+    | otherwise  = go n diffs dsums (lambda++[0]) 
+    where
+      diffs = n : diffSequence lambda                 -- maximum we can add to a given row
+      dsums = reverse $ scanl1 (+) (reverse diffs)    -- partial sums of remaining total we can add
+      go !k (d:ds) (p:ps@(q:_)) (l:ls) 
+        | k > p     = []
+        | otherwise = [ h:tl | a <- [ max 0 (k-q) .. min d k ] , let h = l+a , tl <- go (k-a) ds ps ls ]
+      go !k [d]    _      [l]    = if k <= d 
+                                     then if l+k>0 then [[l+k]] else [[]]
+                                     else []
+      go !k []     _      _      = if k==0 then [[]] else []
+
+-- | The dual Pieri rule computes @s[lambda]*e[n]@ as a sum of @s[mu]@-s (each with coefficient 1)
+dualPieriRule :: Partition -> Int -> [Partition] 
+dualPieriRule lam n = map dualPartition $ pieriRule (dualPartition lam) n
+
+
+{- 
+-- moved to "Math.Combinat.Tableaux.GelfandTsetlin"
+
+-- | Computes the Schur expansion of @h[n1]*h[n2]*h[n3]*...*h[nk]@ via iterating the Pieri rule
+iteratedPieriRule :: Num coeff => [Int] -> Map Partition coeff
+iteratedPieriRule = iteratedPieriRule' (Partition [])
+
+-- | Iterating the Pieri rule, we can compute the Schur expansion of
+-- @h[lambda]*h[n1]*h[n2]*h[n3]*...*h[nk]@
+iteratedPieriRule' :: Num coeff => Partition -> [Int] -> Map Partition coeff
+iteratedPieriRule' plambda ns = iteratedPieriRule'' (plambda,1) ns
+
+{-# SPECIALIZE iteratedPieriRule'' :: (Partition,Int    ) -> [Int] -> Map Partition Int     #-}
+{-# SPECIALIZE iteratedPieriRule'' :: (Partition,Integer) -> [Int] -> Map Partition Integer #-}
+iteratedPieriRule'' :: Num coeff => (Partition,coeff) -> [Int] -> Map Partition coeff
+iteratedPieriRule'' (plambda,coeff0) ns = worker (Map.singleton plambda coeff0) ns where
+  worker old []     = old
+  worker old (n:ns) = worker new ns where
+    stuff = [ (coeff, pieriRule lam n) | (lam,coeff) <- Map.toList old ] 
+    new   = foldl' f Map.empty stuff 
+    f t0 (c,ps) = foldl' (\t p -> Map.insertWith (+) p c t) t0 ps  
+-}
+
+--------------------------------------------------------------------------------
+-- * ASCII Ferrers diagrams
+
+-- | Which orientation to draw the Ferrers diagrams.
+-- For example, the partition [5,4,1] corrsponds to:
+--
+-- In standard English notation:
+-- 
+-- >  @@@@@
+-- >  @@@@
+-- >  @
+--
+--
+-- In English notation rotated by 90 degrees counter-clockwise:
+--
+-- > @  
+-- > @@
+-- > @@
+-- > @@
+-- > @@@
+--
+--
+-- And in French notation:
+--
+-- 
+-- >  @
+-- >  @@@@
+-- >  @@@@@
+--
+--
+data PartitionConvention
+  = EnglishNotation          -- ^ English notation
+  | EnglishNotationCCW       -- ^ English notation rotated by 90 degrees counterclockwise
+  | FrenchNotation           -- ^ French notation (mirror of English notation to the x axis)
+  deriving (Eq,Show)
+
+-- | Synonym for @asciiFerrersDiagram\' EnglishNotation \'\@\'@
+--
+-- Try for example:
+--
+-- > autoTabulate RowMajor (Right 8) (map asciiFerrersDiagram $ partitions 9)
+--
+asciiFerrersDiagram :: Partition -> ASCII
+asciiFerrersDiagram = asciiFerrersDiagram' EnglishNotation '@'
+
+asciiFerrersDiagram' :: PartitionConvention -> Char -> Partition -> ASCII
+asciiFerrersDiagram' conv ch part = ASCII.asciiFromLines (map f ys) where
+  f n = replicate n ch 
+  ys  = case conv of
+          EnglishNotation    -> fromPartition part
+          EnglishNotationCCW -> reverse $ fromPartition $ dualPartition part
+          FrenchNotation     -> reverse $ fromPartition $ part
+
+instance DrawASCII Partition where
+  ascii = asciiFerrersDiagram
+
+--------------------------------------------------------------------------------
+
diff --git a/Math/Combinat/Partitions/Multiset.hs b/Math/Combinat/Partitions/Multiset.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Partitions/Multiset.hs
@@ -0,0 +1,24 @@
+
+-- | Partitions of a multiset
+module Math.Combinat.Partitions.Multiset where
+
+--------------------------------------------------------------------------------
+
+import Data.Array.Unboxed
+import Data.List
+
+import Math.Combinat.Partitions.Vector
+
+--------------------------------------------------------------------------------
+                              
+-- | Partitions of a multiset. Internally, this uses the vector partition algorithm
+partitionMultiset :: (Eq a, Ord a) => [a] -> [[[a]]]
+partitionMultiset xs = parts where
+  parts = (map . map) (f . elems) temp
+  f ns = concat (zipWith replicate ns zs)
+  temp = fasc3B_algorithm_M counts
+  counts = map length ys
+  ys = group (sort xs) 
+  zs = map head ys
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Partitions/NonCrossing.hs b/Math/Combinat/Partitions/NonCrossing.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Partitions/NonCrossing.hs
@@ -0,0 +1,205 @@
+
+-- | Non-crossing partitions.
+--
+-- See eg. <http://en.wikipedia.org/wiki/Noncrossing_partition>
+--
+-- Non-crossing partitions of the set @[1..n]@ are encoded as lists of lists
+-- in standard form: Entries decreasing in each block  and blocks listed in increasing order of their first entries.
+-- For example the partition in the diagram
+--
+-- <<svg/noncrossing.svg>>
+--
+-- is represented as
+--
+-- > NonCrossing [[3],[5,4,2],[7,6,1],[9,8]]
+--
+
+{-# LANGUAGE BangPatterns #-}
+module Math.Combinat.Partitions.NonCrossing where
+
+--------------------------------------------------------------------------------
+
+import Control.Applicative
+
+import Data.List
+import Data.Ord
+
+import System.Random
+
+import Math.Combinat.Numbers
+import Math.Combinat.LatticePaths
+import Math.Combinat.Helper
+import Math.Combinat.Partitions.Set
+import Math.Combinat.Classes
+
+--------------------------------------------------------------------------------
+-- * The type of non-crossing partitions
+
+-- | A non-crossing partition of the set @[1..n]@ in standard form: 
+-- entries decreasing in each block  and blocks listed in increasing order of their first entries.
+newtype NonCrossing = NonCrossing [[Int]] deriving (Eq,Ord,Show,Read)
+
+-- | Checks whether a set partition is noncrossing.
+--
+-- Implementation method: we convert to a Dyck path and then back again, and finally compare. 
+-- Probably not very efficient, but should be better than a naive check for crosses...)
+--
+_isNonCrossing :: [[Int]] -> Bool
+_isNonCrossing zzs0 = _isNonCrossingUnsafe (_standardizeNonCrossing zzs0)
+
+-- | Warning: This function assumes the standard ordering!
+_isNonCrossingUnsafe :: [[Int]] -> Bool
+_isNonCrossingUnsafe zzs = 
+  case _nonCrossingPartitionToDyckPathMaybe zzs of
+    Nothing   -> False
+    Just dyck -> case dyckPathToNonCrossingPartitionMaybe dyck of
+      Nothing                -> False
+      Just (NonCrossing yys) -> yys == zzs
+
+-- | Convert to standard form: entries decreasing in each block 
+-- and blocks listed in increasing order of their first entries.
+_standardizeNonCrossing :: [[Int]] -> [[Int]]
+_standardizeNonCrossing = sortBy (comparing myhead) . map reverseSort where
+  myhead xs = case xs of
+    (x:xs) -> x
+    []     -> error "_standardizeNonCrossing: empty subset"
+
+fromNonCrossing :: NonCrossing -> [[Int]]
+fromNonCrossing (NonCrossing xs) = xs
+
+toNonCrossingUnsafe :: [[Int]] -> NonCrossing
+toNonCrossingUnsafe = NonCrossing
+
+-- | Throws an error if the input is not a non-crossing partition
+toNonCrossing :: [[Int]] -> NonCrossing
+toNonCrossing xxs = case toNonCrossingMaybe xxs of
+  Just nc -> nc
+  Nothing -> error "toNonCrossing: not a non-crossing partition"
+
+toNonCrossingMaybe :: [[Int]] -> Maybe NonCrossing
+toNonCrossingMaybe xxs0 = 
+  if _isNonCrossingUnsafe xxs
+    then Just $ NonCrossing xxs
+    else Nothing
+  where 
+    xxs = _standardizeNonCrossing xxs0
+
+-- | If a set partition is actually non-crossing, then we can convert it
+setPartitionToNonCrossing :: SetPartition -> Maybe NonCrossing
+setPartitionToNonCrossing (SetPartition zzs0) =
+  if _isNonCrossingUnsafe zzs
+    then Just $ NonCrossing zzs
+    else Nothing
+  where
+    zzs = _standardizeNonCrossing zzs0
+
+instance HasNumberOfParts NonCrossing where
+  numberOfParts (NonCrossing p) = length p
+
+--------------------------------------------------------------------------------
+-- * Bijection to Dyck paths
+
+-- | Bijection between Dyck paths and noncrossing partitions
+--
+-- Based on: David Callan: /Sets, Lists and Noncrossing Partitions/
+--
+-- Fails if the input is not a Dyck path.
+dyckPathToNonCrossingPartition :: LatticePath -> NonCrossing
+dyckPathToNonCrossingPartition = NonCrossing . go 0 [] [] [] where
+  go :: Int -> [Int] -> [Int] -> [[Int]] -> LatticePath -> [[Int]] 
+  go !cnt stack small big path =
+    case path of
+      (x:xs) -> case x of 
+        UpStep   -> let cnt' = cnt + 1 in case xs of
+          (y:ys)   -> case y of
+            UpStep   -> go cnt' (cnt':stack) small                  big  xs  
+            DownStep -> go cnt' (cnt':stack) []    (reverse small : big) xs
+          []       -> error "dyckPathToNonCrossingPartition: last step is an UpStep (thus input was not a Dyck path)"
+        DownStep -> case stack of
+          (k:ks)   -> go cnt ks (k:small) big xs
+          []       -> error "dyckPathToNonCrossingPartition: empty stack, shouldn't happen (thus input was not a Dyck path)"
+      [] -> tail $ reverse (reverse small : big)
+
+-- | Safe version of 'dyckPathToNonCrossingPartition'
+dyckPathToNonCrossingPartitionMaybe :: LatticePath -> Maybe NonCrossing
+dyckPathToNonCrossingPartitionMaybe = fmap NonCrossing . go 0 [] [] [] where
+  go :: Int -> [Int] -> [Int] -> [[Int]] -> LatticePath -> Maybe [[Int]] 
+  go !cnt stack small big path =
+    case path of
+      (x:xs) -> case x of 
+        UpStep   -> let cnt' = cnt + 1 in case xs of
+          (y:ys)   -> case y of
+            UpStep   -> go cnt' (cnt':stack) small                  big  xs  
+            DownStep -> go cnt' (cnt':stack) []    (reverse small : big) xs
+          []       -> Nothing
+        DownStep -> case stack of
+          (k:ks)   -> go cnt ks (k:small) big xs
+          []       -> Nothing
+      [] -> Just $ tail $ reverse (reverse small : big)
+
+-- | The inverse bijection (should never fail proper 'NonCrossing'-s)
+nonCrossingPartitionToDyckPath :: NonCrossing -> LatticePath
+nonCrossingPartitionToDyckPath (NonCrossing zzs) = go 0 zzs where
+  go !k (ys@(y:_):yys) = replicate (y-k) UpStep ++ replicate (length ys) DownStep ++ go y yys
+  go !k []             = []
+  go _  _              = error "nonCrossingPartitionToDyckPath: shouldnt't happen"
+
+-- | Safe version 'nonCrossingPartitionToDyckPath'
+_nonCrossingPartitionToDyckPathMaybe :: [[Int]] -> Maybe LatticePath
+_nonCrossingPartitionToDyckPathMaybe = go 0 where
+  go !k (ys@(y:_):yys) = fmap (\zs -> replicate (y-k) UpStep ++ replicate (length ys) DownStep ++ zs) (go y yys)
+  go !k []             = Just []
+  go _  _              = Nothing
+
+--------------------------------------------------------------------------------
+
+{- 
+-- this should be mapped to NonCrossing [[3],[5,4,2],[7,6,1],[9,8]]
+testpath = [u,u,u,d,u,u,d,d,d,u,u,d,d,d,u,u,d,d] where
+  u = UpStep
+  d = DownStep
+
+testnc = NonCrossing [[3],[5,4,2],[7,6,1],[9,8]]
+-}
+
+--------------------------------------------------------------------------------
+-- * Generating non-crossing partitions
+
+-- | Lists all non-crossing partitions of @[1..n]@
+--
+-- Equivalent to (but orders of magnitude faster than) filtering out the non-crossing ones:
+--
+-- > (sort $ catMaybes $ map setPartitionToNonCrossing $ setPartitions n) == sort (nonCrossingPartitions n)
+--
+nonCrossingPartitions :: Int -> [NonCrossing]
+nonCrossingPartitions = map dyckPathToNonCrossingPartition . dyckPaths
+
+-- | Lists all non-crossing partitions of @[1..n]@ into @k@ parts.
+--
+-- > sort (nonCrossingPartitionsWithKParts k n) == sort [ p | p <- nonCrossingPartitions n , numberOfParts p == k ]
+--
+nonCrossingPartitionsWithKParts 
+  :: Int   -- ^ @k@ = number of parts 
+  -> Int   -- ^ @n@ = size of the set
+  -> [NonCrossing]
+nonCrossingPartitionsWithKParts k n = map dyckPathToNonCrossingPartition $ peakingDyckPaths k n
+
+-- | Non-crossing partitions are counted by the Catalan numbers
+countNonCrossingPartitions :: Int -> Integer
+countNonCrossingPartitions = countDyckPaths
+
+-- | Non-crossing partitions with @k@ parts are counted by the Naranaya numbers
+countNonCrossingPartitionsWithKParts 
+  :: Int   -- ^ @k@ = number of parts 
+  -> Int   -- ^ @n@ = size of the set
+  -> Integer
+countNonCrossingPartitionsWithKParts = countPeakingDyckPaths
+
+--------------------------------------------------------------------------------
+
+-- | Uniformly random non-crossing partition
+randomNonCrossingPartition :: RandomGen g => Int -> g -> (NonCrossing,g)
+randomNonCrossingPartition n g0 = (dyckPathToNonCrossingPartition dyck, g1) where
+  (dyck,g1) = randomDyckPath n g0
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Partitions/Plane.hs b/Math/Combinat/Partitions/Plane.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Partitions/Plane.hs
@@ -0,0 +1,124 @@
+
+-- | Plane partitions. See eg. <http://en.wikipedia.org/wiki/Plane_partition>
+--
+-- Plane partitions are encoded as lists of lists of Z heights. For example the plane 
+-- partition in the picture
+-- 
+-- <<svg/plane_partition.svg>>
+--
+-- is encoded as
+--
+-- > PlanePart [ [5,4,3,3,1]
+-- >           , [4,4,2,1]
+-- >           , [3,2]
+-- >           , [2,1]
+-- >           , [1]
+-- >           , [1]
+-- >           ]
+-- 
+{-# LANGUAGE BangPatterns #-}
+module Math.Combinat.Partitions.Plane where
+
+--------------------------------------------------------------------------------
+
+import Data.List
+import Data.Array
+
+import Math.Combinat.Classes
+import Math.Combinat.Partitions
+import Math.Combinat.Tableaux as Tableaux
+import Math.Combinat.Helper
+
+--------------------------------------------------------------------------------
+-- * the type of plane partitions
+
+-- | A plane partition encoded as a tablaeu (the \"Z\" heights are the numbers)
+newtype PlanePart = PlanePart [[Int]] deriving (Eq,Ord,Show)
+
+fromPlanePart :: PlanePart -> [[Int]]
+fromPlanePart (PlanePart xs) = xs
+
+isValidPlanePart :: [[Int]] -> Bool
+isValidPlanePart pps = 
+  and [ table!(i,j) >= table!(i  ,j+1) &&
+        table!(i,j) >= table!(i+1,j  )
+      | i<-[0..y-1] , j<-[0..x-1] 
+      ]
+  where
+    table :: Array (Int,Int) Int
+    table = accumArray const 0 ((0,0),(y,x)) [ ((i,j),k) | (i,ps) <- zip [0..] pps , (j,k) <- zip [0..] ps ]
+    y = length pps
+    x = maximum (map length pps)
+
+instance CanBeEmpty PlanePart where
+  isEmpty = null . fromPlanePart
+  empty   = PlanePart []
+
+-- | Throws an exception if the input is not a plane partition
+toPlanePart :: [[Int]] -> PlanePart
+toPlanePart pps = if isValidPlanePart pps
+  then PlanePart $ filter (not . null) $ map (filter (>0)) $ pps
+  else error "toPlanePart: not a plane partition"
+
+-- | The XY projected shape of a plane partition, as an integer partition
+planePartShape :: PlanePart -> Partition
+planePartShape = Tableaux.tableauShape . fromPlanePart
+
+-- | The Z height of a plane partition
+planePartZHeight :: PlanePart -> Int
+planePartZHeight (PlanePart xs) = 
+  case xs of
+    ((h:_):_) -> h
+    _         -> 0
+
+planePartWeight :: PlanePart -> Int
+planePartWeight (PlanePart xs) = sum' (map sum' xs)
+
+instance HasWeight PlanePart where
+  weight = planePartWeight
+
+--------------------------------------------------------------------------------
+-- * constructing plane partitions
+
+singleLayer :: Partition -> PlanePart
+singleLayer = PlanePart . map (\k -> replicate k 1) . fromPartition 
+
+-- |  Stacks layers of partitions into a plane partition.
+-- Throws an exception if they do not form a plane partition.
+stackLayers :: [Partition] -> PlanePart
+stackLayers layers = if and [ isSubPartitionOf p q | (q,p) <- pairs layers ]
+  then unsafeStackLayers layers
+  else error "stackLayers: the layers do not form a plane partition"
+
+-- | Stacks layers of partitions into a plane partition.
+-- This is unsafe in the sense that we don't check that the partitions fit on the top of each other.
+unsafeStackLayers :: [Partition] -> PlanePart
+unsafeStackLayers []            = PlanePart []
+unsafeStackLayers (bottom:rest) = PlanePart $ foldl addLayer (fromPlanePart $ singleLayer bottom) rest where
+  addLayer :: [[Int]] -> Partition -> [[Int]]
+  addLayer xxs (Partition ps) = [ zipWith (+) xs (replicate p 1 ++ repeat 0) | (xs,p) <- zip xxs (ps ++ repeat 0) ] 
+
+-- | The \"layers\" of a plane partition (in direction @Z@). We should have
+--
+-- > unsafeStackLayers (planePartLayers pp) == pp
+-- 
+planePartLayers :: PlanePart -> [Partition]
+planePartLayers pp@(PlanePart xs) = [ layer h | h<-[1..planePartZHeight pp] ] where
+  layer h = Partition $ filter (>0) $ map sum' $ (map . map) (f h) xs
+  f h = \k -> if k>=h then 1 else 0
+
+--------------------------------------------------------------------------------
+-- * generating plane partitions
+
+-- | Plane partitions of a given weight
+planePartitions :: Int -> [PlanePart]
+planePartitions d 
+  | d <  0     = []
+  | d == 0     = [PlanePart []]
+  | otherwise  = concat [ go (d-n) [p] | n<-[1..d] , p<-partitions n ]
+  where
+    go :: Int -> [Partition] -> [PlanePart]
+    go  0   acc       = [unsafeStackLayers (reverse acc)]
+    go !rem acc@(h:_) = concat [ go (rem-k) (this:acc) | k<-[1..rem] , this <- subPartitions k h ]
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Partitions/Set.hs b/Math/Combinat/Partitions/Set.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Partitions/Set.hs
@@ -0,0 +1,109 @@
+
+-- | Set partitions.
+--
+-- See eg. <http://en.wikipedia.org/wiki/Partition_of_a_set>
+-- 
+
+{-# LANGUAGE BangPatterns #-}
+module Math.Combinat.Partitions.Set where
+
+--------------------------------------------------------------------------------
+
+import Data.List
+import Data.Ord
+
+import System.Random
+
+import Math.Combinat.Sets
+import Math.Combinat.Numbers
+import Math.Combinat.Helper
+import Math.Combinat.Classes
+import Math.Combinat.Partitions.Integer
+
+--------------------------------------------------------------------------------
+-- * The type of set partitions
+
+-- | A partition of the set @[1..n]@ (in standard order)
+newtype SetPartition = SetPartition [[Int]] deriving (Eq,Ord,Show,Read)
+
+_standardizeSetPartition :: [[Int]] -> [[Int]]
+_standardizeSetPartition = sortBy (comparing myhead) . map sort where
+  myhead xs = case xs of
+    (x:xs) -> x
+    []     -> error "_standardizeSetPartition: empty subset"
+
+fromSetPartition :: SetPartition -> [[Int]]
+fromSetPartition (SetPartition zzs) = zzs
+
+toSetPartitionUnsafe :: [[Int]] -> SetPartition
+toSetPartitionUnsafe = SetPartition
+
+toSetPartition :: [[Int]] -> SetPartition
+toSetPartition zzs = if _isSetPartition zzs
+  then SetPartition (_standardizeSetPartition zzs)
+  else error "toSetPartition: not a set partition"
+
+_isSetPartition :: [[Int]] -> Bool
+_isSetPartition zzs = sort (concat zzs) == [1..n] where 
+  n = sum' (map length zzs)
+
+instance HasNumberOfParts SetPartition where
+  numberOfParts (SetPartition p) = length p
+
+--------------------------------------------------------------------------------
+-- * Forgetting the set structure
+
+-- | The \"shape\" of a set partition is the partition we get when we forget the
+-- set structure, keeping only the cardinalities.
+--
+setPartitionShape :: SetPartition -> Partition
+setPartitionShape (SetPartition pps) = mkPartition (map length pps)
+
+--------------------------------------------------------------------------------
+-- * Generating set partitions
+
+-- | Synonym for 'setPartitionsNaive'
+setPartitions :: Int -> [SetPartition]
+setPartitions = setPartitionsNaive
+
+-- | Synonym for 'setPartitionsWithKPartsNaive'
+--
+-- > sort (setPartitionsWithKParts k n) == sort [ p | p <- setPartitions n , numberOfParts p == k ]
+-- 
+setPartitionsWithKParts   
+  :: Int    -- ^ @k@ = number of parts
+  -> Int    -- ^ @n@ = size of the set
+  -> [SetPartition]
+setPartitionsWithKParts = setPartitionsWithKPartsNaive
+
+-- | List all set partitions of @[1..n]@, naive algorithm
+setPartitionsNaive :: Int -> [SetPartition]
+setPartitionsNaive n = map (SetPartition . _standardizeSetPartition) $ go [1..n] where
+  go :: [Int] -> [[[Int]]]
+  go []     = [[]]
+  go (z:zs) = [ s : rest | k <- [1..n] , s0 <- choose (k-1) zs , let s = z:s0 , rest <- go (zs \\ s) ]
+
+-- | Set partitions of the set @[1..n]@ into @k@ parts
+setPartitionsWithKPartsNaive 
+  :: Int    -- ^ @k@ = number of parts
+  -> Int    -- ^ @n@ = size of the set
+  -> [SetPartition]
+setPartitionsWithKPartsNaive k n = map (SetPartition . _standardizeSetPartition) $ go k [1..n] where
+  go :: Int -> [Int] -> [[[Int]]]
+  go !k []     = if k==0 then [[]] else []
+  go  1 zs     = [[zs]]
+  go !k (z:zs) = [ s : rest | l <- [1..n] , s0 <- choose (l-1) zs , let s = z:s0 , rest <- go (k-1) (zs \\ s) ]
+
+
+-- | Set partitions are counted by the Bell numbers
+countSetPartitions :: Int -> Integer
+countSetPartitions = bellNumber 
+
+-- | Set partitions of size @k@ are counted by the Stirling numbers of second kind
+countSetPartitionsWithKParts 
+  :: Int    -- ^ @k@ = number of parts
+  -> Int    -- ^ @n@ = size of the set
+  -> Integer
+countSetPartitionsWithKParts k n = stirling2nd n k
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Partitions/Skew.hs b/Math/Combinat/Partitions/Skew.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Partitions/Skew.hs
@@ -0,0 +1,135 @@
+
+-- | Skew partitions.
+--
+-- Skew partitions are the difference of two integer partitions, denoted by @lambda/mu@.
+--
+-- For example
+--
+-- > mkSkewPartition (Partition [9,7,3,2,2,1] , Partition [5,3,2,1])
+--
+-- creates the skew partition @(9,7,3,2,2,1) / (5,3,2,1)@, which looks like
+--
+-- <<svg/skew3.svg>>
+--
+
+{-# LANGUAGE CPP, BangPatterns #-}
+module Math.Combinat.Partitions.Skew where
+
+--------------------------------------------------------------------------------
+
+import Data.List
+
+import Math.Combinat.Classes
+import Math.Combinat.Partitions.Integer
+import Math.Combinat.ASCII
+
+--------------------------------------------------------------------------------
+-- * Basics
+
+-- | A skew partition @lambda/mu@ is internally represented by the list @[ (mu_i , lambda_i-mu_i) | i<-[1..n] ]@
+newtype SkewPartition = SkewPartition [(Int,Int)] deriving (Eq,Ord,Show)
+
+-- | @mkSkewPartition (lambda,mu)@ creates the skew partition @lambda/mu@.
+-- Throws an error if @mu@ is not a sub-partition of @lambda@.
+mkSkewPartition :: (Partition,Partition) -> SkewPartition
+mkSkewPartition ( lam@(Partition bs) , mu@(Partition as)) = if mu `isSubPartitionOf` lam 
+  then SkewPartition $ zipWith (\b a -> (a,b-a)) bs (as ++ repeat 0)
+  else error "mkSkewPartition: mu should be a subpartition of lambda!" 
+
+-- | Returns 'Nothing' if @mu@ is not a sub-partition of @lambda@.
+safeSkewPartition :: (Partition,Partition) -> Maybe SkewPartition
+safeSkewPartition ( lam@(Partition bs) , mu@(Partition as)) = if mu `isSubPartitionOf` lam 
+  then Just $ SkewPartition $ zipWith (\b a -> (a,b-a)) bs (as ++ repeat 0)
+  else Nothing
+
+-- | The weight of a skew partition is the weight of the outer partition minus the
+-- the weight of the inner partition (that is, the number of boxes present).
+skewPartitionWeight :: SkewPartition -> Int
+skewPartitionWeight (SkewPartition abs) = foldl' (+) 0 (map snd abs)
+
+instance HasWeight SkewPartition where
+  weight = skewPartitionWeight
+
+-- | This function \"cuts off\" the \"uninteresting parts\" of a skew partition
+normalizeSkewPartition :: SkewPartition -> SkewPartition
+normalizeSkewPartition (SkewPartition abs) = SkewPartition abs' where
+  (as,bs) = unzip abs
+  a0 = minimum as
+  k  = length (takeWhile (==0) bs)
+  abs' = zip [ a-a0 | a <- drop k as ] (drop k bs)
+   
+-- | Returns the outer and inner partition of a skew partition, respectively:
+--
+-- > mkSkewPartition . fromSkewPartition == id
+--
+fromSkewPartition :: SkewPartition -> (Partition,Partition)
+fromSkewPartition (SkewPartition list) = (toPartition (zipWith (+) as bs) , toPartition (filter (>0) as)) where
+  (as,bs) = unzip list
+
+-- | The @lambda@ part of @lambda/mu@
+outerPartition :: SkewPartition -> Partition  
+outerPartition = fst . fromSkewPartition 
+
+-- | The @mu@ part of @lambda/mu@
+innerPartition :: SkewPartition -> Partition  
+innerPartition = snd . fromSkewPartition 
+
+-- | The dual skew partition (that is, the mirror image to the main diagonal)
+dualSkewPartition :: SkewPartition -> SkewPartition
+dualSkewPartition = mkSkewPartition . f . fromSkewPartition where
+  f (lam,mu) = (dualPartition lam, dualPartition mu)
+
+instance HasDuality SkewPartition where
+  dual = dualSkewPartition
+
+--------------------------------------------------------------------------------
+-- * Listing skew partitions
+
+-- | Lists all skew partitions with the given outer shape and given (skew) weight
+skewPartitionsWithOuterShape :: Partition -> Int -> [SkewPartition]
+skewPartitionsWithOuterShape outer skewWeight 
+  | innerWeight < 0 || innerWeight > outerWeight = []
+  | otherwise = [ mkSkewPartition (outer,inner) | inner <- subPartitions innerWeight outer ]
+  where
+    outerWeight = weight outer
+    innerWeight = outerWeight - skewWeight 
+
+-- | Lists all skew partitions with the given outer shape and any (skew) weight
+allSkewPartitionsWithOuterShape :: Partition -> [SkewPartition]
+allSkewPartitionsWithOuterShape outer 
+  = concat [ skewPartitionsWithOuterShape outer w | w<-[0..outerWeight] ]
+  where
+    outerWeight = weight outer
+
+-- | Lists all skew partitions with the given inner shape and given (skew) weight
+skewPartitionsWithInnerShape :: Partition -> Int -> [SkewPartition]
+skewPartitionsWithInnerShape inner skewWeight 
+  | innerWeight > outerWeight = []
+  | otherwise = [ mkSkewPartition (outer,inner) | outer <- superPartitions outerWeight inner ]
+  where
+    outerWeight = innerWeight + skewWeight 
+    innerWeight = weight inner 
+
+--------------------------------------------------------------------------------
+-- * ASCII
+
+asciiSkewFerrersDiagram :: SkewPartition -> ASCII
+asciiSkewFerrersDiagram = asciiSkewFerrersDiagram' ('@','.') EnglishNotation
+
+asciiSkewFerrersDiagram' 
+  :: (Char,Char)       
+  -> PartitionConvention -- Orientation
+  -> SkewPartition 
+  -> ASCII
+asciiSkewFerrersDiagram' (outer,inner) orient (SkewPartition abs) = asciiFromLines stuff where
+  stuff = case orient of
+    EnglishNotation    -> ls
+    EnglishNotationCCW -> reverse (transpose ls)
+    FrenchNotation     -> reverse ls
+  ls = [ replicate a inner ++ replicate b outer | (a,b) <- abs ]
+
+instance DrawASCII SkewPartition where
+  ascii = asciiSkewFerrersDiagram     
+
+--------------------------------------------------------------------------------
+
diff --git a/Math/Combinat/Partitions/Vector.hs b/Math/Combinat/Partitions/Vector.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Partitions/Vector.hs
@@ -0,0 +1,82 @@
+
+-- | Vector partitions. See:
+--
+--  * Donald E. Knuth: The Art of Computer Programming, vol 4, pre-fascicle 3B.
+--
+
+{-# LANGUAGE BangPatterns #-}
+module Math.Combinat.Partitions.Vector where
+
+--------------------------------------------------------------------------------
+
+import Data.Array.Unboxed
+import Data.List
+
+--------------------------------------------------------------------------------
+
+-- | Integer vectors. The indexing starts from 1.
+type IntVector = UArray Int Int
+
+-- | Vector partitions. Basically a synonym for 'fasc3B_algorithm_M'.
+vectorPartitions :: IntVector -> [[IntVector]]
+vectorPartitions = fasc3B_algorithm_M . elems
+
+_vectorPartitions :: [Int] -> [[[Int]]]
+_vectorPartitions = map (map elems) . fasc3B_algorithm_M
+
+-- | Generates all vector partitions 
+--   (\"algorithm M\" in Knuth). 
+--   The order is decreasing lexicographic.  
+fasc3B_algorithm_M :: [Int] -> [[IntVector]] 
+{- note to self: Knuth's descriptions of algorithms are still totally unreadable -}
+fasc3B_algorithm_M xs = worker [start] where
+
+  -- n = sum xs
+  m = length xs
+
+  start = [ (j,x,x) | (j,x) <- zip [1..] xs ]  
+  
+  worker stack@(last:_) = 
+    case decrease stack' of
+      Nothing -> [visited]
+      Just stack'' -> visited : worker stack''
+    where
+      stack'  = subtract_rec stack
+      visited = map to_vector stack'
+      
+  decrease (last:rest) = 
+    case span (\(_,_,v) -> v==0) (reverse last) of
+      ( _ , [(_,_,1)] ) -> case rest of
+        [] -> Nothing
+        _  -> decrease rest
+      ( second , (c,u,v):first ) -> Just (modified:rest) where 
+        modified =   
+          reverse first ++ 
+          (c,u,v-1) :  
+          [ (c,u,u) | (c,u,_) <- reverse second ] 
+      _ -> error "fasc3B_algorithm_M: should not happen"
+        
+  to_vector cuvs = 
+    accumArray (flip const) 0 (1,m)
+      [ (c,v) | (c,_,v) <- cuvs ] 
+
+  subtract_rec all@(last:_) = 
+    case subtract last of 
+      []  -> all
+      new -> subtract_rec (new:all) 
+
+  subtract [] = []
+  subtract full@((c,u,v):rest) = 
+    if w >= v 
+      then (c,w,v) : subtract   rest
+      else           subtract_b full
+    where w = u - v
+    
+  subtract_b [] = []
+  subtract_b ((c,u,v):rest) = 
+    if w /= 0 
+      then (c,w,w) : subtract_b rest
+      else           subtract_b rest
+    where w = u - v
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Permutations.hs b/Math/Combinat/Permutations.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Permutations.hs
@@ -0,0 +1,862 @@
+
+-- | Permutations. 
+--
+-- See eg.:
+-- Donald E. Knuth: The Art of Computer Programming, vol 4, pre-fascicle 2B.
+--
+-- WARNING: As of version 0.2.8.0, I changed the convention of how permutations
+-- are represented internally. Also now they act on the /right/ by default!
+--
+
+{-# LANGUAGE CPP, BangPatterns, ScopedTypeVariables, GeneralizedNewtypeDeriving, FlexibleContexts #-}
+module Math.Combinat.Permutations 
+  ( -- * The Permutation type
+    Permutation (..)
+  , fromPermutation
+  , permutationArray
+  , permutationUArray
+  , uarrayToPermutationUnsafe
+  , isPermutation
+  , maybePermutation
+  , toPermutation
+  , toPermutationUnsafe
+  , permutationSize
+    -- * Disjoint cycles
+  , DisjointCycles (..)
+  , fromDisjointCycles
+  , disjointCyclesUnsafe
+  , permutationToDisjointCycles
+  , disjointCyclesToPermutation
+  , numberOfCycles
+    -- * Queries
+  , isIdentityPermutation
+  , isReversePermutation
+  , isEvenPermutation
+  , isOddPermutation
+  , signOfPermutation  
+  , signValueOfPermutation  
+  , module Math.Combinat.Sign   --  , Sign(..)
+  , isCyclicPermutation
+    -- * Some concrete permutations
+  , transposition
+  , transpositions
+  , adjacentTransposition
+  , adjacentTranspositions
+  , cycleLeft
+  , cycleRight
+  , reversePermutation
+    -- * Inversions
+  , inversions
+  , numberOfInversions
+  , numberOfInversionsNaive
+  , numberOfInversionsMerge
+  , bubbleSort2
+  , bubbleSort
+    -- * Permutation groups
+  , identity
+  , inverse
+  , multiply
+  , multiplyMany 
+  , multiplyMany'
+    -- * Action of the permutation group
+  , permute 
+  , permuteList
+  , permuteLeft , permuteRight
+  , permuteLeftList , permuteRightList
+    -- * ASCII drawing
+  , asciiPermutation
+  , asciiDisjointCycles
+  , twoLineNotation 
+  , inverseTwoLineNotation
+  , genericTwoLineNotation
+    -- * List of permutations
+  , permutations
+  , _permutations
+  , permutationsNaive
+  , _permutationsNaive
+  , countPermutations
+    -- * Random permutations
+  , randomPermutation
+  , _randomPermutation
+  , randomCyclicPermutation
+  , _randomCyclicPermutation
+  , randomPermutationDurstenfeld
+  , randomCyclicPermutationSattolo
+    -- * Multisets
+  , permuteMultiset
+  , countPermuteMultiset
+  , fasc2B_algorithm_L
+  ) 
+  where
+
+--------------------------------------------------------------------------------
+
+import Control.Monad
+import Control.Monad.ST
+
+import Data.List hiding ( permutations )
+import Data.Ord ( comparing )
+
+import Data.Array (Array)
+import Data.Array.ST
+import Data.Array.Unboxed
+import Data.Array.IArray
+import Data.Array.MArray
+import Data.Array.Unsafe
+
+import Math.Combinat.ASCII
+import Math.Combinat.Classes
+import Math.Combinat.Helper
+import Math.Combinat.Sign
+import Math.Combinat.Numbers ( factorial , binomial )
+
+import System.Random
+
+--------------------------------------------------------------------------------
+-- * Types
+
+-- | A permutation. Internally it is an (unboxed) array of the integers @[1..n]@, with 
+-- indexing range also being @(1,n)@. 
+--
+-- If this array of integers is @[p1,p2,...,pn]@, then in two-line 
+-- notations, that represents the permutation
+--
+-- > ( 1  2  3  ... n  )
+-- > ( p1 p2 p3 ... pn )
+--
+-- That is, it is the permutation @sigma@ whose (right) action on the set @[1..n]@ is
+--
+-- > sigma(1) = p1
+-- > sigma(2) = p2 
+-- > ...
+--
+-- (NOTE: this changed at version 0.2.8.0!)
+--
+newtype Permutation = Permutation (UArray Int Int) deriving (Eq,Ord) -- ,Show,Read)
+
+instance Show Permutation where
+  showsPrec d (Permutation arr) 
+    = showParen (d > 10)  
+    $ showString "toPermutation " . showsPrec 11 (elems arr)       -- app_prec = 10
+
+instance Read Permutation where
+  readsPrec d r = readParen (d > 10) fun r where
+    fun r = [ (toPermutation p,t) 
+            | ("toPermutation",s) <- lex r
+            , (p,t) <- readsPrec 11 s                              -- app_prec = 10
+            ] 
+
+instance DrawASCII Permutation where
+  ascii = asciiPermutation
+
+-- | Disjoint cycle notation for permutations. Internally it is @[[Int]]@.
+--
+-- The cycles are to be understood as follows: a cycle @[c1,c2,...,ck]@ means
+-- the permutation
+--
+-- > ( c1 c2 c3 ... ck )
+-- > ( c2 c3 c4 ... c1 )
+--
+newtype DisjointCycles = DisjointCycles [[Int]] deriving (Eq,Ord,Show,Read)
+
+fromPermutation :: Permutation -> [Int]
+fromPermutation (Permutation ar) = elems ar
+
+permutationUArray :: Permutation -> UArray Int Int
+permutationUArray (Permutation ar) = ar
+
+-- | Note: this is slower than 'permutationUArray'
+permutationArray :: Permutation -> Array Int Int
+permutationArray (Permutation ar) = listArray (1,n) (elems ar) where
+  (1,n) = bounds ar
+
+-- | Assumes that the input is a permutation of the numbers @[1..n]@.
+toPermutationUnsafe :: [Int] -> Permutation
+toPermutationUnsafe xs = Permutation perm where
+  n    = length xs
+  perm = listArray (1,n) xs
+
+-- | Note: Indexing starts from 1.
+uarrayToPermutationUnsafe :: UArray Int Int -> Permutation
+uarrayToPermutationUnsafe = Permutation
+
+-- | Checks whether the input is a permutation of the numbers @[1..n]@.
+isPermutation :: [Int] -> Bool
+isPermutation xs = (ar!0 == 0) && and [ ar!j == 1 | j<-[1..n] ] where
+  n = length xs
+  -- the zero index is an unidiomatic hack
+  ar = (accumArray (+) 0 (0,n) $ map f xs) :: UArray Int Int
+  f :: Int -> (Int,Int)
+  f !j = if j<1 || j>n then (0,1) else (j,1)
+
+-- | Checks whether the input is a permutation of the numbers @[1..n]@.
+maybePermutation :: [Int] -> Maybe Permutation
+maybePermutation input = runST action where
+  n = length input
+  action :: forall s. ST s (Maybe Permutation)
+  action = do
+    ar <- newArray (1,n) 0 :: ST s (STUArray s Int Int)
+    let go []     = return $ Just (Permutation $ listArray (1,n) input)
+        go (j:js) = if j<1 || j>n 
+          then return Nothing
+          else do
+            z <- readArray ar j
+            writeArray ar j (z+1)
+            if z==0 then go js
+                    else return Nothing               
+    go input
+    
+-- | Checks the input.
+toPermutation :: [Int] -> Permutation
+toPermutation xs = case maybePermutation xs of
+  Just p  -> p
+  Nothing -> error "toPermutation: not a permutation"
+
+-- | Returns @n@, where the input is a permutation of the numbers @[1..n]@
+permutationSize :: Permutation -> Int
+permutationSize (Permutation ar) = snd $ bounds ar
+
+instance HasWidth Permutation where
+  width = permutationSize
+
+-- | Checks whether the permutation is the identity permutation
+isIdentityPermutation :: Permutation -> Bool
+isIdentityPermutation (Permutation ar) = (elems ar == [1..n]) where
+  (1,n) = bounds ar
+
+--------------------------------------------------------------------------------
+-- * ASCII
+
+-- | Synonym for 'twoLineNotation'
+asciiPermutation :: Permutation -> ASCII
+asciiPermutation = twoLineNotation 
+
+asciiDisjointCycles :: DisjointCycles -> ASCII
+asciiDisjointCycles (DisjointCycles cycles) = final where
+  final = hCatWith VTop (HSepSpaces 1) boxes 
+  boxes = [ genericTwoLineNotation (f cyc) | cyc <- cycles ]
+  f cyc = pairs (cyc ++ [head cyc])
+
+-- | The standard two-line notation, moving the element indexed by the top row into
+-- the place indexed by the corresponding element in the bottom row.
+twoLineNotation :: Permutation -> ASCII
+twoLineNotation (Permutation arr) = genericTwoLineNotation $ zip [1..] (elems arr)
+
+-- | The inverse two-line notation, where the it\'s the bottom line 
+-- which is in standard order. The columns of this are a permutation
+-- of the columns 'twoLineNotation'.
+--
+-- Remark: the top row of @inverseTwoLineNotation perm@ is the same 
+-- as the bottom row of @twoLineNotation (inverse perm)@.
+--
+inverseTwoLineNotation :: Permutation -> ASCII
+inverseTwoLineNotation (Permutation arr) =
+  genericTwoLineNotation $ sortBy (comparing snd) $ zip [1..] (elems arr) 
+
+-- | Two-line notation for any set of numbers
+genericTwoLineNotation :: [(Int,Int)] -> ASCII
+genericTwoLineNotation xys = asciiFromLines [ topLine, botLine ] where
+  topLine = "( " ++ intercalate " " us ++ " )"
+  botLine = "( " ++ intercalate " " vs ++ " )"
+  pairs   = [ (show x, show y) | (x,y) <- xys ]
+  (us,vs) = unzip (map f pairs) 
+  f (s,t) = (s',t') where
+    a = length s 
+    b = length t
+    c = max a b
+    s' = replicate (c-a) ' ' ++ s
+    t' = replicate (c-b) ' ' ++ t
+
+--------------------------------------------------------------------------------
+-- * Disjoint cycles
+
+fromDisjointCycles :: DisjointCycles -> [[Int]]
+fromDisjointCycles (DisjointCycles cycles) = cycles
+
+disjointCyclesUnsafe :: [[Int]] -> DisjointCycles 
+disjointCyclesUnsafe = DisjointCycles
+
+instance DrawASCII DisjointCycles where
+  ascii = asciiDisjointCycles
+
+instance HasNumberOfCycles DisjointCycles where
+  numberOfCycles (DisjointCycles cycles) = length cycles
+
+instance HasNumberOfCycles Permutation where
+  numberOfCycles = numberOfCycles . permutationToDisjointCycles
+  
+disjointCyclesToPermutation :: Int -> DisjointCycles -> Permutation
+disjointCyclesToPermutation n (DisjointCycles cycles) = Permutation perm where
+
+  pairs :: [Int] -> [(Int,Int)]
+  pairs xs@(x:_) = worker (xs++[x]) where
+    worker (x:xs@(y:_)) = (x,y):worker xs
+    worker _ = [] 
+  pairs [] = error "disjointCyclesToPermutation: empty cycle"
+
+  perm = runSTUArray $ do
+    ar <- newArray_ (1,n) :: ST s (STUArray s Int Int)
+    forM_ [1..n] $ \i -> writeArray ar i i 
+    forM_ cycles $ \cyc -> forM_ (pairs cyc) $ \(i,j) -> writeArray ar i j
+    return ar -- freeze ar
+  
+-- | Convert to disjoint cycle notation.
+--
+-- This is compatible with Maple's @convert(perm,\'disjcyc\')@ 
+-- and also with Mathematica's @PermutationCycles[perm]@
+--
+-- Note however, that for example Mathematica uses the 
+-- /top row/ to represent a permutation, while we use the
+-- /bottom row/ - thus even though this function looks
+-- identical, the /meaning/ of both the input and output
+-- is different!
+-- 
+permutationToDisjointCycles :: Permutation -> DisjointCycles
+permutationToDisjointCycles (Permutation perm) = res where
+
+  (1,n) = bounds perm
+
+  -- we don't want trivial cycles
+  f :: [Int] -> Bool
+  f [_] = False
+  f _ = True
+  
+  res = runST $ do
+    tag <- newArray (1,n) False 
+    cycles <- unfoldM (step tag) 1 
+    return (DisjointCycles $ filter f cycles)
+    
+  step :: STUArray s Int Bool -> Int -> ST s ([Int],Maybe Int)
+  step tag k = do
+    cyc <- worker tag k k [k] 
+    m <- next tag (k+1)
+    return (reverse cyc, m) 
+    
+  next :: STUArray s Int Bool -> Int -> ST s (Maybe Int)
+  next tag k = if k > n
+    then return Nothing
+    else readArray tag k >>= \b -> if b 
+      then next tag (k+1)  
+      else return (Just k)
+       
+  worker :: STUArray s Int Bool -> Int -> Int -> [Int] -> ST s [Int]
+  worker tag k l cyc = do
+    writeArray tag l True
+    let m = perm ! l
+    if m == k 
+      then return cyc
+      else worker tag k m (m:cyc)      
+
+isEvenPermutation :: Permutation -> Bool
+isEvenPermutation (Permutation perm) = res where
+
+  (1,n) = bounds perm
+  res = runST $ do
+    tag <- newArray (1,n) False 
+    cycles <- unfoldM (step tag) 1 
+    return $ even (sum cycles)
+    
+  step :: STUArray s Int Bool -> Int -> ST s (Int,Maybe Int)
+  step tag k = do
+    cyclen <- worker tag k k 0
+    m <- next tag (k+1)
+    return (cyclen,m)
+    
+  next :: STUArray s Int Bool -> Int -> ST s (Maybe Int)
+  next tag k = if k > n
+    then return Nothing
+    else readArray tag k >>= \b -> if b 
+      then next tag (k+1)  
+      else return (Just k)
+      
+  worker :: STUArray s Int Bool -> Int -> Int -> Int -> ST s Int
+  worker tag k l cyclen = do
+    writeArray tag l True
+    let m = perm ! l
+    if m == k 
+      then return cyclen
+      else worker tag k m (1+cyclen)      
+
+isOddPermutation :: Permutation -> Bool
+isOddPermutation = not . isEvenPermutation
+
+signOfPermutation :: Permutation -> Sign
+signOfPermutation perm = case isEvenPermutation perm of
+  True  -> Plus
+  False -> Minus
+
+-- | Plus 1 or minus 1.
+{-# SPECIALIZE signValueOfPermutation :: Permutation -> Int     #-}
+{-# SPECIALIZE signValueOfPermutation :: Permutation -> Integer #-}
+signValueOfPermutation :: Num a => Permutation -> a
+signValueOfPermutation = signValue . signOfPermutation
+  
+isCyclicPermutation :: Permutation -> Bool
+isCyclicPermutation perm = 
+  case cycles of
+    []    -> True
+    [cyc] -> (length cyc == n)
+    _     -> False
+  where 
+    n = permutationSize perm
+    DisjointCycles cycles = permutationToDisjointCycles perm
+
+--------------------------------------------------------------------------------
+-- * Inversions
+
+-- | An /inversion/ of a permutation @sigma@ is a pair @(i,j)@ such that
+-- @i<j@ and @sigma(i) > sigma(j)@.
+--
+-- This functions returns the inversion of a permutation.
+--
+inversions :: Permutation -> [(Int,Int)]
+inversions (Permutation arr) = list where
+  (_,n) = bounds arr
+  list = [ (i,j) | i<-[1..n-1], j<-[i+1..n], arr!i > arr!j ]
+
+-- | Returns the number of inversions:
+--
+-- > numberOfInversions perm = length (inversions perm)
+--
+-- Synonym for 'numberOfInversionsMerge'
+--
+numberOfInversions :: Permutation -> Int
+numberOfInversions = numberOfInversionsMerge
+
+-- | Returns the number of inversions, using the merge-sort algorithm.
+-- This should be @O(n*log(n))@
+--
+numberOfInversionsMerge :: Permutation -> Int
+numberOfInversionsMerge (Permutation arr) = fst (sortCnt n $ elems arr) where
+  (_,n) = bounds arr
+                                        
+  -- | First argument is length of the list.
+  -- Returns also the inversion count.
+  sortCnt :: Int -> [Int] -> (Int,[Int])
+  sortCnt 0 _     = (0,[] )
+  sortCnt 1 [x]   = (0,[x])
+  sortCnt 2 [x,y] = if x>y then (1,[y,x]) else (0,[x,y])
+  sortCnt n xs    = mergeCnt (sortCnt k us) (sortCnt l vs) where
+    k = div n 2
+    l = n - k 
+    (us,vs) = splitAt k xs
+
+  mergeCnt :: (Int,[Int]) -> (Int,[Int]) -> (Int,[Int])
+  mergeCnt (!c,us) (!d,vs) = (c+d+e,ws) where
+
+    (e,ws) = go 0 us vs 
+
+    go !k xs [] = ( k*length xs , xs )
+    go _  [] ys = ( 0 , ys)
+    go !k xxs@(x:xs) yys@(y:ys) = if x < y
+      then let (a,zs) = go  k     xs yys in (a+k, x:zs)
+      else let (a,zs) = go (k+1) xxs  ys in (a  , y:zs)
+
+-- | Returns the number of inversions, using the definition, thus it's @O(n^2)@.
+--
+numberOfInversionsNaive :: Permutation -> Int
+numberOfInversionsNaive (Permutation arr) = length list where
+  (_,n) = bounds arr
+  list = [ (0::Int) | i<-[1..n-1], j<-[i+1..n], arr!i > arr!j ]
+
+-- | Bubble sorts breaks a permutation into the product of adjacent transpositions:
+--
+-- > multiplyMany' n (map (transposition n) $ bubbleSort2 perm) == perm
+--
+-- Note that while this is not unique, the number of transpositions 
+-- equals the number of inversions.
+--
+bubbleSort2 :: Permutation -> [(Int,Int)]
+bubbleSort2 = map f . bubbleSort where f i = (i,i+1)
+
+-- | Another version of bubble sort. An entry @i@ in the return sequence means
+-- the transposition @(i,i+1)@:
+--
+-- > multiplyMany' n (map (adjacentTransposition n) $ bubbleSort perm) == perm
+--
+bubbleSort :: Permutation -> [Int]
+bubbleSort perm@(Permutation tgt) = runST action where
+  (_,n)           = bounds tgt
+
+  action :: forall s. ST s [Int]
+  action = do
+    fwd <- newArray_ (1,n) :: ST s (STUArray s Int Int)
+    inv <- newArray_ (1,n) :: ST s (STUArray s Int Int)
+    forM_ [1..n] $ \i -> writeArray fwd i i
+    forM_ [1..n] $ \i -> writeArray inv i i
+
+    list <- forM [1..n] $ \x -> do
+
+      let k = tgt ! x        -- we take the number which will be at the @x@-th position at the end
+      i <- readArray inv k   -- number @k@ is at the moment at position @i@
+      let j = x              -- but the final place is at @x@      
+
+      let swaps = move i j
+      forM_ swaps $ \y -> do
+
+        a <- readArray fwd  y
+        b <- readArray fwd (y+1)
+        writeArray fwd (y+1) a
+        writeArray fwd  y    b
+
+        u <- readArray inv a
+        v <- readArray inv b
+        writeArray inv b u
+        writeArray inv a v
+
+      return swaps
+  
+    return (concat list)
+
+  move :: Int -> Int -> [Int]
+  move !i !j
+    | j == i  = []
+    | j >  i  = [i..j-1]
+    | j <  i  = [i-1,i-2..j]
+
+--------------------------------------------------------------------------------
+-- * Some concrete permutations
+
+-- | The permutation @[n,n-1,n-2,...,2,1]@. Note that it is the inverse of itself.
+reversePermutation :: Int -> Permutation
+reversePermutation n = Permutation $ listArray (1,n) [n,n-1..1]
+
+-- | Checks whether the permutation is the reverse permutation @[n,n-1,n-2,...,2,1].
+isReversePermutation :: Permutation -> Bool
+isReversePermutation (Permutation arr) = elems arr == [n,n-1..1] where (1,n) = bounds arr
+
+-- | A transposition (swapping two elements). 
+--
+-- @transposition n (i,j)@ is the permutation of size @n@ which swaps @i@\'th and @j@'th elements.
+--
+transposition :: Int -> (Int,Int) -> Permutation
+transposition n (i,j) = 
+  if i>=1 && j>=1 && i<=n && j<=n 
+    then Permutation $ listArray (1,n) [ f k | k<-[1..n] ]
+    else error "transposition: index out of range"
+  where
+    f k | k == i    = j
+        | k == j    = i
+        | otherwise = k
+
+-- | Product of transpositions.
+--
+-- > transpositions n list == multiplyMany [ transposition n pair | pair <- list ]
+--
+transpositions :: Int -> [(Int,Int)] -> Permutation
+transpositions n list = Permutation (runSTUArray action) where
+
+  action :: ST s (STUArray s Int Int)
+  action = do
+    arr <- newArray_ (1,n) 
+    forM_ [1..n] $ \i -> writeArray arr i i    
+    let doSwap (i,j) = do
+          u <- readArray arr i
+          v <- readArray arr j
+          writeArray arr i v
+          writeArray arr j u          
+    mapM_ doSwap list
+    return arr
+
+-- | @adjacentTransposition n k@ swaps the elements @k@ and @(k+1)@.
+adjacentTransposition :: Int -> Int -> Permutation
+adjacentTransposition n k 
+  | k>0 && k<n  = transposition n (k,k+1)
+  | otherwise   = error "adjacentTransposition: index out of range"
+
+-- | Product of adjacent transpositions.
+--
+-- > adjacentTranspositions n list == multiplyMany [ adjacentTransposition n idx | idx <- list ]
+--
+adjacentTranspositions :: Int -> [Int] -> Permutation
+adjacentTranspositions n list = Permutation (runSTUArray action) where
+
+  action :: ST s (STUArray s Int Int)
+  action = do
+    arr <- newArray_ (1,n) 
+    forM_ [1..n] $ \i -> writeArray arr i i    
+    let doSwap i
+          | i<0 || i>=n  = error "adjacentTranspositions: index out of range"
+          | otherwise    = do
+              u <- readArray arr  i
+              v <- readArray arr (i+1)
+              writeArray arr  i    v
+              writeArray arr (i+1) u          
+    mapM_ doSwap list
+    return arr
+
+-- | The permutation which cycles a list left by one step:
+-- 
+-- > permuteList (cycleLeft 5) "abcde" == "bcdea"
+--
+-- Or in two-line notation:
+--
+-- > ( 1 2 3 4 5 )
+-- > ( 2 3 4 5 1 )
+-- 
+cycleLeft :: Int -> Permutation
+cycleLeft n = Permutation $ listArray (1,n) $ [2..n] ++ [1]
+
+-- | The permutation which cycles a list right by one step:
+-- 
+-- > permuteList (cycleRight 5) "abcde" == "eabcd"
+--
+-- Or in two-line notation:
+--
+-- > ( 1 2 3 4 5 )
+-- > ( 5 1 2 3 4 )
+-- 
+cycleRight :: Int -> Permutation
+cycleRight n = Permutation $ listArray (1,n) $ n : [1..n-1]
+   
+--------------------------------------------------------------------------------
+-- * Permutation groups
+
+-- | Multiplies two permutations together: @p `multiply` q@
+-- means the permutation when we first apply @p@, and then @q@
+-- (that is, the natural action is the /right/ action)
+--
+-- See also 'permute' for our conventions.  
+--
+multiply :: Permutation -> Permutation -> Permutation
+multiply pi1@(Permutation perm1) pi2@(Permutation perm2) = 
+  if (n==m) 
+    then Permutation result
+    else error "multiply: permutations of different sets"  
+  where
+    (_,n) = bounds perm1
+    (_,m) = bounds perm2    
+    result = permute pi2 perm1
+  
+infixr 7 `multiply`  
+
+-- | The inverse permutation.
+inverse :: Permutation -> Permutation    
+inverse (Permutation perm1) = Permutation result
+  where
+    result = array (1,n) $ map swap $ assocs perm1
+    (_,n) = bounds perm1
+    
+-- | The identity (or trivial) permutation.
+identity :: Int -> Permutation 
+identity n = Permutation $ listArray (1,n) [1..n]
+
+-- | Multiply together a /non-empty/ list of permutations (the reason for requiring the list to
+-- be non-empty is that we don\'t know the size of the result). See also 'multiplyMany''.
+multiplyMany :: [Permutation] -> Permutation 
+multiplyMany [] = error "multiplyMany: empty list, we don't know size of the result"
+multiplyMany ps = foldl1' multiply ps    
+
+-- | Multiply together a (possibly empty) list of permutations, all of which has size @n@
+multiplyMany' :: Int -> [Permutation] -> Permutation 
+multiplyMany' n []       = identity n
+multiplyMany' n ps@(p:_) = if n == permutationSize p 
+  then foldl1' multiply ps    
+  else error "multiplyMany': incompatible permutation size(s)"
+
+--------------------------------------------------------------------------------
+-- * Action of the permutation group
+
+-- | /Right/ action of a permutation on a set. If our permutation is 
+-- encoded with the sequence @[p1,p2,...,pn]@, then in the
+-- two-line notation we have
+--
+-- > ( 1  2  3  ... n  )
+-- > ( p1 p2 p3 ... pn )
+--
+-- We adopt the convention that permutations act /on the right/ 
+-- (as in Knuth):
+--
+-- > permute pi2 (permute pi1 set) == permute (pi1 `multiply` pi2) set
+--
+-- Synonym to 'permuteRight'
+--
+{-# SPECIALIZE permute :: Permutation -> Array  Int b   -> Array  Int b   #-}
+{-# SPECIALIZE permute :: Permutation -> UArray Int Int -> UArray Int Int #-}
+permute :: IArray arr b => Permutation -> arr Int b -> arr Int b    
+permute = permuteRight
+
+-- | Right action on lists. Synonym to 'permuteListRight'
+--
+permuteList :: Permutation -> [a] -> [a]
+permuteList = permuteRightList
+    
+-- | The right (standard) action of permutations on sets. 
+-- 
+-- > permuteRight pi2 (permuteRight pi1 set) == permuteRight (pi1 `multiply` pi2) set
+--   
+-- The second argument should be an array with bounds @(1,n)@.
+-- The function checks the array bounds.
+--
+{-# SPECIALIZE permuteRight :: Permutation -> Array  Int b   -> Array  Int b   #-}
+{-# SPECIALIZE permuteRight :: Permutation -> UArray Int Int -> UArray Int Int #-}
+permuteRight :: IArray arr b => Permutation -> arr Int b -> arr Int b    
+permuteRight pi@(Permutation perm) ar = 
+  if (a==1) && (b==n) 
+    then listArray (1,n) [ ar!(perm!i) | i <- [1..n] ] 
+    else error "permuteRight: array bounds do not match"
+  where
+    (_,n) = bounds perm  
+    (a,b) = bounds ar   
+
+-- | The right (standard) action on a list. The list should be of length @n@.
+--
+-- > fromPermutation perm == permuteRightList perm [1..n]
+-- 
+permuteRightList :: forall a . Permutation -> [a] -> [a]    
+permuteRightList perm xs = elems $ permuteRight perm $ arr where
+  arr = listArray (1,n) xs :: Array Int a
+  n   = permutationSize perm
+
+-- | The left (opposite) action of the permutation group.
+--
+-- > permuteLeft pi2 (permuteLeft pi1 set) == permuteLeft (pi2 `multiply` pi1) set
+--
+-- It is related to 'permuteLeft' via:
+--
+-- > permuteLeft  pi arr == permuteRight (inverse pi) arr
+-- > permuteRight pi arr == permuteLeft  (inverse pi) arr
+--
+{-# SPECIALIZE permuteLeft :: Permutation -> Array  Int b   -> Array  Int b   #-}
+{-# SPECIALIZE permuteLeft :: Permutation -> UArray Int Int -> UArray Int Int #-}
+permuteLeft :: IArray arr b => Permutation -> arr Int b -> arr Int b    
+permuteLeft pi@(Permutation perm) ar =    
+  -- permuteRight (inverse pi) ar
+  if (a==1) && (b==n) 
+    then array (1,n) [ ( perm!i , ar!i ) | i <- [1..n] ] 
+    else error "permuteLeft: array bounds do not match"
+  where
+    (_,n) = bounds perm  
+    (a,b) = bounds ar   
+
+-- | The left (opposite) action on a list. The list should be of length @n@.
+--
+-- > permuteLeftList perm set == permuteList (inverse perm) set
+-- > fromPermutation (inverse perm) == permuteLeftList perm [1..n]
+--
+permuteLeftList :: forall a. Permutation -> [a] -> [a]    
+permuteLeftList perm xs = elems $ permuteLeft perm $ arr where
+  arr = listArray (1,n) xs :: Array Int a
+  n   = permutationSize perm
+
+--------------------------------------------------------------------------------
+-- * Permutations of distinct elements
+
+-- | A synonym for 'permutationsNaive'
+permutations :: Int -> [Permutation]
+permutations = permutationsNaive
+
+_permutations :: Int -> [[Int]]
+_permutations = _permutationsNaive
+
+-- | All permutations of @[1..n]@ in lexicographic order, naive algorithm.
+permutationsNaive :: Int -> [Permutation]
+permutationsNaive n = map toPermutationUnsafe $ _permutations n 
+
+_permutationsNaive :: Int -> [[Int]]  
+_permutationsNaive 0 = [[]]
+_permutationsNaive 1 = [[1]]
+_permutationsNaive n = helper [1..n] where
+  helper [] = [[]]
+  helper xs = [ i : ys | i <- xs , ys <- helper (xs `minus` i) ]
+  minus [] _ = []
+  minus (x:xs) i = if x < i then x : minus xs i else xs
+          
+-- | # = n!
+countPermutations :: Int -> Integer
+countPermutations = factorial
+
+--------------------------------------------------------------------------------
+-- * Random permutations
+
+-- | A synonym for 'randomPermutationDurstenfeld'.
+randomPermutation :: RandomGen g => Int -> g -> (Permutation,g)
+randomPermutation = randomPermutationDurstenfeld
+
+_randomPermutation :: RandomGen g => Int -> g -> ([Int],g)
+_randomPermutation n rndgen = (fromPermutation perm, rndgen') where
+  (perm, rndgen') = randomPermutationDurstenfeld n rndgen 
+
+-- | A synonym for 'randomCyclicPermutationSattolo'.
+randomCyclicPermutation :: RandomGen g => Int -> g -> (Permutation,g)
+randomCyclicPermutation = randomCyclicPermutationSattolo
+
+_randomCyclicPermutation :: RandomGen g => Int -> g -> ([Int],g)
+_randomCyclicPermutation n rndgen = (fromPermutation perm, rndgen') where
+  (perm, rndgen') = randomCyclicPermutationSattolo n rndgen 
+
+-- | Generates a uniformly random permutation of @[1..n]@.
+-- Durstenfeld's algorithm (see <http://en.wikipedia.org/wiki/Knuth_shuffle>).
+randomPermutationDurstenfeld :: RandomGen g => Int -> g -> (Permutation,g)
+randomPermutationDurstenfeld = randomPermutationDurstenfeldSattolo False
+
+-- | Generates a uniformly random /cyclic/ permutation of @[1..n]@.
+-- Sattolo's algorithm (see <http://en.wikipedia.org/wiki/Knuth_shuffle>).
+randomCyclicPermutationSattolo :: RandomGen g => Int -> g -> (Permutation,g)
+randomCyclicPermutationSattolo = randomPermutationDurstenfeldSattolo True
+
+randomPermutationDurstenfeldSattolo :: RandomGen g => Bool -> Int -> g -> (Permutation,g)
+randomPermutationDurstenfeldSattolo isSattolo n rnd = res where
+  res = runST $ do
+    ar <- newArray_ (1,n) 
+    forM_ [1..n] $ \i -> writeArray ar i i
+    rnd' <- worker n (if isSattolo then n-1 else n) rnd ar 
+    perm <- Data.Array.Unsafe.unsafeFreeze ar
+    return (Permutation perm, rnd')
+  worker :: RandomGen g => Int -> Int -> g -> STUArray s Int Int -> ST s g 
+  worker n m rnd ar = 
+    if n==1 
+      then return rnd 
+      else do
+        let (k,rnd') = randomR (1,m) rnd
+        when (k /= n) $ do
+          y <- readArray ar k 
+          z <- readArray ar n
+          writeArray ar n y
+          writeArray ar k z
+        worker (n-1) (m-1) rnd' ar 
+
+--------------------------------------------------------------------------------
+-- * Permutations of a multiset
+
+-- | Generates all permutations of a multiset.  
+--   The order is lexicographic. A synonym for 'fasc2B_algorithm_L'
+permuteMultiset :: (Eq a, Ord a) => [a] -> [[a]] 
+permuteMultiset = fasc2B_algorithm_L
+
+-- | # = \\frac { (\sum_i n_i) ! } { \\prod_i (n_i !) }    
+countPermuteMultiset :: (Eq a, Ord a) => [a] -> Integer
+countPermuteMultiset xs = factorial n `div` product [ factorial (length z) | z <- group ys ] 
+  where
+    ys = sort xs
+    n = length xs
+  
+-- | Generates all permutations of a multiset 
+--   (based on \"algorithm L\" in Knuth; somewhat less efficient). 
+--   The order is lexicographic.  
+fasc2B_algorithm_L :: (Eq a, Ord a) => [a] -> [[a]] 
+fasc2B_algorithm_L xs = unfold1 next (sort xs) where
+
+  -- next :: [a] -> Maybe [a]
+  next xs = case findj (reverse xs,[]) of 
+    Nothing -> Nothing
+    Just ( (l:ls) , rs) -> Just $ inc l ls (reverse rs,[]) 
+    Just ( [] , _ ) -> error "permute: should not happen"
+
+  -- we use simple list zippers: (left,right)
+  -- findj :: ([a],[a]) -> Maybe ([a],[a])   
+  findj ( xxs@(x:xs) , yys@(y:_) ) = if x >= y 
+    then findj ( xs , x : yys )
+    else Just ( xxs , yys )
+  findj ( x:xs , [] ) = findj ( xs , [x] )  
+  findj ( [] , _ ) = Nothing
+  
+  -- inc :: a -> [a] -> ([a],[a]) -> [a]
+  inc !u us ( (x:xs) , yys ) = if u >= x
+    then inc u us ( xs , x : yys ) 
+    else reverse (x:us)  ++ reverse (u:yys) ++ xs
+  inc _ _ ( [] , _ ) = error "permute: should not happen"
+      
+--------------------------------------------------------------------------------
+
+
diff --git a/Math/Combinat/Sets.hs b/Math/Combinat/Sets.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Sets.hs
@@ -0,0 +1,212 @@
+
+-- | Subsets. 
+
+{-# LANGUAGE BangPatterns, Rank2Types #-}
+module Math.Combinat.Sets 
+  ( 
+    -- * Choices
+    choose_ , choose , choose' , choose'' , chooseTagged
+    -- * Compositions
+  , combine , compose
+    -- * Tensor products
+  , tuplesFromList
+  , listTensor
+    -- * Sublists
+  , kSublists
+  , sublists
+  , countKSublists
+  , countSublists
+    -- * Random choice
+  , randomChoice
+  ) 
+  where
+
+--------------------------------------------------------------------------------
+
+import Data.List ( sort , mapAccumL )
+import System.Random
+
+import Control.Monad
+import Control.Monad.ST
+import Data.Array.ST
+import Data.Array.MArray
+
+-- import Data.Map (Map)
+-- import qualified Data.Map as Map
+
+import Math.Combinat.Numbers ( binomial )
+import Math.Combinat.Helper  ( swap )
+
+--------------------------------------------------------------------------------
+-- * choices
+
+
+-- | @choose_ k n@ returns all possible ways of choosing @k@ disjoint elements from @[1..n]@
+--
+-- > choose_ k n == choose k [1..n]
+--
+choose_ :: Int -> Int -> [[Int]]
+choose_ k n  = if n<0 || k<0
+  then error "choose_: n and k should nonnegative"
+  else if k>n || k<0 
+    then []
+    else choose k [1..n]
+
+-- | All possible ways to choose @k@ elements from a list, without
+-- repetitions. \"Antisymmetric power\" for lists. Synonym for 'kSublists'.
+choose :: Int -> [a] -> [[a]]
+choose 0 _  = [[]]
+choose k [] = []
+choose k (x:xs) = map (x:) (choose (k-1) xs) ++ choose k xs  
+
+-- | A version of 'choose' which also returns the complementer sets.
+--
+-- > choose k = map fst . choose' k
+--
+choose' :: Int -> [a] -> [([a],[a])]
+choose' 0 xs = [([],xs)]
+choose' k [] = []
+choose' k (x:xs) = map f (choose' (k-1) xs) ++ map g (choose' k xs) where
+  f (as,bs) = (x:as ,   bs)
+  g (as,bs) = (  as , x:bs)
+
+-- | Another variation of 'choose''. This satisfies
+--
+-- > choose'' k == map (\(xs,ys) -> (map fst xs, map snd ys)) . choose' k
+--
+choose'' :: Int -> [(a,b)] -> [([a],[b])]
+choose'' 0 xys = [([] , map snd xys)]
+choose'' k []  = []
+choose'' k ((x,y):xs) = map f (choose'' (k-1) xs) ++ map g (choose'' k xs) where
+  f (as,bs) = (x:as ,   bs)
+  g (as,bs) = (  as , y:bs)
+
+-- | Another variation on 'choose' which tags the elements based on whether they are part of
+-- the selected subset ('Right') or not ('Left'):
+--
+-- > choose k = map rights . chooseTagged k
+--
+chooseTagged :: Int -> [a] -> [[Either a a]]
+chooseTagged 0 xs = [map Left xs]
+chooseTagged k [] = []
+chooseTagged k (x:xs) = map f (chooseTagged (k-1) xs) ++ map g (chooseTagged k xs) where
+  f eis = Right x : eis
+  g eis = Left  x : eis
+
+-- | All possible ways to choose @k@ elements from a list, /with repetitions/. 
+-- \"Symmetric power\" for lists. See also "Math.Combinat.Compositions".
+-- TODO: better name?
+combine :: Int -> [a] -> [[a]]
+combine 0 _  = [[]]
+combine k [] = []
+combine k xxs@(x:xs) = map (x:) (combine (k-1) xxs) ++ combine k xs  
+
+-- | A synonym for 'combine'.
+compose :: Int -> [a] -> [[a]]
+compose = combine
+
+--------------------------------------------------------------------------------
+-- * tensor products
+
+-- | \"Tensor power\" for lists. Special case of 'listTensor':
+--
+-- > tuplesFromList k xs == listTensor (replicate k xs)
+-- 
+-- See also "Math.Combinat.Tuples".
+-- TODO: better name?
+tuplesFromList :: Int -> [a] -> [[a]]
+tuplesFromList 0 _  = [[]]
+tuplesFromList k xs = [ (y:ys) | ys <- tuplesFromList (k-1) xs , y <- xs ]
+--the order seems to be very important, the wrong order causes a memory leak!
+--tuplesFromList k xs = [ (y:ys) | y <- xs, ys <- tuplesFromList (k-1) xs ]
+ 
+-- | \"Tensor product\" for lists.
+listTensor :: [[a]] -> [[a]]
+listTensor [] = [[]]
+listTensor (xs:xss) = [ y:ys | ys <- listTensor xss , y <- xs ]
+--the order seems to be very important, the wrong order causes a memory leak!
+--listTensor (xs:xss) = [ y:ys | y <- xs, ys <- listTensor xss ]
+
+--------------------------------------------------------------------------------
+-- * sublists
+
+-- | Sublists of a list having given number of elements. Synonym for 'choose'.
+kSublists :: Int -> [a] -> [[a]]
+kSublists = choose
+
+-- | @# = \binom { n } { k }@.
+countKSublists :: Int -> Int -> Integer
+countKSublists k n = binomial n k 
+
+-- | All sublists of a list.
+sublists :: [a] -> [[a]]
+sublists [] = [[]]
+sublists (x:xs) = sublists xs ++ map (x:) (sublists xs)  
+--the order seems to be very important, the wrong order causes a memory leak!
+--sublists (x:xs) = map (x:) (sublists xs) ++ sublists xs 
+
+-- | @# = 2^n@.
+countSublists :: Int -> Integer
+countSublists n = 2 ^ n
+
+--------------------------------------------------------------------------------
+-- * random choice
+
+-- | @randomChoice k n@ returns a uniformly random choice of @k@ elements from the set @[1..n]@
+--
+-- Example:
+--
+-- > do
+-- >   cs <- replicateM 10000 (getStdRandom (randomChoice 3 7))
+-- >   mapM_ print $ histogram cs
+-- 
+randomChoice :: RandomGen g => Int -> Int -> g -> ([Int],g)
+randomChoice k n g0 = 
+  if k>n || k<0 
+    then error "randomChoice: k out of range" 
+    else (makeChoiceFromIndicesKnuth n as, g1) 
+  where
+    -- choose numbers from [1..n], [1..n-1], [1..n-2] etc
+    (g1,as) = mapAccumL (\g m -> swap (randomR (1,m) g)) g0 [n,n-1..n-k+1]   
+
+--------------------------------------------------------------------------------
+   
+-- | From a list of $k$ numbers, where the first is in the interval @[1..n]@, 
+-- the second in @[1..n-1]@, the third in @[1..n-2]@, we create a size @k@ subset of @n@.
+--
+-- Knuth's method. The first argument is the number @n@.
+--
+makeChoiceFromIndicesKnuth :: Int -> [Int] -> [Int]
+makeChoiceFromIndicesKnuth n xs = 
+  runST $ do
+    arr <- newArray_ (1,n) :: forall s. ST s (STUArray s Int Int)
+    forM_ [1..n] $ \(!j) -> writeArray arr j j
+    forM_ (zip [n,n-1..] xs) $ \(!j,!i) -> do
+      a <- readArray arr j
+      b <- readArray arr i
+      writeArray arr j b
+      writeArray arr i a
+    sel <- forM (zip [n,n-1..] xs) $ \(!j,_) -> readArray arr j
+    return (sort sel)
+
+-- | From a list of $k$ numbers, where the first is in the interval @[1..n]@, 
+-- the second in @[1..n-1]@, the third in @[1..n-2]@, we create a size @k@ subset of @n@.
+makeChoiceFromIndicesNaive :: [Int] -> [Int]
+makeChoiceFromIndicesNaive = sort . go [] where
+
+  go :: [Int] -> [Int] -> [Int]
+  go acc (b:bs) = b' : go (insert b' acc) bs where b' = skip b acc
+  go _   [] = []
+
+  -- skip over the already occupied positions. Second argument should be a sorted list
+  skip :: Int -> [Int] -> Int
+  skip x (y:ys) = if x>=y then skip (x+1) ys else x
+  skip x [] = x
+
+  -- insert into a sorted list
+  insert :: Int -> [Int] -> [Int]
+  insert x (y:ys) = if x<=y then x:y:ys else y : insert x ys
+  insert x [] = [x]
+
+--------------------------------------------------------------------------------
+
diff --git a/Math/Combinat/Sign.hs b/Math/Combinat/Sign.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Sign.hs
@@ -0,0 +1,90 @@
+
+-- | Signs
+
+{-# LANGUAGE BangPatterns #-}
+module Math.Combinat.Sign where
+
+--------------------------------------------------------------------------------
+
+import           Data.Monoid
+import           System.Random
+
+--------------------------------------------------------------------------------
+
+data Sign
+  = Plus                            -- hmm, this way @Plus < Minus@, not sure about that
+  | Minus
+  deriving (Eq,Ord,Show,Read)
+
+instance Semigroup Sign where
+    (<>) = mulSign
+
+instance Monoid Sign where
+  mempty  = Plus
+  mconcat = productOfSigns
+
+instance Random Sign where
+  random        g = let (b,g') = random g in (if b    then Plus else Minus, g')
+  randomR (u,v) g = let (y,g') = random g in (if u==v then u    else y    , g')
+
+isPlus, isMinus :: Sign -> Bool
+isPlus  s = case s of { Plus  -> True ; _ -> False }
+isMinus s = case s of { Minus -> True ; _ -> False }
+
+{-# SPECIALIZE signValue :: Sign -> Int     #-}
+{-# SPECIALIZE signValue :: Sign -> Integer #-}
+
+-- | @+1@ or @-1@
+signValue :: Num a => Sign -> a
+signValue s = case s of
+  Plus  ->  1
+  Minus -> -1
+
+{-# SPECIALIZE signed :: Sign -> Int     -> Int     #-}
+{-# SPECIALIZE signed :: Sign -> Integer -> Integer #-}
+
+-- | Negate the second argument if the first is 'Minus'
+signed :: Num a => Sign -> a -> a
+signed s y = case s of
+  Plus  -> y
+  Minus -> negate y
+
+{-# SPECIALIZE paritySign :: Int     -> Sign #-}
+{-# SPECIALIZE paritySign :: Integer -> Sign #-}
+
+-- | 'Plus' if even, 'Minus' if odd
+paritySign :: Integral a => a -> Sign
+paritySign x = if even x then Plus else Minus
+
+{-# SPECIALIZE paritySignValue :: Int     -> Integer #-}
+{-# SPECIALIZE paritySignValue :: Integer -> Integer #-}
+
+-- | @(-1)^k@
+paritySignValue :: Integral a => a -> Integer
+paritySignValue k = if odd k then (-1) else 1
+
+{-# SPECIALIZE negateIfOdd :: Int     -> Int     -> Int     #-}
+{-# SPECIALIZE negateIfOdd :: Int     -> Integer -> Integer #-}
+
+-- | Negate the second argument if the first is odd
+negateIfOdd :: (Integral a, Num b) => a -> b -> b
+negateIfOdd k y = if even k then y else negate y
+
+oppositeSign :: Sign -> Sign
+oppositeSign s = case s of
+  Plus  -> Minus
+  Minus -> Plus
+
+mulSign :: Sign -> Sign -> Sign
+mulSign s1 s2 = case s1 of
+  Plus  -> s2
+  Minus -> oppositeSign s2
+
+productOfSigns :: [Sign] -> Sign
+productOfSigns = go Plus where
+  go !acc []     = acc
+  go !acc (x:xs) = case x of
+    Plus  -> go acc xs
+    Minus -> go (oppositeSign acc) xs
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Tableaux.hs b/Math/Combinat/Tableaux.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Tableaux.hs
@@ -0,0 +1,241 @@
+
+-- | Young tableaux and similar gadgets. 
+--
+--   See e.g. William Fulton: Young Tableaux, with Applications to 
+--   Representation theory and Geometry (CUP 1997).
+-- 
+--   The convention is that we use 
+--   the English notation, and we store the tableaux as lists of the rows.
+-- 
+--   That is, the following standard Young tableau of shape [5,4,1]
+-- 
+-- >  1  3  4  6  7
+-- >  2  5  8 10
+-- >  9
+--
+-- <<svg/young_tableau.svg>>
+--
+--   is encoded conveniently as
+-- 
+-- > [ [ 1 , 3 , 4 , 6 , 7 ]
+-- > , [ 2 , 5 , 8 ,10 ]
+-- > , [ 9 ]
+-- > ]
+--
+
+{-# LANGUAGE CPP, BangPatterns, FlexibleInstances, TypeSynonymInstances, MultiParamTypeClasses #-}
+module Math.Combinat.Tableaux where
+
+--------------------------------------------------------------------------------
+
+import Data.List
+
+import Math.Combinat.Classes
+import Math.Combinat.Numbers (factorial,binomial)
+import Math.Combinat.Partitions
+import Math.Combinat.ASCII
+import Math.Combinat.Helper
+
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as Map
+
+--------------------------------------------------------------------------------
+-- * Basic stuff
+
+-- | A tableau is simply represented as a list of lists.
+type Tableau a = [[a]]
+
+-- | ASCII diagram of a tableau
+asciiTableau :: Show a => Tableau a -> ASCII
+asciiTableau t = tabulate (HRight,VTop) (HSepSpaces 1, VSepEmpty) 
+           $ (map . map) asciiShow
+           $ t
+
+instance CanBeEmpty (Tableau a) where
+  empty   = []
+  isEmpty = null
+
+instance Show a => DrawASCII (Tableau a) where 
+  ascii = asciiTableau
+
+_tableauShape :: Tableau a -> [Int]
+_tableauShape t = map length t 
+
+-- | The shape of a tableau
+tableauShape :: Tableau a -> Partition
+tableauShape t = toPartition (_tableauShape t)
+
+instance HasShape (Tableau a) Partition where
+  shape = tableauShape
+
+-- | Number of entries
+tableauWeight :: Tableau a -> Int
+tableauWeight = sum' . map length
+
+instance HasWeight (Tableau a) where
+  weight = tableauWeight
+
+-- | The dual of the tableau is the mirror image to the main diagonal.
+dualTableau :: Tableau a -> Tableau a
+dualTableau = transpose
+
+instance HasDuality (Tableau a) where
+  dual = dualTableau
+
+-- | The content of a tableau is the list of its entries. The ordering is from the left to the right and
+-- then from the top to the bottom
+tableauContent :: Tableau a -> [a]
+tableauContent = concat
+
+-- | An element @(i,j)@ of the resulting tableau (which has shape of the
+-- given partition) means that the vertical part of the hook has length @i@,
+-- and the horizontal part @j@. The /hook length/ is thus @i+j-1@. 
+--
+-- Example:
+--
+-- > > mapM_ print $ hooks $ toPartition [5,4,1]
+-- > [(3,5),(2,4),(2,3),(2,2),(1,1)]
+-- > [(2,4),(1,3),(1,2),(1,1)]
+-- > [(1,1)]
+--
+hooks :: Partition -> Tableau (Int,Int)
+hooks part = zipWith f p [1..] where 
+  p = fromPartition part
+  q = _dualPartition p
+  f l i = zipWith (\x y -> (x-i+1,y)) q [l,l-1..1] 
+
+hookLengths :: Partition -> Tableau Int
+hookLengths part = (map . map) (\(i,j) -> i+j-1) (hooks part) 
+
+--------------------------------------------------------------------------------
+-- * Row and column words
+
+-- | The /row word/ of a tableau is the list of its entry read from the right to the left and then
+-- from the top to the bottom.
+rowWord :: Tableau a -> [a]
+rowWord = concat . reverse
+
+-- | /Semistandard/ tableaux can be reconstructed from their row words
+rowWordToTableau :: Ord a => [a] -> Tableau a
+rowWordToTableau xs = reverse rows where
+  rows = break xs
+  break [] = [[]]
+  break [x] = [[x]]
+  break (x:xs@(y:_)) = if x>y
+    then [x] : break xs
+    else let (h:t) = break xs in (x:h):t
+
+-- | The /column word/ of a tableau is the list of its entry read from the bottom to the top and then from the left to the right
+columnWord :: Tableau a -> [a]
+columnWord = rowWord . transpose
+
+-- | /Standard/ tableaux can be reconstructed from either their column or row words
+columnWordToTableau :: Ord a => [a] -> Tableau a
+columnWordToTableau = transpose . rowWordToTableau
+
+-- | Checks whether a sequence of positive integers is a /lattice word/, 
+-- which means that in every initial part of the sequence any number @i@
+-- occurs at least as often as the number @i+1@
+--
+isLatticeWord :: [Int] -> Bool
+isLatticeWord = go Map.empty where
+  go :: Map Int Int -> [Int] -> Bool
+  go _      []     = True
+  go !table (i:is) =
+    if check i
+      then go table' is
+      else False
+    where
+      table'  = Map.insertWith (+) i 1 table
+      check j = j==1 || cnt (j-1) >= cnt j
+      cnt j   = case Map.lookup j table' of
+        Just k  -> k
+        Nothing -> 0
+
+--------------------------------------------------------------------------------
+-- * Semistandard Young tableaux
+
+-- | A tableau is /semistandard/ if its entries are weekly increasing horizontally
+-- and strictly increasing vertically
+isSemiStandardTableau :: Tableau Int -> Bool
+isSemiStandardTableau t = weak && strict where
+  weak   = and [ isWeaklyIncreasing   xs | xs <- t  ]
+  strict = and [ isStrictlyIncreasing ys | ys <- dt ]
+  dt     = dualTableau t
+   
+-- | Semistandard Young tableaux of given shape, \"naive\" algorithm    
+semiStandardYoungTableaux :: Int -> Partition -> [Tableau Int]
+semiStandardYoungTableaux n part = worker (repeat 0) shape where
+  shape = fromPartition part
+  worker _ [] = [[]] 
+  worker prevRow (s:ss) 
+    = [ (r:rs) | r <- row n s 1 prevRow, rs <- worker (map (+1) r) ss ]
+
+  -- weekly increasing lists of length @len@, pointwise at least @xs@, 
+  -- maximum value @n@, minimum value @prev@.
+  row :: Int -> Int -> Int -> [Int] -> [[Int]]
+  row _ 0   _    _      = [[]]
+  row n len prev (x:xs) = [ (a:as) | a <- [max x prev..n] , as <- row n (len-1) a xs ]
+
+-- | Stanley's hook formula (cf. Fulton page 55)
+countSemiStandardYoungTableaux :: Int -> Partition -> Integer
+countSemiStandardYoungTableaux n shape = k `div` h where
+  h = product $ map fromIntegral $ concat $ hookLengths shape 
+  k = product [ fromIntegral (n+j-i) | (i,j) <- elements shape ]
+
+   
+--------------------------------------------------------------------------------
+-- * Standard Young tableaux
+
+-- | A tableau is /standard/ if it is semistandard and its content is exactly @[1..n]@,
+-- where @n@ is the weight.
+isStandardTableau :: Tableau Int -> Bool
+isStandardTableau t = isSemiStandardTableau t && sort (concat t) == [1..n] where
+  n = sum [ length xs | xs <- t ]
+
+-- | Standard Young tableaux of a given shape.
+--   Adapted from John Stembridge, 
+--   <http://www.math.lsa.umich.edu/~jrs/software/SFexamples/tableaux>.
+standardYoungTableaux :: Partition -> [Tableau Int]
+standardYoungTableaux shape' = map rev $ tableaux shape where
+  shape = fromPartition shape'
+  rev = reverse . map reverse
+  tableaux :: [Int] -> [Tableau Int]
+  tableaux p = 
+    case p of
+      []  -> [[]]
+      [n] -> [[[n,n-1..1]]]
+      _   -> worker (n,k) 0 [] p
+    where
+      n = sum p
+      k = length p
+  worker :: (Int,Int) -> Int -> [Int] -> [Int] -> [Tableau Int]
+  worker _ _ _ [] = []
+  worker nk i ls (x:rs) = case rs of
+    (y:_) -> if x==y 
+      then worker nk (i+1) (x:ls) rs
+      else worker2 nk i ls x rs
+    [] ->  worker2 nk i ls x rs
+  worker2 :: (Int,Int) -> Int -> [Int] -> Int -> [Int] -> [Tableau Int]
+  worker2 nk@(n,k) i ls x rs = new ++ worker nk (i+1) (x:ls) rs where
+    old = if x>1 
+      then             tableaux $ reverse ls ++ (x-1) : rs
+      else map ([]:) $ tableaux $ reverse ls ++ rs   
+    a = k-1-i
+    new = {- debug ( i , a , head old , f a (head old) ) $ -}
+      map (f a) old
+    f :: Int -> Tableau Int -> Tableau Int
+    f _ [] = []
+    f 0 (t:ts) = (n:t) : f (-1) ts
+    f j (t:ts) = t : f (j-1) ts
+  
+-- | hook-length formula
+countStandardYoungTableaux :: Partition -> Integer
+countStandardYoungTableaux part = {- debug (hookLengths part) $ -}
+  factorial n `div` h where
+    h = product $ map fromIntegral $ concat $ hookLengths part 
+    n = weight part
+
+--------------------------------------------------------------------------------
+        
+    
diff --git a/Math/Combinat/Tableaux/GelfandTsetlin.hs b/Math/Combinat/Tableaux/GelfandTsetlin.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Tableaux/GelfandTsetlin.hs
@@ -0,0 +1,341 @@
+
+-- | Gelfand-Tsetlin patterns and Kostka numbers.
+--
+-- Gelfand-Tsetlin patterns (or tableaux) are triangular arrays like
+--
+-- > [ 3 ]
+-- > [ 3 , 2 ]
+-- > [ 3 , 1 , 0 ]
+-- > [ 2 , 0 , 0 , 0 ]
+--
+-- with both rows and columns non-increasing non-negative integers.
+-- Note: these are in bijection with the semi-standard Young tableaux.
+--
+-- If we add the further restriction that
+-- the top diagonal reads @lambda@, 
+-- and the diagonal sums are partial sums of @mu@, where @lambda@ and @mu@ are two
+-- partitions (in this case @lambda=[3,2]@ and @mu=[2,1,1,1]@), 
+-- then the number of the resulting patterns 
+-- or tableaux is the Kostka number @K(lambda,mu)@.
+-- Actually @mu@ doesn't even need to the be non-increasing.
+--
+
+{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}
+module Math.Combinat.Tableaux.GelfandTsetlin where
+
+--------------------------------------------------------------------------------
+
+import Data.List
+import Data.Maybe
+import Data.Monoid
+import Data.Ord
+
+import Control.Monad
+import Control.Monad.Trans.State
+
+import Data.Map (Map)
+import qualified Data.Map as Map
+
+import Math.Combinat.Partitions.Integer
+import Math.Combinat.Tableaux
+import Math.Combinat.Helper
+import Math.Combinat.ASCII
+
+--------------------------------------------------------------------------------
+-- * Kostka numbers
+
+-- | Kostka numbers (via counting Gelfand-Tsetlin patterns). See for example <http://en.wikipedia.org/wiki/Kostka_number>
+--
+-- @K(lambda,mu)==0@ unless @lambda@ dominates @mu@:
+--
+-- > [ mu | mu <- partitions (weight lam) , kostkaNumber lam mu > 0 ] == dominatedPartitions lam
+--
+kostkaNumber :: Partition -> Partition -> Int
+kostkaNumber = countKostkaGelfandTsetlinPatterns
+
+-- | Very naive (and slow) implementation of Kostka numbers, for reference.
+kostkaNumberReferenceNaive :: Partition -> Partition -> Int
+kostkaNumberReferenceNaive plambda pmu@(Partition mu) = length stuff where
+  stuff  = [ (1::Int) | t <- semiStandardYoungTableaux k plambda , cond t ]
+  k      = length mu
+  cond t = [ (head xs, length xs) | xs <- group (sort $ concat t) ] == zip [1..] mu 
+
+--------------------------------------------------------------------------------
+
+-- | Lists all (positive) Kostka numbers @K(lambda,mu)@ with the given @lambda@:
+--
+-- > kostkaNumbersWithGivenLambda lambda == Map.fromList [ (mu , kostkaNumber lambda mu) | mu <- dominatedPartitions lambda ]
+--
+-- It's much faster than computing the individual Kostka numbers, but not as fast
+-- as it could be.
+--
+{-# SPECIALIZE kostkaNumbersWithGivenLambda :: Partition -> Map Partition Int     #-}
+{-# SPECIALIZE kostkaNumbersWithGivenLambda :: Partition -> Map Partition Integer #-}
+kostkaNumbersWithGivenLambda :: forall coeff. Num coeff => Partition -> Map Partition coeff
+kostkaNumbersWithGivenLambda plambda@(Partition lam) = evalState (worker lam) Map.empty where
+
+  worker :: [Int] -> State (Map Partition (Map Partition coeff)) (Map Partition coeff)
+  worker unlam = case unlam of
+    [] -> return $ Map.singleton (Partition []) 1
+    _  -> do
+      cache <- get
+      case Map.lookup (Partition unlam) cache of
+        Just sol -> return sol
+        Nothing  -> do
+          let s = foldl' (+) 0 unlam
+          subsols <- forM (prevLambdas0 unlam) $ \p -> do
+            sub <- worker p 
+            let t = s - foldl' (+) 0 p              
+                f (Partition xs , c) = case xs of
+                  (y:_) -> if t >= y then Just (Partition (t:xs) , c) else Nothing
+                  []    -> if t >  0 then Just (Partition [t]    , c) else Nothing
+            if t > 0
+              then return $ Map.fromList $ mapMaybe f $ Map.toList sub
+              else return $ Map.empty
+
+          let sol = Map.unionsWith (+) subsols
+          put $! (Map.insert (Partition unlam) sol cache)
+          return sol
+
+  -- needs decreasing sequence
+  prevLambdas0 :: [Int] -> [[Int]]
+  prevLambdas0 (l:ls) = go l ls where
+    go b [a]    = [ [x]   | x <- [a..b] ] ++ [ [x,y] | x <- [a..b] , y<-[1..a] ]
+    go b (a:as) = [ x:xs  | x <- [a..b] , xs <- go a as ]
+    go b []     = [] : [ [j] | j <- [1..b] ]
+  prevLambdas0 []  = []
+
+-- | Lists all (positive) Kostka numbers @K(lambda,mu)@ with the given @mu@:
+--
+-- > kostkaNumbersWithGivenMu mu == Map.fromList [ (lambda , kostkaNumber lambda mu) | lambda <- dominatingPartitions mu ]
+--
+-- This function uses the iterated Pieri rule, and is relatively fast.
+--
+kostkaNumbersWithGivenMu :: Partition -> Map Partition Int
+kostkaNumbersWithGivenMu (Partition mu) = iteratedPieriRule (reverse mu)
+
+--------------------------------------------------------------------------------
+-- * Gelfand-Tsetlin patterns
+
+-- | A Gelfand-Tstetlin tableau
+type GT = [[Int]]
+
+asciiGT :: GT -> ASCII
+asciiGT gt = tabulate (HRight,VTop) (HSepSpaces 1, VSepEmpty) 
+           $ (map . map) asciiShow
+           $ gt
+
+kostkaGelfandTsetlinPatterns :: Partition -> Partition -> [GT]
+kostkaGelfandTsetlinPatterns lambda (Partition mu) = kostkaGelfandTsetlinPatterns' lambda mu
+
+-- | Generates all Kostka-Gelfand-Tsetlin tableau, that is, triangular arrays like
+--
+-- > [ 3 ]
+-- > [ 3 , 2 ]
+-- > [ 3 , 1 , 0 ]
+-- > [ 2 , 0 , 0 , 0 ]
+--
+-- with both rows and column non-increasing such that
+-- the top diagonal read lambda (in this case @lambda=[3,2]@) and the diagonal sums
+-- are partial sums of mu (in this case @mu=[2,1,1,1]@)
+--
+-- The number of such GT tableaux is the Kostka
+-- number K(lambda,mu).
+--
+kostkaGelfandTsetlinPatterns' :: Partition -> [Int] -> [GT]
+kostkaGelfandTsetlinPatterns' plam@(Partition lambda0) mu0
+  | minimum mu0 < 0                       = []
+  | wlam == 0                             = if wmu == 0 then [ [] ] else []
+  | wmu  == wlam && plam `dominates` pmu  = list
+  | otherwise                             = []
+  where
+
+    pmu = mkPartition mu0
+
+    nlam = length lambda0
+    nmu  = length mu0
+
+    n = max nlam nmu
+
+    lambda = lambda0 ++ replicate (n - nlam) 0
+    mu     = mu0     ++ replicate (n - nmu ) 0
+
+    revlam = reverse lambda
+
+    wmu  = sum' mu
+    wlam = sum' lambda
+
+    list = worker 
+             revlam 
+             (scanl1 (+) mu) 
+             (replicate (n-1) 0) 
+             (replicate (n  ) 0) 
+             []
+
+    worker
+      :: [Int]       -- lambda_i in reverse order
+      -> [Int]       -- partial sums of mu
+      -> [Int]       -- sums of the tails of previous rows
+      -> [Int]       -- last row
+      -> [[Int]]     -- the lower part of GT tableau we accumulated so far (this is not needed if we only want to count)
+      -> [GT]   
+
+    worker (rl:rls) (smu:smus) (a:acc) (lastx0:lastrowt) table = stuff 
+      where
+        x0 = smu - a
+        stuff = concat 
+          [ worker rls smus (zipWith (+) acc (tail row)) (init row) (row:table)
+          | row <- boundedNonIncrSeqs' x0 (map (max rl) (max lastx0 x0 : lastrowt)) lambda
+          ]
+    worker [rl] _ _ _ table = [ [rl]:table ] 
+    worker []   _ _ _ _     = [ []         ]
+
+    boundedNonIncrSeqs' :: Int -> [Int] -> [Int] -> [[Int]]
+    boundedNonIncrSeqs' = go where
+      go h0 (a:as) (b:bs) = [ h:hs | h <- [(max 0 a)..(min h0 b)] , hs <- go h as bs ]
+      go _  []     _      = [[]]
+      go _  _      []     = [[]]
+
+--------------------------------------------------------------------------------
+
+-- | This returns the corresponding Kostka number:
+--
+-- > countKostkaGelfandTsetlinPatterns lambda mu == length (kostkaGelfandTsetlinPatterns lambda mu)
+-- 
+countKostkaGelfandTsetlinPatterns :: Partition -> Partition -> Int
+countKostkaGelfandTsetlinPatterns plam@(Partition lambda0) pmu@(Partition mu0) 
+  | wlam == 0                             = if wmu == 0 then 1 else 0
+  | wmu  == wlam && plam `dominates` pmu  = cnt
+  | otherwise                             = 0
+  where
+
+    nlam = length lambda0
+    nmu  = length mu0
+
+    n = max nlam nmu
+
+    lambda = lambda0 ++ replicate (n - nlam) 0
+    mu     = mu0     ++ replicate (n - nmu ) 0
+
+    revlam = reverse lambda
+
+    wmu  = sum' mu
+    wlam = sum' lambda
+
+    cnt = worker 
+            revlam 
+            (scanl1 (+) mu) 
+            (replicate (n-1) 0) 
+            (replicate (n  ) 0) 
+
+    worker
+      :: [Int]       -- lambda_i in reverse order
+      -> [Int]       -- partial sums of mu
+      -> [Int]       -- sums of the tails of previous rows
+      -> [Int]       -- last row
+      -> Int
+
+    worker (rl:rls) (smu:smus) (a:acc) (lastx0:lastrowt) = stuff 
+      where
+        x0 = smu - a
+        stuff = sum'
+          [ worker rls smus (zipWith (+) acc (tail row)) (init row) 
+          | row <- boundedNonIncrSeqs' x0 (map (max rl) (max lastx0 x0 : lastrowt)) lambda
+          ]
+    worker [rl] _ _ _ = 1 
+    worker []   _ _ _ = 1
+
+    boundedNonIncrSeqs' :: Int -> [Int] -> [Int] -> [[Int]]
+    boundedNonIncrSeqs' = go where
+      go h0 (a:as) (b:bs) = [ h:hs | h <- [(max 0 a)..(min h0 b)] , hs <- go h as bs ]
+      go _  []     _      = [[]]
+      go _  _      []     = [[]]
+
+--------------------------------------------------------------------------------
+
+{-
+
+-- | All non-increasing sentences between a lower and an upper bound
+boundedNonIncrSeqs :: [Int] -> [Int] -> [[Int]]
+boundedNonIncrSeqs as bs = case bs of  
+  (h0:_) -> boundedNonIncrSeqs' h0 as bs
+  []     -> [[]]
+
+-- | All non-increasing sentences between a lower and an upper bound, and also less-or-equal than the given number
+boundedNonIncrSeqs' :: Int -> [Int] -> [Int] -> [[Int]]
+boundedNonIncrSeqs' = go where
+  go h0 (a:as) (b:bs) = [ h:hs | h <- [(max 0 a)..(min h0 b)] , hs <- go h as bs ]
+  go _  []     _      = [[]]
+  go _  _      []     = [[]]
+
+-- | All non-decreasing sentences between a lower and an upper bound
+boundedNonDecrSeqs :: [Int] -> [Int] -> [[Int]]
+boundedNonDecrSeqs = boundedNonDecrSeqs' 0
+
+-- | All non-decreasing sentences between a lower and an upper bound, and also greator-or-equal then the given number
+boundedNonDecrSeqs' :: Int -> [Int] -> [Int] -> [[Int]]
+boundedNonDecrSeqs' h0 = go (max 0 h0) where
+  go h0 (a:as) (b:bs) = [ h:hs | h <- [(max h0 a)..b] , hs <- go h as bs ]
+  go _  []     _      = [[]]
+  go _  _      []     = [[]]
+
+-}
+
+--------------------------------------------------------------------------------
+-- * The iterated Pieri rule 
+
+-- | Computes the Schur expansion of @h[n1]*h[n2]*h[n3]*...*h[nk]@ via iterating the Pieri rule.
+-- Note: the coefficients are actually the Kostka numbers; the following is true:
+--
+-- > Map.toList (iteratedPieriRule (fromPartition mu))  ==  [ (lam, kostkaNumber lam mu) | lam <- dominatingPartitions mu ]
+-- 
+-- This should be faster than individually computing all these Kostka numbers.
+--
+iteratedPieriRule :: Num coeff => [Int] -> Map Partition coeff
+iteratedPieriRule = iteratedPieriRule' (Partition [])
+
+-- | Iterating the Pieri rule, we can compute the Schur expansion of
+-- @h[lambda]*h[n1]*h[n2]*h[n3]*...*h[nk]@
+iteratedPieriRule' :: Num coeff => Partition -> [Int] -> Map Partition coeff
+iteratedPieriRule' plambda ns = iteratedPieriRule'' (plambda,1) ns
+
+{-# SPECIALIZE iteratedPieriRule'' :: (Partition,Int    ) -> [Int] -> Map Partition Int     #-}
+{-# SPECIALIZE iteratedPieriRule'' :: (Partition,Integer) -> [Int] -> Map Partition Integer #-}
+iteratedPieriRule'' :: Num coeff => (Partition,coeff) -> [Int] -> Map Partition coeff
+iteratedPieriRule'' (plambda,coeff0) ns = worker (Map.singleton plambda coeff0) ns where
+  worker old []     = old
+  worker old (n:ns) = worker new ns where
+    stuff = [ (coeff, pieriRule lam n) | (lam,coeff) <- Map.toList old ] 
+    new   = foldl' f Map.empty stuff 
+    f t0 (c,ps) = foldl' (\t p -> Map.insertWith (+) p c t) t0 ps  
+
+--------------------------------------------------------------------------------
+
+-- | Computes the Schur expansion of @e[n1]*e[n2]*e[n3]*...*e[nk]@ via iterating the Pieri rule.
+-- Note: the coefficients are actually the Kostka numbers; the following is true:
+--
+-- > Map.toList (iteratedDualPieriRule (fromPartition mu))  ==  
+-- >   [ (dualPartition lam, kostkaNumber lam mu) | lam <- dominatingPartitions mu ]
+-- 
+-- This should be faster than individually computing all these Kostka numbers.
+-- It is a tiny bit slower than 'iteratedPieriRule'.
+--
+iteratedDualPieriRule :: Num coeff => [Int] -> Map Partition coeff
+iteratedDualPieriRule = iteratedDualPieriRule' (Partition [])
+
+-- | Iterating the Pieri rule, we can compute the Schur expansion of
+-- @e[lambda]*e[n1]*e[n2]*e[n3]*...*e[nk]@
+iteratedDualPieriRule' :: Num coeff => Partition -> [Int] -> Map Partition coeff
+iteratedDualPieriRule' plambda ns = iteratedDualPieriRule'' (plambda,1) ns
+
+{-# SPECIALIZE iteratedDualPieriRule'' :: (Partition,Int    ) -> [Int] -> Map Partition Int     #-}
+{-# SPECIALIZE iteratedDualPieriRule'' :: (Partition,Integer) -> [Int] -> Map Partition Integer #-}
+iteratedDualPieriRule'' :: Num coeff => (Partition,coeff) -> [Int] -> Map Partition coeff
+iteratedDualPieriRule'' (plambda,coeff0) ns = worker (Map.singleton plambda coeff0) ns where
+  worker old []     = old
+  worker old (n:ns) = worker new ns where
+    stuff = [ (coeff, dualPieriRule lam n) | (lam,coeff) <- Map.toList old ] 
+    new   = foldl' f Map.empty stuff 
+    f t0 (c,ps) = foldl' (\t p -> Map.insertWith (+) p c t) t0 ps  
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Tableaux/GelfandTsetlin/Cone.hs b/Math/Combinat/Tableaux/GelfandTsetlin/Cone.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Tableaux/GelfandTsetlin/Cone.hs
@@ -0,0 +1,261 @@
+
+-- TODO: better name?
+
+-- | This module contains a function to generate (equivalence classes of) 
+-- triangular tableaux of size /k/, strictly increasing to the right and 
+-- to the bottom. For example
+-- 
+-- >  1  
+-- >  2  4  
+-- >  3  5  8  
+-- >  6  7  9  10 
+--
+-- is such a tableau of size 4.
+-- The numbers filling a tableau always consist of an interval @[1..c]@;
+-- @c@ is called the /content/ of the tableaux. There is a unique tableau
+-- of minimal content @2k-1@:
+--
+-- >  1  
+-- >  2  3  
+-- >  3  4  5 
+-- >  4  5  6  7 
+-- 
+-- Let us call the tableaux with maximal content (that is, @m = binomial (k+1) 2@)
+-- /standard/. The number of such standard tableaux are
+--
+-- > 1, 1, 2, 12, 286, 33592, 23178480, ...
+--
+-- OEIS:A003121, \"Strict sense ballot numbers\", 
+-- <https://oeis.org/A003121>.
+--
+-- See 
+-- R. M. Thrall, A combinatorial problem, Michigan Math. J. 1, (1952), 81-88.
+-- 
+-- The number of tableaux with content @c=m-d@ are
+-- 
+-- >  d=  |     0      1      2      3    ...
+-- > -----+----------------------------------------------
+-- >  k=2 |     1
+-- >  k=3 |     2      1
+-- >  k=4 |    12     18      8      1
+-- >  k=5 |   286    858   1001    572    165     22     1
+-- >  k=6 | 33592 167960 361114 436696 326196 155584 47320 8892 962 52 1 
+--
+-- We call these \"GT simplex tableaux\" (in the lack of a better name), since
+-- they are in bijection with the simplicial cones in a canonical simplicial 
+-- decompositions of the Gelfand-Tsetlin cones (the content corresponds
+-- to the dimension), which encode the combinatorics of Kostka numbers.
+--
+
+{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
+module Math.Combinat.Tableaux.GelfandTsetlin.Cone
+  ( 
+    -- * Types
+    Tableau
+  , Tri(..)
+  , TriangularArray
+  , fromTriangularArray
+  , triangularArrayUnsafe
+    -- * ASCII
+  , asciiTriangularArray
+  , asciiTableau
+    -- * Content
+  , gtSimplexContent
+  , _gtSimplexContent
+  , invertGTSimplexTableau
+  , _invertGTSimplexTableau
+    -- * Enumeration
+  , gtSimplexTableaux
+  , _gtSimplexTableaux
+  , countGTSimplexTableaux
+  ) 
+  where
+
+--------------------------------------------------------------------------------
+
+import Data.Ix
+import Data.Ord
+import Data.List
+
+import Control.Monad
+import Control.Monad.ST
+import Data.Array.IArray
+import Data.Array.Unboxed
+import Data.Array.ST
+
+import Math.Combinat.Tableaux (Tableau)
+import Math.Combinat.Helper
+import Math.Combinat.ASCII
+
+--------------------------------------------------------------------------------
+
+-- | Triangular arrays
+type TriangularArray a = Array Tri a
+
+-- | Set of @(i,j)@ pairs with @i>=j>=1@.
+newtype Tri = Tri { unTri :: (Int,Int) } deriving (Eq,Ord,Show)
+
+binom2 :: Int -> Int
+binom2 n = (n*(n-1)) `div` 2
+
+index' :: Tri -> Int
+index' (Tri (i,j)) = binom2 i + j - 1
+
+-- it should be (1+8*m), 
+-- the 2 is a hack to be safe with the floating point stuff
+deIndex' :: Int -> Tri 
+deIndex' m = Tri ( i+1 , m - binom2 (i+1) + 1 ) where
+  i = ( (floor.sqrt.(fromIntegral::Int->Double)) (2+8*m) - 1 ) `div` 2  
+
+instance Ix Tri where
+  index   (a,b) x = index' x - index' a 
+  inRange (a,b) x = (u<=j && j<=v) where
+    u = index' a 
+    v = index' b
+    j = index' x
+  range     (a,b) = map deIndex' [ index' a .. index' b ] 
+  rangeSize (a,b) = index' b - index' a + 1 
+
+triangularArrayUnsafe :: Tableau a -> TriangularArray a
+triangularArrayUnsafe tableau = listArray (Tri (1,1),Tri (k,k)) (concat tableau) 
+  where k = length tableau
+
+fromTriangularArray :: TriangularArray a -> Tableau a
+fromTriangularArray arr = (map.map) snd $ groupBy (equating f) $ assocs arr
+  where f = fst . unTri . fst
+
+--------------------------------------------------------------------------------
+
+asciiTriangularArray :: Show a => TriangularArray a -> ASCII
+asciiTriangularArray = asciiTableau . fromTriangularArray
+
+asciiTableau :: Show a => Tableau a -> ASCII
+asciiTableau xxs = tabulate (HRight,VTop) (HSepSpaces 1, VSepEmpty) 
+                 $ (map . map) asciiShow
+                 $ xxs
+
+instance Show a => DrawASCII (TriangularArray a) where
+  ascii = asciiTriangularArray
+
+-- instance Show a => DrawASCII (Tableau a) where
+--   ascii = asciiTableau
+
+--------------------------------------------------------------------------------
+
+-- "fractional fillings"
+data Hole = Hole Int Int deriving (Eq,Ord,Show)
+
+type ReverseTableau      = [[Int ]] 
+type ReverseHoleTableau  = [[Hole]]      
+
+toHole :: Int -> Hole
+toHole k = Hole k 0
+
+nextHole :: Hole -> Hole
+nextHole (Hole k l) = Hole k (l+1)
+
+reverseTableau :: [[a]] -> [[a]]
+reverseTableau = reverse . map reverse
+
+--------------------------------------------------------------------------------
+
+gtSimplexContent :: TriangularArray Int -> Int
+gtSimplexContent arr = max (arr ! (fst (bounds arr))) (arr ! (snd (bounds arr)))   -- we also handle inverted tableau
+
+_gtSimplexContent :: Tableau Int -> Int
+_gtSimplexContent t = max (head $ head t) (last $ last t)   -- we also handle inverted tableau
+ 
+normalize :: ReverseHoleTableau -> TriangularArray Int 
+normalize = snd . normalize'
+
+-- returns ( content , tableau )
+normalize' :: ReverseHoleTableau -> ( Int , TriangularArray Int )   
+normalize' holes = ( c , array (Tri (1,1), Tri (k,k)) xys ) where
+  k = length holes
+  c = length sorted
+  xys = concat $ zipWith hs [1..] sorted
+  hs a xs     = map (h a) xs
+  h  a (ij,_) = (Tri ij , a)  
+  sorted = groupSortBy snd (concat withPos)
+  withPos = zipWith f [1..] (reverseTableau holes) 
+  f i xs = zipWith (g i) [1..] xs 
+  g i j hole = ((i,j),hole) 
+
+--------------------------------------------------------------------------------
+
+startHole :: [Hole] -> [Int] -> Hole 
+startHole (t:ts) (p:ps) = max t (toHole p)
+startHole (t:ts) []     = t
+startHole []     (p:ps) = toHole p
+startHole []     []     = error "startHole"
+
+-- c is the "content" of the small tableau
+enumHoles :: Int -> Hole -> [Hole]
+enumHoles c start@(Hole k l) 
+  = nextHole start 
+  : [ Hole i 0 | i <- [k+1..c] ] ++ [ Hole i 1 | i <- [k+1..c] ]
+
+helper :: Int -> [Int] -> [Hole] -> [[Hole]]
+helper c [] this = [[]] 
+helper c prev@(p:ps) this = 
+  [ t:rest | t <- enumHoles c (startHole this prev), rest <- helper c ps (t:this) ]
+
+newLines' :: Int -> [Int] -> [[Hole]]
+newLines' c lastReversed = helper c last []  
+  where
+    top  = head lastReversed
+    last = reverse (top : lastReversed)
+
+newLines :: [Int] -> [[Hole]]
+newLines lastReversed = newLines' (head lastReversed) lastReversed
+
+-- | Generates all tableaux of size @k@. Effective for @k<=6@.
+gtSimplexTableaux :: Int -> [TriangularArray Int]
+gtSimplexTableaux 0 = [ triangularArrayUnsafe [] ]
+gtSimplexTableaux 1 = [ triangularArrayUnsafe [[1]] ]
+gtSimplexTableaux k = map normalize $ concatMap f smalls where
+  smalls :: [ [[Int]] ]
+  smalls = map (reverseTableau . fromTriangularArray) $ gtSimplexTableaux (k-1)
+  f :: [[Int]] -> [ [[Hole]] ]
+  f small = map (:smallhole) $ map reverse $ newLines (head small) where
+    smallhole = map (map toHole) small
+
+_gtSimplexTableaux :: Int -> [Tableau Int]
+_gtSimplexTableaux k = map fromTriangularArray $ gtSimplexTableaux k
+
+--------------------------------------------------------------------------------
+
+-- | Note: This is slow (it actually generates all the tableaux)
+countGTSimplexTableaux :: Int -> [Int]
+countGTSimplexTableaux = elems . sizes'
+
+sizes' :: Int -> UArray Int Int
+sizes' k = 
+  runSTUArray $ do
+    let (a,b) = ( 2*k-1 , binom2 (k+1) )
+    ar <- newArray (a,b) 0 :: ST s (STUArray s Int Int)   
+    mapM_ (worker ar) $ gtSimplexTableaux k 
+    return ar
+  where
+    worker :: STUArray s Int Int -> TriangularArray Int -> ST s ()
+    worker ar t = do
+      let c = gtSimplexContent t 
+      n <- readArray ar c  
+      writeArray ar c (n+1)
+     
+--------------------------------------------------------------------------------
+
+-- | We can flip the numbers in the tableau so that the interval @[1..c]@ becomes
+-- @[c..1]@. This way we a get a maybe more familiar form, when each row and each
+-- column is strictly /decreasing/ (to the right and to the bottom).
+invertGTSimplexTableau :: TriangularArray Int -> TriangularArray Int 
+invertGTSimplexTableau t = amap f t where
+  c = gtSimplexContent t 
+  f x = c+1-x  
+
+_invertGTSimplexTableau :: [[Int]] -> [[Int]]
+_invertGTSimplexTableau t = (map . map) f t where
+  c = _gtSimplexContent t  
+  f x = c+1-x
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Tableaux/LittlewoodRichardson.hs b/Math/Combinat/Tableaux/LittlewoodRichardson.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Tableaux/LittlewoodRichardson.hs
@@ -0,0 +1,399 @@
+
+-- | The Littlewood-Richardson rule
+
+module Math.Combinat.Tableaux.LittlewoodRichardson 
+  ( lrCoeff , lrCoeff'
+  , lrMult
+  , lrRule  , _lrRule , lrRuleNaive
+  , lrScalar , _lrScalar
+  ) 
+  where
+
+--------------------------------------------------------------------------------
+
+import Data.List
+import Data.Maybe
+
+import Math.Combinat.Partitions.Integer
+import Math.Combinat.Partitions.Skew
+import Math.Combinat.Tableaux
+import Math.Combinat.Tableaux.Skew
+import Math.Combinat.Helper
+
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as Map
+
+--------------------------------------------------------------------------------
+
+-- | Naive (very slow) reference implementation of the Littlewood-Richardson rule, based 
+-- on the definition "count the semistandard skew tableaux whose row content is a lattice word"
+--
+lrRuleNaive :: SkewPartition -> Map Partition Int
+lrRuleNaive skew = final where
+  n     = skewPartitionWeight skew
+  ssst  = semiStandardSkewTableaux n skew 
+  final = foldl' f Map.empty $ catMaybes [ skewTableauRowContent skew | skew <- ssst  ]
+  f old nu = Map.insertWith (+) nu 1 old
+
+--------------------------------------------------------------------------------
+-- SKEW EXPANSION
+
+-- | @lrRule@ computes the expansion of a skew Schur function 
+-- @s[lambda\/mu]@ via the Littlewood-Richardson rule.
+--
+-- Adapted from John Stembridge's Maple code: 
+-- <http://www.math.lsa.umich.edu/~jrs/software/SFexamples/LR_rule>
+--
+-- > lrRule (mkSkewPartition (lambda,nu)) == Map.fromList list where
+-- >   muw  = weight lambda - weight nu
+-- >   list = [ (mu, coeff) 
+-- >          | mu <- partitions muw 
+-- >          , let coeff = lrCoeff lambda (mu,nu)
+-- >          , coeff /= 0
+-- >          ] 
+--
+lrRule :: SkewPartition -> Map Partition Int
+lrRule skew = _lrRule lam mu where
+  (lam,mu) = fromSkewPartition skew
+
+-- | @_lrRule lambda mu@ computes the expansion of the skew
+-- Schur function @s[lambda\/mu]@ via the Littlewood-Richardson rule.
+--
+_lrRule :: Partition -> Partition -> Map Partition Int
+_lrRule plam@(Partition lam) pmu@(Partition mu0) = 
+  if not (pmu `isSubPartitionOf` plam) 
+    then Map.empty
+    else foldl' f Map.empty [ nu | (nu,_) <- fillings n diagram ]
+  where
+    f old nu = Map.insertWith (+) (Partition nu) 1 old
+    diagram  = [ (i,j) | (i,a,b) <- reverse (zip3 [1..] lam mu) , j <- [b+1..a] ]    
+    mu       = mu0 ++ repeat 0
+    n        = sum' $ zipWith (-) lam mu    -- n == length diagram
+
+{-
+LR_rule:=proc(lambda) local l,mu,alpha,beta,i,j,dgrm;
+  if not `LR_rule/fit`(lambda,args[2]) then RETURN(0) fi;
+  l:=nops(lambda); mu:=[op(args[2]),0$l];
+  dgrm:=[seq(seq([i,-j],j=-lambda[i]..-1-mu[i]),i=1..l)];
+  if nargs>2 then alpha:=args[3];
+    if nargs>3 then beta:=args[4] else beta:=[] fi;
+    if not `LR_rule/fit`(alpha,beta) then RETURN(0) fi;
+    l:=convert([op(lambda),op(beta)],`+`);
+    if l<>convert([op(alpha),op(mu)],`+`) then RETURN(0) fi;
+    nops(LR_fillings(dgrm,[alpha,beta]))
+  else
+    convert([seq(s[op(i[1])],i=LR_fillings(dgrm))],`+`)
+  fi
+end;
+-}
+
+--------------------------------------------------------------------------------
+
+-- | A filling is a pair consisting a shape @nu@ and a lattice permutation @lp@
+type Filling = ( [Int] , [Int] )
+
+-- | A diagram is a set of boxes in a skew shape (in the right order)
+type Diagram = [ (Int,Int) ]
+
+-- | Note: we use reverse ordering in Diagrams compared the Stembridge's code.
+-- Also, for performance reasons, we need the length of the diagram
+fillings :: Int -> Diagram -> [Filling]
+fillings _ []                   = [ ([],[]) ]
+fillings n diagram@((x,y):rest) = concatMap (nextLetter lower upper) (fillings (n-1) rest) where
+  upper = case findIndex (==(x  ,y+1)) diagram of { Just j -> n-j ; Nothing -> 0 }
+  lower = case findIndex (==(x-1,y  )) diagram of { Just j -> n-j ; Nothing -> 0 }
+
+{-
+LR_fillings:=proc(dgrm) local n,x,upper,lower;
+  if dgrm=[] then
+    if nargs=1 then x:=[] else x:=args[2][2] fi;
+    RETURN([[x,[]]])
+  fi;
+  n:=nops(dgrm); x:=dgrm[n];
+  if not member([x[1],x[2]+1],dgrm,'upper') then upper:=0 fi;
+  if not member([x[1]-1,x[2]],dgrm,'lower') then lower:=0 fi;
+  if nargs=1 then
+    map(`LR/nextletter`,LR_fillings([op(1..n-1,dgrm)]),lower,upper)
+  else
+    map(`LR/nextletter`,LR_fillings([op(1..n-1,dgrm)],args[2]),
+      lower,upper,args[2][1])
+  fi;
+end:
+-}
+
+--------------------------------------------------------------------------------
+
+nextLetter :: Int -> Int -> Filling -> [Filling]
+nextLetter lower upper (nu,lpart) = stuff where
+  stuff = [ ( incr i shape , lpart++[i] ) | i<-nlist ] 
+  shape = nu ++ [0] 
+  lb = if lower>0
+    then lpart !! (lower-1)
+    else 0
+  ub = if upper>0 
+    then min (length shape) (lpart !! (upper-1))  
+    else      length shape
+
+  nlist = filter (>0) $ map f [lb+1..ub] 
+  f j   = if j==1 || shape!!(j-2) > shape!!(j-1) then j else 0
+
+{-
+  -- another nlist implementation, but doesn't seem to be faster
+  (h0:hs0) = drop lb (-666:shape)
+  nlist = go h0 hs0 [lb+1..ub] where
+    go !lasth (h:hs) (j:js) = if j==1 || lasth > h 
+      then j : go h hs js 
+      else     go h hs js
+    go _      _      []     = []
+-}
+
+  -- increments the i-th element (starting from 1)
+  incr :: Int -> [Int] -> [Int]
+  incr i (x:xs) = case i of
+    0 ->         finish (x:xs)
+    1 -> (x+1) : finish xs
+    _ -> x     : incr (i-1) xs
+  incr _ []     = []
+
+  -- removes tailing zeros
+  finish :: [Int] -> [Int]
+  finish (x:xs) = if x>0 then x : finish xs else []    
+  finish []     = [] 
+
+{-
+`LR/nextletter`:=proc(T) local shape,lp,lb,ub,i,nl;
+  shape:=[op(T[1]),0]; lp:=T[2]; ub:=nops(shape);
+  if nargs>3 then ub:=min(ub,nops(args[4])) fi;
+  if args[2]=0 then lb:=0 else lb:=lp[args[2]] fi;
+  if args[3]>0 then ub:=min(lp[args[3]],ub) fi;
+  if nargs<4 then
+    nl:=map(proc(x,y) if x=1 or y[x-1]>y[x] then x fi end,[$lb+1..ub],shape)
+  else
+    nl:=map(proc(x,y,z) if y[x]<z[x] and (x=1 or y[x-1]>y[x]) then x fi end,
+      [$lb+1..ub],shape,args[4])
+  fi;
+  nl:=[seq([subsop(i=shape[i]+1,shape),[op(lp),i]],i=nl)];
+  op(subs(0=NULL,nl))
+end:
+-}
+
+--------------------------------------------------------------------------------
+-- COEFF
+
+-- | @lrCoeff lam (mu,nu)@ computes the coressponding Littlewood-Richardson coefficients.
+-- This is also the coefficient of @s[lambda]@ in the product @s[mu]*s[nu]@
+--
+-- Note: This is much slower than using 'lrRule' or 'lrMult' to compute several coefficients
+-- at the same time!
+lrCoeff :: Partition -> (Partition,Partition) -> Int
+lrCoeff lam (mu,nu) = 
+  if nu `isSubPartitionOf` lam
+    then lrScalar (mkSkewPartition (lam,nu)) (mkSkewPartition (mu,emptyPartition))
+    else 0
+
+-- | @lrCoeff (lam\/nu) mu@ computes the coressponding Littlewood-Richardson coefficients.
+-- This is also the coefficient of @s[mu]@ in the product @s[lam\/nu]@
+--
+-- Note: This is much slower than using 'lrRule' or 'lrMult' to compute several coefficients
+-- at the same time!
+lrCoeff' :: SkewPartition -> Partition -> Int
+lrCoeff' skew p = lrScalar skew (mkSkewPartition (p,emptyPartition))
+  
+--------------------------------------------------------------------------------
+-- SCALAR PRODUCT
+
+-- | @lrScalar (lambda\/mu) (alpha\/beta)@ computes the scalar product of the two skew
+-- Schur functions @s[lambda\/mu]@ and @s[alpha\/beta]@ via the Littlewood-Richardson rule.
+--
+-- Adapted from John Stembridge Maple code: 
+-- <http://www.math.lsa.umich.edu/~jrs/software/SFexamples/LR_rule>
+--
+lrScalar :: SkewPartition -> SkewPartition -> Int
+lrScalar lambdaMu alphaBeta = _lrScalar (fromSkewPartition lambdaMu) (fromSkewPartition alphaBeta)
+
+_lrScalar :: (Partition,Partition) -> (Partition,Partition) -> Int
+_lrScalar (plam  @(Partition lam  ) , pmu  @(Partition mu0)  ) 
+         (palpha@(Partition alpha) , pbeta@(Partition beta)) = 
+  if    not (pmu   `isSubPartitionOf` plam  ) 
+     || not (pbeta `isSubPartitionOf` palpha) 
+     || (sum' lam + sum' beta) /= (sum' alpha + sum' mu0)     -- equivalent to (lambda-mu) /= (alpha-beta)
+    then 0
+    else length $ fillings' n diagram (alpha,beta) 
+  where
+    f old nu = Map.insertWith (+) (Partition nu) 1 old
+    diagram  = [ (i,j) | (i,a,b) <- reverse (zip3 [1..] lam mu) , j <- [b+1..a] ]    
+    mu       = mu0 ++ repeat 0
+    n        = sum' $ zipWith (-) lam mu    -- n == length diagram
+
+{-
+LR_rule:=proc(lambda) local l,mu,alpha,beta,i,j,dgrm;
+  if not `LR_rule/fit`(lambda,args[2]) then RETURN(0) fi;
+  l:=nops(lambda); mu:=[op(args[2]),0$l];
+  dgrm:=[seq(seq([i,-j],j=-lambda[i]..-1-mu[i]),i=1..l)];
+  if nargs>2 then alpha:=args[3];
+    if nargs>3 then beta:=args[4] else beta:=[] fi;
+    if not `LR_rule/fit`(alpha,beta) then RETURN(0) fi;
+    l:=convert([op(lambda),op(beta)],`+`);
+    if l<>convert([op(alpha),op(mu)],`+`) then RETURN(0) fi;
+    nops(LR_fillings(dgrm,[alpha,beta]))
+  else
+    convert([seq(s[op(i[1])],i=LR_fillings(dgrm))],`+`)
+  fi
+end;
+-}
+
+--------------------------------------------------------------------------------
+
+-- | Note: we use reverse ordering in Diagrams compared the Stembridge's code.
+-- Also, for performance reasons, we need the length of the diagram
+fillings' :: Int -> Diagram -> ([Int],[Int]) -> [Filling]
+fillings' _         []                     (alpha,beta) = [ (beta,[]) ]
+fillings' n diagram@((x,y):rest) alphaBeta@(alpha,beta) = stuff where
+  stuff = concatMap (nextLetter' lower upper alpha) (fillings' (n-1) rest alphaBeta) 
+  upper = case findIndex (==(x  ,y+1)) diagram of { Just j -> n-j ; Nothing -> 0 }
+  lower = case findIndex (==(x-1,y  )) diagram of { Just j -> n-j ; Nothing -> 0 }
+
+{-
+LR_fillings:=proc(dgrm) local n,x,upper,lower;
+  if dgrm=[] then
+    if nargs=1 then x:=[] else x:=args[2][2] fi;
+    RETURN([[x,[]]])
+  fi;
+  n:=nops(dgrm); x:=dgrm[n];
+  if not member([x[1],x[2]+1],dgrm,'upper') then upper:=0 fi;
+  if not member([x[1]-1,x[2]],dgrm,'lower') then lower:=0 fi;
+  if nargs=1 then
+    map(`LR/nextletter`,LR_fillings([op(1..n-1,dgrm)]),lower,upper)
+  else
+    map(`LR/nextletter`,LR_fillings([op(1..n-1,dgrm)],args[2]),
+      lower,upper,args[2][1])
+  fi;
+end:
+-}
+
+--------------------------------------------------------------------------------
+
+nextLetter' :: Int -> Int -> [Int] -> Filling -> [Filling]
+nextLetter' lower upper alpha (nu,lpart) = stuff where
+  stuff = [ ( incr i shape , lpart++[i] ) | i<-nlist ] 
+  shape = nu ++ [0] 
+  lb = if lower>0
+    then lpart !! (lower-1)
+    else 0
+  ub1 = if upper>0 
+    then min (length shape) (lpart !! (upper-1))  
+    else      length shape
+  ub = min (length alpha) ub1
+  nlist = filter (>0) $ map f [lb+1..ub] 
+  f j   = if (        shape!!(j-1) < alpha!!(j-1)) &&
+             (j==1 || shape!!(j-2) > shape!!(j-1)) 
+          then j 
+          else 0
+
+  -- increments the i-th element (starting from 1)
+  incr :: Int -> [Int] -> [Int]
+  incr i (x:xs) = case i of
+    0 ->         finish (x:xs)
+    1 -> (x+1) : finish xs
+    _ -> x     : incr (i-1) xs
+  incr _ []     = []
+
+  -- removes tailing zeros
+  finish :: [Int] -> [Int]
+  finish (x:xs) = if x>0 then x : finish xs else []    
+  finish []     = [] 
+
+{-
+`LR/nextletter`:=proc(T) local shape,lp,lb,ub,i,nl;
+  shape:=[op(T[1]),0]; lp:=T[2]; ub:=nops(shape);
+  if nargs>3 then ub:=min(ub,nops(args[4])) fi;
+  if args[2]=0 then lb:=0 else lb:=lp[args[2]] fi;
+  if args[3]>0 then ub:=min(lp[args[3]],ub) fi;
+  if nargs<4 then
+    nl:=map(proc(x,y) if x=1 or y[x-1]>y[x] then x fi end,[$lb+1..ub],shape)
+  else
+    nl:=map(proc(x,y,z) if y[x]<z[x] and (x=1 or y[x-1]>y[x]) then x fi end,
+      [$lb+1..ub],shape,args[4])
+  fi;
+  nl:=[seq([subsop(i=shape[i]+1,shape),[op(lp),i]],i=nl)];
+  op(subs(0=NULL,nl))
+end:
+-}
+
+--------------------------------------------------------------------------------
+-- MULTIPLICATION
+
+type Part = [Int]
+
+-- | Computes the expansion of the product of Schur polynomials @s[mu]*s[nu]@ using the
+-- Littlewood-Richardson rule. Note: this is symmetric in the two arguments.
+--
+-- Based on the wikipedia article <https://en.wikipedia.org/wiki/Littlewood-Richardson_rule>
+--
+-- > lrMult mu nu == Map.fromList list where
+-- >   lamw = weight nu + weight mu
+-- >   list = [ (lambda, coeff) 
+-- >          | lambda <- partitions lamw 
+-- >          , let coeff = lrCoeff lambda (mu,nu)
+-- >          , coeff /= 0
+-- >          ] 
+--
+lrMult :: Partition -> Partition -> Map Partition Int
+lrMult pmu@(Partition mu) pnu@(Partition nu) = result where
+  result = foldl' add Map.empty (addMu mu nu) where
+  add !old lambda = Map.insertWith (+) (Partition lambda) 1 old
+
+-- | This basically lists all the outer shapes (with multiplicities) which can be result from the LR rule
+addMu :: Part -> Part -> [Part]
+addMu mu part = go ubs0 mu dmu part where
+
+  go _   []     _      part = [part]
+  go ubs (m:ms) (d:ds) part = concat [ go (drop d ubs') ms ds part' | (ubs',part') <- addRowOf ubs part ]
+
+  ubs0 = take (headOrZero mu) [headOrZero part + 1..]
+  dmu  = diffSeq mu
+
+
+-- | Adds a full row of @(length pcols)@ boxes containing to a partition, with
+-- pcols being the upper bounds of the columns, respectively. We also return the
+-- newly added columns
+addRowOf :: [Int] -> Part -> [([Int],Part)]
+addRowOf pcols part = go 0 pcols part [] where
+  go !lb []        p ncols = [(reverse ncols , p)]
+  go !lb (!ub:ubs) p ncols = concat [ go col ubs (addBox ij p) (col:ncols) | ij@(row,col) <- newBoxes (lb+1) ub p ]
+
+-- | Returns the (row,column) pairs of the new boxes which 
+-- can be added to the given partition with the given column bounds
+-- and the 1-Rieri rule 
+newBoxes :: Int -> Int -> Part -> [(Int,Int)]
+newBoxes lb ub part = reverse $ go [1..] part (headOrZero part + 1) where
+  go (!i:_ ) []      !lp
+    | lb <= 1 && 1 <= ub && lp > 0  =  [(i,1)]
+    | otherwise                     =  []
+  go (!i:is) (!j:js) !lp 
+    | j1 <  lb            =  []
+    | j1 <= ub && lp > j  =  (i,j1) : go is js j       
+    | otherwise           =           go is js j
+    where 
+      j1 = j+1
+
+-- | Adds a box to a partition
+addBox :: (Int,Int) -> Part -> Part
+addBox (k,_) part = go 1 part where
+  go !i (p:ps) = if i==k then (p+1):ps else p : go (i+1) ps
+  go !i []     = if i==k then [1] else error "addBox: shouldn't happen"
+
+-- | Safe head defaulting to zero           
+headOrZero :: [Int] -> Int
+headOrZero xs = case xs of 
+  (!x:_) -> x
+  []     -> 0
+
+-- | Computes the sequence of differences from a partition (including the last difference to zero)
+diffSeq :: Part -> [Int]
+diffSeq = go where
+  go (p:ps@(q:_)) = (p-q) : go ps
+  go [p]          = [p]
+  go []           = []
+
+--------------------------------------------------------------------------------  
diff --git a/Math/Combinat/Tableaux/Skew.hs b/Math/Combinat/Tableaux/Skew.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Tableaux/Skew.hs
@@ -0,0 +1,223 @@
+
+-- | Skew tableaux are skew partitions filled with numbers.
+--
+-- For example:
+--
+-- <<svg/skew_tableau.svg>>
+--
+
+{-# LANGUAGE CPP, BangPatterns, ScopedTypeVariables, MultiParamTypeClasses #-}
+
+module Math.Combinat.Tableaux.Skew where
+
+--------------------------------------------------------------------------------
+
+import Data.List
+
+import Math.Combinat.Classes
+import Math.Combinat.Partitions.Integer
+import Math.Combinat.Partitions.Skew
+import Math.Combinat.Tableaux
+import Math.Combinat.ASCII
+import Math.Combinat.Helper
+
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as Map
+
+--------------------------------------------------------------------------------
+-- * Basics
+-- | A skew tableau is represented by a list of offsets and entries
+newtype SkewTableau a = SkewTableau [(Int,[a])] deriving (Eq,Ord,Show)
+
+-- unSkewTableau :: SkewTableau a -> [(Int,[a])]
+-- unSkewTableau (SkewTableau a) = a
+
+instance Functor SkewTableau where
+  fmap f (SkewTableau t) = SkewTableau [ (a, map f xs) | (a,xs) <- t ]
+
+-- | The shape of a skew tableau 
+skewTableauShape :: SkewTableau a -> SkewPartition
+skewTableauShape (SkewTableau list) = SkewPartition [ (o,length xs) | (o,xs) <- list ]
+
+instance HasShape (SkewTableau a) SkewPartition where
+  shape = skewTableauShape
+
+-- | The weight of a tableau is the weight of its shape, or the number of entries
+skewTableauWeight :: SkewTableau a -> Int
+skewTableauWeight = skewPartitionWeight . skewTableauShape
+
+instance HasWeight (SkewTableau a) where
+  weight = skewTableauWeight
+
+--------------------------------------------------------------------------------
+
+-- | The dual of a skew tableau, that is, its mirror image to the main diagonal
+dualSkewTableau :: forall a. SkewTableau a -> SkewTableau a
+dualSkewTableau (SkewTableau axs) = SkewTableau (go axs) where
+
+  go []  = []  
+  go axs = case sub 0 axs of
+    (0,[]) -> []
+    this   -> this : go (strip axs)
+
+  strip :: [(Int,[a])] -> [(Int,[a])]
+  strip []            = []
+  strip ((a,xs):rest) = if a>0 
+    then (a-1,xs) : strip rest
+    else case xs of
+      []     -> []
+      (z:zs) -> case zs of
+        []      -> []
+        _       -> (0,zs) : strip rest
+
+  sub :: Int -> [(Int,[a])] -> (Int,[a])
+  sub !b [] = (b,[])
+  sub !b ((a,this):rest) = if a>0 
+    then sub (b+1) rest  
+    else (b,ys) where      
+      ys = map head $ takeWhile (not . null) (this : map snd rest)
+
+{-
+test_dualSkewTableau :: [SkewTableau Int]
+test_dualSkewTableau = bad where 
+  ps = allPartitions 11
+  bad = [ st 
+        | p<-ps , q<-ps 
+        , (q `isSubPartitionOf` p) 
+        , let sp = mkSkewPartition (p,q) 
+        , let st = fillSkewPartitionWithRowWord sp [1..] 
+        , dualSkewTableau (dualSkewTableau st) /= st
+        ]
+-}
+
+instance HasDuality (SkewTableau a) where
+  dual = dualSkewTableau
+
+--------------------------------------------------------------------------------
+-- * Semistandard tableau
+
+-- | A tableau is /semistandard/ if its entries are weekly increasing horizontally
+-- and strictly increasing vertically
+isSemiStandardSkewTableau :: SkewTableau Int -> Bool
+isSemiStandardSkewTableau st@(SkewTableau axs) = weak && strict where
+  weak   = and [ isWeaklyIncreasing   xs | (a,xs) <- axs ]
+  strict = and [ isStrictlyIncreasing ys | (b,ys) <- bys ]
+  SkewTableau bys = dualSkewTableau st
+
+-- | A tableau is /standard/ if it is semistandard and its content is exactly @[1..n]@,
+-- where @n@ is the weight.
+isStandardSkewTableau :: SkewTableau Int -> Bool
+isStandardSkewTableau st = isSemiStandardSkewTableau st && sort (skewTableauRowWord st) == [1..n] where
+  n = skewTableauWeight st
+  
+--------------------------------------------------------------------------------
+
+-- | All semi-standard skew tableaux filled with the numbers @[1..n]@
+semiStandardSkewTableaux :: Int -> SkewPartition -> [SkewTableau Int]
+semiStandardSkewTableaux n (SkewPartition abs) = map SkewTableau stuff where
+
+  stuff = worker as bs ds (repeat 1) 
+  (as,bs) = unzip abs
+  ds = diffSequence as
+  
+  -- | @worker inner outerMinusInner innerdiffs lowerbound
+  worker :: [Int] -> [Int] -> [Int] -> [Int] -> [[(Int,[Int])]]
+  worker (a:as) (b:bs) (d:ds) lb = [ (a,this):rest 
+                                   | this <- row b 1 lb 
+                                   , let lb' = (replicate d 1 ++ map (+1) this) 
+                                   , rest <- worker as bs ds lb' ] 
+  worker []     _      _      _  = [ [] ]
+
+  -- @row length minimum lowerbound@
+  row 0  _  _       = [[]]
+  row _  _  []      = []
+  row !k !m (!a:as) = [ x:xs | x <- [(max a m)..n] , xs <- row (k-1) x as ] 
+
+{-
+-- | from a sequence @[a1,a2,..,an]@ computes the sequence of differences
+-- @[a1-a2,a2-a3,...,an-0]@
+diffSequence :: [Int] -> [Int]
+diffSequence = go where
+  go (x:ys@(y:_)) = (x-y) : go ys 
+  go [x] = [x]
+  go []  = []
+-}
+
+--------------------------------------------------------------------------------
+-- * ASCII
+
+-- | ASCII drawing of a skew tableau (using the English notation)
+asciiSkewTableau :: Show a => SkewTableau a -> ASCII
+asciiSkewTableau = asciiSkewTableau' "." EnglishNotation
+
+asciiSkewTableau' 
+  :: Show a
+  => String               -- ^ string representing the elements of the inner (unfilled) partition
+  -> PartitionConvention  -- ^ orientation
+  -> SkewTableau a 
+  -> ASCII
+asciiSkewTableau' innerstr orient (SkewTableau axs) = tabulate (HRight,VTop) (HSepSpaces 1, VSepEmpty) stuff where
+  stuff = case orient of
+    EnglishNotation    -> es
+    EnglishNotationCCW -> reverse (transpose es)
+    FrenchNotation     -> reverse es
+  inner = asciiFromString innerstr
+  es = [ replicate a inner ++ map asciiShow xs | (a,xs) <- axs ]
+
+instance Show a => DrawASCII (SkewTableau a) where
+  ascii = asciiSkewTableau
+
+--------------------------------------------------------------------------------
+-- * Row \/ column words
+
+-- | The reversed (right-to-left) rows, concatenated
+skewTableauRowWord :: SkewTableau a -> [a]
+skewTableauRowWord (SkewTableau axs) = concatMap (reverse . snd) axs
+
+-- | The reversed (bottom-to-top) columns, concatenated
+skewTableauColumnWord :: SkewTableau a -> [a]
+skewTableauColumnWord = skewTableauRowWord . dualSkewTableau
+
+-- | Fills a skew partition with content, in row word order 
+fillSkewPartitionWithRowWord :: SkewPartition -> [a] -> SkewTableau a
+fillSkewPartitionWithRowWord (SkewPartition abs) xs = SkewTableau $ go abs xs where
+  go ((b,a):rest) xs = let (ys,zs) = splitAt a xs in (b,reverse ys) : go rest zs
+  go []           xs = []
+
+-- | Fills a skew partition with content, in column word order 
+fillSkewPartitionWithColumnWord :: SkewPartition -> [a] -> SkewTableau a
+fillSkewPartitionWithColumnWord shape content 
+  = dualSkewTableau 
+  $ fillSkewPartitionWithRowWord (dualSkewPartition shape) content
+
+--------------------------------------------------------------------------------
+
+-- | If the skew tableau's row word is a lattice word, we can make a partition from its content
+skewTableauRowContent :: SkewTableau Int -> Maybe Partition
+skewTableauRowContent (SkewTableau axs) = go Map.empty rowword where
+
+  rowword = concatMap (reverse . snd) axs
+
+  finish table = Partition (f 1) where
+    f !i = case lkp i of
+      0 -> []
+      y -> y : f (i+1) 
+    lkp j = case Map.lookup j table of
+      Just k  -> k
+      Nothing -> 0
+
+  go :: Map Int Int -> [Int] -> Maybe Partition
+  go !table []     = Just (finish table)
+  go !table (i:is) =
+    if check i
+      then go table' is
+      else Nothing
+    where
+      table'  = Map.insertWith (+) i 1 table
+      check j = j==1 || cnt (j-1) >= cnt j
+      cnt j   = case Map.lookup j table' of
+        Just k  -> k
+        Nothing -> 0
+
+--------------------------------------------------------------------------------
+
diff --git a/Math/Combinat/Trees.hs b/Math/Combinat/Trees.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Trees.hs
@@ -0,0 +1,9 @@
+
+module Math.Combinat.Trees
+  ( module Math.Combinat.Trees.Binary
+  , module Math.Combinat.Trees.Nary
+  ) where
+
+import Math.Combinat.Trees.Binary
+import Math.Combinat.Trees.Nary
+
diff --git a/Math/Combinat/Trees/Binary.hs b/Math/Combinat/Trees/Binary.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Trees/Binary.hs
@@ -0,0 +1,491 @@
+
+-- | Binary trees, forests, etc. See:
+--   Donald E. Knuth: The Art of Computer Programming, vol 4, pre-fascicle 4A.
+--
+-- For example, here are all the binary trees on 4 nodes:
+--
+-- <<svg/bintrees.svg>>
+--
+
+{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
+module Math.Combinat.Trees.Binary 
+  ( -- * Types
+    BinTree(..)
+  , leaf 
+  , graft
+  , BinTree'(..)
+  , forgetNodeDecorations
+  , Paren(..)
+  , parenthesesToString
+  , stringToParentheses  
+  , numberOfNodes
+  , numberOfLeaves
+    -- * Conversion to rose trees (@Data.Tree@)
+  , toRoseTree , toRoseTree'
+  , module Data.Tree 
+    -- * Enumerate leaves
+  , enumerateLeaves_ 
+  , enumerateLeaves 
+  , enumerateLeaves'
+    -- * Nested parentheses
+  , nestedParentheses 
+  , randomNestedParentheses
+  , nthNestedParentheses
+  , countNestedParentheses
+  , fasc4A_algorithm_P
+  , fasc4A_algorithm_W
+  , fasc4A_algorithm_U
+    -- * Generating binary trees
+  , binaryTrees
+  , countBinaryTrees
+  , binaryTreesNaive
+  , randomBinaryTree
+  , fasc4A_algorithm_R
+    -- * ASCII drawing
+  , asciiBinaryTree_
+    -- * Graphviz drawing
+  , Dot
+  , graphvizDotBinTree
+  , graphvizDotBinTree'
+  , graphvizDotForest
+  , graphvizDotTree  
+    -- * Bijections
+  , forestToNestedParentheses
+  , forestToBinaryTree
+  , nestedParenthesesToForest
+  , nestedParenthesesToForestUnsafe
+  , nestedParenthesesToBinaryTree
+  , nestedParenthesesToBinaryTreeUnsafe
+  , binaryTreeToForest
+  , binaryTreeToNestedParentheses
+  ) 
+  where
+
+--------------------------------------------------------------------------------
+
+import Control.Applicative
+import Control.Monad
+import Control.Monad.ST
+
+import Data.Array
+import Data.Array.ST
+import Data.Array.Unsafe
+
+import Data.List
+import Data.Tree (Tree(..),Forest(..))
+
+import Data.Monoid
+import Data.Foldable (Foldable(foldMap))
+import Data.Traversable (Traversable(traverse))
+
+import System.Random
+
+import Math.Combinat.Numbers (factorial,binomial)
+
+import Math.Combinat.Trees.Graphviz 
+  ( Dot 
+  , graphvizDotBinTree , graphvizDotBinTree' 
+  , graphvizDotForest  , graphvizDotTree 
+  )
+import Math.Combinat.Classes
+import Math.Combinat.Helper
+import Math.Combinat.ASCII as ASCII
+
+--------------------------------------------------------------------------------
+-- * Types
+
+-- | A binary tree with leaves decorated with type @a@.
+data BinTree a
+  = Branch (BinTree a) (BinTree a)
+  | Leaf a
+  deriving (Eq,Ord,Show,Read)
+
+leaf :: BinTree ()
+leaf = Leaf ()
+
+-- | The monadic join operation of binary trees
+graft :: BinTree (BinTree a) -> BinTree a
+graft = go where
+  go (Branch l r) = Branch (go l) (go r)
+  go (Leaf   t  ) = t 
+
+--------------------------------------------------------------------------------
+
+-- | A binary tree with leaves and internal nodes decorated 
+-- with types @a@ and @b@, respectively.
+data BinTree' a b
+  = Branch' (BinTree' a b) b (BinTree' a b)
+  | Leaf' a
+  deriving (Eq,Ord,Show,Read)
+
+forgetNodeDecorations :: BinTree' a b -> BinTree a
+forgetNodeDecorations = go where
+  go (Branch' left _ right) = Branch (go left) (go right)
+  go (Leaf'   decor       ) = Leaf decor 
+
+--------------------------------------------------------------------------------
+
+instance HasNumberOfNodes (BinTree a) where
+  numberOfNodes = go where
+    go (Leaf   _  ) = 0
+    go (Branch l r) = go l + go r + 1
+
+instance HasNumberOfLeaves (BinTree a) where
+  numberOfLeaves = go where
+    go (Leaf   _  ) = 1
+    go (Branch l r) = go l + go r 
+
+
+instance HasNumberOfNodes (BinTree' a b) where
+  numberOfNodes = go where
+    go (Leaf'   _    ) = 0
+    go (Branch' l _ r) = go l + go r + 1
+
+instance HasNumberOfLeaves (BinTree' a b) where
+  numberOfLeaves = go where
+    go (Leaf'   _    ) = 1
+    go (Branch' l _ r) = go l + go r 
+
+--------------------------------------------------------------------------------
+-- * Enumerate leaves
+
+-- | Enumerates the leaves a tree, starting from 0, ignoring old labels
+enumerateLeaves_ :: BinTree a -> BinTree Int
+enumerateLeaves_ = snd . go 0 where
+  go !k t = case t of
+    Leaf   _   -> (k+1 , Leaf k)
+    Branch l r -> (k'', Branch l' r')  where
+                    (k' ,l') = go k  l
+                    (k'',r') = go k' r
+
+-- | Enumerates the leaves a tree, starting from zero, and also returns the number of leaves
+enumerateLeaves' :: BinTree a -> (Int, BinTree (a,Int))
+enumerateLeaves' = go 0 where
+  go !k t = case t of
+    Leaf   y   -> (k+1 , Leaf (y,k))
+    Branch l r -> (k'', Branch l' r')  where
+                    (k' ,l') = go k  l
+                    (k'',r') = go k' r
+
+-- | Enumerates the leaves a tree, starting from zero
+enumerateLeaves :: BinTree a -> BinTree (a,Int)
+enumerateLeaves = snd . enumerateLeaves'
+
+--------------------------------------------------------------------------------
+-- * conversion to 'Data.Tree'
+
+-- | Convert a binary tree to a rose tree (from "Data.Tree")
+toRoseTree :: BinTree a -> Tree (Maybe a)
+toRoseTree = go where
+  go (Branch t1 t2) = Node Nothing  [go t1, go t2]
+  go (Leaf x)       = Node (Just x) [] 
+
+toRoseTree' :: BinTree' a b -> Tree (Either b a)
+toRoseTree' = go where
+  go (Branch' t1 y t2) = Node (Left  y) [go t1, go t2]
+  go (Leaf' x)         = Node (Right x) [] 
+  
+--------------------------------------------------------------------------------
+-- instances
+  
+instance Functor BinTree where
+  fmap f = go where
+    go (Branch left right) = Branch (go left) (go right)
+    go (Leaf x) = Leaf (f x)
+  
+instance Foldable BinTree where
+  foldMap f = go where
+    go (Leaf x) = f x
+    go (Branch left right) = (go left) `mappend` (go right)  
+
+instance Traversable BinTree where
+  traverse f = go where 
+    go (Leaf x) = Leaf <$> f x
+    go (Branch left right) = Branch <$> go left <*> go right
+
+instance Applicative BinTree where
+  pure    = Leaf
+  u <*> t = go u where
+    go (Branch l r) = Branch (go l) (go r)
+    go (Leaf   f  ) = fmap f t
+
+instance Monad BinTree where
+  return    = Leaf
+  (>>=) t f = go t where
+    go (Branch l r) = Branch (go l) (go r)
+    go (Leaf   y  ) = f y 
+
+--------------------------------------------------------------------------------
+-- * Nested parentheses
+
+data Paren 
+  = LeftParen 
+  | RightParen 
+  deriving (Eq,Ord,Show,Read)
+
+parenToChar :: Paren -> Char
+parenToChar LeftParen = '('
+parenToChar RightParen = ')'
+
+parenthesesToString :: [Paren] -> String
+parenthesesToString = map parenToChar
+
+stringToParentheses :: String -> [Paren]
+stringToParentheses [] = []
+stringToParentheses (x:xs) = p : stringToParentheses xs where
+  p = case x of
+    '(' -> LeftParen
+    ')' -> RightParen
+    _ -> error "stringToParentheses: invalid character"
+
+--------------------------------------------------------------------------------
+-- * Bijections
+
+forestToNestedParentheses :: Forest a -> [Paren]
+forestToNestedParentheses = forest where
+  -- forest :: Forest a -> [Paren]
+  forest = concatMap tree 
+  -- tree :: Tree a -> [Paren]
+  tree (Node _ sf) = LeftParen : forest sf ++ [RightParen]
+
+forestToBinaryTree :: Forest a -> BinTree ()
+forestToBinaryTree = forest where
+  -- forest :: Forest a -> BinTree ()
+  forest = foldr Branch leaf . map tree 
+  -- tree :: Tree a -> BinTree ()
+  tree (Node _ sf) = case sf of
+    [] -> leaf
+    _  -> forest sf 
+   
+nestedParenthesesToForest :: [Paren] -> Maybe (Forest ())
+nestedParenthesesToForest ps = 
+  case parseForest ps of 
+    (rest,forest) -> case rest of
+      [] -> Just forest
+      _  -> Nothing
+  where  
+    parseForest :: [Paren] -> ( [Paren] , Forest () )
+    parseForest ps = unfoldEither parseTree ps
+    parseTree :: [Paren] -> Either [Paren] ( [Paren] , Tree () )  
+    parseTree orig@(LeftParen:ps) = let (rest,ts) = parseForest ps in case rest of
+      (RightParen:qs) -> Right (qs, Node () ts)
+      _ -> Left orig
+    parseTree qs = Left qs
+
+nestedParenthesesToForestUnsafe :: [Paren] -> Forest ()
+nestedParenthesesToForestUnsafe = fromJust . nestedParenthesesToForest
+
+nestedParenthesesToBinaryTree :: [Paren] -> Maybe (BinTree ())
+nestedParenthesesToBinaryTree ps = 
+  case parseForest ps of 
+    (rest,forest) -> case rest of
+      [] -> Just forest
+      _  -> Nothing
+  where  
+    parseForest :: [Paren] -> ( [Paren] , BinTree () )
+    parseForest ps = let (rest,ts) = unfoldEither parseTree ps in (rest , foldr Branch leaf ts)
+    parseTree :: [Paren] -> Either [Paren] ( [Paren] , BinTree () )  
+    parseTree orig@(LeftParen:ps) = let (rest,ts) = parseForest ps in case rest of
+      (RightParen:qs) -> Right (qs, ts)
+      _ -> Left orig
+    parseTree qs = Left qs
+    
+nestedParenthesesToBinaryTreeUnsafe :: [Paren] -> BinTree ()
+nestedParenthesesToBinaryTreeUnsafe = fromJust . nestedParenthesesToBinaryTree
+
+binaryTreeToNestedParentheses :: BinTree a -> [Paren]
+binaryTreeToNestedParentheses = worker where
+  worker (Branch l r) = LeftParen : worker l ++ RightParen : worker r
+  worker (Leaf _) = []
+
+binaryTreeToForest :: BinTree a -> Forest ()
+binaryTreeToForest = worker where
+  worker (Branch l r) = Node () (worker l) : worker r
+  worker (Leaf _) = []
+
+--------------------------------------------------------------------------------
+-- * Nested parentheses
+
+-- | Generates all sequences of nested parentheses of length @2n@ in
+-- lexigraphic order.
+-- 
+-- Synonym for 'fasc4A_algorithm_P'.
+--
+nestedParentheses :: Int -> [[Paren]]
+nestedParentheses = fasc4A_algorithm_P
+
+-- | Synonym for 'fasc4A_algorithm_W'.
+randomNestedParentheses :: RandomGen g => Int -> g -> ([Paren],g)
+randomNestedParentheses = fasc4A_algorithm_W
+
+-- | Synonym for 'fasc4A_algorithm_U'.
+nthNestedParentheses :: Int -> Integer -> [Paren]
+nthNestedParentheses = fasc4A_algorithm_U
+
+countNestedParentheses :: Int -> Integer
+countNestedParentheses = countBinaryTrees
+
+-- | Generates all sequences of nested parentheses of length 2n.
+-- Order is lexicographical (when right parentheses are considered 
+-- smaller then left ones).
+-- Based on \"Algorithm P\" in Knuth, but less efficient because of
+-- the \"idiomatic\" code.
+fasc4A_algorithm_P :: Int -> [[Paren]]
+fasc4A_algorithm_P 0 = [[]]
+fasc4A_algorithm_P 1 = [[LeftParen,RightParen]]
+fasc4A_algorithm_P n = unfold next ( start , [] ) where 
+  start = concat $ replicate n [RightParen,LeftParen]  -- already reversed!
+   
+  next :: ([Paren],[Paren]) -> ( [Paren] , Maybe ([Paren],[Paren]) )
+  next ( (a:b:ls) , [] ) = next ( ls , b:a:[] )
+  next ( lls@(l:ls) , rrs@(r:rs) ) = ( visit , new ) where
+    visit = reverse lls ++ rrs
+    new = 
+      {- debug (reverse ls,l,r,rs) $ -} 
+      case l of 
+        RightParen -> Just ( ls , LeftParen:RightParen:rs )
+        LeftParen  -> 
+          {- debug ("---",reverse ls,l,r,rs) $ -}
+          findj ( lls , [] ) ( reverse (RightParen:rs) , [] ) 
+  next _ = error "fasc4A_algorithm_P: fatal error shouldn't happen"
+
+  findj :: ([Paren],[Paren]) -> ([Paren],[Paren]) -> Maybe ([Paren],[Paren])
+  findj ( [] , _ ) _ = Nothing
+  findj ( lls@(l:ls) , rs) ( xs , ys ) = 
+    {- debug ((reverse ls,l,rs),(reverse xs,ys)) $ -}
+    case l of
+      LeftParen  -> case xs of
+        (a:_:as) -> findj ( ls, RightParen:rs ) ( as , LeftParen:a:ys )
+        _ -> findj ( lls, [] ) ( reverse rs ++ xs , ys) 
+      RightParen -> Just ( reverse ys ++ xs ++ reverse (LeftParen:rs) ++ ls , [] )
+    
+-- | Generates a uniformly random sequence of nested parentheses of length 2n.    
+-- Based on \"Algorithm W\" in Knuth.
+fasc4A_algorithm_W :: RandomGen g => Int -> g -> ([Paren],g)
+fasc4A_algorithm_W n' rnd = worker (rnd,n,n,[]) where
+  n = fromIntegral n' :: Integer  
+  -- the numbers we use are of order n^2, so for n >> 2^16 
+  -- on a 32 bit machine, we need big integers.
+  worker :: RandomGen g => (g,Integer,Integer,[Paren]) -> ([Paren],g)
+  worker (rnd,_,0,parens) = (parens,rnd)
+  worker (rnd,p,q,parens) = 
+    if x<(q+1)*(q-p) 
+      then worker (rnd' , p   , q-1 , LeftParen :parens)
+      else worker (rnd' , p-1 , q   , RightParen:parens)
+    where 
+      (x,rnd') = randomR ( 0 , (q+p)*(q-p+1)-1 ) rnd
+
+-- | Nth sequence of nested parentheses of length 2n. 
+-- The order is the same as in 'fasc4A_algorithm_P'.
+-- Based on \"Algorithm U\" in Knuth.
+fasc4A_algorithm_U 
+  :: Int               -- ^ n
+  -> Integer           -- ^ N; should satisfy 1 <= N <= C(n) 
+  -> [Paren]
+fasc4A_algorithm_U n' bign0 = reverse $ worker (bign0,c0,n,n,[]) where
+  n = fromIntegral n' :: Integer
+  c0 = foldl f 1 [2..n]  
+  f c p = ((4*p-2)*c) `div` (p+1) 
+  worker :: (Integer,Integer,Integer,Integer,[Paren]) -> [Paren]
+  worker (_   ,_,_,0,parens) = parens
+  worker (bign,c,p,q,parens) = 
+    if bign <= c' 
+      then worker (bign    , c'   , p   , q-1 , RightParen:parens)
+      else worker (bign-c' , c-c' , p-1 , q   , LeftParen :parens)
+    where
+      c' = ((q+1)*(q-p)*c) `div` ((q+p)*(q-p+1))
+  
+--------------------------------------------------------------------------------
+-- * Generating binary trees
+
+-- | Generates all binary trees with @n@ nodes. 
+--   At the moment just a synonym for 'binaryTreesNaive'.
+binaryTrees :: Int -> [BinTree ()]
+binaryTrees = binaryTreesNaive
+
+-- | # = Catalan(n) = \\frac { 1 } { n+1 } \\binom { 2n } { n }.
+--
+-- This is also the counting function for forests and nested parentheses.
+countBinaryTrees :: Int -> Integer
+countBinaryTrees n = binomial (2*n) n `div` (1 + fromIntegral n)
+    
+-- | Generates all binary trees with n nodes. The naive algorithm.
+binaryTreesNaive :: Int -> [BinTree ()]
+binaryTreesNaive 0 = [ leaf ]
+binaryTreesNaive n = 
+  [ Branch l r 
+  | i <- [0..n-1] 
+  , l <- binaryTreesNaive i 
+  , r <- binaryTreesNaive (n-1-i) 
+  ]
+
+-- | Generates an uniformly random binary tree, using 'fasc4A_algorithm_R'.
+randomBinaryTree :: RandomGen g => Int -> g -> (BinTree (), g)
+randomBinaryTree n rnd = (tree,rnd') where
+  (decorated,rnd') = fasc4A_algorithm_R n rnd      
+  tree = fmap (const ()) $ forgetNodeDecorations decorated
+
+-- | Grows a uniformly random binary tree. 
+-- \"Algorithm R\" (Remy's procudere) in Knuth.
+-- Nodes are decorated with odd numbers, leaves with even numbers (from the
+-- set @[0..2n]@). Uses mutable arrays internally.
+fasc4A_algorithm_R :: RandomGen g => Int -> g -> (BinTree' Int Int, g)
+fasc4A_algorithm_R n0 rnd = res where
+  res = runST $ do
+    ar <- newArray (0,2*n0) 0
+    rnd' <- worker rnd 1 ar
+    links <- Data.Array.Unsafe.unsafeFreeze ar
+    return (toTree links, rnd')
+  toTree links = f (links!0) where
+    f i = if odd i 
+      then Branch' (f $ links!i) i (f $ links!(i+1)) 
+      else Leaf' i  
+  worker :: RandomGen g => g -> Int -> STUArray s Int Int -> ST s g
+  worker rnd n ar = do 
+    if n > n0
+      then return rnd
+      else do
+        writeArray ar (n2-b)   n2
+        lk <- readArray ar k
+        writeArray ar (n2-1+b) lk
+        writeArray ar k        (n2-1)
+        worker rnd' (n+1) ar      
+    where  
+      n2 = n+n
+      (x,rnd') = randomR (0,4*n-3) rnd
+      (k,b) = x `divMod` 2
+      
+--------------------------------------------------------------------------------      
+-- * ASCII drawing  
+
+-- | Draws a binary tree in ASCII, ignoring node labels.
+--
+-- Example:
+--
+-- > autoTabulate RowMajor (Right 5) $ map asciiBinaryTree_ $ binaryTrees 4
+--
+asciiBinaryTree_ :: BinTree a -> ASCII
+asciiBinaryTree_ = ASCII.asciiFromLines . fst . go where
+
+  go :: BinTree a -> ([String],Int)
+  go (Leaf x) = ([],0)
+  go (Branch t1 t2) = ( new , j1+m ) where
+    (ls1,j1) = go t1
+    (ls2,j2) = go t2
+    w1 = blockWidth ls1
+    w2 = blockWidth ls2
+    m = max 1 $ (w1-j1+j2+2) `div` 2
+    s = 2*m - (w1-j1+j2)
+    spaces = [replicate s ' ']
+    ls = hConcatLines [ ls1 , spaces , ls2 ]
+    top = [ replicate (j1+m-i) ' ' ++ "/" ++ replicate (2*(i-1)) ' ' ++ "\\" | i<-[1..m] ]
+    new = mkLinesUniformWidth $ vConcatLines [ top , ls ] 
+        
+  blockWidth ls = case ls of
+    (l:_) -> length l
+    []    -> 0
+
+instance DrawASCII (BinTree ()) where
+  ascii = asciiBinaryTree_ 
+
+--------------------------------------------------------------------------------      
diff --git a/Math/Combinat/Trees/Binary.hs-boot b/Math/Combinat/Trees/Binary.hs-boot
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Trees/Binary.hs-boot
@@ -0,0 +1,22 @@
+
+
+module Math.Combinat.Trees.Binary where
+
+--------------------------------------------------------------------------------
+
+import Data.Tree ( Tree(..) , Forest(..) )
+
+--------------------------------------------------------------------------------
+
+-- | A binary tree with leaves decorated with type @a@.
+data BinTree a
+  = Branch (BinTree a) (BinTree a)
+  | Leaf a
+
+-- | A binary tree with leaves and internal nodes decorated 
+-- with types @a@ and @b@, respectively.
+data BinTree' a b
+  = Branch' (BinTree' a b) b (BinTree' a b)
+  | Leaf' a
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Trees/Graphviz.hs b/Math/Combinat/Trees/Graphviz.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Trees/Graphviz.hs
@@ -0,0 +1,115 @@
+
+-- | Creates graphviz @.dot@ files from trees.
+
+module Math.Combinat.Trees.Graphviz 
+  ( Dot
+  , graphvizDotBinTree
+  , graphvizDotBinTree'
+  , graphvizDotForest
+  , graphvizDotTree
+  )
+  where
+
+--------------------------------------------------------------------------------
+
+import Data.Tree
+
+import Control.Applicative
+
+import {-# SOURCE #-} Math.Combinat.Trees.Binary ( BinTree(..)         , BinTree'(..)          )
+import {-# SOURCE #-} Math.Combinat.Trees.Nary   ( addUniqueLabelsTree , addUniqueLabelsForest )
+
+--------------------------------------------------------------------------------
+
+type Dot = String
+
+digraphBracket :: String -> [String] -> String   
+digraphBracket name lines = 
+  "digraph " ++ name ++ " {\n" ++ 
+  concatMap (\xs -> "  "++xs++"\n") lines    
+  ++ "}\n"
+  
+--------------------------------------------------------------------------------
+
+graphvizDotBinTree :: Show a => String -> BinTree a -> Dot
+graphvizDotBinTree graphname tree = 
+  digraphBracket graphname $ binTreeDot' tree
+
+graphvizDotBinTree' :: (Show a, Show b) => String -> BinTree' a b -> Dot
+graphvizDotBinTree' graphname tree = 
+  digraphBracket graphname $ binTree'Dot' tree
+  
+binTreeDot' :: Show a => BinTree a -> [String]
+binTreeDot' tree = lines where
+  lines = worker (0::Int) "r" tree 
+  name path = "node_"++path
+  worker depth path (Leaf x) = 
+    [ name path ++ "[shape=box,label=\"" ++ show x ++ "\"" ++ "];" ]
+  worker depth path (Branch left right) 
+    = [vertex,leftedge,rightedge] ++ 
+      worker (depth+1) ('l':path) left ++ 
+      worker (depth+1) ('r':path) right
+    where 
+      vertex = name path ++ "[shape=circle,style=filled,height=0.25,label=\"\"];"
+      leftedge  = name path ++ " -> " ++ name ('l':path) ++ "[tailport=sw];"
+      rightedge = name path ++ " -> " ++ name ('r':path) ++ "[tailport=se];"
+
+binTree'Dot' :: (Show a, Show b) => BinTree' a b -> [String]
+binTree'Dot' tree = lines where
+  lines = worker (0::Int) "r" tree 
+  name path = "node_"++path
+  worker depth path (Leaf' x) = 
+    [ name path ++ "[shape=box,label=\"" ++ show x ++ "\"" ++ "];" ]
+  worker depth path (Branch' left y right) 
+    = [vertex,leftedge,rightedge] ++ 
+      worker (depth+1) ('l':path) left ++ 
+      worker (depth+1) ('r':path) right
+    where 
+      vertex = name path ++ "[shape=ellipse,label=\"" ++ show y ++ "\"];"
+      leftedge  = name path ++ " -> " ++ name ('l':path) ++ "[tailport=sw];"
+      rightedge = name path ++ " -> " ++ name ('r':path) ++ "[tailport=se];"
+
+--------------------------------------------------------------------------------
+    
+-- | Generates graphviz @.dot@ file from a forest. The first argument tells whether
+-- to make the individual trees clustered subgraphs; the second is the name of the
+-- graph.
+graphvizDotForest
+  :: Show a 
+  => Bool        -- ^ make the individual trees clustered subgraphs
+  -> Bool        -- ^ reverse the direction of the arrows
+  -> String      -- ^ name of the graph
+  -> Forest a 
+  -> Dot
+graphvizDotForest clustered revarrows graphname forest = digraphBracket graphname lines where
+  lines = concat $ zipWith cluster [(1::Int)..] (addUniqueLabelsForest forest) 
+  name unique = "node_"++show unique
+  cluster j tree = let treelines = worker (0::Int) tree in case clustered of
+    False -> treelines
+    True  -> ("subgraph cluster_"++show j++" {") : map ("  "++) treelines ++ ["}"] 
+  worker depth (Node (label,unique) subtrees) = vertex : edges ++ concatMap (worker (depth+1)) subtrees where
+    vertex = name unique ++ "[label=\"" ++ show label ++ "\"" ++ "];"
+    edges = map edge subtrees
+    edge (Node (_,unique') _) = if not revarrows 
+      then name unique  ++ " -> " ++ name unique'   
+      else name unique' ++ " -> " ++ name unique
+      
+-- | Generates graphviz @.dot@ file from a tree. The first argument is
+-- the name of the graph.
+graphvizDotTree
+  :: Show a 
+  => Bool     -- ^ reverse the direction of the arrow
+  -> String   -- ^ name of the graph
+  -> Tree a 
+  -> Dot
+graphvizDotTree revarrows graphname tree = digraphBracket graphname lines where
+  lines = worker (0::Int) (addUniqueLabelsTree tree) 
+  name unique = "node_"++show unique
+  worker depth (Node (label,unique) subtrees) = vertex : edges ++ concatMap (worker (depth+1)) subtrees where
+    vertex = name unique ++ "[label=\"" ++ show label ++ "\"" ++ "];"
+    edges = map edge subtrees
+    edge (Node (_,unique') _) = if not revarrows 
+      then name unique  ++ " -> " ++ name unique'   
+      else name unique' ++ " -> " ++ name unique
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Trees/Nary.hs b/Math/Combinat/Trees/Nary.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Trees/Nary.hs
@@ -0,0 +1,432 @@
+
+-- | N-ary trees.
+
+{-# LANGUAGE FlexibleInstances    #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+module Math.Combinat.Trees.Nary
+  (
+    -- * Types
+    module Data.Tree
+  , Tree(..)
+    -- * Regular trees
+  , ternaryTrees
+  , regularNaryTrees
+  , semiRegularTrees
+  , countTernaryTrees
+  , countRegularNaryTrees
+    -- * \"derivation trees\"
+  , derivTrees
+    -- * ASCII drawings
+  , asciiTreeVertical_
+  , asciiTreeVertical
+  , asciiTreeVerticalLeavesOnly
+    -- * Graphviz drawing
+  , Dot
+  , graphvizDotTree
+  , graphvizDotForest
+    -- * Classifying nodes
+  , classifyTreeNode
+  , isTreeLeaf  , isTreeNode
+  , isTreeLeaf_ , isTreeNode_
+  , treeNodeNumberOfChildren
+    -- * Counting nodes
+  , countTreeNodes
+  , countTreeLeaves
+  , countTreeLabelsWith
+  , countTreeNodesWith
+    -- * Left and right spines
+  , leftSpine  , leftSpine_
+  , rightSpine , rightSpine_
+  , leftSpineLength , rightSpineLength
+    -- * Unique labels
+  , addUniqueLabelsTree
+  , addUniqueLabelsForest
+  , addUniqueLabelsTree_
+  , addUniqueLabelsForest_
+    -- * Labelling by depth
+  , labelDepthTree
+  , labelDepthForest
+  , labelDepthTree_
+  , labelDepthForest_
+    -- * Labelling by number of children
+  , labelNChildrenTree
+  , labelNChildrenForest
+  , labelNChildrenTree_
+  , labelNChildrenForest_
+
+  ) where
+
+
+--------------------------------------------------------------------------------
+
+import           Data.List
+import           Data.Tree
+
+import           Control.Applicative
+
+--import Control.Monad.State
+import           Control.Monad.Trans.State
+import           Data.Traversable                  (traverse)
+
+import           Math.Combinat.Compositions        (compositions)
+import           Math.Combinat.Numbers             (binomial, factorial)
+import           Math.Combinat.Partitions.Multiset (partitionMultiset)
+import           Math.Combinat.Sets                (listTensor)
+
+import           Math.Combinat.Trees.Graphviz      (Dot, graphvizDotForest,
+                                                    graphvizDotTree)
+
+import           Math.Combinat.ASCII               as ASCII
+import           Math.Combinat.Classes
+import           Math.Combinat.Helper
+
+--------------------------------------------------------------------------------
+
+instance HasNumberOfNodes (Tree a) where
+  numberOfNodes = go where
+    go (Node label subforest) = if null subforest
+      then 0
+      else 1 + sum' (map go subforest)
+
+instance HasNumberOfLeaves (Tree a) where
+  numberOfLeaves = go where
+    go (Node label subforest) = if null subforest
+      then 1
+      else sum' (map go subforest)
+
+--------------------------------------------------------------------------------
+
+-- | @regularNaryTrees d n@ returns the list of (rooted) trees on @n@ nodes where each
+-- node has exactly @d@ children. Note that the leaves do not count in @n@.
+-- Naive algorithm.
+regularNaryTrees
+  :: Int         -- ^ degree = number of children of each node
+  -> Int         -- ^ number of nodes
+  -> [Tree ()]
+regularNaryTrees d = go where
+  go 0 = [ Node () [] ]
+  go n = [ Node () cs
+         | is <- compositions d (n-1)
+         , cs <- listTensor [ go i | i<-is ]
+         ]
+
+-- | Ternary trees on @n@ nodes (synonym for @regularNaryTrees 3@)
+ternaryTrees :: Int -> [Tree ()]
+ternaryTrees = regularNaryTrees 3
+
+-- | We have
+--
+-- > length (regularNaryTrees d n) == countRegularNaryTrees d n == \frac {1} {(d-1)n+1} \binom {dn} {n}
+--
+countRegularNaryTrees :: (Integral a, Integral b) => a -> b -> Integer
+countRegularNaryTrees d n = binomial (dd*nn) nn `div` ((dd-1)*nn+1) where
+  dd = fromIntegral d :: Integer
+  nn = fromIntegral n :: Integer
+
+-- | @\# = \\frac {1} {(2n+1} \\binom {3n} {n}@
+countTernaryTrees :: Integral a => a -> Integer
+countTernaryTrees = countRegularNaryTrees (3::Int)
+
+--------------------------------------------------------------------------------
+
+-- | All trees on @n@ nodes where the number of children of all nodes is
+-- in element of the given set. Example:
+--
+-- > autoTabulate RowMajor (Right 5) $ map asciiTreeVertical
+-- >                                 $ map labelNChildrenTree_
+-- >                                 $ semiRegularTrees [2,3] 2
+-- >
+-- > [ length $ semiRegularTrees [2,3] n | n<-[0..] ] == [1,2,10,66,498,4066,34970,312066,2862562,26824386,...]
+--
+-- The latter sequence is A027307 in OEIS: <https://oeis.org/A027307>
+--
+-- Remark: clearly, we have
+--
+-- > semiRegularTrees [d] n == regularNaryTrees d n
+--
+--
+semiRegularTrees
+  :: [Int]         -- ^ set of allowed number of children
+  -> Int           -- ^ number of nodes
+  -> [Tree ()]
+semiRegularTrees []    n = if n==0 then [Node () []] else []
+semiRegularTrees dset_ n =
+  if head dset >=1
+    then go n
+    else error "semiRegularTrees: expecting a list of positive integers"
+  where
+    dset = map head $ group $ sort $ dset_
+
+    go 0 = [ Node () [] ]
+    go n = [ Node () cs
+           | d <- dset
+           , is <- compositions d (n-1)
+           , cs <- listTensor [ go i | i<-is ]
+           ]
+
+{-
+
+NOTES:
+
+A006318 = [ length $ semiRegularTrees [1,2] n | n<-[0..] ] == [1,2,6,22,90,394,1806,8558,41586,206098,1037718.. ]
+??      = [ length $ semiRegularTrees [1,3] n | n<-[0..] ] == [1,2,8,44,280,1936,14128,107088,834912,6652608 .. ]
+??      = [ length $ semiRegularTrees [1,4] n | n<-[0..] ] == [1,2,10,74,642,6082,60970,635818,6826690
+
+A027307 = [ length $ semiRegularTrees [2,3] n | n<-[0..] ] == [1,2,10,66,498,4066,34970,312066,2862562,26824386,...]
+A219534 = [ length $ semiRegularTrees [2,4] n | n<-[0..] ] == [1,2,12,100,968,10208,113792,1318832 ..]
+??      = [ length $ semiRegularTrees [2,5] n | n<-[0..] ] == [1,2,14,142,1690,21994,303126,4348102 ..]
+
+A144097 = [ length $ semiRegularTrees [3,4] n | n<-[0..] ] == [1,2,14,134,1482,17818,226214,2984206,40503890..]
+
+A107708 = [ length $ semiRegularTrees [1,2,3]   n | n<-[0..] ] == [1,3,18,144,1323,13176,138348,1507977 .. ]
+??      = [ length $ semiRegularTrees [1,2,3,4] n | n<-[0..] ] == [1,4,40,560,9120,161856,3036800,59242240 .. ]
+
+-}
+
+--------------------------------------------------------------------------------
+
+-- | Vertical ASCII drawing of a tree, without labels. Example:
+--
+-- > autoTabulate RowMajor (Right 5) $ map asciiTreeVertical_ $ regularNaryTrees 2 4
+--
+-- Nodes are denoted by @\@@, leaves by @*@.
+--
+asciiTreeVertical_ :: Tree a -> ASCII
+asciiTreeVertical_ tree = ASCII.asciiFromLines (go tree) where
+  go :: Tree b -> [String]
+  go (Node _ cs) = case cs of
+    [] -> ["-*"]
+    _  -> concat $ mapWithFirstLast f $ map go cs
+
+  f :: Bool -> Bool -> [String] -> [String]
+  f bf bl (l:ls) = let indent = if bl           then "  "  else  "| "
+                       gap    = if bl           then []    else ["| "]
+                       branch = if bl && not bf
+                                  then "\\-"
+                                  else if bf then "@-"
+                                             else "+-"
+                   in  (branch++l) : map (indent++) ls ++ gap
+
+instance DrawASCII (Tree ()) where
+  ascii = asciiTreeVertical_
+
+-- | Prints all labels. Example:
+--
+-- > asciiTreeVertical $ addUniqueLabelsTree_ $ (regularNaryTrees 3 9) !! 666
+--
+-- Nodes are denoted by @(label)@, leaves by @label@.
+--
+asciiTreeVertical :: Show a => Tree a -> ASCII
+asciiTreeVertical tree = ASCII.asciiFromLines (go tree) where
+  go :: Show b => Tree b -> [String]
+  go (Node x cs) = case cs of
+    [] -> ["-- " ++ show x]
+    _  -> concat $ mapWithFirstLast (f (show x)) $ map go cs
+
+  f :: String -> Bool -> Bool -> [String] -> [String]
+  f label bf bl (l:ls) =
+        let spaces = (map (const ' ') label  )
+            dashes = (map (const '-') spaces )
+            indent = if bl then "  " ++spaces++"  " else  " |" ++ spaces ++ "  "
+            gap    = if bl then []                  else [" |" ++ spaces ++ "  "]
+            branch = if bl && not bf
+                           then " \\"++dashes++"--"
+                           else if bf
+                             then "-(" ++ label  ++ ")-"
+                             else " +" ++ dashes ++ "--"
+        in  (branch++l) : map (indent++) ls ++ gap
+
+-- | Prints the labels for the leaves, but not for the  nodes.
+asciiTreeVerticalLeavesOnly :: Show a => Tree a -> ASCII
+asciiTreeVerticalLeavesOnly tree = ASCII.asciiFromLines (go tree) where
+  go :: Show b => Tree b -> [String]
+  go (Node x cs) = case cs of
+    [] -> ["- " ++ show x]
+    _  -> concat $ mapWithFirstLast f $ map go cs
+
+  f :: Bool -> Bool -> [String] -> [String]
+  f bf bl (l:ls) = let indent = if bl           then "  "  else  "| "
+                       gap    = if bl           then []    else ["| "]
+                       branch = if bl && not bf
+                                  then "\\-"
+                                  else if bf then "@-"
+                                             else "+-"
+                   in  (branch++l) : map (indent++) ls ++ gap
+
+--------------------------------------------------------------------------------
+
+-- | The leftmost spine (the second element of the pair is the leaf node)
+leftSpine  :: Tree a -> ([a],a)
+leftSpine = go where
+  go (Node x cs) = case cs of
+    [] -> ([],x)
+    _  -> let (xs,y) = go (head cs) in (x:xs,y)
+
+rightSpine  :: Tree a -> ([a],a)
+rightSpine = go where
+  go (Node x cs) = case cs of
+    [] -> ([],x)
+    _  -> let (xs,y) = go (last cs) in (x:xs,y)
+
+-- | The leftmost spine without the leaf node
+leftSpine_  :: Tree a -> [a]
+leftSpine_ = go where
+  go (Node x cs) = case cs of
+    [] -> []
+    _  -> x : go (head cs)
+
+rightSpine_ :: Tree a -> [a]
+rightSpine_ = go where
+  go (Node x cs) = case cs of
+    [] -> []
+    _  -> x : go (last cs)
+
+-- | The length (number of edges) on the left spine
+--
+-- > leftSpineLength tree == length (leftSpine_ tree)
+--
+leftSpineLength  :: Tree a -> Int
+leftSpineLength = go 0 where
+  go n (Node x cs) = case cs of
+    [] -> n
+    _  -> go (n+1) (head cs)
+
+rightSpineLength :: Tree a -> Int
+rightSpineLength = go 0 where
+  go n (Node x cs) = case cs of
+    [] -> n
+    _  -> go (n+1) (last cs)
+
+--------------------------------------------------------------------------------
+
+-- | 'Left' is leaf, 'Right' is node
+classifyTreeNode :: Tree a -> Either a a
+classifyTreeNode (Node x cs) = case cs of { [] -> Left x ; _ -> Right x }
+
+isTreeLeaf :: Tree a -> Maybe a
+isTreeLeaf (Node x cs) = case cs of { [] -> Just x ; _ -> Nothing }
+
+isTreeNode :: Tree a -> Maybe a
+isTreeNode (Node x cs) = case cs of { [] -> Nothing ; _ -> Just x }
+
+isTreeLeaf_ :: Tree a -> Bool
+isTreeLeaf_ (Node x cs) = case cs of { [] -> True ; _ -> False }
+
+isTreeNode_ :: Tree a -> Bool
+isTreeNode_ (Node x cs) = case cs of { [] -> False ; _ -> True }
+
+treeNodeNumberOfChildren :: Tree a -> Int
+treeNodeNumberOfChildren (Node _ cs) = length cs
+
+--------------------------------------------------------------------------------
+-- counting
+
+countTreeNodes :: Tree a -> Int
+countTreeNodes = go where
+  go (Node x cs) = case cs of
+    [] -> 0
+    _  -> 1 + sum (map go cs)
+
+countTreeLeaves :: Tree a -> Int
+countTreeLeaves = go where
+  go (Node x cs) = case cs of
+    [] -> 1
+    _  -> sum (map go cs)
+
+countTreeLabelsWith :: (a -> Bool) -> Tree a -> Int
+countTreeLabelsWith f = go where
+  go (Node label cs) = (if f label then 1 else 0) + sum (map go cs)
+
+countTreeNodesWith :: (Tree a -> Bool) -> Tree a -> Int
+countTreeNodesWith f = go where
+  go node@(Node _ cs) = (if f node then 1 else 0) + sum (map go cs)
+
+--------------------------------------------------------------------------------
+
+-- | Adds unique labels to the nodes (including leaves) of a 'Tree'.
+addUniqueLabelsTree :: Tree a -> Tree (a,Int)
+addUniqueLabelsTree tree = head (addUniqueLabelsForest [tree])
+
+-- | Adds unique labels to the nodes (including leaves) of a 'Forest'
+addUniqueLabelsForest :: Forest a -> Forest (a,Int)
+addUniqueLabelsForest forest = evalState (mapM globalAction forest) 1 where
+  globalAction tree =
+    unwrapMonad $ traverse localAction tree
+  localAction x = WrapMonad $ do
+    i <- get
+    put (i+1)
+    return (x,i)
+
+addUniqueLabelsTree_ :: Tree a -> Tree Int
+addUniqueLabelsTree_ = fmap snd . addUniqueLabelsTree
+
+addUniqueLabelsForest_ :: Forest a -> Forest Int
+addUniqueLabelsForest_ = map (fmap snd) . addUniqueLabelsForest
+
+--------------------------------------------------------------------------------
+
+-- | Attaches the depth to each node. The depth of the root is 0.
+labelDepthTree :: Tree a -> Tree (a,Int)
+labelDepthTree tree = worker 0 tree where
+  worker depth (Node label subtrees) = Node (label,depth) (map (worker (depth+1)) subtrees)
+
+labelDepthForest :: Forest a -> Forest (a,Int)
+labelDepthForest forest = map labelDepthTree forest
+
+labelDepthTree_ :: Tree a -> Tree Int
+labelDepthTree_ = fmap snd . labelDepthTree
+
+labelDepthForest_ :: Forest a -> Forest Int
+labelDepthForest_ = map (fmap snd) . labelDepthForest
+
+--------------------------------------------------------------------------------
+
+-- | Attaches the number of children to each node.
+labelNChildrenTree :: Tree a -> Tree (a,Int)
+labelNChildrenTree (Node x subforest) =
+  Node (x, length subforest) (map labelNChildrenTree subforest)
+
+labelNChildrenForest :: Forest a -> Forest (a,Int)
+labelNChildrenForest forest = map labelNChildrenTree forest
+
+labelNChildrenTree_ :: Tree a -> Tree Int
+labelNChildrenTree_ = fmap snd . labelNChildrenTree
+
+labelNChildrenForest_ :: Forest a -> Forest Int
+labelNChildrenForest_ = map (fmap snd) . labelNChildrenForest
+
+--------------------------------------------------------------------------------
+
+-- | Computes the set of equivalence classes of rooted trees (in the
+-- sense that the leaves of a node are /unordered/)
+-- with @n = length ks@ leaves where the set of heights of
+-- the leaves matches the given set of numbers.
+-- The height is defined as the number of /edges/ from the leaf to the root.
+--
+-- TODO: better name?
+derivTrees :: [Int] -> [Tree ()]
+derivTrees xs = derivTrees' (map (+1) xs)
+
+derivTrees' :: [Int] -> [Tree ()]
+derivTrees' [] = []
+derivTrees' [n] =
+  if n>=1
+    then [unfoldTree f 1]
+    else []
+  where
+    f k = if k<n then ((),[k+1]) else ((),[])
+derivTrees' ks =
+  if and (map (>0) ks)
+    then
+      [ Node () sub
+      | part <- parts
+      , let subtrees = map g part
+      , sub <- listTensor subtrees
+      ]
+    else []
+  where
+    parts = partitionMultiset ks
+    g xs = derivTrees' (map (\x->x-1) xs)
+
+--------------------------------------------------------------------------------
+
diff --git a/Math/Combinat/Trees/Nary.hs-boot b/Math/Combinat/Trees/Nary.hs-boot
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Trees/Nary.hs-boot
@@ -0,0 +1,16 @@
+
+module Math.Combinat.Trees.Nary where
+
+--------------------------------------------------------------------------------
+
+import Data.Tree
+
+--------------------------------------------------------------------------------
+
+addUniqueLabelsTree   :: Tree   a -> Tree   (a,Int) 
+addUniqueLabelsForest :: Forest a -> Forest (a,Int) 
+
+addUniqueLabelsTree_   :: Tree   a -> Tree   Int
+addUniqueLabelsForest_ :: Forest a -> Forest Int
+
+--------------------------------------------------------------------------------
diff --git a/Math/Combinat/Tuples.hs b/Math/Combinat/Tuples.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/Tuples.hs
@@ -0,0 +1,61 @@
+
+-- | Tuples.
+
+module Math.Combinat.Tuples where
+
+import Math.Combinat.Helper
+
+-------------------------------------------------------
+-- Tuples
+
+-- | \"Tuples\" fitting into a give shape. The order is lexicographic, that is,
+--
+-- > sort ts == ts where ts = tuples' shape
+--
+--   Example: 
+--
+-- > tuples' [2,3] = 
+-- >   [[0,0],[0,1],[0,2],[0,3],[1,0],[1,1],[1,2],[1,3],[2,0],[2,1],[2,2],[2,3]]
+--
+tuples' :: [Int] -> [[Int]]
+tuples' [] = [[]]
+tuples' (s:ss) = [ x:xs | x <- [0..s] , xs <- tuples' ss ] 
+
+-- | positive \"tuples\" fitting into a give shape.
+tuples1' :: [Int] -> [[Int]]
+tuples1' [] = [[]]
+tuples1' (s:ss) = [ x:xs | x <- [1..s] , xs <- tuples1' ss ] 
+
+-- | # = \\prod_i (m_i + 1)
+countTuples' :: [Int] -> Integer
+countTuples' shape = product $ map f shape where
+  f k = 1 + fromIntegral k
+
+-- | # = \\prod_i m_i
+countTuples1' :: [Int] -> Integer
+countTuples1' shape = product $ map fromIntegral shape
+
+tuples 
+  :: Int    -- ^ length (width)
+  -> Int    -- ^ maximum (height)
+  -> [[Int]]
+tuples len k = tuples' (replicate len k)
+
+tuples1 
+  :: Int    -- ^ length (width)
+  -> Int    -- ^ maximum (height)
+  -> [[Int]]
+tuples1 len k = tuples1' (replicate len k)
+
+-- | # = (m+1) ^ len
+countTuples :: Int -> Int -> Integer
+countTuples len k = (1 + fromIntegral k) ^ len
+
+-- | # = m ^ len
+countTuples1 :: Int -> Int -> Integer
+countTuples1 len k = fromIntegral k ^ len
+
+binaryTuples :: Int -> [[Bool]]
+binaryTuples len = map (map intToBool) (tuples len 1)
+
+-------------------------------------------------------
diff --git a/Math/Combinat/TypeLevel.hs b/Math/Combinat/TypeLevel.hs
new file mode 100644
--- /dev/null
+++ b/Math/Combinat/TypeLevel.hs
@@ -0,0 +1,117 @@
+
+-- | Type-level hackery.
+--
+-- This module is used for groups whose parameters are encoded as type-level natural numbers,
+-- for example finite cyclic groups, free groups, symmetric groups and braid groups.
+--
+
+{-# LANGUAGE PolyKinds, DataKinds, KindSignatures, ScopedTypeVariables, 
+             ExistentialQuantification, Rank2Types #-}
+
+module Math.Combinat.TypeLevel 
+  ( -- * Proxy
+    Proxy(..)
+  , proxyUndef
+  , proxyOf
+  , proxyOf1
+  , proxyOf2
+  , asProxyTypeOf   -- defined in Data.Proxy
+  , asProxyTypeOf1
+    -- * Type-level naturals as type arguments
+  , typeArg 
+  , iTypeArg
+    -- * Hiding the type parameter
+  , Some (..)
+  , withSome , withSomeM
+  , selectSome , selectSomeM
+  , withSelected , withSelectedM
+  )
+  where
+
+--------------------------------------------------------------------------------
+
+import Data.Proxy ( Proxy(..) , asProxyTypeOf )
+import GHC.TypeLits
+
+import Math.Combinat.Numbers.Primes ( isProbablyPrime )
+
+--------------------------------------------------------------------------------
+-- * Proxy
+
+proxyUndef :: Proxy a -> a
+proxyUndef _ = error "proxyUndef"
+
+proxyOf :: a -> Proxy a
+proxyOf _ = Proxy
+
+proxyOf1 :: f a -> Proxy a
+proxyOf1 _ = Proxy
+
+proxyOf2 :: g (f a) -> Proxy a
+proxyOf2 _ = Proxy
+
+asProxyTypeOf1 :: f a -> Proxy a -> f a 
+asProxyTypeOf1 y _ = y
+
+--------------------------------------------------------------------------------
+-- * Type-level naturals as type arguments
+
+typeArg :: KnownNat n => f (n :: Nat) -> Integer
+typeArg = natVal . proxyOf1
+
+iTypeArg :: KnownNat n => f (n :: Nat) -> Int
+iTypeArg = fromIntegral . typeArg
+
+--------------------------------------------------------------------------------
+-- * Hiding the type parameter
+
+-- | Hide the type parameter of a functor. Example: @Some Braid@
+data Some f = forall n. KnownNat n => Some (f n)
+
+-- | Uses the value inside a 'Some'
+withSome :: Some f -> (forall n. KnownNat n => f n -> a) -> a
+withSome some polyFun = case some of { Some stuff -> polyFun stuff }
+
+-- | Monadic version of 'withSome'
+withSomeM :: Monad m => Some f -> (forall n. KnownNat n => f n -> m a) -> m a
+withSomeM some polyAct = case some of { Some stuff -> polyAct stuff }
+
+-- | Given a polymorphic value, we select at run time the
+-- one specified by the second argument
+selectSome :: Integral int => (forall n. KnownNat n => f n) -> int -> Some f
+selectSome poly n = case someNatVal (fromIntegral n :: Integer) of
+  Nothing   -> error "selectSome: not a natural number"
+  Just snat -> case snat of
+    SomeNat pxy -> Some (asProxyTypeOf1 poly pxy)
+
+-- | Monadic version of 'selectSome'
+selectSomeM :: forall m f int. (Integral int, Monad m) => (forall n. KnownNat n => m (f n)) -> int -> m (Some f)
+selectSomeM runPoly n = case someNatVal (fromIntegral n :: Integer) of
+  Nothing   -> error "selectSomeM: not a natural number"
+  Just snat -> case snat of
+    SomeNat pxy -> do
+      poly <- runPoly 
+      return $ Some (asProxyTypeOf1 poly pxy)
+
+-- | Combination of 'selectSome' and 'withSome': we make a temporary structure
+-- of the given size, but we immediately consume it.
+withSelected 
+  :: Integral int 
+  => (forall n. KnownNat n => f n -> a) 
+  -> (forall n. KnownNat n => f n) 
+  -> int 
+  -> a
+withSelected f x n = withSome (selectSome x n) f
+
+-- | (Half-)monadic version of 'withSelected'
+withSelectedM 
+  :: forall m f int a. (Integral int, Monad m) 
+  => (forall n. KnownNat n => f n -> a) 
+  -> (forall n. KnownNat n => m (f n)) 
+  -> int 
+  -> m a
+withSelectedM f mx n = do 
+  s <- selectSomeM mx n
+  return (withSome s f)
+
+--------------------------------------------------------------------------------
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,3 @@
+#! /usr/bin/env runhaskell
+> import Distribution.Simple
+> main = defaultMain
diff --git a/combinat-compat.cabal b/combinat-compat.cabal
new file mode 100644
--- /dev/null
+++ b/combinat-compat.cabal
@@ -0,0 +1,102 @@
+cabal-version: 1.18
+name: combinat-compat
+version: 0.2.8.2
+license: BSD3
+license-file: LICENSE
+copyright: (c) 2008-2016 Balazs Komuves
+maintainer: bkomuves (plus) hackage (at) gmail (dot) com
+author: Balazs Komuves
+stability: Experimental
+tested-with: ghc ==8.4.1
+homepage: http://code.haskell.org/~bkomuves/
+synopsis: Generate and manipulate various combinatorial objects.
+description:
+    A collection of functions to generate, count, manipulate
+    and visualize all kinds of combinatorial objects like
+    partitions, compositions, trees, permutations, braids,
+    Young tableaux, and so on.
+    Forked from the [combinat](http://hackage.haskell.org/package/combinat) package.
+category: Math
+build-type: Simple
+extra-source-files:
+    svg/*.svg
+    svg/src/gen_figures.hs
+extra-doc-files: svg/*.svg
+
+library
+    exposed-modules:
+        Math.Combinat
+        Math.Combinat.Classes
+        Math.Combinat.Numbers
+        Math.Combinat.Numbers.Series
+        Math.Combinat.Numbers.Primes
+        Math.Combinat.Sign
+        Math.Combinat.Sets
+        Math.Combinat.Tuples
+        Math.Combinat.Compositions
+        Math.Combinat.Groups.Thompson.F
+        Math.Combinat.Groups.Free
+        Math.Combinat.Groups.Braid
+        Math.Combinat.Groups.Braid.NF
+        Math.Combinat.Partitions
+        Math.Combinat.Partitions.Integer
+        Math.Combinat.Partitions.Skew
+        Math.Combinat.Partitions.Set
+        Math.Combinat.Partitions.NonCrossing
+        Math.Combinat.Partitions.Plane
+        Math.Combinat.Partitions.Multiset
+        Math.Combinat.Partitions.Vector
+        Math.Combinat.Permutations
+        Math.Combinat.Tableaux
+        Math.Combinat.Tableaux.Skew
+        Math.Combinat.Tableaux.GelfandTsetlin
+        Math.Combinat.Tableaux.GelfandTsetlin.Cone
+        Math.Combinat.Tableaux.LittlewoodRichardson
+        Math.Combinat.Trees
+        Math.Combinat.Trees.Binary
+        Math.Combinat.Trees.Nary
+        Math.Combinat.Trees.Graphviz
+        Math.Combinat.LatticePaths
+        Math.Combinat.ASCII
+        Math.Combinat.Helper
+        Math.Combinat.TypeLevel
+    hs-source-dirs: .
+    default-language: Haskell2010
+    default-extensions: CPP BangPatterns
+    other-extensions: MultiParamTypeClasses ScopedTypeVariables
+                      GeneralizedNewtypeDeriving DataKinds KindSignatures
+    ghc-options: -fwarn-tabs -fno-warn-unused-matches
+                 -fno-warn-name-shadowing -fno-warn-unused-imports
+    build-depends:
+        base >=4.11 && <5,
+        array >=0.5,
+        containers -any,
+        random -any,
+        transformers -any
+
+test-suite combinat-tests
+    type: exitcode-stdio-1.0
+    main-is: TestSuite.hs
+    hs-source-dirs: test
+    other-modules:
+        Tests.Braid
+        Tests.Common
+        Tests.LatticePaths
+        Tests.Permutations
+        Tests.Series
+        Tests.SkewTableaux
+        Tests.Thompson
+        Tests.Partitions.Integer
+        Tests.Partitions.Skew
+    default-language: Haskell2010
+    default-extensions: CPP BangPatterns
+    build-depends:
+        base >=4 && <5,
+        array >=0.5,
+        containers -any,
+        random -any,
+        transformers -any,
+        combinat-compat -any,
+        QuickCheck >=2,
+        test-framework -any,
+        test-framework-quickcheck2 -any
diff --git a/svg/bintrees.svg b/svg/bintrees.svg
new file mode 100644
--- /dev/null
+++ b/svg/bintrees.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="750.0" height="206.08695652173907" font-size="1" viewBox="0 0 750 206" stroke="rgb(0,0,0)" stroke-opacity="1"><g><g fill="rgb(0,0,0)" fill-opacity="0.0"><g stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="1.5725913258888558" stroke-linecap="butt" stroke-linejoin="miter" font-size="1.0em" stroke-miterlimit="10.0"><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 618.3370976849237,109.9350649350649 l 33.879164313946916,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 618.3370976849237,109.9350649350649 l -8.469791078486729,18.068887634105018 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 655.0395256916995,130.26256352343304 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 609.867306606437,128.0039525691699 l 25.409373235460187,17.50423489553924 " /></g><g stroke-width="0.9034443817052511"><path d="M 609.867306606437,128.0039525691699 l -8.469791078486729,18.06888763410502 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 638.0999435347261,148.33145115753805 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 601.3975155279502,146.07284020327495 l 16.939582156973458,17.50423489553924 " /></g><g stroke-width="0.9034443817052511"><path d="M 601.3975155279502,146.07284020327495 l -8.469791078486729,18.06888763410502 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 621.1603613777526,166.4003387916431 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 592.9277244494634,164.14172783737996 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 592.9277244494634,164.14172783737996 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 604.220779220779,184.4692264257481 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 587.2811970638056,184.4692264257481 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 596.3156408808582,164.14172783737996 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 604.7854319593449,146.07284020327495 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 613.2552230378317,128.0039525691699 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 621.7250141163184,109.9350649350649 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 522.3461321287408,109.9350649350649 l 33.879164313946916,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 522.3461321287408,109.9350649350649 l -8.469791078486729,18.068887634105018 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 559.0485601355166,130.26256352343304 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 513.876341050254,128.0039525691699 l 25.409373235460187,17.50423489553924 " /></g><g stroke-width="0.9034443817052511"><path d="M 513.876341050254,128.0039525691699 l -8.469791078486729,18.06888763410502 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 542.1089779785432,148.33145115753805 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 505.40654997176733,146.07284020327495 l 8.469791078486729,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 505.40654997176733,146.07284020327495 l -16.939582156973458,17.50423489553924 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 513.876341050254,164.14172783737996 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 513.876341050254,164.14172783737996 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 525.1693958215698,184.4692264257481 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 508.22981366459624,184.4692264257481 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 517.2642574816488,164.14172783737996 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 491.29023150762276,166.4003387916431 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 508.794466403162,146.07284020327495 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 517.2642574816488,128.0039525691699 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 525.7340485601355,109.9350649350649 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 426.35516657255783,118.9695087521174 l 33.879164313946916,17.50423489553924 " /></g><g stroke-width="0.9034443817052511"><path d="M 426.35516657255783,118.9695087521174 l -8.469791078486729,18.06888763410502 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 463.0575945793337,139.29700734048555 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 417.8853754940711,137.03839638622242 l 16.939582156973458,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 417.8853754940711,137.03839638622242 l -16.939582156973458,18.06888763410502 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 434.82495765104454,155.10728402032743 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 434.82495765104454,155.10728402032743 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 446.1180124223602,175.43478260869557 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 429.17843026538674,175.43478260869557 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 438.2128740824392,155.10728402032743 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 400.94579333709765,155.10728402032743 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 400.94579333709765,155.10728402032743 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 412.2388481084133,175.43478260869557 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 395.29926595143985,175.43478260869557 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 404.3337097684923,155.10728402032743 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 421.2732919254658,137.03839638622242 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 429.7430830039525,118.9695087521174 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 330.3642010163749,109.9350649350649 l 33.879164313946916,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 330.3642010163749,109.9350649350649 l -8.469791078486729,18.068887634105018 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 367.06662902315077,130.26256352343304 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 321.8944099378882,128.0039525691699 l 8.469791078486729,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 321.8944099378882,128.0039525691699 l -25.409373235460187,17.50423489553924 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 330.364201016375,146.07284020327495 l 16.939582156973458,17.50423489553924 " /></g><g stroke-width="0.9034443817052511"><path d="M 330.364201016375,146.07284020327495 l -8.469791078486729,18.06888763410502 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 350.12704686617735,166.4003387916431 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 321.89440993788827,164.14172783737996 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 321.89440993788827,164.14172783737996 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 333.18746470920394,184.4692264257481 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 316.24788255223046,184.4692264257481 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 325.28232636928294,164.14172783737996 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 333.75211744776965,146.07284020327495 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 299.30830039525694,148.33145115753805 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 325.2823263692829,128.0039525691699 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 333.7521174477696,109.9350649350649 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 234.37323546019198,109.9350649350649 l 33.879164313946916,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 234.37323546019198,109.9350649350649 l -8.469791078486729,18.068887634105018 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 271.0756634669678,130.26256352343304 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 225.90344438170524,128.0039525691699 l 8.469791078486729,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 225.90344438170524,128.0039525691699 l -25.409373235460187,17.50423489553924 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 234.37323546019198,146.07284020327495 l 8.469791078486729,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 234.37323546019198,146.07284020327495 l -16.939582156973458,17.50423489553924 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 242.8430265386787,164.14172783737996 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 242.8430265386787,164.14172783737996 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 254.13608130999432,184.4692264257481 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 237.19649915302088,184.4692264257481 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 246.23094297007341,164.14172783737996 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 220.25691699604744,166.4003387916431 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 237.76115189158668,146.07284020327495 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 203.31733483907396,148.33145115753805 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 229.29136081309994,128.0039525691699 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 237.76115189158668,109.9350649350649 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 138.38226990400904,118.9695087521174 l 25.409373235460187,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 138.38226990400904,118.9695087521174 l -16.939582156973458,18.06888763410502 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 163.79164313946922,137.03839638622242 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 163.79164313946922,137.03839638622242 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 175.08469791078483,157.36589497459056 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 158.14511575381138,157.36589497459056 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 167.17955957086392,137.03839638622242 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 121.44268774703558,137.03839638622242 l 16.939582156973458,17.50423489553924 " /></g><g stroke-width="0.9034443817052511"><path d="M 121.44268774703558,137.03839638622242 l -8.469791078486729,18.06888763410502 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 141.20553359683794,157.36589497459056 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 112.97289666854886,155.10728402032743 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 112.97289666854886,155.10728402032743 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 124.2659514398645,175.43478260869557 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 107.32636928289104,175.43478260869557 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 116.36081309994354,155.10728402032743 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 124.83060417843026,137.03839638622242 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 141.77018633540374,118.9695087521174 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 42.39130434782612,118.9695087521174 l 25.409373235460187,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 42.39130434782612,118.9695087521174 l -16.939582156973458,18.06888763410502 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 67.8006775832863,137.03839638622242 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 67.8006775832863,137.03839638622242 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 79.09373235460195,157.36589497459056 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 62.15415019762848,157.36589497459056 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 71.18859401468099,137.03839638622242 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 25.451722190852664,137.03839638622242 l 8.469791078486729,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 25.451722190852664,137.03839638622242 l -16.939582156973458,17.50423489553924 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 33.92151326933939,155.10728402032743 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 33.92151326933939,155.10728402032743 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 45.21456804065503,175.43478260869557 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 28.274985883681573,175.43478260869557 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 37.309429700734086,155.10728402032743 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 11.335403726708115,157.36589497459056 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 28.839638622247357,137.03839638622242 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 45.779220779220815,118.9695087521174 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 618.3370976849237,18.461321287408232 l 16.939582156973458,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 618.3370976849237,18.461321287408232 l -25.409373235460187,18.06888763410502 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 635.2766798418971,36.53020892151325 l 16.939582156973458,17.50423489553924 " /></g><g stroke-width="0.9034443817052511"><path d="M 635.2766798418971,36.53020892151325 l -8.469791078486729,18.06888763410502 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 655.0395256916995,56.8577075098814 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 626.8068887634104,54.59909655561827 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 626.8068887634104,54.59909655561827 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 638.099943534726,74.92659514398642 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 621.1603613777526,74.92659514398642 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 630.1948051948051,54.59909655561827 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 638.6645962732919,36.53020892151325 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 592.9277244494635,36.53020892151326 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 592.9277244494635,36.53020892151326 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 604.2207792207791,56.85770750988141 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 587.2811970638057,56.85770750988141 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 596.3156408808583,36.53020892151326 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 621.7250141163184,18.461321287408232 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 522.3461321287408,18.461321287408232 l 16.939582156973458,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 522.3461321287408,18.461321287408232 l -25.409373235460187,18.06888763410502 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 539.2857142857142,36.53020892151325 l 8.469791078486729,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 539.2857142857142,36.53020892151325 l -16.939582156973458,17.50423489553924 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 547.755505364201,54.59909655561827 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 547.755505364201,54.59909655561827 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 559.0485601355166,74.92659514398642 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 542.1089779785432,74.92659514398642 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 551.1434217955957,54.59909655561827 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 525.1693958215698,56.8577075098814 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 542.673630717109,36.53020892151325 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 496.9367588932806,36.53020892151326 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 496.9367588932806,36.53020892151326 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 508.2298136645963,56.85770750988141 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 491.2902315076228,56.85770750988141 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 500.3246753246753,36.53020892151326 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 525.7340485601355,18.461321287408232 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 426.35516657255783,9.42687747035572 l 8.469791078486729,18.068887634105018 " /></g><g stroke-width="0.9034443817052511"><path d="M 426.35516657255783,9.42687747035572 l -33.879164313946916,17.504234895539238 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 434.8249576510446,27.495765104460737 l 25.409373235460187,17.50423489553924 " /></g><g stroke-width="0.9034443817052511"><path d="M 434.8249576510446,27.495765104460737 l -8.469791078486729,18.06888763410502 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 463.05759457933374,47.82326369282889 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 426.3551665725579,45.564652738565755 l 16.939582156973458,17.50423489553924 " /></g><g stroke-width="0.9034443817052511"><path d="M 426.3551665725579,45.564652738565755 l -8.469791078486729,18.06888763410502 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 446.11801242236027,65.89215132693391 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 417.8853754940712,63.63354037267077 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 417.8853754940712,63.63354037267077 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 429.17843026538685,83.96103896103892 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 412.2388481084134,83.96103896103892 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 421.27329192546586,63.63354037267077 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 429.74308300395256,45.564652738565755 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 438.21287408243927,27.495765104460737 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 395.29926595143985,29.754376058723867 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 429.7430830039525,9.42687747035572 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 330.3642010163749,9.42687747035572 l 8.469791078486729,18.068887634105018 " /></g><g stroke-width="0.9034443817052511"><path d="M 330.3642010163749,9.42687747035572 l -33.879164313946916,17.504234895539238 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 338.8339920948617,27.495765104460737 l 25.409373235460187,17.50423489553924 " /></g><g stroke-width="0.9034443817052511"><path d="M 338.8339920948617,27.495765104460737 l -8.469791078486729,18.06888763410502 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 367.0666290231508,47.82326369282889 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 330.364201016375,45.564652738565755 l 8.469791078486729,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 330.364201016375,45.564652738565755 l -16.939582156973458,17.50423489553924 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 338.8339920948617,63.63354037267077 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 338.8339920948617,63.63354037267077 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 350.12704686617735,83.96103896103892 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 333.1874647092039,83.96103896103892 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 342.22190852625636,63.63354037267077 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 316.2478825522304,65.89215132693391 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 333.75211744776965,45.564652738565755 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 342.22190852625636,27.495765104460737 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 299.30830039525694,29.754376058723867 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 333.7521174477696,9.42687747035572 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 234.37323546019198,18.461321287408232 l 8.469791078486729,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 234.37323546019198,18.461321287408232 l -33.879164313946916,17.50423489553924 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 242.8430265386787,36.53020892151325 l 16.939582156973458,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 242.8430265386787,36.53020892151325 l -16.939582156973458,18.06888763410502 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 259.78260869565213,54.59909655561827 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 259.78260869565213,54.59909655561827 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 271.07566346696774,74.92659514398642 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 254.1360813099943,74.92659514398642 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 263.1705251270468,54.59909655561827 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 225.90344438170524,54.59909655561827 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 225.90344438170524,54.59909655561827 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 237.19649915302085,74.92659514398642 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 220.2569169960474,74.92659514398642 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 229.29136081309994,54.59909655561827 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 246.23094297007341,36.53020892151325 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 203.31733483907396,38.78881987577638 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 237.76115189158668,18.461321287408232 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 138.38226990400904,9.42687747035572 l 8.469791078486729,18.068887634105018 " /></g><g stroke-width="0.9034443817052511"><path d="M 138.38226990400904,9.42687747035572 l -33.879164313946916,17.504234895539238 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 146.85206098249577,27.495765104460737 l 8.469791078486729,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 146.85206098249577,27.495765104460737 l -25.409373235460187,17.50423489553924 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 155.3218520609825,45.564652738565755 l 16.939582156973458,17.50423489553924 " /></g><g stroke-width="0.9034443817052511"><path d="M 155.3218520609825,45.564652738565755 l -8.469791078486729,18.06888763410502 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 175.08469791078485,65.89215132693391 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 146.85206098249577,63.63354037267077 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 146.85206098249577,63.63354037267077 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 158.1451157538114,83.96103896103892 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 141.20553359683794,83.96103896103892 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 150.23997741389047,63.63354037267077 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 158.7097684923772,45.564652738565755 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 124.26595143986451,47.82326369282889 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 150.23997741389047,27.495765104460737 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 107.32636928289104,29.754376058723867 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 141.77018633540374,9.42687747035572 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 42.39130434782612,9.42687747035572 l 8.469791078486729,18.068887634105018 " /></g><g stroke-width="0.9034443817052511"><path d="M 42.39130434782612,9.42687747035572 l -33.879164313946916,17.504234895539238 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 50.86109542631285,27.495765104460737 l 8.469791078486729,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 50.86109542631285,27.495765104460737 l -25.409373235460187,17.50423489553924 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 59.33088650479958,45.564652738565755 l 8.469791078486729,18.06888763410502 " /></g><g stroke-width="0.9034443817052511"><path d="M 59.33088650479958,45.564652738565755 l -16.939582156973458,17.50423489553924 " /></g><g><g><g stroke-width="0.9034443817052511"><g stroke-width="0.9034443817052511"><path d="M 67.8006775832863,63.63354037267077 l 8.469791078486729,17.504234895539238 " /></g><g stroke-width="0.9034443817052511"><path d="M 67.8006775832863,63.63354037267077 l -8.469791078486729,17.504234895539238 " /></g><g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 79.09373235460195,83.96103896103892 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 62.15415019762848,83.96103896103892 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 71.18859401468099,63.63354037267077 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 45.21456804065503,65.89215132693391 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 62.71880293619427,45.564652738565755 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 28.274985883681573,47.82326369282889 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 54.24901185770754,27.495765104460737 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g><g><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.9034443817052511"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"></g><g fill="rgb(0,0,255)" fill-opacity="1.0"><path d="M 11.335403726708115,29.754376058723867 l -6.268904712733802e-16,-5.646527385657819 h -5.646527385657819 l -6.268904712733802e-16,5.646527385657819 Z" /></g></g></g></g></g></g><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"></g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 45.779220779220815,9.42687747035572 c 0.0,-1.8710945787604518 -1.5168218526342394,-3.3879164313946917 -3.387916431394691 -3.3879164313946917c -1.8710945787604518,-1.145677154876327e-16 -3.3879164313946917,1.5168218526342394 -3.3879164313946917 3.3879164313946903c -2.291354309752654e-16,1.8710945787604518 1.5168218526342392,3.3879164313946917 3.3879164313946903 3.3879164313946917c 1.8710945787604518,3.4370314646289805e-16 3.3879164313946917,-1.516821852634239 3.387916431394692 -3.3879164313946903Z" /></g></g></g><g fill="rgb(255,0,0)" fill-opacity="1.0"></g></g></g></g></g></g></g></g></g></g></g></svg>
diff --git a/svg/dyck_path.svg b/svg/dyck_path.svg
new file mode 100644
--- /dev/null
+++ b/svg/dyck_path.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg stroke="rgb(0,0,0)" version="1.1" xmlns="http://www.w3.org/2000/svg" height="97.7011" viewBox="0 0 500 98" stroke-opacity="1" font-size="1" xmlns:xlink="http://www.w3.org/1999/xlink" width="500.0000"><defs></defs><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 453.7182,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 436.3027,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 418.8871,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 401.4716,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 384.0561,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 366.6405,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 349.2250,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 331.8095,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 314.3939,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 296.9784,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 279.5629,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 262.1473,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 244.7318,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 227.3163,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 209.9007,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 192.4852,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 175.0697,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 157.6541,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 140.2386,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 122.8231,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 105.4075,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 87.9920,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 70.5765,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 53.1609,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 35.7454,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 18.3299,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 0.9143,87.9920 v -87.0777 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 0.9143,0.9143 h 452.8039 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 0.9143,18.3299 h 452.8039 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 0.9143,35.7454 h 452.8039 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 0.9143,53.1609 h 452.8039 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 0.9143,70.5765 h 452.8039 "/></g><defs></defs><g stroke="rgb(128,128,128)" fill="rgb(0,0,0)" stroke-width="0.4353883664228493" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 0.9143,87.9920 h 452.8039 "/></g><defs></defs><g stroke="rgb(255,0,0)" fill="rgb(0,0,0)" stroke-width="0.8707767328456986" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 0.9143,87.9920 l 17.4155,-17.4155 l 17.4155,-17.4155 l 17.4155,17.4155 l 17.4155,-17.4155 l 17.4155,-17.4155 l 17.4155,-17.4155 l 17.4155,17.4155 l 17.4155,-17.4155 l 17.4155,17.4155 l 17.4155,17.4155 l 17.4155,-17.4155 l 17.4155,17.4155 l 17.4155,-17.4155 l 17.4155,-17.4155 l 17.4155,-17.4155 l 17.4155,17.4155 l 17.4155,17.4155 l 17.4155,17.4155 l 17.4155,17.4155 l 17.4155,17.4155 l 17.4155,-17.4155 l 17.4155,17.4155 l 17.4155,-17.4155 l 17.4155,-17.4155 l 17.4155,17.4155 l 17.4155,17.4155 "/></g></svg>
diff --git a/svg/ferrers.svg b/svg/ferrers.svg
new file mode 100644
--- /dev/null
+++ b/svg/ferrers.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256.0" height="160.0" font-size="1" viewBox="0 0 256 160" stroke="rgb(0,0,0)" stroke-opacity="1"><g><g fill="rgb(0,0,0)" fill-opacity="0.0"><g stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="0.8095430810031051" stroke-linecap="butt" stroke-linejoin="miter" font-size="1.0em" stroke-miterlimit="10.0"><g stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="1.4545454545454546"><g><path d="M 232.72727272727272,0.0 v 29.09090909090909 " /></g><g><path d="M 203.63636363636363,0.0 v 29.09090909090909 " /></g><g><path d="M 174.54545454545453,0.0 v 58.18181818181818 " /></g><g><path d="M 145.45454545454544,0.0 v 58.18181818181818 " /></g><g><path d="M 116.36363636363636,0.0 v 58.18181818181818 " /></g><g><path d="M 87.27272727272727,0.0 v 116.36363636363636 " /></g><g><path d="M 58.18181818181818,0.0 v 116.36363636363636 " /></g><g><path d="M 29.09090909090909,0.0 v 145.45454545454544 " /></g><g><path d="M 0.0,0.0 v 145.45454545454544 " /></g><g><path d="M 0.0,145.45454545454544 h 29.09090909090909 " /></g><g><path d="M 0.0,116.36363636363636 h 87.27272727272727 " /></g><g><path d="M 0.0,87.27272727272727 h 87.27272727272727 " /></g><g><path d="M 0.0,58.18181818181818 h 174.54545454545453 " /></g><g><path d="M 0.0,29.09090909090909 h 232.72727272727272 " /></g><g><path d="M 0.0,0.0 h 232.72727272727272 " /></g></g><g stroke="rgb(255,0,0)" stroke-opacity="1.0"><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 23.272727272727273,130.9090909090909 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 81.45454545454544,101.81818181818181 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 52.36363636363636,101.81818181818181 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 23.272727272727273,101.81818181818181 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 81.45454545454544,72.72727272727272 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 52.36363636363636,72.72727272727272 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 23.272727272727273,72.72727272727272 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 168.72727272727272,43.63636363636363 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 139.63636363636363,43.63636363636363 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 110.54545454545453,43.63636363636363 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 81.45454545454544,43.63636363636363 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 52.36363636363636,43.63636363636363 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 23.272727272727273,43.63636363636363 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 226.9090909090909,14.545454545454545 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 197.8181818181818,14.545454545454545 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 168.72727272727272,14.545454545454545 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 139.63636363636363,14.545454545454545 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 110.54545454545453,14.545454545454545 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 81.45454545454544,14.545454545454545 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 52.36363636363636,14.545454545454545 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,0,0)" fill-opacity="1.0" stroke-width="0.7272727272727273"><g fill="rgb(255,0,0)" fill-opacity="1.0"><g fill="rgb(255,0,0)" fill-opacity="1.0"><path d="M 23.272727272727273,14.545454545454545 c 0.0,-4.819939634886924 -3.907333092385801,-8.727272727272727 -8.727272727272725 -8.727272727272727c -4.819939634886924,-2.951264350961418e-16 -8.727272727272727,3.9073330923858007 -8.727272727272727 8.727272727272723c -5.902528701922836e-16,4.819939634886924 3.9073330923858003,8.727272727272727 8.727272727272723 8.727272727272727c 4.819939634886924,8.853793052884255e-16 8.727272727272727,-3.9073330923858 8.72727272727273 -8.727272727272723Z" /></g></g></g></g></g></g></g></svg>
diff --git a/svg/noncrossing.svg b/svg/noncrossing.svg
new file mode 100644
--- /dev/null
+++ b/svg/noncrossing.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256.0" height="252.11078477112528" font-size="1" viewBox="0 0 256 252" stroke="rgb(0,0,0)" stroke-opacity="1"><g><g fill="rgb(0,0,0)" fill-opacity="0.0"><g stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="1.0161918000173635" stroke-linecap="butt" stroke-linejoin="miter" font-size="1.0em" stroke-miterlimit="10.0"><g fill="rgb(0,0,255)" fill-opacity="1.0" stroke-width="0.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><text transform="matrix(24.788545388692057,0.0,0.0,24.788545388692057,58.9536640353175,45.86642929392933)" dominant-baseline="middle" text-anchor="middle" stroke="none">9</text></g></g><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><text transform="matrix(24.788545388692057,0.0,0.0,24.788545388692057,22.214876033057877,109.49987672234495)" dominant-baseline="middle" text-anchor="middle" stroke="none">8</text></g></g></g><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><text transform="matrix(24.788545388692057,0.0,0.0,24.788545388692057,34.97412320562606,181.86116324413933)" dominant-baseline="middle" text-anchor="middle" stroke="none">7</text></g></g></g><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><text transform="matrix(24.788545388692057,0.0,0.0,24.788545388692057,91.26121199774039,229.09163868964527)" dominant-baseline="middle" text-anchor="middle" stroke="none">6</text></g></g></g><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><text transform="matrix(24.788545388692057,0.0,0.0,24.788545388692057,164.7387880022597,229.09163868964527)" dominant-baseline="middle" text-anchor="middle" stroke="none">5</text></g></g></g><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><text transform="matrix(24.788545388692057,0.0,0.0,24.788545388692057,221.02587679437403,181.86116324413928)" dominant-baseline="middle" text-anchor="middle" stroke="none">4</text></g></g></g><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><text transform="matrix(24.788545388692057,0.0,0.0,24.788545388692057,233.78512396694217,109.49987672234491)" dominant-baseline="middle" text-anchor="middle" stroke="none">3</text></g></g></g><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><text transform="matrix(24.788545388692057,0.0,0.0,24.788545388692057,197.04633596468253,45.8664292939293)" dominant-baseline="middle" text-anchor="middle" stroke="none">2</text></g></g></g><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,255)" fill-opacity="1.0"><text transform="matrix(24.788545388692057,0.0,0.0,24.788545388692057,128.00000000000003,20.735618217640905)" dominant-baseline="middle" text-anchor="middle" stroke="none">1</text></g></g></g></g></g><g stroke="rgb(255,0,0)" stroke-opacity="1.0" stroke-width="6.6102787703178825"><path d="M 210.62848462897355,119.47665734926427 c 0.0,-45.63445196221021 -36.99403266676331,-82.62848462897352 -82.62848462897351 -82.62848462897352c -45.63445196221021,-2.7942119913062335e-15 -82.62848462897352,36.9940326667633 -82.62848462897352 82.6284846289735c -5.588423982612467e-15,45.63445196221021 36.9940326667633,82.62848462897352 82.62848462897348 82.62848462897352c 45.63445196221021,8.3826359739187e-15 82.62848462897352,-36.994032666763296 82.62848462897357 -82.6284846289735Z" /></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(255,165,0)" fill-opacity="1.0" stroke-width="1.6525696925794706"><g fill="rgb(255,165,0)" fill-opacity="1.0"><g fill="rgb(255,165,0)" fill-opacity="1.0"><g fill="rgb(255,165,0)" fill-opacity="1.0"><g fill="rgb(255,165,0)" fill-opacity="1.0"><g><g fill="rgb(255,165,0)" fill-opacity="1.0"><g fill="rgb(255,165,0)" fill-opacity="1.0"><path d="M 67.73159719683085,52.04814162444856 l -28.260606155584327,48.94880571416585 c -2.281722598110509,3.952059468705467 -0.9276470236638324,9.005538309828474 3.0244124450416336 11.287260907938983c 3.952059468705467,2.2817225981105094 9.005538309828474,0.9276470236638328 11.287260907938983 -3.0244124450416328l 28.260606155584327,-48.94880571416585 c 2.281722598110509,-3.952059468705467 0.927647023663832,-9.005538309828474 -3.0244124450416345 -11.287260907938983c -3.952059468705467,-2.2817225981105094 -9.005538309828474,-0.9276470236638324 -11.287260907938986 3.0244124450416336Z" /></g></g></g><g><g fill="rgb(255,165,0)" fill-opacity="1.0"><g fill="rgb(255,165,0)" fill-opacity="1.0"><path d="M 51.13037662242908,167.12060881308778 l 43.29776060931872,36.331134958081485 c 3.495801834043109,2.933326029614429 8.707639732390161,2.4773492973742828 11.64096576200459 -1.0184525366688244c 0.9323202645137305,-1.1110960246826247 1.5557430576990043,-2.448030519071843 1.8076080788896125 -3.8764280347477618l 28.260606155584334,-160.2738619015418 c 0.7924339422066867,-4.494116209684779 -2.20837244062142,-8.779711863939754 -6.702488650306198 -9.57214580614644c -3.4209150223784612,-0.6031996171562244 -6.853821179049025,0.9975908134267045 -8.59066525441043 4.005892996777644l -71.55836676490306,123.94272694346033 c -2.004953927164585,3.472682068683816 -1.2271875654055806,7.883614296037856 1.84458006382242 10.46113338078537Z" /></g></g></g></g><g><g fill="rgb(255,165,0)" fill-opacity="1.0"><g fill="rgb(255,165,0)" fill-opacity="1.0"><path d="M 161.57186276825226,203.45174377116925 l 43.29776060931873,-36.33113495808153 c 2.2613872731848765,-1.8975292268784263 3.3386754938661554,-4.8573542874446884 2.8260606155584305 -7.764537727256825l -18.44580063822424,-104.61133380785381 c -0.7924339422066877,-4.494116209684778 -5.078029596461661,-7.494922592512885 -9.57214580614644 -6.702488650306197c -3.4209150223784626,0.603199617156225 -6.099289033149974,3.281573627927738 -6.702488650306198 6.7024886503062l -24.851959971094487,140.94246876593533 c -0.7924339422066862,4.494116209684779 2.208372440621421,8.779711863939752 6.702488650306198 9.57214580614644c 2.4053330636402883,0.4241251167099334 4.875068211267832,-0.23763842161562668 6.746085190588008 -1.807608078889615Z" /></g></g></g></g><g><g fill="rgb(255,165,0)" fill-opacity="1.0"><g fill="rgb(255,165,0)" fill-opacity="1.0"><path d="M 217.63602074516058,105.12837157006305 c 0.0,-4.5634451962210205 -3.699403266676331,-8.262848462897352 -8.26284846289735 -8.262848462897352c -4.5634451962210205,-2.794211991306233e-16 -8.262848462897352,3.6994032666763306 -8.262848462897352 8.262848462897349c -5.588423982612466e-16,4.5634451962210205 3.69940326667633,8.262848462897352 8.262848462897349 8.262848462897352c 4.5634451962210205,8.3826359739187e-16 8.262848462897352,-3.6994032666763297 8.262848462897356 -8.262848462897349Z" /></g></g></g></g></g></g><g fill="rgb(0,0,0)" fill-opacity="1.0" stroke-width="0.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g stroke="rgb(0,0,0)" stroke-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><path d="M 79.01885810476983,56.17956585589723 c 0.0,-2.2817225981105103 -1.8497016333381655,-4.131424231448676 -4.131424231448675 -4.131424231448676c -2.2817225981105103,-1.3971059956531165e-16 -4.131424231448676,1.8497016333381653 -4.131424231448676 4.131424231448674c -2.794211991306233e-16,2.2817225981105103 1.849701633338165,4.131424231448676 4.131424231448674 4.131424231448676c 2.2817225981105103,4.19131798695935e-16 4.131424231448676,-1.8497016333381648 4.131424231448678 -4.131424231448674Z" /></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><path d="M 50.75825194918551,105.12837157006308 c 0.0,-2.2817225981105103 -1.8497016333381655,-4.131424231448676 -4.131424231448675 -4.131424231448676c -2.2817225981105103,-1.3971059956531165e-16 -4.131424231448676,1.8497016333381653 -4.131424231448676 4.131424231448674c -2.794211991306233e-16,2.2817225981105103 1.849701633338165,4.131424231448676 4.131424231448674 4.131424231448676c 2.2817225981105103,4.19131798695935e-16 4.131424231448676,-1.8497016333381648 4.131424231448678 -4.131424231448674Z" /></g></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><path d="M 60.57305746654565,160.79089966375108 c 0.0,-2.2817225981105103 -1.8497016333381655,-4.131424231448676 -4.131424231448675 -4.131424231448676c -2.2817225981105103,-1.3971059956531165e-16 -4.131424231448676,1.8497016333381653 -4.131424231448676 4.131424231448674c -2.794211991306233e-16,2.2817225981105103 1.849701633338165,4.131424231448676 4.131424231448674 4.131424231448676c 2.2817225981105103,4.19131798695935e-16 4.131424231448676,-1.8497016333381648 4.131424231448678 -4.131424231448674Z" /></g></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><path d="M 103.87081807586438,197.12203462183254 c 0.0,-2.2817225981105103 -1.8497016333381655,-4.131424231448676 -4.131424231448675 -4.131424231448676c -2.2817225981105103,-1.3971059956531165e-16 -4.131424231448676,1.8497016333381653 -4.131424231448676 4.131424231448674c -2.794211991306233e-16,2.2817225981105103 1.849701633338165,4.131424231448676 4.131424231448674 4.131424231448676c 2.2817225981105103,4.19131798695935e-16 4.131424231448676,-1.8497016333381648 4.131424231448678 -4.131424231448674Z" /></g></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><path d="M 160.39203038703306,197.12203462183254 c 0.0,-2.2817225981105103 -1.8497016333381655,-4.131424231448676 -4.131424231448675 -4.131424231448676c -2.2817225981105103,-1.3971059956531165e-16 -4.131424231448676,1.8497016333381653 -4.131424231448676 4.131424231448674c -2.794211991306233e-16,2.2817225981105103 1.849701633338165,4.131424231448676 4.131424231448674 4.131424231448676c 2.2817225981105103,4.19131798695935e-16 4.131424231448676,-1.8497016333381648 4.131424231448678 -4.131424231448674Z" /></g></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><path d="M 203.68979099635177,160.79089966375102 c 0.0,-2.2817225981105103 -1.8497016333381655,-4.131424231448676 -4.131424231448675 -4.131424231448676c -2.2817225981105103,-1.3971059956531165e-16 -4.131424231448676,1.8497016333381653 -4.131424231448676 4.131424231448674c -2.794211991306233e-16,2.2817225981105103 1.849701633338165,4.131424231448676 4.131424231448674 4.131424231448676c 2.2817225981105103,4.19131798695935e-16 4.131424231448676,-1.8497016333381648 4.131424231448678 -4.131424231448674Z" /></g></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><path d="M 213.5045965137119,105.12837157006305 c 0.0,-2.2817225981105103 -1.8497016333381655,-4.131424231448676 -4.131424231448675 -4.131424231448676c -2.2817225981105103,-1.3971059956531165e-16 -4.131424231448676,1.8497016333381653 -4.131424231448676 4.131424231448674c -2.794211991306233e-16,2.2817225981105103 1.849701633338165,4.131424231448676 4.131424231448674 4.131424231448676c 2.2817225981105103,4.19131798695935e-16 4.131424231448676,-1.8497016333381648 4.131424231448678 -4.131424231448674Z" /></g></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><path d="M 185.24399035812755,56.179565855897216 c 0.0,-2.2817225981105103 -1.8497016333381655,-4.131424231448676 -4.131424231448675 -4.131424231448676c -2.2817225981105103,-1.3971059956531165e-16 -4.131424231448676,1.8497016333381653 -4.131424231448676 4.131424231448674c -2.794211991306233e-16,2.2817225981105103 1.849701633338165,4.131424231448676 4.131424231448674 4.131424231448676c 2.2817225981105103,4.19131798695935e-16 4.131424231448676,-1.8497016333381648 4.131424231448678 -4.131424231448674Z" /></g></g></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><path d="M 132.1314242314487,36.84817272029075 c 0.0,-2.2817225981105103 -1.8497016333381655,-4.131424231448676 -4.131424231448675 -4.131424231448676c -2.2817225981105103,-1.3971059956531165e-16 -4.131424231448676,1.8497016333381653 -4.131424231448676 4.131424231448674c -2.794211991306233e-16,2.2817225981105103 1.849701633338165,4.131424231448676 4.131424231448674 4.131424231448676c 2.2817225981105103,4.19131798695935e-16 4.131424231448676,-1.8497016333381648 4.131424231448678 -4.131424231448674Z" /></g></g></g></g></g></g></g></g></g></svg>
diff --git a/svg/plane_partition.svg b/svg/plane_partition.svg
new file mode 100644
--- /dev/null
+++ b/svg/plane_partition.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="320.0" height="285.52595130832407" font-size="1" viewBox="0 0 320 286" stroke="rgb(0,0,0)" stroke-opacity="1"><g><g fill="rgb(0,0,0)" fill-opacity="0.0"><g stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="1.209087619115596" stroke-linecap="butt" stroke-linejoin="miter" font-size="1.0em" stroke-miterlimit="10.0"><g fill="rgb(124,252,0)" fill-opacity="1.0" stroke-width="1.5268767449642997"><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 174.54545454545453,15.268767449643008 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 174.54545454545453,76.343837248215 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 148.099173553719,61.075069798572 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g></g><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 200.99173553719007,61.075069798572 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g></g></g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 121.65289256198346,106.881372147501 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 253.88429752066116,122.15013959714398 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g></g><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 227.4380165289256,106.881372147501 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g></g></g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 95.20661157024792,152.68767449643 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 148.099173553719,152.68767449643 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g></g><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 200.99173553719007,152.68767449642996 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g></g></g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 42.31404958677686,213.76274429500197 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 68.7603305785124,198.493976845359 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g></g><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 121.65289256198346,198.493976845359 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g></g><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 227.4380165289256,198.49397684535896 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g></g><g><g fill="rgb(124,252,0)" fill-opacity="1.0"><g fill="rgb(124,252,0)" fill-opacity="1.0"><path d="M 280.3305785123967,198.49397684535896 l -26.446280991735534,15.268767449642999 l 26.446280991735538,15.268767449642993 l 26.446280991735534,-15.268767449642999 Z" /></g></g></g></g></g></g></g><g fill="rgb(205,92,92)" fill-opacity="1.0" stroke-width="1.5268767449642997"><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 200.99173553719007,61.075069798572 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 200.99173553719007,122.15013959714398 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 227.4380165289256,106.881372147501 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g></g></g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 148.099173553719,152.68767449643 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 200.99173553719007,152.68767449642996 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 280.3305785123967,167.95644194607297 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g></g></g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 121.65289256198346,198.493976845359 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 174.54545454545453,198.49397684535896 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 227.4380165289256,198.49397684535896 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 280.3305785123967,198.49397684535896 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g></g></g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 68.76033057851241,259.56904664393096 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 95.20661157024793,244.30027919428795 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 148.099173553719,244.30027919428795 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 174.54545454545453,229.03151174464494 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 253.88429752066116,244.30027919428795 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(205,92,92)" fill-opacity="1.0"><g fill="rgb(205,92,92)" fill-opacity="1.0"><path d="M 306.77685950413223,244.30027919428795 v -30.53753489928599 l -26.446280991735534,15.268767449642999 v 30.53753489928599 Z" /></g></g></g></g></g></g></g><g fill="rgb(95,158,160)" fill-opacity="1.0" stroke-width="1.5268767449642997"><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 148.099173553719,61.075069798572 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 148.099173553719,122.15013959714399 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 121.65289256198346,106.881372147501 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g></g></g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 227.4380165289256,167.95644194607297 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 200.99173553719007,152.68767449642996 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 148.099173553719,152.68767449643 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 95.20661157024792,152.68767449643 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g></g></g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 227.4380165289256,198.49397684535896 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 174.54545454545453,198.49397684535896 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 121.65289256198346,198.493976845359 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 68.7603305785124,198.493976845359 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g></g></g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 253.88429752066116,244.30027919428795 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 200.99173553719007,244.30027919428795 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 174.54545454545453,229.03151174464494 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 95.20661157024793,244.30027919428795 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g></g><g><g fill="rgb(95,158,160)" fill-opacity="1.0"><g fill="rgb(95,158,160)" fill-opacity="1.0"><path d="M 15.867768595041298,259.56904664393096 v -30.53753489928599 l 26.446280991735538,15.268767449642993 v 30.53753489928599 Z" /></g></g></g></g></g></g></g></g></g></g></svg>
diff --git a/svg/skew3.svg b/svg/skew3.svg
new file mode 100644
--- /dev/null
+++ b/svg/skew3.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg stroke="rgb(0,0,0)" version="1.1" xmlns="http://www.w3.org/2000/svg" height="171.6044" viewBox="0 0 256 172" stroke-opacity="1" font-size="1" xmlns:xlink="http://www.w3.org/1999/xlink" width="256.0000"><defs></defs><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 231.5125,1.3427 v 25.5744 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 205.9381,1.3427 v 25.5744 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 180.3636,1.3427 v 51.1489 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 154.7892,1.3427 v 51.1489 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 129.2148,1.3427 v 51.1489 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 103.6404,26.9171 v 25.5744 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 78.0659,26.9171 v 51.1489 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 52.4915,52.4915 v 76.7233 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 26.9171,78.0659 v 76.7233 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.3427,103.6404 v 51.1489 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.3427,154.7892 h 25.5744 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.3427,129.2148 h 51.1489 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.3427,103.6404 h 51.1489 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 26.9171,78.0659 h 51.1489 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 52.4915,52.4915 h 127.8721 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 78.0659,26.9171 h 153.4466 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.2787212787212787" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 129.2148,1.3427 h 102.2977 "/></g></svg>
diff --git a/svg/skew_tableau.svg b/svg/skew_tableau.svg
new file mode 100644
--- /dev/null
+++ b/svg/skew_tableau.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg stroke="rgb(0,0,0)" version="1.1" xmlns="http://www.w3.org/2000/svg" height="214.5055" viewBox="0 0 320 215" stroke-opacity="1" font-size="1" xmlns:xlink="http://www.w3.org/1999/xlink" width="320.0000"><defs></defs><defs></defs><g stroke="rgb(211,211,211)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 161.5185,1.6783 v 31.9680 "/></g><defs></defs><g stroke="rgb(211,211,211)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 129.5504,1.6783 v 31.9680 "/></g><defs></defs><g stroke="rgb(211,211,211)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 97.5824,1.6783 v 63.9361 "/></g><defs></defs><g stroke="rgb(211,211,211)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 65.6144,1.6783 v 95.9041 "/></g><defs></defs><g stroke="rgb(211,211,211)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 33.6464,1.6783 v 127.8721 "/></g><defs></defs><g stroke="rgb(211,211,211)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.6783,1.6783 v 127.8721 "/></g><defs></defs><g stroke="rgb(211,211,211)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.6783,129.5504 h 31.9680 "/></g><defs></defs><g stroke="rgb(211,211,211)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.6783,97.5824 h 63.9361 "/></g><defs></defs><g stroke="rgb(211,211,211)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.6783,65.6144 h 95.9041 "/></g><defs></defs><g stroke="rgb(211,211,211)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.6783,33.6464 h 159.8402 "/></g><defs></defs><g stroke="rgb(211,211,211)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.6783,1.6783 h 159.8402 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 289.3906,1.6783 v 31.9680 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 257.4226,1.6783 v 31.9680 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 225.4545,1.6783 v 63.9361 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 193.4865,1.6783 v 63.9361 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 161.5185,1.6783 v 63.9361 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 129.5504,33.6464 v 31.9680 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 97.5824,33.6464 v 63.9361 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 65.6144,65.6144 v 95.9041 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 33.6464,97.5824 v 95.9041 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.6783,129.5504 v 63.9361 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.6783,193.4865 h 31.9680 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.6783,161.5185 h 63.9361 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 1.6783,129.5504 h 63.9361 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 33.6464,97.5824 h 63.9361 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 65.6144,65.6144 h 159.8402 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 97.5824,33.6464 h 191.8082 "/></g><defs></defs><g stroke="rgb(0,0,0)" fill="rgb(0,0,0)" stroke-width="1.5984015984015982" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="0.0"><path d="M 161.5185,1.6783 h 127.8721 "/></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,17.6623,186.4535)" dominant-baseline="middle">7</text></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,49.6304,154.4855)" dominant-baseline="middle">5</text></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,17.6623,154.4855)" dominant-baseline="middle">1</text></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,49.6304,122.5175)" dominant-baseline="middle">2</text></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,81.5984,90.5495)" dominant-baseline="middle">1</text></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,209.4705,58.5814)" dominant-baseline="middle">2</text></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,177.5025,58.5814)" dominant-baseline="middle">2</text></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,145.5345,58.5814)" dominant-baseline="middle">1</text></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,113.5664,58.5814)" dominant-baseline="middle">1</text></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,273.4066,26.6134)" dominant-baseline="middle">1</text></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,241.4386,26.6134)" dominant-baseline="middle">1</text></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,209.4705,26.6134)" dominant-baseline="middle">1</text></g><defs></defs><g stroke="rgb(0,0,255)" fill="rgb(0,0,255)" stroke-width="0.0" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-linejoin="miter" stroke-opacity="1.0" fill-opacity="1.0" font-size="27.172827172827166px"><text stroke="none" text-anchor="middle" transform="matrix(1.0000,0.0000,0.0000,1.0000,177.5025,26.6134)" dominant-baseline="middle">1</text></g></svg>
diff --git a/svg/src/gen_figures.hs b/svg/src/gen_figures.hs
new file mode 100644
--- /dev/null
+++ b/svg/src/gen_figures.hs
@@ -0,0 +1,81 @@
+
+-- | A script to generate the SVG figures in the documentation.
+-- We use the @combinat-diagrams@ library for that.
+
+module Main where
+
+--------------------------------------------------------------------------------
+
+import Math.Combinat.Partitions.Integer
+import Math.Combinat.Partitions.Plane
+import Math.Combinat.Partitions.NonCrossing
+import Math.Combinat.Partitions.Skew
+import Math.Combinat.Tableaux
+import Math.Combinat.Tableaux.Skew
+import Math.Combinat.LatticePaths
+import Math.Combinat.Trees.Binary
+
+import Math.Combinat.Diagrams.Partitions.Integer
+import Math.Combinat.Diagrams.Partitions.Plane
+import Math.Combinat.Diagrams.Partitions.NonCrossing
+import Math.Combinat.Diagrams.Partitions.Skew
+import Math.Combinat.Diagrams.Tableaux
+import Math.Combinat.Diagrams.Tableaux.Skew
+import Math.Combinat.Diagrams.LatticePaths
+import Math.Combinat.Diagrams.Trees.Binary
+
+import Diagrams.Core
+import Diagrams.Prelude
+import Diagrams.Backend.SVG
+
+--------------------------------------------------------------------------------
+
+export fpath size what = renderSVG fpath size $ pad 1.10 what
+
+vcatSep = vcat' (with & sep .~ 1) 
+hcatSep = hcat' (with & sep .~ 1) 
+
+boxSep m xs = pad 1.05 $ vcatSep $ map hcatSep $ yys where
+  yys = go xs where
+    go [] = []
+    go zs = take m zs : go (drop m zs) 
+
+padding fac diag = pad fac $ centerXY diag
+margin  siz diag = hcat [ strutX siz , vcat [ strutY siz , centerXY diag , strutY siz ] , strutX siz ]
+
+--------------------------------------------------------------------------------
+
+main = do 
+
+  export "plane_partition.svg" (mkWidth 320) $ margin 0.05 $ drawPlanePartition3D $
+    PlanePart [[5,4,3,3,1],[4,4,2,1],[3,2],[2,1],[1],[1]] 
+
+  export "noncrossing.svg" (mkWidth 256) $ padding 1.10 $ drawNonCrossingCircleDiagram' orange True $
+    NonCrossing [[3],[5,4,2],[7,6,1],[9,8]]
+
+  export "young_tableau.svg" (mkWidth 256) $ margin 0.05 $ drawTableau $ 
+    [ [ 1 , 3 , 4 , 6 , 7 ]
+    , [ 2 , 5 , 8 ,10 ]
+    , [ 9 ]
+    ]
+
+  let u = UpStep
+      d = DownStep
+      path = [ u,u,d,u,u,u,d,u,d,d,u,d,u,u,u,d,d,d,d,d,u,d,u,u,d,d ]     
+  export "dyck_path.svg" (mkWidth 500) $ margin 0.05 $ drawLatticePath $ path
+  -- print (pathHeight path, pathNumberOfZeroTouches path, pathNumberOfPeaks path)
+
+  export "ferrers.svg" (mkWidth 256) $ margin 0.05 $ drawFerrersDiagram' EnglishNotation red True $
+    Partition [8,6,3,3,1]
+
+  export "bintrees.svg" (mkWidth 750) $ boxSep 7 $ map drawBinTree_ (binaryTrees 4)
+
+  let skew = mkSkewPartition (Partition [9,7,3,2,2,1] , Partition [5,3,2,1])
+  -- export "skew.svg"  (mkWidth 256) $ margin 0.05 $ drawSkewFerrersDiagram  skew
+  -- export "skew2.svg" (mkWidth 256) $ margin 0.05 $ drawSkewFerrersDiagram' EnglishNotation green True (True,True) skew
+  export "skew3.svg" (mkWidth 256) $ margin 0.05 $ drawSkewPartitionBoxes  EnglishNotation skew
+
+  let skewtableau  = (semiStandardSkewTableaux 7 skew) !! 123
+  export "skew_tableau.svg" (mkWidth 320) $ margin 0.05 $ drawSkewTableau' EnglishNotation blue True skewtableau
+
+--------------------------------------------------------------------------------
diff --git a/svg/young_tableau.svg b/svg/young_tableau.svg
new file mode 100644
--- /dev/null
+++ b/svg/young_tableau.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256.0" height="153.60000000000002" font-size="1" viewBox="0 0 256 154" stroke="rgb(0,0,0)" stroke-opacity="1"><g><g fill="rgb(0,0,0)" fill-opacity="0.0"><g stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="0.793186989303279" stroke-linecap="butt" stroke-linejoin="miter" font-size="1.0em" stroke-miterlimit="10.0"><g stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="2.3272727272727276"><g><path d="M 232.72727272727275,0.0 v 46.54545454545455 " /></g><g><path d="M 186.1818181818182,0.0 v 93.0909090909091 " /></g><g><path d="M 139.63636363636363,0.0 v 93.0909090909091 " /></g><g><path d="M 93.0909090909091,0.0 v 93.0909090909091 " /></g><g><path d="M 46.54545454545455,0.0 v 139.63636363636363 " /></g><g><path d="M 0.0,0.0 v 139.63636363636363 " /></g><g><path d="M 0.0,139.63636363636363 h 46.54545454545455 " /></g><g><path d="M 0.0,93.0909090909091 h 186.1818181818182 " /></g><g><path d="M 0.0,46.54545454545455 h 232.72727272727275 " /></g><g><path d="M 0.0,0.0 h 232.72727272727275 " /></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0"><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(0,0,0)" fill-opacity="1.0" stroke-width="0.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><text transform="matrix(39.56363636363636,0.0,0.0,39.56363636363636,23.272727272727273,129.39636363636365)" dominant-baseline="middle" text-anchor="middle" stroke="none">9</text></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(0,0,0)" fill-opacity="1.0" stroke-width="0.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><text transform="matrix(39.56363636363636,0.0,0.0,39.56363636363636,162.9090909090909,82.8509090909091)" dominant-baseline="middle" text-anchor="middle" stroke="none">10</text></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(0,0,0)" fill-opacity="1.0" stroke-width="0.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><text transform="matrix(39.56363636363636,0.0,0.0,39.56363636363636,116.36363636363637,82.8509090909091)" dominant-baseline="middle" text-anchor="middle" stroke="none">8</text></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(0,0,0)" fill-opacity="1.0" stroke-width="0.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><text transform="matrix(39.56363636363636,0.0,0.0,39.56363636363636,69.81818181818181,82.8509090909091)" dominant-baseline="middle" text-anchor="middle" stroke="none">5</text></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(0,0,0)" fill-opacity="1.0" stroke-width="0.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><text transform="matrix(39.56363636363636,0.0,0.0,39.56363636363636,23.272727272727273,82.8509090909091)" dominant-baseline="middle" text-anchor="middle" stroke="none">2</text></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(0,0,0)" fill-opacity="1.0" stroke-width="0.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><text transform="matrix(39.56363636363636,0.0,0.0,39.56363636363636,209.45454545454547,36.305454545454545)" dominant-baseline="middle" text-anchor="middle" stroke="none">7</text></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(0,0,0)" fill-opacity="1.0" stroke-width="0.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><text transform="matrix(39.56363636363636,0.0,0.0,39.56363636363636,162.9090909090909,36.305454545454545)" dominant-baseline="middle" text-anchor="middle" stroke="none">6</text></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(0,0,0)" fill-opacity="1.0" stroke-width="0.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><text transform="matrix(39.56363636363636,0.0,0.0,39.56363636363636,116.36363636363637,36.305454545454545)" dominant-baseline="middle" text-anchor="middle" stroke="none">4</text></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(0,0,0)" fill-opacity="1.0" stroke-width="0.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><text transform="matrix(39.56363636363636,0.0,0.0,39.56363636363636,69.81818181818181,36.305454545454545)" dominant-baseline="middle" text-anchor="middle" stroke="none">3</text></g></g><g stroke="rgb(0,0,0)" stroke-opacity="1.0" fill="rgb(0,0,0)" fill-opacity="1.0" stroke-width="0.0"><g fill="rgb(0,0,0)" fill-opacity="1.0"><text transform="matrix(39.56363636363636,0.0,0.0,39.56363636363636,23.272727272727273,36.305454545454545)" dominant-baseline="middle" text-anchor="middle" stroke="none">1</text></g></g></g></g></g></g></svg>
diff --git a/test/TestSuite.hs b/test/TestSuite.hs
new file mode 100644
--- /dev/null
+++ b/test/TestSuite.hs
@@ -0,0 +1,41 @@
+
+module Main where
+
+--------------------------------------------------------------------------------
+
+import Test.Framework
+import Test.Framework.Providers.QuickCheck2
+
+import Tests.Permutations       ( testgroup_Permutations      )
+import Tests.Partitions.Integer ( testgroup_IntegerPartitions )
+import Tests.Partitions.Skew    ( testgroup_SkewPartitions    )
+import Tests.Braid              ( testgroup_Braid 
+                                , testgroup_Braid_NF          )
+import Tests.Series             ( testgroup_PowerSeries       )
+import Tests.SkewTableaux       ( testgroup_SkewTableaux      )
+import Tests.Thompson           ( testgroup_ThompsonF         )
+import Tests.LatticePaths       ( testgroup_LatticePaths      )
+
+--------------------------------------------------------------------------------
+
+main :: IO ()
+main = defaultMain tests
+
+tests :: [Test]
+tests = 
+  [ testgroup_Permutations
+  , testGroup "Partitions" 
+      [ testgroup_IntegerPartitions
+      , testgroup_SkewPartitions
+      ]
+  , testgroup_SkewTableaux
+  , testgroup_ThompsonF
+  , testgroup_LatticePaths
+  , testGroup "Braids" 
+      [ testgroup_Braid 
+      , testgroup_Braid_NF 
+      ]
+  , testgroup_PowerSeries  
+  ]
+
+--------------------------------------------------------------------------------
diff --git a/test/Tests/Braid.hs b/test/Tests/Braid.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests/Braid.hs
@@ -0,0 +1,278 @@
+
+-- | Tests for braids. 
+
+{-# LANGUAGE 
+      CPP, BangPatterns, 
+      ScopedTypeVariables, ExistentialQuantification,
+      DataKinds, KindSignatures, Rank2Types,
+      TypeOperators, TypeFamilies,
+      StandaloneDeriving #-}
+
+module Tests.Braid where
+
+--------------------------------------------------------------------------------
+
+import Math.Combinat.Groups.Braid
+import Math.Combinat.Groups.Braid.NF
+
+import Tests.Permutations ()     -- arbitrary instance
+import Tests.Common
+
+import Test.Framework
+import Test.Framework.Providers.QuickCheck2
+import Test.QuickCheck
+import Test.QuickCheck.Gen
+
+import Data.Proxy
+import GHC.TypeLits
+
+import Control.Monad
+
+import Data.List ( mapAccumL , foldl' )
+
+import Data.Array.Unboxed
+import Data.Array.ST
+import Data.Array.IArray
+import Data.Array.MArray
+import Data.Array.Unsafe
+import Data.Array.Base
+
+import Control.Monad.ST
+
+import System.Random
+
+import Math.Combinat.Sign
+import Math.Combinat.Helper
+import Math.Combinat.TypeLevel
+import Math.Combinat.Numbers.Series
+
+import Math.Combinat.Permutations ( Permutation(..) )
+import qualified Math.Combinat.Permutations as P
+
+--------------------------------------------------------------------------------
+-- * Types and instances
+
+maxBraidWordLen :: Int
+maxBraidWordLen = 600
+
+maxStrands :: Int
+maxStrands = 18         -- normal forms are very slow for large ones
+
+shrinkBraid :: KnownNat n => Braid n -> [Braid n]
+shrinkBraid (Braid gens) = map Braid list where
+  len  = length gens
+  list = [ take i gens ++ drop (i+1) gens | i<-[0..len-1] ]
+
+-- someRndBraid :: Int -> (forall (n :: Nat). KnownNat n => g -> (Braid n, g)) -> g -> (SomeBraid, g)
+-- someRndBraid n f = \g -> let (x,g') = f g in (someBraid n x, g')
+
+-- | equality as /braid words/
+(=:=) :: Braid n -> Braid n -> Bool
+(=:=) (Braid gens1) (Braid gens2) = (gens1 == gens2)
+
+data UnreducedBraid   = forall n. KnownNat n => Unreduced (Braid n)              
+data ReducedBraid     = forall n. KnownNat n => Reduced   (Braid n)              
+data PositiveBraid    = forall n. KnownNat n => PositiveB (Braid n)              
+data PerturbedBraid   = forall n. KnownNat n => Perturbed (Braid n)   (Braid n)  
+data PermutationBraid = forall n. KnownNat n => PermBraid Permutation (Braid n)  
+data TwoBraids        = forall n. KnownNat n => TwoBraids (Braid n)   (Braid n)  
+
+deriving instance Show UnreducedBraid
+deriving instance Show ReducedBraid
+deriving instance Show PositiveBraid
+deriving instance Show PerturbedBraid
+deriving instance Show PermutationBraid
+deriving instance Show TwoBraids
+
+instance KnownNat n => Random (Braid n) where
+  randomR _ = random
+  random g0 = (b, g2) where
+    n = numberOfStrands b
+    (l,g1) = randomR (0,maxBraidWordLen) g0
+    (b,g2) = randomBraidWord l g1
+
+instance Random UnreducedBraid where
+  randomR _ = random
+  random = runRand $ do
+    n <- randChoose (2,maxStrands)
+    l <- randChoose (0,maxBraidWordLen)
+    withSelectedM Unreduced (rand $ randomBraidWord l) n
+
+instance Random PositiveBraid where
+  randomR _ = random
+  random  = runRand $ do
+    n <- randChoose (2,maxStrands)
+    l <- randChoose (0,maxBraidWordLen)
+    withSelectedM PositiveB (rand $ randomPositiveBraidWord l) n
+
+instance Random PerturbedBraid where
+  randomR _ = random
+  random  = runRand $ do
+    Unreduced b <- rand random
+    k <- randChoose (20,1000)
+    c <- rand $ randomPerturbBraidWord k b 
+    return (Perturbed b c)
+
+instance KnownNat n => Arbitrary (Braid n) where
+  arbitrary = choose_
+  shrink    = shrinkBraid
+
+instance Arbitrary UnreducedBraid where
+  arbitrary = choose_
+  shrink (Unreduced b) = map Unreduced (shrinkBraid b)
+
+instance Arbitrary PositiveBraid where
+  arbitrary = choose_
+  shrink (PositiveB b) = map PositiveB (shrinkBraid b)
+
+instance Arbitrary ReducedBraid where
+  arbitrary = do
+    Unreduced braid <- arbitrary
+    return $ Reduced $ freeReduceBraidWord braid
+
+instance Arbitrary PerturbedBraid where
+  arbitrary = choose_
+  shrink _  = []
+
+instance Arbitrary TwoBraids where
+  shrink _  = []
+  arbitrary = do
+    n <- choose (2::Int, maxStrands)
+    let snat = case someNatVal (fromIntegral n :: Integer) of
+          Just sn -> sn
+          Nothing -> error "TwoBraids/arbitrary: shouldn't happen"
+    case snat of 
+      SomeNat pxy -> do
+        (braid1,braid2) <- choosePair_
+        return $ TwoBraids (asProxyTypeOf1 braid1 pxy) (asProxyTypeOf1 braid2 pxy)
+
+mkPermBraid :: Permutation -> PermutationBraid
+mkPermBraid perm = 
+  case snat of    
+    SomeNat pxy -> PermBraid perm (asProxyTypeOf1 (permutationBraid perm) pxy)
+  where
+    n = P.permutationSize perm
+    Just snat = someNatVal (fromIntegral n :: Integer)
+
+instance Arbitrary PermutationBraid where
+  arbitrary = do
+    perm <- arbitrary
+    return $ mkPermBraid perm
+  shrink (PermBraid x b) = [ PermBraid (braidPermutation s) s | s <- shrinkBraid b ]
+
+--------------------------------------------------------------------------------
+-- * test groups
+
+testgroup_Braid :: Test
+testgroup_Braid = testGroup "Braid"
+  
+  [ testProperty "linking matrix is invariant of reduction"    prop_link_reduce 
+  , testProperty "linking matrix is invariant of perturbation" prop_link_perturb
+  
+  , testProperty "tau^2 = identity"                    prop_tau_square
+  , testProperty "tau commutes with braidPermutation"  prop_permTau_1
+
+  , testProperty "braidPermutation . permutationBraid = identity"  prop_permBraid_perm
+  , testProperty "permutation braid is indeed a permutation braid" prop_permBraid_valid
+  , testProperty "multiplication commutes with braidPermutation" prop_braidPerm_comp
+
+  , testProperty "positive braids have positive links" prop_link_positive
+  , testProperty "definition of linking"               prop_linking
+
+  ] 
+
+--------------------------------------------------------------------------------
+
+testgroup_Braid_NF :: Test
+testgroup_Braid_NF = testGroup "Braid/NF"
+  
+  [ testProperty "NF with naive inverse elimination == less naive inverse elimination"  prop_braidnf_naive
+  , testProperty "NF with reduction == NF without reduction"                            prop_braidnf_reduce
+
+  , testProperty "NF = NF of representative word of NF"   prop_braidnf_reprs
+  , testProperty "NF = NF of perturbed word"              prop_braidnf_perturb
+
+  , testProperty "linking of word == linking of representative of NF"   prop_braidnf_link
+
+  , testProperty "NF of positive word is positive"   prop_braidnf_pos
+
+  , testProperty "Lemma 2.5"   prop_lemma_2_5
+
+  , testProperty "permutationBraid and tau commutes, up to NF"   prop_permTau_2
+  ]
+
+--------------------------------------------------------------------------------
+-- * braid properties
+
+prop_link_reduce :: UnreducedBraid -> Bool
+prop_link_reduce (Unreduced braid) = linkingMatrix braid == linkingMatrix braid' where
+  braid' = freeReduceBraidWord braid
+
+prop_link_perturb :: PerturbedBraid -> Bool
+prop_link_perturb (Perturbed braid1 braid2) = linkingMatrix braid1 == linkingMatrix braid2 
+
+prop_tau_square :: ReducedBraid -> Bool
+prop_tau_square (Reduced braid) = braidWord (tau (tau braid)) == braidWord braid
+
+prop_permTau_1 :: PermutationBraid -> Bool
+prop_permTau_1 (PermBraid perm braid) = tauPerm perm == braidPermutation (tau braid)
+
+prop_permBraid_perm :: PermutationBraid -> Bool
+prop_permBraid_perm (PermBraid perm braid) = (braidPermutation braid == perm)
+
+prop_permBraid_valid :: PermutationBraid -> Bool
+prop_permBraid_valid (PermBraid perm braid) = isPermutationBraid braid
+
+prop_braidPerm_comp :: TwoBraids -> Bool
+prop_braidPerm_comp (TwoBraids b1 b2) = (p == q) where
+  p = braidPermutation (compose b1 b2) 
+  q = braidPermutation b1 `P.multiply` braidPermutation b2
+
+prop_link_positive :: PositiveBraid -> Bool
+prop_link_positive (PositiveB braid) = all (>=0) $ elems $ linkingMatrix braid
+
+prop_linking :: UnreducedBraid -> Bool
+prop_linking (Unreduced braid) = (linkingMatrix braid == matrix) where
+  n = numberOfStrands braid
+  matrix = array ((1,1),(n,n)) [ ((i,j),strandLinking braid i j) | i<-[1..n], j<-[1..n] ]
+
+--------------------------------------------------------------------------------
+
+prop_braidnf_naive :: UnreducedBraid -> Bool
+prop_braidnf_naive (Unreduced braid) = (braidNormalFormNaive' braid == braidNormalForm' braid)
+
+prop_braidnf_reduce :: UnreducedBraid -> Bool
+prop_braidnf_reduce (Unreduced braid) = (braidNormalForm' braid == braidNormalForm braid)
+
+prop_braidnf_reprs :: ReducedBraid -> Bool
+prop_braidnf_reprs (Reduced braid) = (nf == nf') where
+  nf  = braidNormalForm braid 
+  nf' = braidNormalForm braid'
+  braid' = nfReprWord nf
+
+prop_braidnf_perturb :: PerturbedBraid -> Bool
+prop_braidnf_perturb (Perturbed braid1 braid2) = (braidNormalForm braid1 == braidNormalForm braid2)
+
+prop_braidnf_link :: UnreducedBraid -> Bool
+prop_braidnf_link (Unreduced braid) = (linkingMatrix braid == linkingMatrix braid') where
+  nf  = braidNormalForm braid 
+  braid' = nfReprWord nf
+
+prop_braidnf_pos :: PositiveBraid -> Bool
+prop_braidnf_pos (PositiveB braid) = (_nfDeltaExp (braidNormalForm braid) >= 0)
+ 
+prop_lemma_2_5 :: Permutation -> Bool
+prop_lemma_2_5 p = and [ check i | i<-[1..n-1] ] where
+  n = P.permutationSize p
+  w = _permutationBraid p
+  s = permWordStartingSet n w
+  check i = _isPermutationBraid n (i:w) == (not $ elem i s)
+
+prop_permTau_2 :: PermutationBraid -> Bool
+prop_permTau_2 (PermBraid perm braid) = (nf1 == nf2) where
+  nf1 = braidNormalForm $ permutationBraid (tauPerm perm)
+  nf2 = braidNormalForm $ tau braid
+
+--------------------------------------------------------------------------------
+
+
diff --git a/test/Tests/Common.hs b/test/Tests/Common.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests/Common.hs
@@ -0,0 +1,35 @@
+
+-- | Helper routines for tests
+
+{-# LANGUAGE Rank2Types #-}
+module Tests.Common where
+
+--------------------------------------------------------------------------------
+
+import Test.QuickCheck
+import Test.QuickCheck.Gen
+
+import System.Random
+
+--------------------------------------------------------------------------------
+
+-- | Generates a random element.
+choose_ :: Random a => Gen a
+choose_ = MkGen (\r _ -> let (x,_) = random r in x)
+
+-- | Generates two random elements 
+choosePair_ :: Random a => Gen (a,a)
+choosePair_ = do
+  x <- choose_
+  y <- choose_
+  return (x,y)
+
+-- | Generates a random element.
+myMkGen :: (forall g. RandomGen g => g -> (a,g)) -> Gen a
+myMkGen fun = MkGen (\r _ -> let (x,_) = fun r in x)
+
+-- | Generates a random element.
+myMkSizedGen :: (forall g. RandomGen g => Int -> g -> (a,g)) -> Gen a
+myMkSizedGen fun = MkGen (\r siz -> let (x,_) = fun siz r in x)
+
+--------------------------------------------------------------------------------
diff --git a/test/Tests/LatticePaths.hs b/test/Tests/LatticePaths.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests/LatticePaths.hs
@@ -0,0 +1,111 @@
+
+-- | Tests for lattice paths 
+--
+
+{-# LANGUAGE CPP, ScopedTypeVariables, GeneralizedNewtypeDeriving, FlexibleContexts #-}
+module Tests.LatticePaths where
+
+--------------------------------------------------------------------------------
+
+import Math.Combinat.LatticePaths
+
+import Test.Framework
+import Test.Framework.Providers.QuickCheck2
+import Test.QuickCheck
+import System.Random
+
+import Control.Monad
+
+import Data.List  
+
+import Math.Combinat.Classes
+import Math.Combinat.Helper
+import Math.Combinat.Sign
+import Math.Combinat.Numbers ( factorial , binomial )
+
+--------------------------------------------------------------------------------
+-- * instances
+
+-- | Half-length of a Dyck path
+newtype Half = Half Int deriving (Eq,Show)
+
+-- | First number is (usually) less or equal than the second
+data HalfPair = HalfPair Int Int deriving (Eq,Show)
+
+maxHalfSize :: Int
+maxHalfSize = 11     -- number of paths grow exponentially
+
+instance Arbitrary Half where
+  arbitrary = liftM Half $ choose (0,maxHalfSize)    
+
+instance Arbitrary HalfPair where
+  arbitrary = do
+    n <- choose (0,maxHalfSize)     
+    k <- choose (0,n+1)
+    return (HalfPair k n)
+
+fi :: Int -> Integer
+fi = fromIntegral
+
+--------------------------------------------------------------------------------
+-- * test group
+
+testgroup_LatticePaths :: Test
+testgroup_LatticePaths = testGroup "Lattice paths"
+  
+  [ testProperty "dyck paths are in reverse lexicographic order"      prop_revlex
+  , testProperty "naive Dyck path algorithm = less naive algorithm"   prop_dyck_naive
+  , testProperty "counting Dyck paths"                                prop_count
+  , testProperty "counting Lattice paths"                             prop_count_lattice
+
+  , testProperty "bounded Dyck paths, def, v1"                        prop_bounded_1
+  , testProperty "bounded Dyck paths, def, v2"                        prop_bounded_2
+  , testProperty "bounded Dyck paths w/ high bound = all dyck paths"  prop_not_bounded
+
+  , testProperty "zero-touching Dyck paths"              prop_touching
+  , testProperty "Dyck paths w/ k peaks"                 prop_peaking
+
+  ]
+
+--------------------------------------------------------------------------------
+-- * test properties         
+
+prop_revlex :: Bool
+prop_revlex = and [ sort (dyckPaths m) == reverse (dyckPaths m) | m <- [0..maxHalfSize] ]
+
+prop_dyck_naive :: Bool
+prop_dyck_naive = and [ sort (dyckPathsNaive m) == sort (dyckPaths m) | m <- [0..maxHalfSize] ]
+
+prop_count :: Bool
+prop_count = and [ fi (length (dyckPaths m)) == countDyckPaths m | m <- [0..maxHalfSize] ]
+
+prop_count_lattice :: HalfPair -> Bool
+prop_count_lattice (HalfPair y x) = fi (length (latticePaths (x,y))) == countLatticePaths (x,y)
+
+prop_bounded_1 :: HalfPair -> Bool
+prop_bounded_1 (HalfPair h m) = (one == two) where
+  one = sort (boundedDyckPaths h m ) 
+  two = sort [ p | p <- dyckPaths m  , pathHeight p <= h ]
+  
+prop_bounded_2 :: Half -> Half -> Bool
+prop_bounded_2 (Half h) (Half m) = (one == two) where
+  one = sort (boundedDyckPaths h  m ) 
+  two = sort [ p | p <- dyckPaths m  , pathHeight p <= h  ]
+
+prop_not_bounded :: Bool
+prop_not_bounded = and [ sort (boundedDyckPaths m m) == sort (dyckPaths m) | m <- [0..maxHalfSize] ]
+
+prop_touching :: HalfPair -> Bool
+prop_touching (HalfPair k m) = (one == two && fi (length one) == cnt) where
+  one = sort (touchingDyckPaths k m) 
+  two = sort [ p | p <- dyckPaths m , pathNumberOfZeroTouches p == k ]
+  cnt = countTouchingDyckPaths k m
+
+prop_peaking :: HalfPair -> Bool
+prop_peaking (HalfPair k m) = (one == two && fi (length one) == cnt) where
+  one = sort (peakingDyckPaths k m) 
+  two = sort [ p | p <- dyckPaths m , pathNumberOfPeaks p == k ]
+  cnt = countPeakingDyckPaths k m
+
+--------------------------------------------------------------------------------
+
diff --git a/test/Tests/Partitions/Integer.hs b/test/Tests/Partitions/Integer.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests/Partitions/Integer.hs
@@ -0,0 +1,107 @@
+
+-- | Tests for integer partitions.
+
+{-# LANGUAGE CPP, BangPatterns #-}
+module Tests.Partitions.Integer where
+
+--------------------------------------------------------------------------------
+
+import Test.Framework
+import Test.Framework.Providers.QuickCheck2
+import Test.QuickCheck
+
+import Tests.Common
+
+import Math.Combinat.Partitions.Integer
+
+import Data.List
+import Control.Monad
+
+-- import Data.Map (Map)
+-- import qualified Data.Map as Map
+
+import Math.Combinat.Classes
+import Math.Combinat.Numbers ( factorial , binomial , multinomial )
+import Math.Combinat.Helper
+
+--------------------------------------------------------------------------------
+-- * Types and instances
+
+newtype PartitionWeight     = PartitionWeight     Int              deriving (Eq,Show)
+data    PartitionWeightPair = PartitionWeightPair Int Int          deriving (Eq,Show)
+data    PartitionIntPair    = PartitionIntPair    Partition Int    deriving (Eq,Show)
+
+maxPartitionSize :: Int
+maxPartitionSize = 44
+
+instance Arbitrary Partition where
+  arbitrary = do
+    n <- choose (0,maxPartitionSize)
+    myMkGen (randomPartition n)
+
+instance Arbitrary PartitionWeight where
+  arbitrary = liftM PartitionWeight $ choose (0,maxPartitionSize)
+
+instance Arbitrary PartitionWeightPair where
+  arbitrary = do
+    n <- choose (0,maxPartitionSize)
+    k <- choose (0,n+2)
+    return (PartitionWeightPair n k)
+
+instance Arbitrary PartitionIntPair where
+  arbitrary = do
+    part <- arbitrary
+    k <- choose (0,partitionWeight part + 2)
+    return (PartitionIntPair part k)
+
+--------------------------------------------------------------------------------
+-- * test group
+
+testgroup_IntegerPartitions :: Test
+testgroup_IntegerPartitions = testGroup "Integer Partitions"  
+
+  [ testProperty "partitions in a box"             prop_partitions_in_bigbox
+  , testProperty "partitions with k parts"         prop_kparts
+  , testProperty "odd partitions"                  prop_odd_partitions 
+  , testProperty "partitions with distinct parts"  prop_distinct_partitions  
+  , testProperty "subpartitions"                   prop_subparts
+  , testProperty "dual^2 is identity"              prop_dual_dual
+  , testProperty "dominated partitions"            prop_dominated_list
+  , testProperty "dominating partitions"           prop_dominating_list
+  , testProperty "counting partitions"             prop_countParts
+  ]
+
+--------------------------------------------------------------------------------
+-- * properties
+
+prop_partitions_in_bigbox :: PartitionWeight -> Bool
+prop_partitions_in_bigbox (PartitionWeight n) = (partitions n == partitions' (n,n) n)
+
+prop_kparts :: PartitionWeightPair -> Bool
+prop_kparts (PartitionWeightPair n k) = (partitionsWithKParts k n == [ mu | mu <- partitions n, numberOfParts mu == k ])
+
+prop_odd_partitions :: PartitionWeight -> Bool
+prop_odd_partitions (PartitionWeight n) = 
+  (partitionsWithOddParts n == [ mu | mu <- partitions n, and (map odd (fromPartition mu)) ])
+
+prop_distinct_partitions :: PartitionWeight -> Bool
+prop_distinct_partitions (PartitionWeight n) = 
+  (partitionsWithDistinctParts n == [ mu | mu <- partitions n, let xs = fromPartition mu, xs == nub xs ])
+
+prop_subparts :: PartitionIntPair -> Bool
+prop_subparts (PartitionIntPair lam d) = (subPartitions d lam) == sort [ p | p <- partitions d, isSubPartitionOf p lam ]
+
+prop_dual_dual :: Partition -> Bool
+prop_dual_dual lam = (lam == dualPartition (dualPartition lam))
+
+prop_dominated_list :: Partition -> Bool
+prop_dominated_list lam = (dominatedPartitions  lam == [ mu  | mu  <- partitions (weight lam), lam `dominates` mu ])
+
+prop_dominating_list :: Partition -> Bool
+prop_dominating_list mu  = (dominatingPartitions mu  == [ lam | lam <- partitions (weight mu ), lam `dominates` mu ])
+
+prop_countParts :: Bool
+prop_countParts = (take 50 partitionCountList == take 50 partitionCountListNaive)
+
+--------------------------------------------------------------------------------
+
diff --git a/test/Tests/Partitions/Skew.hs b/test/Tests/Partitions/Skew.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests/Partitions/Skew.hs
@@ -0,0 +1,85 @@
+
+-- | Tests for skew partitions.
+--
+
+{-# LANGUAGE CPP, BangPatterns #-}
+module Tests.Partitions.Skew where
+
+--------------------------------------------------------------------------------
+
+import Test.Framework
+import Test.Framework.Providers.QuickCheck2
+import Test.QuickCheck
+
+import Tests.Common
+import Tests.Partitions.Integer ()     -- Arbitrary instances
+
+import Math.Combinat.Partitions.Integer
+import Math.Combinat.Partitions.Skew
+
+import Data.List
+
+import Math.Combinat.Classes
+
+--------------------------------------------------------------------------------
+-- * instances
+
+instance Arbitrary SkewPartition where
+  arbitrary = do
+    p <- arbitrary
+    let n = partitionWeight p
+    d <- choose (0,n)
+    let qs = subPartitions d p
+        ln = length qs
+    k <- choose (0,ln-1)
+    let q = qs !! k
+    return $ mkSkewPartition (p,q) 
+
+--------------------------------------------------------------------------------
+-- * test group
+
+testgroup_SkewPartitions :: Test
+testgroup_SkewPartitions = testGroup "Skew Partitions"  
+
+  [ testProperty "dual^2 = identity"              prop_dual_dual
+  , testProperty "dual vs. inner/outer dual"      prop_dual_from
+  , testProperty "to . from = identity"           prop_from_to
+  , testProperty "from . to = identity"           prop_to_from
+  , testProperty "from . to . from = from"        prop_from_to_from
+  , testProperty "weight vs. inner/outer weight"  prop_weight
+  ]
+
+--------------------------------------------------------------------------------
+-- * properties
+
+prop_dual_dual :: SkewPartition -> Bool
+prop_dual_dual sp = (dualSkewPartition (dualSkewPartition sp) == sp)
+
+prop_dual_from :: SkewPartition -> Bool
+prop_dual_from sp = (p == dual p' && q == dual q') where
+  (p,q)   = fromSkewPartition sp
+  sp'     = dualSkewPartition sp
+  (p',q') = fromSkewPartition sp'
+
+prop_from_to :: SkewPartition -> Bool
+prop_from_to sp = (mkSkewPartition (fromSkewPartition sp) == sp)
+
+prop_to_from :: (Partition,Partition) -> Bool
+prop_to_from (p,q) = 
+  case mb of
+    Nothing -> True
+    Just sp -> fromSkewPartition sp == (p,q)
+  where
+    mb = safeSkewPartition (p,q)
+
+prop_from_to_from :: SkewPartition -> Bool
+prop_from_to_from sp = (pq == pq') where
+  pq  = fromSkewPartition sp
+  sp' = mkSkewPartition pq
+  pq' = fromSkewPartition sp'
+
+prop_weight :: SkewPartition -> Bool
+prop_weight sp = (skewPartitionWeight sp == weight p - weight q) where
+  (p,q) = fromSkewPartition sp
+
+--------------------------------------------------------------------------------
diff --git a/test/Tests/Permutations.hs b/test/Tests/Permutations.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests/Permutations.hs
@@ -0,0 +1,219 @@
+
+-- | Tests for permutations. 
+--
+
+{-# LANGUAGE CPP, ScopedTypeVariables, GeneralizedNewtypeDeriving, FlexibleContexts #-}
+module Tests.Permutations where
+
+--------------------------------------------------------------------------------
+
+import Math.Combinat.Permutations
+
+import Test.Framework
+import Test.Framework.Providers.QuickCheck2
+import Test.QuickCheck
+import System.Random
+
+import Control.Monad
+import Control.Monad.ST
+
+import Data.List hiding (permutations)
+
+import Data.Array (Array)
+import Data.Array.ST
+import Data.Array.Unboxed
+import Data.Array.IArray
+import Data.Array.MArray
+import Data.Array.Unsafe
+
+import Math.Combinat.ASCII
+import Math.Combinat.Classes
+import Math.Combinat.Helper
+import Math.Combinat.Sign
+import Math.Combinat.Numbers (factorial,binomial)
+
+--------------------------------------------------------------------------------
+-- * generating permutations (random & arbitrary instances, spec types etc)
+
+minPermSize = 1
+maxPermSize = 123
+
+newtype Elem = Elem Int deriving Eq
+newtype Nat  = Nat { fromNat :: Int } deriving (Eq,Ord,Show,Num,Random)
+
+naturalSet :: Permutation -> Array Int Elem
+naturalSet perm = listArray (1,n) [ Elem i | i<-[1..n] ] where
+  n = permutationSize perm
+
+permInternalSet :: Permutation -> Array Int Elem
+permInternalSet perm@(Permutation arr) = listArray (1,n) [ Elem (arr!i) | i<-[1..n] ] where
+  n = permutationSize perm
+
+sameSize :: Permutation ->  Permutation -> Bool
+sameSize perm1 perm2 = ( permutationSize perm1 == permutationSize perm2)
+
+newtype CyclicPermutation = Cyclic { fromCyclic :: Permutation } deriving Show
+
+data SameSize = SameSize Permutation Permutation deriving Show
+
+instance Random Permutation where
+  random g = randomPermutation size g1 where
+    (size,g1) = randomR (minPermSize,maxPermSize) g
+  randomR _ = random
+
+instance Random CyclicPermutation where
+  random g = (Cyclic cycl,g2) where
+    (size,g1) = randomR (minPermSize,maxPermSize) g
+    (cycl,g2) = randomCyclicPermutation size g1
+  randomR _ = random
+
+instance Random DisjointCycles where
+  random g = (disjcyc,g2) where
+    (size,g1) = randomR (minPermSize,maxPermSize) g
+    (perm,g2) = randomPermutation size g1
+    disjcyc   = permutationToDisjointCycles perm
+  randomR _ = random
+
+instance Random SameSize where
+  random g = (SameSize prm1 prm2, g3) where
+    (size,g1) = randomR (minPermSize,maxPermSize) g
+    (prm1,g2) = randomPermutation size g1 
+    (prm2,g3) = randomPermutation size g2
+  randomR _ = random
+
+instance Arbitrary Nat where
+  arbitrary = choose (Nat 0 , Nat 50)
+     
+instance Arbitrary Permutation       where arbitrary = choose undefined
+instance Arbitrary CyclicPermutation where arbitrary = choose undefined
+instance Arbitrary DisjointCycles    where arbitrary = choose undefined
+instance Arbitrary SameSize          where arbitrary = choose undefined
+
+--------------------------------------------------------------------------------
+-- * test group
+
+testgroup_Permutations :: Test
+testgroup_Permutations = testGroup "Permutations"
+  
+  [ testProperty "disjoint cycles /1" prop_disjcyc_1
+  , testProperty "disjoint cycles /2" prop_disjcyc_2 
+
+  , testProperty "disjoint cycles compatibility" prop_disjcyc_Mathematica
+
+  , testProperty "random cyclic permutation is indeed cyclic" prop_randCyclic
+  , testProperty "inverse^2 is identity"                      prop_inverse
+
+  , testProperty "permutation action is group action"              prop_mulPerm
+  , testProperty "left permutation action is left group action"    prop_mulPermLeft
+  , testProperty "right permutation action is right group action"  prop_mulPermRight
+
+  , testProperty "permutation action convetion"        prop_perm
+  , testProperty "left permutation action convention"  prop_permLeft
+  , testProperty "right permutation action convention" prop_permRight
+  , testProperty "left/right permutation action convention" prop_permLeftRight
+
+  , testProperty "cycle left"  prop_cycleLeft
+  , testProperty "cycle right" prop_cycleRight
+
+  , testProperty "sign of permutation is multiplicative"     prop_mulSign      
+  , testProperty "inverse is compatible with multiplication" prop_invMul
+
+  , testProperty "parity of cyclic permutaiton" prop_cyclSign
+  , testProperty "random permutation is valid"  prop_permIsPerm
+  , testProperty "definition of parity"         prop_isEven
+
+  , testProperty "bubbleSort works"    prop_bubbleSort
+  , testProperty "bubbleSort2 works"   prop_bubbleSort2
+  , testProperty "number of inversions = steps in bubble sort"         prop_bubble_inversions
+  , testProperty "number of inversions = actual number of inversions"  prop_number_inversions 
+  , testProperty "number of inversions is the same for the inverse permutation"  prop_ninversions_inverse
+  , testProperty "merge sort algorithm = naive inversion count"                  prop_merge_inversions
+
+  ]
+
+--------------------------------------------------------------------------------
+-- * test properties
+          
+prop_disjcyc_1 perm = ( perm == disjointCyclesToPermutation n (permutationToDisjointCycles perm) )
+  where n = permutationSize perm
+
+prop_disjcyc_2 k dcyc = ( dcyc == permutationToDisjointCycles (disjointCyclesToPermutation n dcyc) )
+  where 
+    n = fromNat k + m 
+    m = case fromDisjointCycles dcyc of
+      []  -> 1
+      xxs -> maximum (concat xxs)
+
+-- PermutationCycles[ { 12, 15, 5, 6, 2, 7, 17, 9, 20, 3, 11, 18, 22, 21, 8, 10, 4, 19, 14, 16, 23, 1, 13 } ]
+-- Cycles           [ {{1, 12, 18, 19, 14, 21, 23, 13, 22}, {2, 15, 8, 9, 20, 16, 10, 3, 5}, {4, 6, 7, 17}} ]
+prop_disjcyc_Mathematica = (permutationToDisjointCycles   perm == disjcyc) 
+                        && (disjointCyclesToPermutation n disjcyc == perm)
+  where
+    n       = permutationSize perm
+    perm    = toPermutation  [ 12, 15, 5, 6, 2, 7, 17, 9, 20, 3, 11, 18, 22, 21, 8, 10, 4, 19, 14, 16, 23, 1, 13 ]
+    disjcyc = DisjointCycles [ [1, 12, 18, 19, 14, 21, 23, 13, 22], [2, 15, 8, 9, 20, 16, 10, 3, 5], [4, 6, 7, 17] ]
+
+xperm    = toPermutation  [ 12, 15, 5, 6, 2, 7, 17, 9, 20, 3, 11, 18, 22, 21, 8, 10, 4, 19, 14, 16, 23, 1, 13 ]
+xdisjcyc = DisjointCycles [ [1, 12, 18, 19, 14, 21, 23, 13, 22], [2, 15, 8, 9, 20, 16, 10, 3, 5], [4, 6, 7, 17] ]
+
+prop_randCyclic cycl = ( isCyclicPermutation (fromCyclic cycl) )
+
+prop_inverse perm = ( perm == inverse (inverse perm) ) 
+
+prop_mulPerm (SameSize perm1 perm2) = 
+    ( permute perm2 (permute perm1 set) == permute (perm1 `multiply` perm2) set ) 
+  where 
+    set = naturalSet perm1
+
+prop_mulPermRight (SameSize perm1 perm2) = 
+    ( permuteRight perm2 (permuteRight perm1 set) == permuteRight (perm1 `multiply` perm2) set ) 
+  where 
+    set = naturalSet perm1
+
+prop_mulPermLeft (SameSize perm1 perm2) = 
+    ( permuteLeft perm2 (permuteLeft perm1 set) == permuteLeft (perm2 `multiply` perm1) set ) 
+  where 
+    set = naturalSet perm1
+
+prop_perm          perm = permute perm (naturalSet perm) == permInternalSet perm
+prop_permLeft      perm = permuteLeft  perm (permInternalSet perm) == naturalSet perm
+prop_permRight     perm = permuteRight perm (naturalSet perm) == permInternalSet perm
+prop_permLeftRight perm = permuteLeft (inverse perm) (naturalSet perm) == permuteRight (perm) (naturalSet perm) 
+
+prop_cycleLeft  = permuteList (cycleLeft  5) "abcde" == "bcdea"
+prop_cycleRight = permuteList (cycleRight 5) "abcde" == "eabcd"
+
+prop_mulSign (SameSize perm1 perm2) = 
+    ( sgn perm1 * sgn perm2 == sgn (perm1 `multiply` perm2) ) 
+  where 
+    sgn = signValue . signOfPermutation :: Permutation -> Int
+
+prop_invMul (SameSize perm1 perm2) =   
+  ( inverse perm2 `multiply` inverse perm1 == inverse (perm1 `multiply` perm2) ) 
+
+prop_cyclSign cycl = ( isEvenPermutation perm == odd n ) where
+  perm = fromCyclic cycl
+  n = permutationSize perm
+  
+prop_permIsPerm perm = ( isPermutation (fromPermutation perm) ) 
+
+prop_isEven perm = ( isEvenPermutation perm == isEvenAlternative perm ) where
+  isEvenAlternative p = 
+    even $ sum $ map (\x->x-1) $ map length $ fromDisjointCycles $ permutationToDisjointCycles p
+
+prop_bubbleSort perm = multiplyMany' n (map (adjacentTransposition n) $ bubbleSort perm) == perm where
+  n = permutationSize perm
+
+prop_bubbleSort2 perm = multiplyMany' n (map (transposition n) $ bubbleSort2 perm) == perm where
+  n = permutationSize perm
+
+prop_bubble_inversions perm = length (bubbleSort perm) == numberOfInversions perm
+
+prop_number_inversions perm = length (inversions perm) == numberOfInversions perm
+
+prop_ninversions_inverse perm = numberOfInversions perm == numberOfInversions (inverse perm)
+
+prop_merge_inversions perm = (numberOfInversionsMerge perm == numberOfInversionsNaive perm)
+
+--------------------------------------------------------------------------------
+
diff --git a/test/Tests/Series.hs b/test/Tests/Series.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests/Series.hs
@@ -0,0 +1,303 @@
+
+-- | Tests for power series
+--
+
+{-# LANGUAGE CPP, GeneralizedNewtypeDeriving #-}
+module Tests.Series where
+
+--------------------------------------------------------------------------------
+
+import Math.Combinat.Numbers.Series
+
+import Test.Framework
+import Test.Framework.Providers.QuickCheck2
+import Test.QuickCheck
+import System.Random
+
+import Data.List
+
+import Math.Combinat.Sign
+import Math.Combinat.Numbers
+import Math.Combinat.Partitions.Integer
+import Math.Combinat.Helper
+
+--------------------------------------------------------------------------------
+-- * code used only for tests
+
+-- | Expansion of @1 / (1-x^k)@. Included for completeness only; 
+-- it equals to @coinSeries [k]@, and for example
+-- for @k=4@ it is simply
+-- 
+-- > [1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0...]
+--
+pseries1 :: Int -> [Integer]
+pseries1 k1 = convolveWithPSeries1 k1 unitSeries 
+
+-- | The expansion of @1 / (1-x^k_1-x^k_2)@
+pseries2 :: Int -> Int -> [Integer]
+pseries2 k1 k2 = convolveWithPSeries2 k1 k2 unitSeries 
+
+-- | The expansion of @1 / (1-x^k_1-x^k_2-x^k_3)@
+pseries3 :: Int -> Int -> Int -> [Integer]
+pseries3 k1 k2 k3 = convolveWithPSeries3 k1 k2 k3 unitSeries
+
+--------------------------------------------------------------------------------
+
+-- | Convolve with (the expansion of) @1 / (1-x^k1)@
+convolveWithPSeries1 :: Int -> [Integer] -> [Integer]
+convolveWithPSeries1 k1 series1 = xs where
+  series = series1 ++ repeat 0 
+  xs = zipWith (+) series ( replicate k1 0 ++ xs )
+
+-- | Convolve with (the expansion of) @1 / (1-x^k1-x^k2)@
+convolveWithPSeries2 :: Int -> Int -> [Integer] -> [Integer]
+convolveWithPSeries2 k1 k2 series1 = xs where
+  series = series1 ++ repeat 0 
+  xs = zipWith3 (\x y z -> x + y + z)
+    series
+    ( replicate k1 0 ++ xs )
+    ( replicate k2 0 ++ xs )
+    
+-- | Convolve with (the expansion of) @1 / (1-x^k_1-x^k_2-x^k_3)@
+convolveWithPSeries3 :: Int -> Int -> Int -> [Integer] -> [Integer]
+convolveWithPSeries3 k1 k2 k3 series1 = xs where
+  series = series1 ++ repeat 0 
+  xs = zipWith4 (\x y z w -> x + y + z + w)
+    series
+    ( replicate k1 0 ++ xs )
+    ( replicate k2 0 ++ xs )
+    ( replicate k3 0 ++ xs )
+
+--------------------------------------------------------------------------------
+
+-- | @1 / (1 - a*x^k)@. 
+-- For example, for @a=3@ and @k=2@ it is just
+-- 
+-- > [1,0,3,0,9,0,27,0,81,0,243,0,729,0,2187,0,6561,0,19683,0...]
+--
+pseries1' :: Num a => (a,Int) -> [a]
+pseries1' ak1 = convolveWithPSeries1' ak1 unitSeries
+
+-- | @1 / (1 - a_1*x^k_1 - a_2*x^k_2)@
+pseries2' :: Num a => (a,Int) -> (a,Int) -> [a]
+pseries2' ak1 ak2 = convolveWithPSeries2' ak1 ak2 unitSeries
+
+-- | @1 / (1 - a_1*x^k_1 - a_2*x^k_2 - a_3*x^k_3)@
+pseries3' :: Num a => (a,Int) -> (a,Int) -> (a,Int) -> [a]
+pseries3' ak1 ak2 ak3 = convolveWithPSeries3' ak1 ak2 ak3 unitSeries
+
+--------------------------------------------------------------------------------
+
+-- | Convolve with @1 / (1 - a*x^k)@. 
+convolveWithPSeries1' :: Num a => (a,Int) -> [a] -> [a]
+convolveWithPSeries1' (a1,k1) series1 = xs where
+  series = series1 ++ repeat 0 
+  xs = zipWith (+)
+    series
+    ( replicate k1 0 ++ map (*a1) xs )
+
+-- | Convolve with @1 / (1 - a_1*x^k_1 - a_2*x^k_2)@
+convolveWithPSeries2' :: Num a => (a,Int) -> (a,Int) -> [a] -> [a]
+convolveWithPSeries2' (a1,k1) (a2,k2) series1 = xs where
+  series = series1 ++ repeat 0 
+  xs = zipWith3 (\x y z -> x + y + z)
+    series
+    ( replicate k1 0 ++ map (*a1) xs )
+    ( replicate k2 0 ++ map (*a2) xs )
+    
+-- | Convolve with @1 / (1 - a_1*x^k_1 - a_2*x^k_2 - a_3*x^k_3)@
+convolveWithPSeries3' :: Num a => (a,Int) -> (a,Int) -> (a,Int) -> [a] -> [a]
+convolveWithPSeries3' (a1,k1) (a2,k2) (a3,k3) series1 = xs where
+  series = series1 ++ repeat 0 
+  xs = zipWith4 (\x y z w -> x + y + z + w)
+    series
+    ( replicate k1 0 ++ map (*a1) xs )
+    ( replicate k2 0 ++ map (*a2) xs )
+    ( replicate k3 0 ++ map (*a3) xs )
+
+--------------------------------------------------------------------------------
+-- * Types and instances
+
+{-
+swap :: (a,b) -> (b,a)
+swap (x,y) = (y,x)
+-}
+
+-- compare the first 500 elements of the infinite lists
+(=!=) :: (Eq a, Num a) => [a] -> [a] -> Bool
+(=!=) xs1 ys1 = (take m xs == take m ys) where 
+  m = 500
+  xs = xs1 ++ repeat 0
+  ys = ys1 ++ repeat 0
+
+infix 4 =!=
+
+newtype Nat = Nat { fromNat :: Int } deriving (Eq,Ord,Show,Num,Random)
+newtype Ser = Ser { fromSer :: [Integer] } deriving (Eq,Ord,Show)
+newtype Exp  = Exp  { fromExp  ::  Int  } deriving (Eq,Ord,Show,Num,Random)
+newtype Exps = Exps { fromExps :: [Int] } deriving (Eq,Ord,Show)
+newtype CoeffExp  = CoeffExp  { fromCoeffExp  ::  (Integer,Int)  } deriving (Eq,Ord,Show)
+newtype CoeffExps = CoeffExps { fromCoeffExps :: [(Integer,Int)] } deriving (Eq,Ord,Show)
+
+minSerSize = 0    :: Int
+maxSerSize = 1000 :: Int
+
+minSerValue = -10000 :: Int
+maxSerValue =  10000 :: Int
+
+rndList :: (RandomGen g, Random a) => Int -> (a, a) -> g -> ([a], g)
+rndList n minmax g = swap $ mapAccumL f g [1..n] where
+  f g _ = swap $ randomR minmax g 
+
+instance Arbitrary Nat where
+  arbitrary = choose (Nat 0 , Nat 750)
+
+instance Arbitrary Exp where
+  arbitrary = choose (Exp 1 , Exp 32)
+
+instance Arbitrary CoeffExp where
+  arbitrary = do
+    coeff <- choose (minSerValue, maxSerValue) :: Gen Int
+    exp   <- arbitrary :: Gen Exp
+    return $ CoeffExp (fromIntegral coeff, fromExp exp)
+   
+instance Random Ser where
+  random g = (Ser $ map fi list, g2) where
+    (size,g1) = randomR (minSerSize,maxSerSize) g
+    (list,g2) = rndList size (minSerValue,maxSerValue) g1
+    fi :: Int -> Integer
+    fi = fromIntegral 
+  randomR _ = random
+
+instance Random Exps where
+  random g = (Exps list, g2) where
+    (size,g1) = randomR (0,10) g
+    (list,g2) = rndList size (1,32) g1
+  randomR _ = random
+
+instance Random CoeffExps where
+  random g = (CoeffExps (zip (map fromIntegral list2) list1), g3) where
+    (size,g1) = randomR (0,10) g
+    (list1,g2) = rndList size (1,32) g1
+    (list2,g3) = rndList size (minSerValue,maxSerValue) g2
+  randomR _ = random
+  
+instance Arbitrary Ser where
+  arbitrary = choose undefined
+
+instance Arbitrary Exps where
+  arbitrary = choose undefined
+
+instance Arbitrary CoeffExps where
+  arbitrary = choose undefined
+
+--------------------------------------------------------------------------------
+-- * test group
+
+testgroup_PowerSeries :: Test
+testgroup_PowerSeries = testGroup "Power series"
+  [ 
+    testProperty "convPSeries1 vs generic"     prop_conv1_vs_gen
+  , testProperty "convPSeries2 vs generic"     prop_conv2_vs_gen
+  , testProperty "convPSeries3 vs generic"     prop_conv3_vs_gen
+  , testProperty "convPSeries1' vs generic"    prop_conv1_vs_gen'
+  , testProperty "convPSeries2' vs generic"    prop_conv2_vs_gen'
+  , testProperty "convPSeries3' vs generic"    prop_conv3_vs_gen'
+  , testProperty "convolve_pseries"            prop_convolve_pseries 
+  , testProperty "convolve_pseries'"           prop_convolve_pseries' 
+  , testProperty "coinSeries vs pseries"       prop_coin_vs_pseries
+  , testProperty "coinSeries vs pseries'"      prop_coin_vs_pseries'
+
+    -- these are very slow, because random is slow
+  , testProperty "leftIdentity"    prop_leftIdentity
+  , testProperty "rightIdentity"   prop_rightIdentity
+  , testProperty "commutativity"   prop_commutativity
+  , testProperty "associativity"   prop_associativity
+  ]
+
+--------------------------------------------------------------------------------
+-- * properties
+     
+prop_leftIdentity ser = ( xs =!= unitSeries `convolve` xs ) where 
+  xs = fromSer ser 
+
+prop_rightIdentity ser = ( unitSeries `convolve` xs =!= xs ) where 
+  xs = fromSer ser 
+
+prop_commutativity ser1 ser2 = ( xs `convolve` ys =!= ys `convolve` xs ) where 
+  xs = fromSer ser1
+  ys = fromSer ser2
+
+prop_associativity ser1 ser2 ser3 = ( one =!= two ) where
+  one = (xs `convolve` ys) `convolve` zs
+  two = xs `convolve` (ys `convolve` zs)
+  xs = fromSer ser1
+  ys = fromSer ser2
+  zs = fromSer ser3
+  
+prop_conv1_vs_gen exp1 ser = ( one =!= two ) where
+  one = convolveWithPSeries1 k1 xs 
+  two = convolveWithPSeries [k1] xs
+  k1 = fromExp exp1
+  xs = fromSer ser  
+
+prop_conv2_vs_gen exp1 exp2 ser = (one =!= two) where
+  one = convolveWithPSeries2 k1 k2 xs 
+  two = convolveWithPSeries [k2,k1] xs
+  k1 = fromExp exp1
+  k2 = fromExp exp2
+  xs = fromSer ser  
+
+prop_conv3_vs_gen exp1 exp2 exp3 ser = (one =!= two) where
+  one = convolveWithPSeries3 k1 k2 k3 xs 
+  two = convolveWithPSeries [k2,k3,k1] xs
+  k1 = fromExp exp1
+  k2 = fromExp exp2
+  k3 = fromExp exp3
+  xs = fromSer ser  
+
+prop_conv1_vs_gen' exp1 ser = ( one =!= two ) where
+  one = convolveWithPSeries1' ak1 xs 
+  two = convolveWithPSeries' [ak1] xs
+  ak1 = fromCoeffExp exp1
+  xs = fromSer ser  
+
+prop_conv2_vs_gen' exp1 exp2 ser = (one =!= two) where
+  one = convolveWithPSeries2' ak1 ak2 xs 
+  two = convolveWithPSeries' [ak2,ak1] xs
+  ak1 = fromCoeffExp exp1
+  ak2 = fromCoeffExp exp2
+  xs = fromSer ser  
+
+prop_conv3_vs_gen' exp1 exp2 exp3 ser = (one =!= two) where
+  one = convolveWithPSeries3' ak1 ak2 ak3 xs 
+  two = convolveWithPSeries' [ak2,ak3,ak1] xs
+  ak1 = fromCoeffExp exp1
+  ak2 = fromCoeffExp exp2
+  ak3 = fromCoeffExp exp3
+  xs = fromSer ser  
+
+prop_convolve_pseries exps1 ser = (one =!= two) where
+  one = convolveWithPSeries ks1 xs 
+  two = xs `convolve` pseries ks1 
+  ks1 = fromExps exps1
+  xs = fromSer ser  
+
+prop_convolve_pseries' cexps1 ser = (one =!= two) where
+  one = convolveWithPSeries' aks1 xs 
+  two = xs `convolve` pseries' aks1 
+  aks1 = fromCoeffExps cexps1
+  xs = fromSer ser  
+
+prop_coin_vs_pseries exps1 = (one =!= two) where
+  one = coinSeries ks1 
+  two = convolveMany (map pseries1 ks1)
+  ks1 = fromExps exps1
+
+prop_coin_vs_pseries' cexps1 = (one =!= two) where
+  one = coinSeries' aks1 
+  two = convolveMany (map pseries1' aks1)
+  aks1 = fromCoeffExps cexps1
+    
+--------------------------------------------------------------------------------
+
diff --git a/test/Tests/SkewTableaux.hs b/test/Tests/SkewTableaux.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests/SkewTableaux.hs
@@ -0,0 +1,103 @@
+
+-- | Tests for skew tableaux
+
+{-# LANGUAGE FlexibleInstances #-}
+module Tests.SkewTableaux where
+
+--------------------------------------------------------------------------------
+
+import Control.Monad
+
+import Test.Framework
+import Test.Framework.Providers.QuickCheck2
+import Test.QuickCheck
+import Test.QuickCheck.Gen
+
+import Tests.Partitions.Integer ()
+import Tests.Partitions.Skew    ()      -- arbitrary instances
+
+import Math.Combinat.Tableaux
+import Math.Combinat.Tableaux.Skew
+import Math.Combinat.Partitions.Integer
+import Math.Combinat.Partitions.Skew
+
+--------------------------------------------------------------------------------
+-- * code
+
+numberOfNonEmptyRows :: SkewPartition -> Int
+numberOfNonEmptyRows (SkewPartition xys) = length [ True | (x,y) <- xys, y/=0 ]
+
+-- | Breaks a skew partition into disjoint parts
+disjointParts :: SkewPartition -> [SkewPartition]
+disjointParts (SkewPartition xys) = map normalizeSkewPartition list where
+
+  list = map SkewPartition $ filter (not . isEmpty) $ break xys
+
+  isEmpty :: [(Int,Int)] -> Bool
+  isEmpty xys = and [ y==0 | (x,y) <- xys ]
+
+  break :: [(Int,Int)] -> [[(Int,Int)]]
+  break []   = [[]]
+  break [xy] = [[xy]]
+  break ( xy@(x,y) : rest@((x',y'):_) ) = if x >= x'+y' 
+    then [xy] : break rest
+    else let (     xys  : rest' ) = break rest
+         in  ( (xy:xys) : rest' )
+  
+  
+
+
+--------------------------------------------------------------------------------
+-- * instances 
+
+instance Arbitrary (SkewTableau Int) where
+  arbitrary = do
+    shape <- arbitrary
+    let w = skewPartitionWeight shape
+    content <- replicateM w $ choose (1,1000)
+    return $ fillSkewPartitionWithRowWord shape content
+
+--------------------------------------------------------------------------------
+-- * test group
+
+testgroup_SkewTableaux :: Test
+testgroup_SkewTableaux = testGroup "Skew tableaux"
+  [ testProperty "dual^2 = identity"            prop_skew_dual_dual
+  , testProperty "fill . rowWord = identity"    prop_rowWord
+  , testProperty "fill . columnWord = identity" prop_columnWord
+  , testProperty "fill respectes the shape"     prop_fill_shape 
+  , testProperty "semistandard skew tableaux are indeed semistandard"   prop_semistandard 
+  ]
+
+--------------------------------------------------------------------------------
+-- * properties
+
+prop_skew_dual_dual :: SkewTableau Int -> Bool
+prop_skew_dual_dual st = (dualSkewTableau (dualSkewTableau st) == st)
+
+prop_rowWord :: SkewTableau Int -> Bool
+prop_rowWord st = (fillSkewPartitionWithRowWord shape content == st) where
+  shape   = skewTableauShape st
+  content = skewTableauRowWord st
+
+prop_columnWord :: SkewTableau Int -> Bool
+prop_columnWord st = (fillSkewPartitionWithColumnWord shape content == st) where
+  shape   = skewTableauShape st
+  content = skewTableauColumnWord st
+
+prop_fill_shape :: SkewPartition -> Bool
+prop_fill_shape shape = (shape == shape') where
+  tableau = fillSkewPartitionWithColumnWord shape [1..]
+  shape'  = skewTableauShape tableau
+
+prop_semistandard :: SkewPartition -> Bool
+prop_semistandard shape = and 
+  [ isSemiStandardSkewTableau st 
+  | n  <- [kk..nn] 
+  , st <- take 500 (semiStandardSkewTableaux n shape)         -- we only take the first 500 because impossibly slow otherwise
+  ]
+  where
+    nn = min (kk + 10) (skewPartitionWeight shape)
+    kk = maximum $ 0 : (map numberOfNonEmptyRows $ disjointParts shape)
+
+--------------------------------------------------------------------------------
diff --git a/test/Tests/Thompson.hs b/test/Tests/Thompson.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests/Thompson.hs
@@ -0,0 +1,134 @@
+
+-- | Tests for Thompson's group F
+--
+
+{-# LANGUAGE CPP, GeneralizedNewtypeDeriving, FlexibleInstances, TypeSynonymInstances #-}
+module Tests.Thompson where
+
+--------------------------------------------------------------------------------
+
+import Prelude hiding ( (**) )
+import Control.Monad
+import Data.List
+
+import Math.Combinat.Groups.Thompson.F
+import qualified Math.Combinat.Trees.Binary as B
+
+import Tests.Common
+
+import Test.Framework
+import Test.Framework.Providers.QuickCheck2
+import Test.QuickCheck
+import System.Random
+
+import Math.Combinat.Helper
+
+
+--------------------------------------------------------------------------------
+-- * code
+
+(**) :: TDiag -> TDiag -> TDiag
+(**) x y = x `compose` y
+
+(//) :: TDiag -> TDiag -> TDiag
+(//) x y = x `compose` (inverse y)
+
+growth_n_sphere     = [1,4,12,36,108,314,906,2576,7280,20352] :: [Int]
+growth_pos_n_sphere = [1,2, 4, 9, 20, 45,101, 227, 510, 1146] :: [Int]
+
+--------------------------------------------------------------------------------
+-- * instances
+
+-- | A pair of trees with the same size
+data TPair = TPair !T !T deriving (Eq,Show)
+
+newtype Unreduced = Unreduced TDiag deriving (Eq,Show)
+
+instance Arbitrary T where
+  arbitrary = liftM fromBinTree $ myMkSizedGen B.randomBinaryTree
+
+instance Arbitrary TPair where
+  arbitrary = myMkSizedGen $ \siz -> runRand $ do
+    t1 <- rand (B.randomBinaryTree siz)
+    t2 <- rand (B.randomBinaryTree siz)
+    return $ TPair (fromBinTree t1) (fromBinTree t2)
+
+instance Arbitrary TDiag where
+  arbitrary = do 
+    TPair t1 t2 <- arbitrary
+    return $ mkTDiag t1 t2
+
+instance Arbitrary Unreduced where
+  arbitrary = do 
+    TPair t1 t2 <- arbitrary
+    return $ Unreduced $ mkTDiagDontReduce t1 t2
+
+--------------------------------------------------------------------------------
+-- * test group
+
+testgroup_ThompsonF :: Test
+testgroup_ThompsonF = testGroup "Thompson's group F"
+  [ testProperty "identity element"                    prop_identity
+  , testProperty "associativity"                       prop_assoc
+  , testProperty "standard relations"                  prop_relations
+  , testProperty "quotient of positives"               prop_quot_positive
+  , testProperty "telescopic product"                  prop_telescope
+  , testProperty "cyclic telescopic product (3)"       prop_cyclic_product_3
+  , testProperty "cyclic telescopic product (4)"       prop_cyclic_product_4
+  , testProperty "positive diagrams form a monoid"     prop_positive_product
+  , testProperty "composition commutes with reduction" prop_reduce_composition
+  , testProperty "inverse commutes with reduction"     prop_reduce_inverse
+  ]
+
+--------------------------------------------------------------------------------
+-- * properties
+    
+prop_relations :: Bool
+prop_relations = and [ rel k n | n<-[1..30] , k<-[0..n-1] ] where
+  rel k n = (inverse $ xk k) `compose` (xk n) `compose` (xk k) == xk (n+1)
+
+prop_quot_positive :: TPair -> Bool
+prop_quot_positive (TPair t1 t2) = (mkTDiag t1 t2) == (positive t1 // positive t2)
+
+prop_identity :: TDiag -> Bool
+prop_identity x = (x ** identity) == x && (identity ** x) == x
+
+prop_assoc :: TDiag -> TDiag -> TDiag -> Bool
+prop_assoc a b c = (p == q) where
+  p = compose (compose a b) c
+  q = compose a (compose b c)
+
+prop_telescope :: TDiag -> TDiag -> TDiag -> TDiag -> Bool
+prop_telescope u v w z = (a `compose` b `compose` c) == (u // z) where
+  a = u // v
+  b = v // w
+  c = w // z
+
+prop_cyclic_product_3 :: TDiag -> TDiag -> TDiag -> Bool
+prop_cyclic_product_3 u v w = (a `compose` b `compose` c) == identity where
+  a = u // v
+  b = v // w
+  c = w // u
+
+prop_cyclic_product_4 :: TDiag -> TDiag -> TDiag -> TDiag -> Bool
+prop_cyclic_product_4 u v w z = (a `compose` b `compose` c `compose` d) == identity where
+  a = u // v
+  b = v // w
+  c = w // z
+  d = z // u
+
+prop_positive_product :: T -> T -> Bool
+prop_positive_product x y = isPositive (positive x `compose` positive y)
+
+prop_reduce_composition :: Unreduced -> Unreduced -> Bool
+prop_reduce_composition (Unreduced x) (Unreduced y) = lhs == rhs where
+  lhs = reduce (x `composeDontReduce` y)
+  rhs = compose (reduce x) (reduce y)
+
+prop_reduce_inverse :: Unreduced -> Bool
+prop_reduce_inverse (Unreduced x) = lhs == rhs where
+  lhs = reduce (inverse x)
+  rhs = inverse (reduce x)
+
+--------------------------------------------------------------------------------
+
