combinat 0.2.10.0 → 0.2.10.1
raw patch · 95 files changed
+12113/−12068 lines, 95 filesdep ~QuickCheckdep ~arraydep ~compact-word-vectorsPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: QuickCheck, array, compact-word-vectors, containers, random, tasty, tasty-hunit, tasty-quickcheck, test-framework, test-framework-quickcheck2, transformers
API changes (from Hackage documentation)
- Math.Combinat.Trees.Binary: Node :: a -> Forest a -> Tree a
+ Math.Combinat.Trees.Binary: Node :: a -> [Tree a] -> Tree a
- Math.Combinat.Trees.Binary: [subForest] :: Tree a -> Forest a
+ Math.Combinat.Trees.Binary: [subForest] :: Tree a -> [Tree a]
- Math.Combinat.Trees.Nary: Node :: a -> Forest a -> Tree a
+ Math.Combinat.Trees.Nary: Node :: a -> [Tree a] -> Tree a
- Math.Combinat.Trees.Nary: [subForest] :: Tree a -> Forest a
+ Math.Combinat.Trees.Nary: [subForest] :: Tree a -> [Tree a]
Files
- LICENSE +1/−1
- Math/Combinat.hs +0/−76
- Math/Combinat/ASCII.hs +0/−438
- Math/Combinat/Classes.hs +0/−66
- Math/Combinat/Compositions.hs +0/−109
- Math/Combinat/Groups/Braid.hs +0/−744
- Math/Combinat/Groups/Braid/NF.hs +0/−534
- Math/Combinat/Groups/Free.hs +0/−523
- Math/Combinat/Groups/Thompson/F.hs +0/−404
- Math/Combinat/Helper.hs +0/−329
- Math/Combinat/LatticePaths.hs +0/−386
- Math/Combinat/Numbers.hs +0/−12
- Math/Combinat/Numbers/Integers.hs +0/−113
- Math/Combinat/Numbers/Primes.hs +0/−361
- Math/Combinat/Numbers/Sequences.hs +0/−307
- Math/Combinat/Numbers/Series.hs +0/−434
- Math/Combinat/Partitions.hs +0/−22
- Math/Combinat/Partitions/Integer.hs +0/−459
- Math/Combinat/Partitions/Integer/Compact.hs +0/−355
- Math/Combinat/Partitions/Integer/Count.hs +0/−215
- Math/Combinat/Partitions/Integer/IntList.hs +0/−398
- Math/Combinat/Partitions/Integer/Naive.hs +0/−214
- Math/Combinat/Partitions/Multiset.hs +0/−24
- Math/Combinat/Partitions/NonCrossing.hs +0/−205
- Math/Combinat/Partitions/Plane.hs +0/−124
- Math/Combinat/Partitions/Set.hs +0/−109
- Math/Combinat/Partitions/Skew.hs +0/−153
- Math/Combinat/Partitions/Skew/Ribbon.hs +0/−364
- Math/Combinat/Partitions/Vector.hs +0/−82
- Math/Combinat/Permutations.hs +0/−969
- Math/Combinat/RootSystems.hs +0/−319
- Math/Combinat/Sets.hs +0/−212
- Math/Combinat/Sets/VennDiagrams.hs +0/−150
- Math/Combinat/Sign.hs +0/−114
- Math/Combinat/Tableaux.hs +0/−242
- Math/Combinat/Tableaux/GelfandTsetlin.hs +0/−341
- Math/Combinat/Tableaux/GelfandTsetlin/Cone.hs +0/−261
- Math/Combinat/Tableaux/LittlewoodRichardson.hs +0/−399
- Math/Combinat/Tableaux/Skew.hs +0/−224
- Math/Combinat/Trees.hs +0/−9
- Math/Combinat/Trees/Binary.hs +0/−492
- Math/Combinat/Trees/Binary.hs-boot +0/−22
- Math/Combinat/Trees/Graphviz.hs +0/−115
- Math/Combinat/Trees/Nary.hs +0/−430
- Math/Combinat/Trees/Nary.hs-boot +0/−16
- Math/Combinat/Tuples.hs +0/−61
- Math/Combinat/TypeLevel.hs +0/−117
- README.md +31/−0
- combinat.cabal +26/−14
- src/Math/Combinat.hs +76/−0
- src/Math/Combinat/ASCII.hs +438/−0
- src/Math/Combinat/Classes.hs +66/−0
- src/Math/Combinat/Compositions.hs +109/−0
- src/Math/Combinat/Groups/Braid.hs +744/−0
- src/Math/Combinat/Groups/Braid/NF.hs +536/−0
- src/Math/Combinat/Groups/Free.hs +523/−0
- src/Math/Combinat/Groups/Thompson/F.hs +404/−0
- src/Math/Combinat/Helper.hs +329/−0
- src/Math/Combinat/LatticePaths.hs +386/−0
- src/Math/Combinat/Numbers.hs +12/−0
- src/Math/Combinat/Numbers/Integers.hs +113/−0
- src/Math/Combinat/Numbers/Primes.hs +361/−0
- src/Math/Combinat/Numbers/Sequences.hs +307/−0
- src/Math/Combinat/Numbers/Series.hs +434/−0
- src/Math/Combinat/Partitions.hs +22/−0
- src/Math/Combinat/Partitions/Integer.hs +459/−0
- src/Math/Combinat/Partitions/Integer/Compact.hs +355/−0
- src/Math/Combinat/Partitions/Integer/Count.hs +215/−0
- src/Math/Combinat/Partitions/Integer/IntList.hs +398/−0
- src/Math/Combinat/Partitions/Integer/Naive.hs +214/−0
- src/Math/Combinat/Partitions/Multiset.hs +24/−0
- src/Math/Combinat/Partitions/NonCrossing.hs +205/−0
- src/Math/Combinat/Partitions/Plane.hs +124/−0
- src/Math/Combinat/Partitions/Set.hs +109/−0
- src/Math/Combinat/Partitions/Skew.hs +153/−0
- src/Math/Combinat/Partitions/Skew/Ribbon.hs +364/−0
- src/Math/Combinat/Partitions/Vector.hs +82/−0
- src/Math/Combinat/Permutations.hs +969/−0
- src/Math/Combinat/RootSystems.hs +319/−0
- src/Math/Combinat/Sets.hs +212/−0
- src/Math/Combinat/Sets/VennDiagrams.hs +150/−0
- src/Math/Combinat/Sign.hs +114/−0
- src/Math/Combinat/Tableaux.hs +242/−0
- src/Math/Combinat/Tableaux/GelfandTsetlin.hs +341/−0
- src/Math/Combinat/Tableaux/GelfandTsetlin/Cone.hs +261/−0
- src/Math/Combinat/Tableaux/LittlewoodRichardson.hs +399/−0
- src/Math/Combinat/Tableaux/Skew.hs +224/−0
- src/Math/Combinat/Trees.hs +9/−0
- src/Math/Combinat/Trees/Binary.hs +492/−0
- src/Math/Combinat/Trees/Binary.hs-boot +22/−0
- src/Math/Combinat/Trees/Graphviz.hs +115/−0
- src/Math/Combinat/Trees/Nary.hs +430/−0
- src/Math/Combinat/Trees/Nary.hs-boot +16/−0
- src/Math/Combinat/Tuples.hs +61/−0
- src/Math/Combinat/TypeLevel.hs +117/−0
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2008-2018, Balazs Komuves+Copyright (c) 2008-2023, Balazs Komuves All rights reserved. Redistribution and use in source and binary forms, with or without
− Math/Combinat.hs
@@ -1,76 +0,0 @@---- | 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
− Math/Combinat/ASCII.hs
@@ -1,438 +0,0 @@---- | 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----------------------------------------------------------------------------------
− Math/Combinat/Classes.hs
@@ -1,66 +0,0 @@---- | 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----------------------------------------------------------------------------------
− Math/Combinat/Compositions.hs
@@ -1,109 +0,0 @@---- | 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----------------------------------------------------------------------------------
− Math/Combinat/Groups/Braid.hs
@@ -1,744 +0,0 @@---- | 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 perm = P.toPermutationUnsafeN n [ (n+1) - perm !!! (n-i) | i<-[0..n-1] ] where- n = P.permutationSize perm------------------------------------------------------------------------------------- * 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.identityPermutation 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 = P.uarrayToPermutationUnsafe (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 = runST action where- n = P.permutationSize perm-- 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 = P.lookupPermutation perm phase -- (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)-----------------------------------------------------------------------------------
− Math/Combinat/Groups/Braid/NF.hs
@@ -1,534 +0,0 @@---- | 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.multiplyPermutation` (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.multiplyPermutation` 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.multiplyPermutation` 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.inversePermutation---- | This satisfies--- --- > permutationFinishingSet p == permWordFinishingSet n (_permutationBraid p)----permutationFinishingSet :: Permutation -> [Int]-permutationFinishingSet perm- = [ i | i<-[1..n-1] , perm !!! i > perm !!! (i+1) ] where n = P.permutationSize perm---- | 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.multiplyPermutation s preal- q' = P.multiplyPermutation 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 _ [] = []---}-----------------------------------------------------------------------------------
− Math/Combinat/Groups/Free.hs
@@ -1,523 +0,0 @@---- | 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 ]---}------------------------------------------------------------------------------------
− Math/Combinat/Groups/Thompson/F.hs
@@ -1,404 +0,0 @@---- | 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]------------------------------------------------------------------------------------
− Math/Combinat/Helper.hs
@@ -1,329 +0,0 @@---- | Miscellaneous helper functions used internally--{-# 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--interleave :: [a] -> [a] -> [a]-interleave (x:xs) (y:ys) = x : y : interleave xs ys-interleave [x] [] = x : []-interleave [] [] = []-interleave _ _ = error "interleave: shouldn't happen"--evens, odds :: [a] -> [a] -evens (x:xs) = x : odds xs-evens [] = []-odds (x:xs) = evens xs-odds [] = []------------------------------------------------------------------------------------- * multiplication---- | Product of list of integers, but in interleaved order (for a list of big numbers,--- it should be faster than the linear order)-productInterleaved :: [Integer] -> Integer-productInterleaved = go where- go [] = 1- go [x] = x- go [x,y] = x*y- go list = go (evens list) * go (odds list)---- | Faster implementation of @product [ i | i <- [a+1..b] ]@-productFromTo :: Integral a => a -> a -> Integer-productFromTo = go where- go !a !b - | dif < 1 = 1- | dif < 5 = product [ fromIntegral i | i<-[a+1..b] ]- | otherwise = go a half * go half b- where- dif = b - a- half = div (a+b+1) 2---- | Faster implementation of product @[ i | i <- [a+1,a+3,..b] ]@-productFromToStride2 :: Integral a => a -> a -> Integer-productFromToStride2 = go where- go !a !b - | dif < 1 = 1- | dif < 9 = product [ fromIntegral i | i<-[a+1,a+3..b] ]- | otherwise = go a half * go half b- where- dif = b - a- half = a + 2*(div dif 4)------------------------------------------------------------------------------------- * 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--reverseComparing :: Ord b => (a -> b) -> a -> a -> Ordering-reverseComparing f x y = compare (f y) (f x)--reverseCompare :: Ord a => a -> a -> Ordering-reverseCompare x y = compare y x -- 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----------------------------------------------------------------------------------
− Math/Combinat/LatticePaths.hs
@@ -1,386 +0,0 @@---- | 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-----------------------------------------------------------------------------------
− Math/Combinat/Numbers.hs
@@ -1,12 +0,0 @@--module Math.Combinat.Numbers - ( module Math.Combinat.Numbers.Integers- , module Math.Combinat.Numbers.Primes- , module Math.Combinat.Numbers.Sequences- ) - where--import Math.Combinat.Numbers.Integers-import Math.Combinat.Numbers.Primes-import Math.Combinat.Numbers.Sequences-
− Math/Combinat/Numbers/Integers.hs
@@ -1,113 +0,0 @@---- | Operations on integers--module Math.Combinat.Numbers.Integers - ( -- * Integer logarithm- integerLog2- , ceilingLog2- -- * Integer square root- , isSquare- , integerSquareRoot- , ceilingSquareRoot- , integerSquareRoot' - , integerSquareRootNewton'- )- where-------------------------------------------------------------------------------------- import Math.Combinat.Numbers--import Data.List ( group , sort )-import Data.Bits--import System.Random------------------------------------------------------------------------------------- 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 - ]--}-----------------------------------------------------------------------------------
− Math/Combinat/Numbers/Primes.hs
@@ -1,361 +0,0 @@---- | Prime numbers and related number theoretical stuff.--module Math.Combinat.Numbers.Primes - ( -- * Elementary number theory- divides- , divisors, squareFreeDivisors, squareFreeDivisors_ - , divisorSum , divisorSum'- , moebiusMu , eulerTotient , liouvilleLambda- -- * List of prime numbers- , primes- , primesSimple- , primesTMWE- -- * Prime factorization- , factorize, factorizeNaive- , productOfFactors- , integerFactorsTrialDivision- , groupIntegerFactors- -- * Modulo @m@ arithmetic- , powerMod- -- * Prime testing- , millerRabinPrimalityTest- , isProbablyPrime- , isVeryProbablyPrime- )- where------------------------------------------------------------------------------------import Data.List ( group , sort , foldl' )--import Math.Combinat.Sign-import Math.Combinat.Helper-import Math.Combinat.Numbers.Integers---- import Math.Combinat.Sets ( sublists ) -- cyclic dependency...-import Math.Combinat.Tuples ( tuples' )--import Data.Bits--import System.Random-------------------------------------------------------------------------------------- | @d `divides` n@-divides :: Integer -> Integer -> Bool-divides d n = (mod n d == 0)--{-# SPECIALIZE moebiusMu :: Int -> Int #-}-{-# SPECIALIZE moebiusMu :: Integer -> Integer #-}--- | The Moebius mu function-moebiusMu :: (Integral a, Num b) => a -> b-moebiusMu n - | any (>1) expos = 0- | even (length primes) = 1- | otherwise = -1- where- factors = groupIntegerFactors $ integerFactorsTrialDivision $ fromIntegral n- (primes,expos) = unzip factors--{-# SPECIALIZE liouvilleLambda :: Int -> Int #-}-{-# SPECIALIZE liouvilleLambda :: Integer -> Integer #-}--- | The Liouville lambda function-liouvilleLambda :: (Integral a, Num b) => a -> b-liouvilleLambda n = - if odd (foldl' (+) 0 $ map snd grps)- then -1- else 1- where- grps = groupIntegerFactors $ integerFactorsTrialDivision $ fromIntegral n---- | Sum ofthe of the divisors-divisorSum :: Integer -> Integer-divisorSum n = foldl' (+) 0 [ d | d <- divisors n]---- | Sum of @k@-th powers of the divisors-divisorSum' :: Int -> Integer -> Integer-divisorSum' k n = foldl' (+) 0 [ d^k | d <- divisors n]---- | Euler's totient function-eulerTotient :: Integer -> Integer-eulerTotient n = div n prodp * prodp1 where- grps = groupIntegerFactors $ integerFactorsTrialDivision n- ps = map fst grps- prodp = foldl' (*) 1 [ p | p <- ps ] - prodp1 = foldl' (*) 1 [ p-1 | p <- ps ] ---- | Divisors of @n@ (note: the result is /not/ ordered!)-divisors :: Integer -> [Integer]-divisors n = [ f tup | tup <- tuples' expos ] where- grps = groupIntegerFactors $ integerFactorsTrialDivision n- (ps,expos) = unzip grps- f es = foldl' (*) 1 $ zipWith (^) ps es---- | List of square-free divisors together with their Mobius mu value--- (note: the result is /not/ ordered!)-squareFreeDivisors :: Integer -> [(Integer,Sign)]-squareFreeDivisors n = map f (sublists primes) where- grps = groupIntegerFactors $ integerFactorsTrialDivision n- primes = map fst grps- f ps = ( foldl' (*) 1 ps , if even (length ps) then Plus else Minus)---- | List of square-free divisors --- (note: the result is /not/ ordered!)-squareFreeDivisors_ :: Integer -> [Integer]-squareFreeDivisors_ n = map f (sublists primes) where- grps = groupIntegerFactors $ integerFactorsTrialDivision n- primes = map fst grps- f ps = foldl' (*) 1 ps---- | To avoid cyclic dependencies, I made a local copy of this...-sublists :: [a] -> [[a]]-sublists [] = [[]]-sublists (x:xs) = sublists xs ++ map (x:) (sublists xs) ------------------------------------------------------------------------------------- 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--factorize :: Integer -> [(Integer,Int)]-factorize = factorizeNaive--factorizeNaive :: Integer -> [(Integer,Int)]-factorizeNaive = groupIntegerFactors . integerFactorsTrialDivision--productOfFactors :: [(Integer,Int)] -> Integer-productOfFactors = productInterleaved . map (uncurry pow) where- pow _ 0 = 1- pow p 1 = p- pow 2 n = shiftL 1 n- pow p 2 = p*p- pow p n = if even n- then (pow p (shiftR n 1))^2- else p * (pow p (shiftR n 1))^2 ---- | 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] ] --}------------------------------------------------------------------------------------- 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)----------------------------------------------------------------------------------
− Math/Combinat/Numbers/Sequences.hs
@@ -1,307 +0,0 @@---- | Some important number sequences. --- --- See the \"On-Line Encyclopedia of Integer Sequences\",--- <https://oeis.org> .--{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}-module Math.Combinat.Numbers.Sequences where------------------------------------------------------------------------------------import Data.Array-import Data.Bits ( shiftL , shiftR , (.&.) )--import Math.Combinat.Helper -import Math.Combinat.Sign--import Math.Combinat.Numbers.Primes ( primes , factorize , productOfFactors )--import qualified Data.Map.Strict as Map -- used for factorialPrimeExponentsNaive------------------------------------------------------------------------------------- * Factorial---- | The factorial function (A000142).-factorial :: Integral a => a -> Integer-factorial = factorialSplit---- | Faster implementation of the factorial function-factorialSplit :: Integral a => a -> Integer-factorialSplit n = productFromTo 1 n---- | Naive implementation of factorial-factorialNaive :: Integral a => a -> Integer-factorialNaive n- | n < 0 = error "factorialNaive: input should be nonnegative"- | n == 0 = 1- | otherwise = product [1..fromIntegral n]---- | \"Swing factorial\" algorithm-factorialSwing :: Integral a => a -> Integer-factorialSwing n = productOfFactors (factorialPrimeExponents $ fromIntegral n) where-------------------------------------------------------------------------------------- | Prime factorization of the factorial (using the \"swing factorial\" algorithm)-factorialPrimeExponents :: Int -> [(Integer,Int)]-factorialPrimeExponents n = filter cond $ zip primes (factorialPrimeExponents_ n) where- cond (_,!e) = e > 0--factorialPrimeExponentsNaive :: forall a. Integral a => a -> [(Integer,Int)]-factorialPrimeExponentsNaive n = result where- fi = fromIntegral :: a -> Integer- result = Map.toList - $ Map.unionsWith (+) - $ map Map.fromList - $ map factorize - $ map fi [1..n] --factorialPrimeExponents_ :: Int -> [Int]-factorialPrimeExponents_ = go where- go 0 = []- go 1 = []- go 2 = [1]- go !n = longAdd half swing where- half = map (flip shiftL 1) $ go (shiftR n 1)- swing = swingFactorialExponents_ n-- longAdd :: [Int] -> [Int] -> [Int]- longAdd xs [] = xs- longAdd [] ys = ys- longAdd (!x:xs) (!y:ys) = (x+y) : longAdd xs ys---- | Prime factorizaiton of the \"swing factorial\" function)-swingFactorialExponents_ :: Int -> [Int]-swingFactorialExponents_ = go where- go 0 = []- go 1 = []- go 2 = [1]- go n = expo2 : map expo (tail ps) where-- nn = fromIntegral n :: Integer-- ps :: [Integer]- ps = takeWhile (<=nn) primes -- expo2 :: Int- expo2 = go 0 (shiftR n 1) where- go :: Int -> Int -> Int- go !acc !r - | r < 1 = acc- | otherwise = go acc' r' - where- acc' = acc + (r .&. 1)- r' = shiftR r 1-- expo :: Integer -> Int- expo pp = go 0 (div n p) where- p = fromInteger pp :: Int- go :: Int -> Int -> Int- go !acc !r - | r < 1 = acc- | otherwise = go acc' r' - where- acc' = acc + (r .&. 1)- r' = div r p-------------------------------------------------------------------------------------- | The double factorial-doubleFactorial :: Integral a => a -> Integer-doubleFactorial = doubleFactorialSplit---- | Faster implementation of the double factorial function-doubleFactorialSplit :: Integral a => a -> Integer-doubleFactorialSplit n- | n < 0 = error "doubleFactorialSplit: input should be nonnegative"- | n == 0 = 1- | odd n = productFromToStride2 2 n- | otherwise = let halfn = div n 2 - in shiftL (factorialSplit halfn) (fromIntegral halfn)---- | Naive implementation of the double factorial (A006882).-doubleFactorialNaive :: Integral a => a -> Integer-doubleFactorialNaive n- | n < 0 = error "doubleFactorialNaive: input should be nonnegative"- | n == 0 = 1- | odd n = product [1,3..fromIntegral n]- | otherwise = product [2,4..fromIntegral n]------------------------------------------------------------------------------------- * Binomial and multinomial---- | Binomial numbers (A007318). Note: This is zero for @n<0@ or @k<0@; see also 'signedBinomial' below.-binomial :: Integral a => a -> a -> Integer-binomial = binomialSplit---- | Faster implementation of binomial-binomialSplit :: Integral a => a -> a -> Integer-binomialSplit n k - | k > n = 0- | k < 0 = 0- | k > (n `div` 2) = binomialSplit n (n-k)- | otherwise = (productFromTo (n-k) n) `div` (productFromTo 1 k)---- | A007318. Note: This is zero for @n<0@ or @k<0@; see also 'signedBinomial' below.-binomialNaive :: Integral a => a -> a -> Integer-binomialNaive 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-------------------------------------------------------------------------------------
− Math/Combinat/Numbers/Series.hs
@@ -1,434 +0,0 @@---- | 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, BangPatterns, 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)---- | A different implementation, taken from:------ M. Douglas McIlroy: Power Series, Power Serious -mulSeries :: Num a => [a] -> [a] -> [a]-mulSeries xs ys = go (xs ++ repeat 0) (ys ++ repeat 0) where- go (f:fs) ggs@(g:gs) = f*g : (scaleSeries f gs) `addSeries` go fs ggs---- | Multiplication of power series. This implementation is a synonym for 'convolve'-mulSeriesNaive :: Num a => [a] -> [a] -> [a]-mulSeriesNaive = 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---- | Division of series.------ Taken from: M. Douglas McIlroy: Power Series, Power Serious -divSeries :: (Eq a, Fractional a) => [a] -> [a] -> [a]-divSeries xs ys = go (xs ++ repeat 0) (ys ++ repeat 0) where- go (0:fs) (0:gs) = go fs gs- go (f:fs) ggs@(g:gs) = let q = f/g in q : go (fs `subSeries` scaleSeries q gs) ggs---- | 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@.------ This implementation is taken from------ M. Douglas McIlroy: Power Series, Power Serious -composeSeries :: (Eq a, Num a) => [a] -> [a] -> [a]-composeSeries xs ys = go (xs ++ repeat 0) (ys ++ repeat 0) where- go (f:fs) (0:gs) = f : mulSeries gs (go fs (0:gs))- go (f:fs) (_:gs) = error "PowerSeries/composeSeries: we expect the the constant term of the inner series to be zero"---- | @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 f g = composeSeries g f---- | Naive implementation of 'composeSeries' (via 'substituteNaive')-composeSeriesNaive :: (Eq a, Num a) => [a] -> [a] -> [a]-composeSeriesNaive g f = substituteNaive f g---- | Naive implementation of 'substitute'-substituteNaive :: (Eq a, Num a) => [a] -> [a] -> [a]-substituteNaive as_ bs_ = - case head as of- 0 -> [ f n | n<-[0..] ]- _ -> error "PowerSeries/substituteNaive: 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---- | 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)------ This implementation is taken from:------ M. Douglas McIlroy: Power Series, Power Serious -lagrangeInversion :: (Eq a, Fractional a) => [a] -> [a]-lagrangeInversion xs = go (xs ++ repeat 0) where- go (0:fs) = rs where rs = 0 : divSeries unitSeries (composeSeries fs rs)- go (_:fs) = error "lagrangeInversion: the series should start with (0 + a1*x + a2*x^2 + ...) where a1 is non-zero"---- | 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)----integralLagrangeInversionNaive :: (Eq a, Num a) => [a] -> [a]-integralLagrangeInversionNaive series_ = - case series of- (0:1:rest) -> 0 : 1 : [ f n | n<-[1..] ]- _ -> error "integralLagrangeInversionNaive: 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- ] ---- | Naive implementation of 'lagrangeInversion'-lagrangeInversionNaive :: (Eq a, Fractional a) => [a] -> [a]-lagrangeInversionNaive 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 "lagrangeInversionNaive: 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- ] -------------------------------------------------------------------------------------- * Differentiation and integration--differentiateSeries :: Num a => [a] -> [a]-differentiateSeries (y:ys) = go (1::Int) ys where- go !n (x:xs) = fromIntegral n * x : go (n+1) xs- go _ [] = []--integrateSeries :: Fractional a => [a] -> [a]-integrateSeries ys = 0 : go (1::Int) ys where- go !n (x:xs) = x / (fromIntegral n) : go (n+1) xs- go _ [] = []------------------------------------------------------------------------------------- * 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)))---- | Alternative implementation using differential equations.------ Taken from: M. Douglas McIlroy: Power Series, Power Serious-cosSeries2, sinSeries2 :: Fractional a => [a]-cosSeries2 = unitSeries `subSeries` integrateSeries sinSeries2-sinSeries2 = integrateSeries cosSeries2---- | 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- -----------------------------------------------------------------------------------
− Math/Combinat/Partitions.hs
@@ -1,22 +0,0 @@---- | 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----------------------------------------------------------------------------------
− Math/Combinat/Partitions/Integer.hs
@@ -1,459 +0,0 @@---- | 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 - ( -- module Math.Combinat.Partitions.Integer.Count- module Math.Combinat.Partitions.Integer.Naive- -- * Types and basic stuff- , Partition- -- * Conversion to\/from lists- , fromPartition - , mkPartition - , toPartition - , toPartitionUnsafe - , isPartition - -- * Conversion to\/from exponent vectors- , toExponentVector- , fromExponentVector- , dropTailingZeros- -- * Union and sum- , unionOfPartitions- , sumOfPartitions- -- * Generating partitions- , partitions - , partitions'- , allPartitions - , allPartitionsGrouped - , allPartitions' - , allPartitionsGrouped' - -- * Counting partitions- , countPartitions- , countPartitions'- , countAllPartitions- , countAllPartitions'- , countPartitionsWithKParts - -- * Random partitions- , randomPartition- , randomPartitions- -- * Dominating \/ dominated partitions- , dominanceCompare- , dominatedPartitions - , dominatingPartitions - -- * Conjugate lexicographic ordering- , conjugateLexicographicCompare - , ConjLex (..) , fromConjLex - -- * Partitions with given number of parts- , partitionsWithKParts- -- * Partitions with only odd\/distinct parts- , partitionsWithOddParts - , partitionsWithDistinctParts- -- * Sub- and super-partitions of a given partition- , subPartitions - , allSubPartitions - , superPartitions - -- * ASCII Ferrers diagrams- , PartitionConvention(..)- , asciiFerrersDiagram - , asciiFerrersDiagram'- )- 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--import Math.Combinat.Partitions.Integer.Naive hiding () -- this is for haddock!-import Math.Combinat.Partitions.Integer.IntList-import Math.Combinat.Partitions.Integer.Count-------------------------------------------------------------------------------------- * Conversion to\/from lists--fromPartition :: Partition -> [Int]-fromPartition (Partition_ part) = part- --- | Sorts the input, and cuts the nonpositive elements.-mkPartition :: [Int] -> Partition-mkPartition xs = toPartitionUnsafe $ sortBy (reverseCompare) $ filter (>0) xs---- | 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"---- | Assumes that the input is decreasing.-toPartitionUnsafe :: [Int] -> Partition-toPartitionUnsafe = 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------------------------------------------------------------------------------------- * Conversion to\/from exponent vectors- --- | Converts a partition to an exponent vector.------ For example, ------ > toExponentVector (Partition [4,4,2,2,2,1]) == [1,3,0,2]------ meaning @(1^1,2^3,3^0,4^2)@.----toExponentVector :: Partition -> [Int]-toExponentVector part = fun 1 $ reverse $ group (fromPartition part) where- fun _ [] = []- fun !k gs@(this@(i:_):rest) - | k < i = replicate (i-k) 0 ++ fun i gs- | otherwise = length this : fun (k+1) rest--fromExponentVector :: [Int] -> Partition-fromExponentVector expos = Partition $ concat $ reverse $ zipWith f [1..] expos where- f !i !e = replicate e i--dropTailingZeros :: [Int] -> [Int]-dropTailingZeros = reverse . dropWhile (==0) . reverse--{---- alternative implementation-toExponentialVector2 :: Partition -> [Int]-toExponentialVector2 p = go 1 (toExponentialForm p) where- go _ [] = []- go !i ef@((j,e):rest) = if i<j - then 0 : go (i+1) ef- else e : go (i+1) rest--}------------------------------------------------------------------------------------- * Union and sum---- | This is simply the union of parts. For example ------ > Partition [4,2,1] `unionOfPartitions` Partition [4,3,1] == Partition [4,4,3,2,1,1]------ Note: This is the dual of pointwise sum, 'sumOfPartitions'----unionOfPartitions :: Partition -> Partition -> Partition -unionOfPartitions (Partition_ xs) (Partition_ ys) = mkPartition (xs ++ ys)---- | Pointwise sum of the parts. For example:------ > Partition [3,2,1,1] `sumOfPartitions` Partition [4,3,1] == Partition [7,5,2,1]------ Note: This is the dual of 'unionOfPartitions'----sumOfPartitions :: Partition -> Partition -> Partition -sumOfPartitions (Partition_ xs) (Partition_ ys) = Partition_ (longZipWith 0 0 (+) xs ys)------------------------------------------------------------------------------------- * Generating partitions---- | Partitions of @d@.-partitions :: Int -> [Partition]-partitions = map toPartitionUnsafe . _partitions---- | 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 -------------------------------------------------------------------------------------- | 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--------------------------------------------------------------------------------------- * 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 of partitions counts 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-- cnt = countPartitions- - 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)------------------------------------------------------------------------------------- * Dominating \/ dominated partitions---- | Dominance partial ordering as a partial ordering.-dominanceCompare :: Partition -> Partition -> Maybe Ordering-dominanceCompare p q - | p==q = Just EQ- | p `dominates` q = Just GT- | q `dominates` p = Just LT- | otherwise = Nothing---- | 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)---- | 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)------------------------------------------------------------------------------------- * Conjugate lexicographic ordering--conjugateLexicographicCompare :: Partition -> Partition -> Ordering-conjugateLexicographicCompare p q = compare (dualPartition q) (dualPartition p) --newtype ConjLex = ConjLex Partition deriving (Eq,Show)--fromConjLex :: ConjLex -> Partition-fromConjLex (ConjLex p) = p--instance Ord ConjLex where- compare (ConjLex p) (ConjLex q) = conjugateLexicographicCompare p q---- {- CONJUGATE LEXICOGRAPHIC ordering is a refinement of dominance partial ordering -}--- let test n = [ ConjLex p >= ConjLex q | p <- partitions n , q <-partitions n , p `dominates` q ]--- and (test 20)---- {- LEXICOGRAPHIC ordering is a refinement of dominance partial ordering -}--- let test n = [ p >= q | p <- partitions n , q <-partitions n , p `dominates` q ]--- and (test 20)------------------------------------------------------------------------------------- * 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) ]------------------------------------------------------------------------------------- * 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---- | 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)---- | All sub-partitions of a given partition-allSubPartitions :: Partition -> [Partition]-allSubPartitions (Partition_ ps) = map Partition_ (_allSubPartitions ps)---- | 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 toPartitionUnsafe (_superPartitions d 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-----------------------------------------------------------------------------------
− Math/Combinat/Partitions/Integer/Compact.hs
@@ -1,355 +0,0 @@--{- | Compact representation of integer partitions.--Partitions are conceptually nonincreasing sequences of /positive/ integers.--This implementation uses the @compact-word-vectors@ library internally to provide-a much more memory-efficient Partition type that the naive lists of integer.-This is very helpful when building large tables indexed by partitions, for example; -and hopefully quite a bit faster, too.--Note: This is an internal module, you are not supposed to import it directly.-It is also not fully ready to be used yet...---}--{-# LANGUAGE BangPatterns, PatternSynonyms, ViewPatterns #-}-module Math.Combinat.Partitions.Integer.Compact where------------------------------------------------------------------------------------import Data.Bits-import Data.Word-import Data.Ord-import Data.List ( intercalate , group , sort , sortBy , foldl' , scanl' ) --import Data.Vector.Compact.WordVec ( WordVec , Shape(..) )-import qualified Data.Vector.Compact.WordVec as V--import Math.Combinat.Compositions ( compositions' )------------------------------------------------------------------------------------- * The compact partition data type--newtype Partition - = Partition WordVec - deriving Eq--instance Show Partition where- showsPrec = showsPrecPartition--showsPrecPartition :: Int -> Partition -> ShowS-showsPrecPartition prec (Partition vec)- = showParen (prec > 10) - $ showString "Partition"- . showChar ' ' - . shows (V.toList vec)--instance Ord Partition where- compare = cmpLexico- ------------------------------------------------------------------------------------ * Pattern synonyms ---- | Pattern sysnonyms allows us to use existing code with minimal modifications-pattern Nil :: Partition-pattern Nil <- (isEmpty -> True) where- Nil = empty--pattern Cons :: Int -> Partition -> Partition-pattern Cons x xs <- (uncons -> Just (x,xs)) where- Cons x xs = cons x xs---- | Simulated newtype constructor -pattern Partition_ :: [Int] -> Partition-pattern Partition_ xs <- (toList -> xs) where- Partition_ xs = fromDescList xs--pattern Head :: Int -> Partition -pattern Head h <- (height -> h)--pattern Tail :: Partition -> Partition-pattern Tail xs <- (partitionTail -> xs)--pattern Length :: Int -> Partition -pattern Length n <- (width -> n) ------------------------------------------------------------------------------------- * Lexicographic comparison---- | The lexicographic ordering-cmpLexico :: Partition -> Partition -> Ordering-cmpLexico (Partition vec1) (Partition vec2) = compare (V.toList vec1) (V.toList vec2)------------------------------------------------------------------------------------- * Basic (de)constructrion--empty :: Partition-empty = Partition (V.empty)--isEmpty :: Partition -> Bool-isEmpty (Partition vec) = V.null vec------------------------------------------------------------------------------------singleton :: Int -> Partition-singleton x - | x > 0 = Partition (V.singleton $ i2w x)- | x == 0 = empty- | otherwise = error "Parittion/singleton: negative input"------------------------------------------------------------------------------------uncons :: Partition -> Maybe (Int,Partition)-uncons (Partition vec) = case V.uncons vec of- Nothing -> Nothing- Just (h,tl) -> Just (w2i h, Partition tl)---- | @partitionTail p == snd (uncons p)@-partitionTail :: Partition -> Partition-partitionTail (Partition vec) = Partition (V.tail vec)------------------------------------------------------------------------------------- | We assume that @x >= partitionHeight p@!-cons :: Int -> Partition -> Partition-cons !x (Partition !vec) - | V.null vec = Partition (if x > 0 then V.singleton y else V.empty) - | y >= h = Partition (V.cons y vec)- | otherwise = error "Partition/cons: invalid element to cons"- where - y = i2w x- h = V.head vec-------------------------------------------------------------------------------------- | We assume that the element is not bigger than the last element!-snoc :: Partition -> Int -> Partition-snoc (Partition !vec) !x- | x == 0 = Partition vec- | V.null vec = Partition (V.singleton y)- | y <= V.last vec = Partition (V.snoc vec y)- | otherwise = error "Partition/snoc: invalid element to snoc"- where- y = i2w x------------------------------------------------------------------------------------- * exponential form--toExponentialForm :: Partition -> [(Int,Int)]-toExponentialForm = map (\xs -> (head xs,length xs)) . group . toAscList--fromExponentialForm :: [(Int,Int)] -> Partition-fromExponentialForm = fromDescList . concatMap f . sortBy g where- f (!i,!e) = replicate e i- g (!i, _) (!j,_) = compare j i------------------------------------------------------------------------------------- * Width and height of the bounding rectangle---- | Width, or the number of parts-width :: Partition -> Int-width (Partition vec) = V.vecLen vec---- | Height, or the first (that is, the largest) element-height :: Partition -> Int-height (Partition vec) = w2i (V.head vec)---- | Width and height -widthHeight :: Partition -> (Int,Int)-widthHeight (Partition vec) = (V.vecLen vec , w2i (V.head vec))------------------------------------------------------------------------------------- * Differential sequence---- | From a non-increasing sequence @[a1,a2,..,an]@ this computes the sequence of differences--- @[a1-a2,a2-a3,...,an-0]@-diffSequence :: Partition -> [Int]-diffSequence = go . toDescList where- go (x:ys@(y:_)) = (x-y) : go ys - go [x] = [x]- go [] = []---------------------------------------------- | From a non-increasing sequence @[a1,a2,..,an]@ this computes the reversed sequence of differences--- @[ a[n]-0 , a[n-1]-a[n] , ... , a[2]-a[3] , a[1]-a[2] ] @-reverseDiffSequence :: Partition -> [Int]-reverseDiffSequence p = go (0 : toAscList p) where- go (x:ys@(y:_)) = (y-x) : go ys - go [x] = []- go [] = []------------------------------------------------------------------------------------- * Dual partition--dualPartition :: Partition -> Partition-dualPartition compact@(Partition vec) - | V.null vec = Partition V.empty- | otherwise = Partition (V.fromList' shape $ map i2w dual)- where- height = V.head vec- len = V.vecLen vec- shape = Shape (w2i height) (V.bitsNeededFor $ i2w len)- dual = concat- [ replicate d j- | (j,d) <- zip (descendToOne len) (reverseDiffSequence compact)- ]------------------------------------------------------------------------------------- * Conversion to list--toList :: Partition -> [Int]-toList = toDescList---- | returns a descending (non-increasing) list-toDescList :: Partition -> [Int]-toDescList (Partition vec) = map w2i (V.toList vec)---- | Returns a reversed (ascending; non-decreasing) list-toAscList :: Partition -> [Int]-toAscList (Partition vec) = map w2i (V.toRevList vec)------------------------------------------------------------------------------------- * Conversion from list--fromDescList :: [Int] -> Partition-fromDescList list = fromDescList' (length list) list---- | We assume that the input is a non-increasing list of /positive/ integers!-fromDescList' - :: Int -- ^ length- -> [Int] -- ^ the list- -> Partition-fromDescList' !len !list = Partition (V.fromList' (Shape len bits) $ map i2w list) where- bits = case list of- [] -> 4- (x:xs) -> V.bitsNeededFor (i2w x)------------------------------------------------------------------------------------- * Partial orderings---- @ |p `isSubPartitionOf` q@-isSubPartitionOf :: Partition -> Partition -> Bool-isSubPartitionOf p q = and $ zipWith (<=) (toList p) (toList q ++ repeat 0)---- | @q `dominates` p@-dominates :: Partition -> Partition -> Bool-dominates (Partition vec_q) (Partition vec_p) = and $ zipWith (>=) (sums (qs ++ repeat 0)) (sums ps) where - sums = tail . scanl' (+) 0- ps = V.toList vec_p- qs = V.toList vec_q------------------------------------------------------------------------------------- * Pieri rule---- | Expands to product @s[lambda]*h[k]@ as a sum of @s[mu]@-s. See <https://en.wikipedia.org/wiki/Pieri's_formula>-pieriRule :: Partition -> Int -> [Partition]-pieriRule = error "Partitions/Integer/Compact: pieriRule not implemented yet"--{---- | Expands to product @s[lambda]*h[1] = s[lambda]*e[1]@ as a sum of @s[mu]@-s. See <https://en.wikipedia.org/wiki/Pieri's_formula>-pieriRuleSingleBox :: Partition -> [Partition]-pieriRuleSingleBox !compact = case compact of-- Nibble 0 -> [ singleton 1 ]-- Nibble w | h < 15 -> - [ Nibble (w + shiftL 1 (60-4*i)) | (i,d)<-zip [0..n-1] diffs1 , d>0 ] ++ [ snoc compact 1 ]-- Medium1 w | h < 255 -> - [ Medium1 (w + shiftL 1 (56-8*i)) | (i,d)<-zip [0..n-1] diffs1 , d>0 ] ++ [ snoc compact 1 ]-- Medium2 w1 w2 | h < 255 -> - let (diffs1a,diffs1b) = splitAt 8 diffs1 - in [ Medium2 (w1 + shiftL 1 (56-8*i)) w2 | (i,d)<-zip [0..7 ] diffs1a , d>0 ] ++- [ Medium2 w1 (w2 + shiftL 1 (56-8*i)) | (i,d)<-zip [0..n-9] diffs1b , d>0 ] ++- [ snoc compact 1 ]-- Medium3 w1 w2 w3 | h < 255 -> - let (diffs1a,tmp ) = splitAt 8 diffs1 - (diffs1b,diffs1c) = splitAt 8 tmp- in [ Medium3 (w1 + shiftL 1 (56-8*i)) w2 w3 | (i,d)<-zip [0..7 ] diffs1a , d>0 ] ++- [ Medium3 w1 (w2 + shiftL 1 (56-8*i)) w3 | (i,d)<-zip [0..7 ] diffs1b , d>0 ] ++- [ Medium3 w1 w2 (w3 + shiftL 1 (56-8*i)) | (i,d)<-zip [0..n-17] diffs1c , d>0 ] ++- [ snoc compact 1 ]- - _ -> genericSingleBox-- where- (n,h) = widthHeight compact- list = toDescList compact- diffs1 = 1 : diffSequence compact-- genericSingleBox :: [Partition]- genericSingleBox = map (fromDescList' n) (go list diffs1) ++ [ fromDescList' (n+1) (list ++ [1]) ] where- go :: [Int] -> [Int] -> [[Int]]- go (a:as) (d:ds) = if d > 0 then ((a+1):as) : map (a:) (go as ds) - else map (a:) (go as ds)- go [] _ = []---- | Expands to product @s[lambda]*h[k]@ as a sum of @s[mu]@-s. See <https://en.wikipedia.org/wiki/Pieri's_formula>-pieriRule :: Partition -> Int -> [Partition]-pieriRule !compact !k - | k < 0 = []- | k == 0 = [ compact ]- | k == 1 = pieriRuleSingleBox compact- | h == 0 = [ singleton k ]- | h + k <= 15 && n < 15 = case compact of { Nibble w -> - [ Nibble (w + encode c) | c <- comps ] }- | otherwise = [ fromDescList' (n+b) xs | c <- comps , let (b,xs) = add c ] -- where- (n,h) = widthHeight compact- list = toDescList compact- bounds = k : {- map (min k) -} (diffSequence compact) - comps = compositions' bounds k-- add clist = go list clist where- go (!p:ps) (!c:cs) = let (b,rest) = go ps cs in (b, (p+c):rest)- go [] [c] = if c>0 then (1,[c]) else (0,[])- go _ _ = error "Compact/pieriRule/add: shouldn't happen"-- encode :: [Int] -> Word64- encode = go 60 where- go !k [c] = if c==0 then 0 else shiftL (i2w c) k + 1- go !k (c:cs) = shiftL (i2w c) k + go (k-4) cs- go !k [] = error "Compact/pieriRule/encode: shouldn't happen"--}------------------------------------------------------------------------------------- * local (internally used) utility functions--{-# INLINE i2w #-}-i2w :: Int -> Word-i2w = fromIntegral--{-# INLINE w2i #-}-w2i :: Word -> Int-w2i = fromIntegral--{-# INLINE sum' #-}-sum' :: [Word] -> Word-sum' = foldl' (+) 0--{-# INLINE safeTail #-}-safeTail :: [Int] -> [Int]-safeTail xs = case xs of { [] -> [] ; _ -> tail xs }--{-# INLINE descendToZero #-}-descendToZero :: Int -> [Int]-descendToZero !n- | n > 0 = n : descendToZero (n-1) - | n == 0 = [0]- | n < 0 = []--{-# INLINE descendToOne #-}-descendToOne :: Int -> [Int]-descendToOne !n- | n > 1 = n : descendToOne (n-1) - | n == 1 = [1]- | n < 1 = []------------------------------------------------------------------------------------
− Math/Combinat/Partitions/Integer/Count.hs
@@ -1,215 +0,0 @@---- | Counting partitions of integers.--{-# LANGUAGE CPP, BangPatterns, ScopedTypeVariables #-}-module Math.Combinat.Partitions.Integer.Count where------------------------------------------------------------------------------------import Data.List-import Control.Monad ( liftM , replicateM )---- import Data.Map (Map)--- import qualified Data.Map as Map--import Math.Combinat.Numbers ( factorial , binomial , multinomial )-import Math.Combinat.Numbers.Integers -- Primes-import Math.Combinat.Helper--import Data.Array-import System.Random------------------------------------------------------------------------------------- * Infinite tables of integers---- | A data structure which is essentially an infinite list of @Integer@-s,--- but fast lookup (for reasonable small inputs)-newtype TableOfIntegers = TableOfIntegers [Array Int Integer]--lookupInteger :: TableOfIntegers -> Int -> Integer-lookupInteger (TableOfIntegers table) !n - | n >= 0 = (table !! k) ! r- | n < 0 = 0- where- (k,r) = divMod n 1024--makeTableOfIntegers- :: ((Int -> Integer) -> (Int -> Integer))- -> TableOfIntegers-makeTableOfIntegers user = table where- calc = user lkp- lkp = lookupInteger table- table = TableOfIntegers- [ listArray (0,1023) (map calc [a..b]) - | k<-[0..] - , let a = 1024*k - , let b = 1024*(k+1) - 1 - ]------------------------------------------------------------------------------------- * Counting partitions---- | Number of partitions of @n@ (looking up a table built using Euler's algorithm)-countPartitions :: Int -> Integer-countPartitions = lookupInteger partitionCountTable ---- | This uses the power series expansion of the infinite product. It is slower than the above.-countPartitionsInfiniteProduct :: Int -> Integer-countPartitionsInfiniteProduct k = partitionCountListInfiniteProduct !! k---- | This uses 'countPartitions'', and is (very) slow-countPartitionsNaive :: Int -> Integer-countPartitionsNaive d = countPartitions' (d,d) d-------------------------------------------------------------------------------------- | This uses Euler's algorithm to compute p(n)------ See eg.:--- NEIL CALKIN, JIMENA DAVIS, KEVIN JAMES, ELIZABETH PEREZ, AND CHARLES SWANNACK--- COMPUTING THE INTEGER PARTITION FUNCTION--- <http://www.math.clemson.edu/~kevja/PAPERS/ComputingPartitions-MathComp.pdf>----partitionCountTable :: TableOfIntegers-partitionCountTable = table where-- table = makeTableOfIntegers fun-- fun lkp !n - | n > 1 = foldl' (+) 0 - [ (if even k then negate else id) - ( lkp (n - div (k*(3*k+1)) 2)- + lkp (n - div (k*(3*k-1)) 2)- )- | k <- [1..limit n]- ]- | n < 0 = 0- | n == 0 = 1- | n == 1 = 1-- limit :: Int -> Int- limit !n = fromInteger $ ceilingSquareRoot (1 + div (nn+nn+1) 3) where- nn = fromIntegral n :: Integer---- | An infinite list containing all @p(n)@, starting from @p(0)@.-partitionCountList :: [Integer]-partitionCountList = map countPartitions [0..]-------------------------------------------------------------------------------------- | 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 reasonably fast for small numbers.------ > partitionCountListInfiniteProduct == map countPartitions [0..]----partitionCountListInfiniteProduct :: [Integer]-partitionCountListInfiniteProduct = 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 very slow.----partitionCountListNaive :: [Integer]-partitionCountListNaive = map countPartitionsNaive [0..]------------------------------------------------------------------------------------- * Counting all partitions--countAllPartitions :: Int -> Integer-countAllPartitions d = sum' [ countPartitions i | i <- [0..d] ]---- | Count all partitions fitting into a rectangle.--- # = \\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------------------------------------------------------------------------------------- * Counting fitting into a rectangle---- | Number of of d, fitting into a given rectangle. Naive recursive algorithm.-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] ] ------------------------------------------------------------------------------------- * Partitions with given number of parts---- | Count partitions of @n@ into @k@ parts.------ Naive recursive algorithm.----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) ]--}--{---- > 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) ]--}----------------------------------------------------------------------------------
− Math/Combinat/Partitions/Integer/IntList.hs
@@ -1,398 +0,0 @@---- | Partition functions working on lists of integers.--- --- It's not recommended to use this module directly.--{-# LANGUAGE CPP, BangPatterns, ScopedTypeVariables #-}-module Math.Combinat.Partitions.Integer.IntList where------------------------------------------------------------------------------------import Data.List-import Control.Monad ( liftM , replicateM )--import Math.Combinat.Numbers ( factorial , binomial , multinomial )-import Math.Combinat.Helper--import Data.Array-import System.Random--import Math.Combinat.Partitions.Integer.Count ( countPartitions )------------------------------------------------------------------------------------- * Type and basic stuff---- | Sorts the input, and cuts the nonpositive elements.-_mkPartition :: [Int] -> [Int]-_mkPartition xs = sortBy (reverseCompare) $ filter (>0) xs- --- | 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---_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 [5,4,1] ==--- > [ (1,1), (1,2), (1,3), (1,4), (1,5)--- > , (2,1), (2,2), (2,3), (2,4)--- > , (3,1)--- > ]-----_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 :: [Int] -> [(Int,Int)]-_toExponentialForm = reverse . map (\xs -> (head xs,length xs)) . group--_fromExponentialForm :: [(Int,Int)] -> [Int]-_fromExponentialForm = sortBy reverseCompare . go where- go ((j,e):rest) = replicate e j ++ go rest- go [] = [] -------------------------------------------------------------------------------------- * Generating 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) ]---- | All integer partitions up to a given degree (that is, all integer partitions whose sum is less or equal to @d@)-_allPartitions :: Int -> [[Int]]-_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 -> [[[Int]]]-_allPartitionsGrouped d = [ _partitions 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) ]-------------------------------------------------------------------------------------- * 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 -> ([Int], 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 -> ([[Int]], g)-_randomPartitions howmany n = runRand $ replicateM howmany (worker n []) where-- cnt = countPartitions- - finish :: [(Int,Int)] -> [Int]- 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 [Int]- 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 :: [Int] -> [Int] -> Bool-_dominates qs 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 :: [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 :: [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- -> [[Int]]-_partitionsWithKParts k n = 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) ]------------------------------------------------------------------------------------- * Partitions with only odd\/distinct parts---- | Partitions of @n@ with only odd parts-_partitionsWithOddParts :: Int -> [[Int]]-_partitionsWithOddParts d = (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 -> [[Int]]-_partitionsWithEvenParts d = (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 -> [[Int]]-_partitionsWithDistinctParts d = (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 :: [Int] -> [Int] -> Bool-_isSubPartitionOf ps qs = and $ zipWith (<=) ps (qs ++ repeat 0)---- | This is provided for convenience\/completeness only, as:------ > isSuperPartitionOf q p == isSubPartitionOf p q----_isSuperPartitionOf :: [Int] -> [Int] -> Bool-_isSuperPartitionOf qs 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 -> [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 :: [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 -> [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>------ | 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 :: [Int] -> Int -> [[Int]] -_dualPieriRule lam n = map _dualPartition $ _pieriRule (_dualPartition lam) n----------------------------------------------------------------------------------
− Math/Combinat/Partitions/Integer/Naive.hs
@@ -1,214 +0,0 @@---- | Naive implementation of partitions of integers, encoded as list of @Int@-s.------ Integer partitions are nonincreasing sequences of positive integers.------ This is an internal module, you are not supposed to import it directly.---- --{-# LANGUAGE CPP, BangPatterns, ScopedTypeVariables, PatternSynonyms, ViewPatterns #-}-module Math.Combinat.Partitions.Integer.Naive 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--import Math.Combinat.Partitions.Integer.IntList-import Math.Combinat.Partitions.Integer.Count ( countPartitions )------------------------------------------------------------------------------------- * 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-------------------------------------------------------------------------------------toList :: Partition -> [Int]-toList (Partition xs) = xs--fromList :: [Int] -> Partition -fromList = mkPartition where- mkPartition xs = Partition $ sortBy (reverseCompare) $ filter (>0) xs--fromListUnsafe :: [Int] -> Partition-fromListUnsafe = Partition-------------------------------------------------------------------------------------isEmptyPartition :: Partition -> Bool-isEmptyPartition (Partition p) = null p--emptyPartition :: Partition-emptyPartition = Partition []--instance CanBeEmpty Partition where- empty = emptyPartition- isEmpty = isEmptyPartition---- | 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---- | 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------------------------------------------------------------------------------------- * Pattern synonyms ---- | Pattern sysnonyms allows us to use existing code with minimal modifications-pattern Nil :: Partition-pattern Nil <- (isEmpty -> True) where- Nil = empty--pattern Cons :: Int -> Partition -> Partition-pattern Cons x xs <- (unconsPartition -> Just (x,xs)) where- Cons x (Partition xs) = Partition (x:xs)---- | Simulated newtype constructor -pattern Partition_ :: [Int] -> Partition-pattern Partition_ xs = Partition xs--pattern Head :: Int -> Partition -pattern Head h <- (head . toDescList -> h)--pattern Tail :: Partition -> Partition-pattern Tail xs <- (Partition . tail . toDescList -> xs)--pattern Length :: Int -> Partition -pattern Length n <- (partitionWidth -> n) - ------------------------------------------------------------------------------------- * 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 . toDescList--fromExponentialForm :: [(Int,Int)] -> Partition-fromExponentialForm = Partition . _fromExponentialForm where------------------------------------------------------------------------------------- * List-like operations---- | From a sequence @[a1,a2,..,an]@ computes the sequence of differences--- @[a1-a2,a2-a3,...,an-0]@-diffSequence :: Partition -> [Int]-diffSequence = go . toDescList where- go (x:ys@(y:_)) = (x-y) : go ys - go [x] = [x]- go [] = []--unconsPartition :: Partition -> Maybe (Int,Partition)-unconsPartition (Partition xs) = case xs of- (y:ys) -> Just (y, Partition ys)- [] -> Nothing--toDescList :: Partition -> [Int]-toDescList (Partition xs) = xs-------------------------------------------------------------------------------------- * 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------------------------------------------------------------------------------------- * Containment partial ordering---- | 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)- ------------------------------------------------------------------------------------ * 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---- | 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------------------------------------------------------------------------------------
− Math/Combinat/Partitions/Multiset.hs
@@ -1,24 +0,0 @@---- | 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----------------------------------------------------------------------------------
− Math/Combinat/Partitions/NonCrossing.hs
@@ -1,205 +0,0 @@---- | 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----------------------------------------------------------------------------------
− Math/Combinat/Partitions/Plane.hs
@@ -1,124 +0,0 @@---- | 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 ]----------------------------------------------------------------------------------
− Math/Combinat/Partitions/Set.hs
@@ -1,109 +0,0 @@---- | 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----------------------------------------------------------------------------------
− Math/Combinat/Partitions/Skew.hs
@@ -1,153 +0,0 @@---- | 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---- | See "partitionElements"-skewPartitionElements :: SkewPartition -> [(Int, Int)]-skewPartitionElements (SkewPartition abs) = concat- [ [ (i,j) | j <- [a+1 .. a+b] ]- | (i,(a,b)) <- zip [1..] abs- ]------------------------------------------------------------------------------------- * 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 ------------------------------------------------------------------------------------- connected components--{--connectedComponents :: SkewPartition -> [((Int,Int),SkewPartition)]-connectedComponents = error "connectedComponents: not implemented yet"--isConnectedSkewPartition :: SkewPartition -> Bool-isConnectedSkewPartition skewp = length (connectedComponents skewp) == 1--}------------------------------------------------------------------------------------- * 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 -----------------------------------------------------------------------------------
− Math/Combinat/Partitions/Skew/Ribbon.hs
@@ -1,364 +0,0 @@---- | Ribbons (also called border strips, skew hooks, skew rim hooks, etc...).------ Ribbons are skew partitions that are 1) connected, 2) do not contain--- 2x2 blocks. Intuitively, they are 1-box wide continuous strips on--- the boundary.------ An alternative definition that they are skew partitions whose projection--- to the diagonal line is a continuous segment of width 1.--{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}-module Math.Combinat.Partitions.Skew.Ribbon where------------------------------------------------------------------------------------import Data.Array-import Data.List-import Data.Maybe--import qualified Data.Map as Map--import Math.Combinat.Sets-import Math.Combinat.Partitions.Integer-import Math.Combinat.Partitions.Integer.IntList ( _diffSequence )-import Math.Combinat.Partitions.Skew-import Math.Combinat.Tableaux-import Math.Combinat.Tableaux.LittlewoodRichardson-import Math.Combinat.Tableaux.GelfandTsetlin-import Math.Combinat.Helper------------------------------------------------------------------------------------- * Corners (TODO: move to Partitions - but we also want to refactor that)---- | The coordinates of the outer corners -outerCorners :: Partition -> [(Int,Int)]-outerCorners = outerCornerBoxes---- | The coordinates of the inner corners, including the two on the two coordinate--- axes. For the partition @[5,4,1]@ the result should be @[(0,5),(1,4),(2,1),(3,0)]@-extendedInnerCorners:: Partition -> [(Int,Int)]-extendedInnerCorners (Partition_ ps) = (0, head ps') : catMaybes mbCorners where- ps' = ps ++ [0]- mbCorners = zipWith3 f [1..] (tail ps') (_diffSequence ps') - f !y !x !k = if k > 0 then Just (y,x) else Nothing---- | Sequence of all the (extended) corners-extendedCornerSequence :: Partition -> [(Int,Int)]-extendedCornerSequence (Partition_ ps) = {- if null ps then [(0,0)] else -} interleave inner outer where- inner = (0, head ps') : [ (y,x) | (y,x,k) <- zip3 [1..] (tail ps') diff , k>0 ]- outer = [ (y,x) | (y,x,k) <- zip3 [1..] ps' diff , k>0 ]- diff = _diffSequence ps'- ps' = ps ++ [0]---- | The inner corner /boxes/ of the partition. Coordinates are counted from 1--- (cf.the 'elements' function), and the first coordinate is the row, the second--- the column (in English notation).------ For the partition @[5,4,1]@ the result should be @[(1,4),(2,1)]@------ > innerCornerBoxes lambda == (tail $ init $ extendedInnerCorners lambda)----innerCornerBoxes :: Partition -> [(Int,Int)]-innerCornerBoxes (Partition_ ps) = - case ps of- [] -> []- _ -> catMaybes mbCorners - where- mbCorners = zipWith3 f [1..] (tail ps) (_diffSequence ps) - f !y !x !k = if k > 0 then Just (y,x) else Nothing---- | The outer corner /boxes/ of the partition. Coordinates are counted from 1--- (cf.the 'elements' function), and the first coordinate is the row, the second--- the column (in English notation).------ For the partition @[5,4,1]@ the result should be @[(1,5),(2,4),(3,1)]@-outerCornerBoxes :: Partition -> [(Int,Int)]-outerCornerBoxes (Partition_ ps) = catMaybes mbCorners where- mbCorners = zipWith3 f [1..] ps (_diffSequence ps) - f !y !x !k = if k > 0 then Just (y,x) else Nothing---- | The outer and inner corner boxes interleaved, so together they form --- the turning points of the full border strip-cornerBoxSequence :: Partition -> [(Int,Int)]-cornerBoxSequence (Partition_ ps) = if null ps then [] else interleave outer inner where- inner = [ (y,x) | (y,x,k) <- zip3 [1..] tailps diff , k>0 ]- outer = [ (y,x) | (y,x,k) <- zip3 [1..] ps diff , k>0 ]- diff = _diffSequence ps- tailps = case ps of { [] -> [] ; _-> tail ps }-------------------------------------------------------------------------------------- | Naive (and very slow) implementation of @innerCornerBoxes@, for testing purposes-innerCornerBoxesNaive :: Partition -> [(Int,Int)]-innerCornerBoxesNaive part = filter f boxes where- boxes = elements part- f (y,x) = elem (y+1,x ) boxes- && elem (y ,x+1) boxes- && not (elem (y+1,x+1) boxes)---- | Naive (and very slow) implementation of @outerCornerBoxes@, for testing purposes-outerCornerBoxesNaive :: Partition -> [(Int,Int)]-outerCornerBoxesNaive part = filter f boxes where- boxes = elements part- f (y,x) = not (elem (y+1,x ) boxes)- && not (elem (y ,x+1) boxes)- && not (elem (y+1,x+1) boxes)------------------------------------------------------------------------------------- * Ribbon---- | A skew partition is a a ribbon (or border strip) if and only if projected--- to the diagonals the result is an interval.-isRibbon :: SkewPartition -> Bool-isRibbon skewp = go Nothing proj where- proj = Map.toList - $ Map.fromListWith (+) [ (x-y , 1) | (y,x) <- skewPartitionElements skewp ]- go Nothing [] = False- go (Just _) [] = True- go Nothing ((a,h):rest) = (h == 1) && go (Just a) rest - go (Just b) ((a,h):rest) = (h == 1) && (a == b+1) && go (Just a) rest--{---- | Naive (and slow) reference implementation of "isRibbon"-isRibbonNaive :: SkewPartition -> Bool-isRibbonNaive skewp = isConnectedSkewPartition skewp && no2x2 where- boxes = skewPartitionElements skewp- no2x2 = and - [ not ( elem (y+1,x ) boxes && - elem (y ,x+1) boxes && - elem (y+1,x+1) boxes ) -- no 2x2 blocks - | (y,x) <- boxes - ]--}--toRibbon :: SkewPartition -> Maybe Ribbon-toRibbon skew = - if not (isRibbon skew)- then Nothing- else Just ribbon - where- ribbon = Ribbon- { rbShape = skew- , rbLength = skewPartitionWeight skew- , rbHeight = height- , rbWidth = width- }- elems = skewPartitionElements skew- height = (length $ group $ sort $ map fst elems) - 1 -- TODO: optimize these- width = (length $ group $ sort $ map snd elems) - 1---- | Border strips (or ribbons) are defined to be skew partitions which are --- connected and do not contain 2x2 blocks.--- --- The /length/ of a border strip is the number of boxes it contains,--- and its /height/ is defined to be one less than the number of rows--- (in English notation) it occupies. The /width/ is defined symmetrically to --- be one less than the number of columns it occupies.----data Ribbon = Ribbon- { rbShape :: SkewPartition- , rbLength :: Int- , rbHeight :: Int- , rbWidth :: Int- }- deriving (Eq,Ord,Show)------------------------------------------------------------------------------------- * Inner border strips---- | Ribbons (or border strips) are defined to be skew partitions which are --- connected and do not contain 2x2 blocks. This function returns the--- border strips whose outer partition is the given one.-innerRibbons :: Partition -> [Ribbon]-innerRibbons part@(Partition ps) = if null ps then [] else strips where-- strips = [ mkStrip i j - | i<-[1..n] , _canStartStrip (annArr!i)- , j<-[i..n] , _canEndStrip (annArr!j)- ]-- n = length annList- annList = annotatedInnerBorderStrip part- annArr = listArray (1,n) annList-- mkStrip !i1 !i2 = Ribbon shape len height width where- ps' = ps ++ [0]- shape = SkewPartition [ (p-k,k) | (i,p,q) <- zip3 [1..] ps (tail ps') , let k = indent i p q ] - indent !i !p !q - | i < y1 = 0- | i > y2 = 0- | i == y2 = p - x2 + 1 -- the order is important here !!!- | otherwise = p - q + 1 -- because of the case y1 == y2 == i-- len = i2 - i1 + 1- height = y2 - y1- width = x1 - x2- BorderBox _ _ y1 x1 = annArr ! i1- BorderBox _ _ y2 x2 = annArr ! i2---- | Inner border strips (or ribbons) of the given length-innerRibbonsOfLength :: Partition -> Int -> [Ribbon]-innerRibbonsOfLength part@(Partition ps) givenLength = if null ps then [] else strips where-- strips = [ mkStrip i j - | i<-[1..n] , _canStartStrip (annArr!i)- , j<-[i..n] , _canEndStrip (annArr!j)- , j-i+1 == givenLength- ]-- n = length annList- annList = annotatedInnerBorderStrip part- annArr = listArray (1,n) annList-- mkStrip !i1 !i2 = Ribbon shape givenLength height width where- ps' = ps ++ [0]- shape = SkewPartition [ (p-k,k) | (i,p,q) <- zip3 [1..] ps (tail ps') , let k = indent i p q ] - indent !i !p !q - | i < y1 = 0- | i > y2 = 0- | i == y2 = p - x2 + 1 -- the order is important here !!!- | otherwise = p - q + 1 -- because of the case y1 == y2 == i-- height = y2 - y1- width = x1 - x2- BorderBox _ _ y1 x1 = annArr ! i1- BorderBox _ _ y2 x2 = annArr ! i2-------------------------------------------------------------------------------------- * Outer border strips---- | Hooks of length @n@ (TODO: move to the partition module)-listHooks :: Int -> [Partition]-listHooks 0 = []-listHooks 1 = [ Partition [1] ]-listHooks n = [ Partition (k : replicate (n-k) 1) | k<-[1..n] ]---- | Outer border strips (or ribbons) of the given length-outerRibbonsOfLength :: Partition -> Int -> [Ribbon]-outerRibbonsOfLength part@(Partition ps) givenLength = result where-- result = if null ps - then [ Ribbon shape givenLength ht wd- | p <- listHooks givenLength- , let shape = mkSkewPartition (p,part)- , let ht = partitionWidth p - 1 -- pretty inconsistent names here :(((- , let wd = partitionHeight p - 1- ]- else strips -- strips = [ mkStrip i j - | i<-[1..n] , _canStartStrip (annArr!i)- , j<-[i..n] , _canEndStrip (annArr!j)- , j-i+1 == givenLength- ]- - ysize = partitionWidth part- xsize = partitionHeight part- - annList = [ BorderBox True False 1 x | x <- reverse [xsize+2 .. xsize+givenLength ] ]- ++ annList0 - ++ [ BorderBox False True y 1 | y <- [ysize+2 .. ysize+givenLength ] ]- - n = length annList- annList0 = annotatedOuterBorderStrip part- annArr = listArray (1,n) annList-- mkStrip !i1 !i2 = Ribbon shape len height width where- ps' = (-666) : ps ++ replicate (givenLength) 0- shape = SkewPartition [ (p,k) | (i,p,q) <- zip3 [1..max ysize y2] (tail ps') ps' , let k = indent i p q ] - indent !i !p !q - | i < y1 = 0- | i > y2 = 0- | i == y1 = x1 - p -- the order is important here !!!--- | i == y2 = x2 - p - | otherwise = q - p + 1 -- len = i2 - i1 + 1- height = y2 - y1- width = x1 - x2- BorderBox _ _ y1 x1 = annArr ! i1- BorderBox _ _ y2 x2 = annArr ! i2------------------------------------------------------------------------------------- * Naive implementations (for testing)---- | Naive (and slow) implementation listing all inner border strips-innerRibbonsNaive :: Partition -> [Ribbon]-innerRibbonsNaive outer = list where- list = [ Ribbon skew (len skew) (ht skew) (wt skew)- | skew <- allSkewPartitionsWithOuterShape outer- , isRibbon skew- ]- len skew = length (skewPartitionElements skew)- ht skew = (length $ group $ sort $ map fst $ skewPartitionElements skew) - 1- wt skew = (length $ group $ sort $ map snd $ skewPartitionElements skew) - 1----- | Naive (and slow) implementation listing all inner border strips of the given length-innerRibbonsOfLengthNaive :: Partition -> Int -> [Ribbon]-innerRibbonsOfLengthNaive outer givenLength = list where- pweight = partitionWeight outer- list = [ Ribbon skew (len skew) (ht skew) (wt skew)- | skew <- skewPartitionsWithOuterShape outer givenLength- , isRibbon skew- ]- len skew = length (skewPartitionElements skew)- ht skew = (length $ group $ sort $ map fst $ skewPartitionElements skew) - 1- wt skew = (length $ group $ sort $ map snd $ skewPartitionElements skew) - 1---- | Naive (and slow) implementation listing all outer border strips of the given length-outerRibbonsOfLengthNaive :: Partition -> Int -> [Ribbon]-outerRibbonsOfLengthNaive inner givenLength = list where- pweight = partitionWeight inner- list = [ Ribbon skew (len skew) (ht skew) (wt skew)- | skew <- skewPartitionsWithInnerShape inner givenLength- , isRibbon skew- ]- len skew = length (skewPartitionElements skew)- ht skew = (length $ group $ sort $ map fst $ skewPartitionElements skew) - 1- wt skew = (length $ group $ sort $ map snd $ skewPartitionElements skew) - 1------------------------------------------------------------------------------------- * Annotated borders---- | A box on the border of a partition-data BorderBox = BorderBox- { _canStartStrip :: !Bool- , _canEndStrip :: !Bool- , _yCoord :: !Int- , _xCoord :: !Int- }- deriving Show- --- | The boxes of the full inner border strip, annotated with whether a border strip --- can start or end there.-annotatedInnerBorderStrip :: Partition -> [BorderBox]-annotatedInnerBorderStrip partition = if isEmptyPartition partition then [] else list where- list = goVert (head corners) (tail corners) - corners = extendedCornerSequence partition -- goVert (y1,x ) ((y2,_ ):rest) = [ BorderBox True (y==y2) y x | y<-[y1+1..y2] ] ++ goHoriz (y2,x) rest- goVert _ [] = [] -- goHoriz (y ,x1) ((_, x2):rest) = case rest of- [] -> [ BorderBox False True y x | x<-[x1-1,x1-2..x2+1] ]- _ -> [ BorderBox False (x/=x2) y x | x<-[x1-1,x1-2..x2 ] ] ++ goVert (y,x2) rest---- | The boxes of the full outer border strip, annotated with whether a border strip --- can start or end there.-annotatedOuterBorderStrip :: Partition -> [BorderBox]-annotatedOuterBorderStrip partition = if isEmptyPartition partition then [] else list where- list = goVert (head corners) (tail corners) - corners = extendedCornerSequence partition -- goVert (y1,x ) ((y2,_ ):rest) = [ BorderBox (y==y1) (y/=y2) (y+1) (x+1) | y<-[y1..y2] ] ++ goHoriz (y2,x) rest- goVert _ [] = [] -- goHoriz (y ,x1) ((_, x2):rest) = case rest of- [] -> [ BorderBox True (x==0) (y+1) (x+1) | x<-[x1-1,x1-2..x2 ] ]- _ -> [ BorderBox True False (y+1) (x+1) | x<-[x1-1,x1-2..x2+1] ] ++ goVert (y,x2) rest-----------------------------------------------------------------------------------
− Math/Combinat/Partitions/Vector.hs
@@ -1,82 +0,0 @@---- | 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----------------------------------------------------------------------------------
− Math/Combinat/Permutations.hs
@@ -1,969 +0,0 @@---- | 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- , lookupPermutation , (!!!)- , permutationArray- , permutationUArray- , uarrayToPermutationUnsafe- , isPermutation- , maybePermutation- , toPermutation- , toPermutationUnsafe- , toPermutationUnsafeN- , permutationSize- -- * Disjoint cycles- , DisjointCycles (..)- , fromDisjointCycles- , disjointCyclesUnsafe- , permutationToDisjointCycles- , disjointCyclesToPermutation- , numberOfCycles- , concatPermutations- -- * 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- , identityPermutation- , inversePermutation- , multiplyPermutation- , productOfPermutations- , productOfPermutations'- -- * Action of the permutation group- , permuteArray - , permuteList- , permuteArrayLeft , permuteArrayRight- , permuteListLeft , permuteListRight- -- * Sorting- , sortingPermutationAsc - , sortingPermutationDesc- -- * 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 Data.Vector.Compact.WordVec ( WordVec )-import qualified Data.Vector.Compact.WordVec as V--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------------------------------------------------------------------------------------- WordVec helpers--toUArray :: WordVec -> UArray Int Int-toUArray vec = listArray (1,n) (map fromIntegral $ V.toList vec) where n = V.vecLen vec--fromUArray :: UArray Int Int -> WordVec-fromUArray arr = fromPermListN n (map fromIntegral $ elems arr) where- (1,n) = bounds arr---- | maximum = length-fromPermListN :: Int -> [Int] -> WordVec-fromPermListN n perm = V.fromList' shape (map fromIntegral perm) where- shape = V.Shape n bits- bits = V.bitsNeededFor (fromIntegral n :: Word)--fromPermList :: [Int] -> WordVec-fromPermList perm = V.fromList (map fromIntegral perm)--(.!) :: WordVec -> Int -> Int-(.!) vec idx = fromIntegral (V.unsafeIndex (idx-1) vec)--_elems :: WordVec -> [Int]-_elems = map fromIntegral . V.toList--_assocs :: WordVec -> [(Int,Int)]-_assocs vec = zip [1..] (_elems vec)--_bound :: WordVec -> Int-_bound = V.vecLen--{- --- the old internal representation (UArray Int Int)--_elems :: UArray Int Int -> [Int]-_elems = elems--_assocs :: UArray Int Int -> [(Int,Int)]-_assocs = elems--_bound :: UArray Int Int -> Int-_bound = snd . bounds--}---toPermN :: Int -> [Int] -> Permutation-toPermN n xs = Permutation (fromPermListN n xs)------------------------------------------------------------------------------------- * Types---- | A permutation. Internally it is an (compact) vector --- of the integers @[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 WordVec 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) = toUArray ar--permutationArray :: Permutation -> Array Int Int-permutationArray (Permutation ar) = listArray (1,n) (_elems ar) where- n = _bound ar---- | Assumes that the input is a permutation of the numbers @[1..n]@.-toPermutationUnsafe :: [Int] -> Permutation-toPermutationUnsafe xs = Permutation (fromPermList xs) ---- | This is faster than 'toPermutationUnsafe', but you need to supply @n@.-toPermutationUnsafeN :: Int -> [Int] -> Permutation-toPermutationUnsafeN n xs = Permutation (fromPermListN n xs) ---- | Note: Indexing starts from 1.-uarrayToPermutationUnsafe :: UArray Int Int -> Permutation-uarrayToPermutationUnsafe = Permutation . fromUArray---- | 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 (toPermutationUnsafe 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) = _bound ar---- | Returns the image @sigma(k)@ of @k@ under the permutation @sigma@.--- --- Note: we don't check the bounds! It may even crash if you index out of bounds!-lookupPermutation :: Permutation -> Int -> Int-lookupPermutation (Permutation ar) idx = ar .! idx---- infix 8 !!!---- | Infix version of 'lookupPermutation'-(!!!) :: Permutation -> Int -> Int-(!!!) (Permutation ar) idx = ar .! idx--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- n = _bound ar---- | Given a permutation of @n@ and a permutation of @m@, we return--- a permutation of @n+m@ resulting by putting them next to each other.--- This should satisfy------ > permuteList p1 xs ++ permuteList p2 ys == permuteList (concatPermutations p1 p2) (xs++ys)----concatPermutations :: Permutation -> Permutation -> Permutation -concatPermutations perm1 perm2 = toPermutationUnsafe list where- n = permutationSize perm1- list = fromPermutation perm1 ++ map (+n) (fromPermutation perm2)------------------------------------------------------------------------------------- * 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 (inversePermutation 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 $ fromUArray 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-- n = _bound 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-- n = _bound 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 = _bound 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 = _bound 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 = _bound 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 = _bound 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 $ fromPermListN 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 n = _bound 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 $ fromPermListN 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 (fromUArray $ 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 (fromUArray $ 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 $ fromPermListN 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 $ fromPermListN n (n : [1..n-1])- ------------------------------------------------------------------------------------ * Permutation groups---- | Multiplies two permutations together: @p `multiplyPermutation` q@--- means the permutation when we first apply @p@, and then @q@--- (that is, the natural action is the /right/ action)------ See also 'permuteArray' for our conventions. ----multiplyPermutation :: Permutation -> Permutation -> Permutation-multiplyPermutation pi1@(Permutation perm1) pi2@(Permutation perm2) = - if (n==m) - then Permutation $ fromUArray result- else error "multiplyPermutation: permutations of different sets" - where- n = _bound perm1- m = _bound perm2 - result = permuteArray pi2 (toUArray perm1)- -infixr 7 `multiplyPermutation` ---- | The inverse permutation.-inversePermutation :: Permutation -> Permutation -inversePermutation (Permutation perm1) = Permutation $ fromUArray result- where- result = array (1,n) $ map swap $ _assocs perm1- n = _bound perm1- --- | The identity (or trivial) permutation.-identityPermutation :: Int -> Permutation -identityPermutation n = Permutation $ fromPermListN 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''.-productOfPermutations :: [Permutation] -> Permutation -productOfPermutations [] = error "productOfPermutations: empty list, we don't know size of the result"-productOfPermutations ps = foldl1' multiplyPermutation ps ---- | Multiply together a (possibly empty) list of permutations, all of which has size @n@-productOfPermutations' :: Int -> [Permutation] -> Permutation -productOfPermutations' n [] = identityPermutation n-productOfPermutations' n ps@(p:_) = if n == permutationSize p - then foldl1' multiplyPermutation ps - else error "productOfPermutations': 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):------ > permuteArray pi2 (permuteArray pi1 set) == permuteArray (pi1 `multiplyPermutation` pi2) set------ Synonym to 'permuteArrayRight'----{-# SPECIALIZE permuteArray :: Permutation -> Array Int b -> Array Int b #-}-{-# SPECIALIZE permuteArray :: Permutation -> UArray Int Int -> UArray Int Int #-}-permuteArray :: IArray arr b => Permutation -> arr Int b -> arr Int b -permuteArray = permuteArrayRight---- | Right action on lists. Synonym to 'permuteListRight'----permuteList :: Permutation -> [a] -> [a]-permuteList = permuteListRight- --- | The right (standard) action of permutations on sets. --- --- > permuteArrayRight pi2 (permuteArrayRight pi1 set) == permuteArrayRight (pi1 `multiplyPermutation` pi2) set--- --- The second argument should be an array with bounds @(1,n)@.--- The function checks the array bounds.----{-# SPECIALIZE permuteArrayRight :: Permutation -> Array Int b -> Array Int b #-}-{-# SPECIALIZE permuteArrayRight :: Permutation -> UArray Int Int -> UArray Int Int #-}-permuteArrayRight :: IArray arr b => Permutation -> arr Int b -> arr Int b -permuteArrayRight pi@(Permutation perm) ar = - if (a==1) && (b==n) - then listArray (1,n) [ ar!(perm.!i) | i <- [1..n] ] - else error "permuteArrayRight: array bounds do not match"- where- n = _bound perm- (a,b) = bounds ar ---- | The right (standard) action on a list. The list should be of length @n@.------ > fromPermutation perm == permuteListRight perm [1..n]--- -permuteListRight :: forall a . Permutation -> [a] -> [a] -permuteListRight perm xs = elems $ permuteArrayRight perm $ arr where- arr = listArray (1,n) xs :: Array Int a- n = permutationSize perm---- | The left (opposite) action of the permutation group.------ > permuteArrayLeft pi2 (permuteArrayLeft pi1 set) == permuteArrayLeft (pi2 `multiplyPermutation` pi1) set------ It is related to 'permuteLeftArray' via:------ > permuteArrayLeft pi arr == permuteArrayRight (inversePermutation pi) arr--- > permuteArrayRight pi arr == permuteArrayLeft (inversePermutation pi) arr----{-# SPECIALIZE permuteArrayLeft :: Permutation -> Array Int b -> Array Int b #-}-{-# SPECIALIZE permuteArrayLeft :: Permutation -> UArray Int Int -> UArray Int Int #-}-permuteArrayLeft :: IArray arr b => Permutation -> arr Int b -> arr Int b -permuteArrayLeft 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 "permuteArrayLeft: array bounds do not match"- where- n = _bound perm- (a,b) = bounds ar ---- | The left (opposite) action on a list. The list should be of length @n@.------ > permuteListLeft perm set == permuteList (inversePermutation perm) set--- > fromPermutation (inversePermutation perm) == permuteListLeft perm [1..n]----permuteListLeft :: forall a. Permutation -> [a] -> [a] -permuteListLeft perm xs = elems $ permuteArrayLeft perm $ arr where- arr = listArray (1,n) xs :: Array Int a- n = permutationSize perm-------------------------------------------------------------------------------------- | Given a list of things, we return a permutation which sorts them into--- ascending order, that is:------ > permuteList (sortingPermutationAsc xs) xs == sort xs------ Note: if the things are not unique, then the sorting permutations is not--- unique either; we just return one of them.----sortingPermutationAsc :: Ord a => [a] -> Permutation-sortingPermutationAsc xs = toPermutation (map fst sorted) where- sorted = sortBy (comparing snd) $ zip [1..] xs---- | Given a list of things, we return a permutation which sorts them into--- descending order, that is:------ > permuteList (sortingPermutationDesc xs) xs == reverse (sort xs)------ Note: if the things are not unique, then the sorting permutations is not--- unique either; we just return one of them.----sortingPermutationDesc :: Ord a => [a] -> Permutation-sortingPermutationDesc xs = toPermutation (map fst sorted) where- sorted = sortBy (reverseComparing snd) $ zip [1..] xs------------------------------------------------------------------------------------- * 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 (fromUArray 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"- -----------------------------------------------------------------------------------
− Math/Combinat/RootSystems.hs
@@ -1,319 +0,0 @@- --- | Naive (very inefficient) algorithm to generate the irreducible (Dynkin) root systems --- --- Based on <https://en.wikipedia.org/wiki/Root_system> - -{-# LANGUAGE BangPatterns, FlexibleInstances, TypeSynonymInstances, FlexibleContexts #-} -module Math.Combinat.RootSystems where - --------------------------------------------------------------------------------- - -import Control.Monad - -import Data.Array - -import Data.Set (Set) -import qualified Data.Set as Set - -import Data.List -import Data.Ord - -import Math.Combinat.Numbers.Primes -import Math.Combinat.Sets - --------------------------------------------------------------------------------- --- * Half-integers - --- | The type of half-integers (internally represented by their double) --- --- TODO: refactor this into its own module -newtype HalfInt - = HalfInt Int - deriving (Eq,Ord) - -half :: HalfInt -half = HalfInt 1 - -divByTwo :: Int -> HalfInt -divByTwo n = HalfInt n - -mulByTwo :: HalfInt -> Int -mulByTwo (HalfInt n) = n - -scaleBy :: Int -> HalfInt -> HalfInt -scaleBy k (HalfInt n) = HalfInt (k*n) - -instance Show HalfInt where - show (HalfInt n) = case divMod n 2 of - (k,0) -> show k - (_,1) -> show n ++ "/2" - -instance Num HalfInt where - fromInteger = HalfInt . (*2) . fromInteger - a + b = divByTwo $ mulByTwo a + mulByTwo b - a - b = divByTwo $ mulByTwo a - mulByTwo b - a * b = case divMod (mulByTwo a * mulByTwo b) 4 of - (k,0) -> HalfInt (2*k) - (k,2) -> HalfInt (2*k+1) - _ -> error "the result of multiplication is not a half-integer" - negate = divByTwo . negate . mulByTwo - signum = divByTwo . signum . mulByTwo - abs = divByTwo . abs . mulByTwo - --------------------------------------------------------------------------------- --- * Vectors of half-integers - -type HalfVec = [HalfInt] - -instance Num HalfVec where - fromInteger = error "HalfVec/fromInteger" - (+) = safeZip (+) - (-) = safeZip (-) - (*) = safeZip (*) - negate = map negate - abs = map abs - signum = map signum - -scaleVec :: Int -> HalfVec -> HalfVec -scaleVec k = map (scaleBy k) - -negateVec :: HalfVec -> HalfVec -negateVec = map negate - --- dotProd :: HalfVec -> HalfVec --- dotProd xs ys = foldl' (+) 0 $ safeZip (*) xs ys - -safeZip :: (a -> b -> c) -> [a] -> [b] -> [c] -safeZip f = go where - go (x:xs) (y:ys) = f x y : go xs ys - go [] [] = [] - go _ _ = error "safeZip: the lists do not have equal length" - --------------------------------------------------------------------------------- --- * Dynkin diagrams - -data Dynkin - = A !Int - | B !Int - | C !Int - | D !Int - | E6 | E7 | E8 - | F4 - | G2 - deriving (Eq,Show) - --------------------------------------------------------------------------------- --- * The roots of root systems - --- | The ambient dimension of (our representation of the) system (length of the vector) -ambientDim :: Dynkin -> Int -ambientDim d = case d of - A n -> n+1 -- it's an n dimensional subspace of (n+1) dimensions - B n -> n - C n -> n - D n -> n - E6 -> 6 - E7 -> 8 -- sublattice of E8 ? - E8 -> 8 - F4 -> 4 - G2 -> 3 -- it's a 2 dimensional subspace of 3 dimensions - -simpleRootsOf :: Dynkin -> [HalfVec] -simpleRootsOf d = - - case d of - - A n -> [ e i - e (i+1) | i <- [1..n] ] - - B n -> [ e i - e (i+1) | i <- [1..n-1] ] ++ [e n] - - C n -> [ e i - e (i+1) | i <- [1..n-1] ] ++ [scaleVec 2 (e n)] - - D n -> [ e i - e (i+1) | i <- [1..n-1] ] ++ [e (n-1) + e n] - - E6 -> simpleRootsE6_123 - E7 -> simpleRootsE7_12 - E8 -> simpleRootsE8_even - - F4 -> [ [ 1,-1, 0, 0] - , [ 0, 1,-1, 0] - , [ 0, 0, 1, 0] - , [-h,-h,-h,-h] - ] - - G2 -> [ [ 1,-1, 0] - , [-1, 2,-1] - ] - - where - h = half - n = ambientDim d - - e :: Int -> HalfVec - e i = replicate (i-1) 0 ++ [1] ++ replicate (n-i) 0 - -positiveRootsOf :: Dynkin -> Set HalfVec -positiveRootsOf = positiveRoots . simpleRootsOf - -negativeRootsOf :: Dynkin -> Set HalfVec -negativeRootsOf = Set.map negate . positiveRootsOf - -allRootsOf :: Dynkin -> Set HalfVec -allRootsOf dynkin = Set.unions [ pos , neg ] where - simple = simpleRootsOf dynkin - pos = positiveRoots simple - neg = Set.map negate pos - --------------------------------------------------------------------------------- --- * Positive roots - --- | Finds a vector, which is hopefully not orthognal to any root --- (generated by the given simple roots), and has positive dot product with each of them. -findPositiveHyperplane :: [HalfVec] -> [Double] -findPositiveHyperplane vs = w where - n = length (head vs) - w0 = map (fromIntegral . mulByTwo) (foldl1 (+) vs) :: [Double] - w = zipWith (+) w0 perturb - perturb = map small $ map fromIntegral $ take n primes - small :: Double -> Double - small x = x / (10**10) - -positiveRoots :: [HalfVec] -> Set HalfVec -positiveRoots simples = Set.fromList pos where - roots = mirrorClosure simples - w = findPositiveHyperplane simples - pos = [ r | r <- Set.toList roots , dot4 r > 0 ] where - - dot4 :: HalfVec -> Double - dot4 a = foldl' (+) 0 $ safeZip (*) w $ map (fromIntegral . mulByTwo) a - -basisOfPositives :: Set HalfVec -> [HalfVec] -basisOfPositives set = Set.toList (Set.difference set set2) where - set2 = Set.fromList [ a + b | [a,b] <- choose 2 (Set.toList set) ] - - --------------------------------------------------------------------------------- --- * Operations on half-integer vectors - --- | bracket b a = (a,b)/(a,a) -bracket :: HalfVec -> HalfVec -> HalfInt -bracket b a = - case divMod (2*a_dot_b) (a_dot_a) of - (n,0) -> divByTwo n - _ -> error "bracket: result is not a half-integer" - where - a_dot_b = foldl' (+) 0 $ safeZip (*) (map mulByTwo a) (map mulByTwo b) - a_dot_a = foldl' (+) 0 $ safeZip (*) (map mulByTwo a) (map mulByTwo a) - --- | mirror b a = b - 2*(a,b)/(a,a) * a -mirror :: HalfVec -> HalfVec -> HalfVec -mirror b a = b - scaleVec (mulByTwo $ bracket b a) a - --- | Cartan matrix of a list of (simple) roots -cartanMatrix :: [HalfVec] -> Array (Int,Int) Int -cartanMatrix list = array ((1,1),(n,n)) [ ((i,j), f i j) | i<-[1..n] , j<-[1..n] ] where - n = length list - arr = listArray (1,n) list - f !i !j = mulByTwo $ bracket (arr!j) (arr!i) - -printMatrix :: Show a => Array (Int,Int) a -> IO () -printMatrix arr = do - let ((1,1),(n,m)) = bounds arr - arr' = fmap show arr - let ks = [ 1 + maximum [ length (arr'!(i,j)) | i<-[1..n] ] | j<-[1..m] ] - forM_ [1..n] $ \i -> do - putStrLn $ flip concatMap [1..m] $ \j -> extendTo (ks!!(j-1)) $ arr' ! (i,j) - where - extendTo n s = replicate (n-length s) ' ' ++ s - --------------------------------------------------------------------------------- --- * Mirroring - --- | We mirror stuff until there is no more things happening --- (very naive algorithm, but seems to work) -mirrorClosure :: [HalfVec] -> Set HalfVec -mirrorClosure = go . Set.fromList where - - go set - | n' > n = go set' - | n'' > n = go set'' - | otherwise = set - where - n = Set.size set - n' = Set.size set' - n'' = Set.size set'' - set' = mirrorStep set - set'' = Set.union set (Set.map negateVec set) - -mirrorStep :: Set HalfVec -> Set HalfVec -mirrorStep old = Set.union old new where - new = Set.fromList [ mirror b a | [a,b] <- choose 2 $ Set.toList old ] - --------------------------------------------------------------------------------- --- * E6, E7 and E8 - --- | This is a basis of E6 as the subset of the even E8 root system --- where the first three coordinates agree (they are consolidated --- into the first coordinate here) -simpleRootsE6_123:: [HalfVec] -simpleRootsE6_123 = roots where - h = half - roots = - [ [-h,-h,-h,-h,-h,-h,-h,-h] - , [ h, h, h, h, h, h,-h,-h] - , [ 0, 0, 0, 0,-1, 0, 1, 0] - , [ 0, 0, 0, 0, 0, 0,-1, 1] - , [-h,-h,-h, h, h, h, h,-h] - , [ 0, 0, 0,-1, 1, 0, 0, 0] - ] - --- | This is a basis of E8 as the subset of the even E8 root system --- where the first two coordinates agree (they are consolidated --- into the first coordinate here) -simpleRootsE7_12:: [HalfVec] -simpleRootsE7_12 = roots where - h = half - roots = - [ [-h,-h,-h,-h,-h,-h,-h,-h] - , [ h, h, h, h, h, h,-h,-h] - , [ h, h,-h,-h,-h,-h, h, h] - , [-h,-h, h, h,-h, h, h,-h] - , [ 0, 0, 0,-1, 1, 0, 0, 0] - , [ 0, 0,-1, 1, 0, 0, 0, 0] - , [ 0, 0, 0, 0, 0, 0,-1, 1] - ] - --- | This is a basis of E7 as the subset of the even E8 root system --- for which the sum of coordinates sum to zero -simpleRootsE7_diag :: [HalfVec] -simpleRootsE7_diag = roots where - roots = [ e i - e (i+1) | i <-[2..7] ] ++ [[h,h,h,h,-h,-h,-h,-h]] - h = half - n = 8 - - e :: Int -> HalfVec - e i = replicate (i-1) 0 ++ [1] ++ replicate (n-i) 0 - -simpleRootsE8_even :: [HalfVec] -simpleRootsE8_even = roots where - roots = [v1,v2,v3,v4,v5,v7,v8,v6] - - [v1,v2,v3,v4,v5,v6,v7,v8] = roots0 - roots0 = [ e i - e (i+1) | i <-[1..6] ] ++ [ e 6 + e 7 , replicate 8 (-h) ] - - h = half - n = 8 - - e :: Int -> HalfVec - e i = replicate (i-1) 0 ++ [1] ++ replicate (n-i) 0 - -simpleRootsE8_odd :: [HalfVec] -simpleRootsE8_odd = roots where - roots = [ e i - e (i+1) | i <-[1..7] ] ++ [[-h,-h,-h,-h,-h , h,h,h]] - h = half - n = 8 - - e :: Int -> HalfVec - e i = replicate (i-1) 0 ++ [1] ++ replicate (n-i) 0 - ---------------------------------------------------------------------------------
− Math/Combinat/Sets.hs
@@ -1,212 +0,0 @@---- | 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]-----------------------------------------------------------------------------------
− Math/Combinat/Sets/VennDiagrams.hs
@@ -1,150 +0,0 @@---- | Venn diagrams. See <https://en.wikipedia.org/wiki/Venn_diagram>------ TODO: write a more efficient implementation (for example an array of size @2^n@)-----{-# LANGUAGE BangPatterns #-}-module Math.Combinat.Sets.VennDiagrams where------------------------------------------------------------------------------------import Data.List--import GHC.TypeLits-import Data.Proxy--import qualified Data.Map as Map-import Data.Map (Map)--import Math.Combinat.Compositions-import Math.Combinat.ASCII-------------------------------------------------------------------------------------- | Venn diagrams of @n@ sets. Each possible zone is annotated with a value--- of type @a@. A typical use case is to annotate with the cardinality of the--- given zone.------ Internally this is representated by a map from @[Bool]@, where @True@ means element --- of the set, @False@ means not.------ TODO: write a more efficient implementation (for example an array of size 2^n)-newtype VennDiagram a = VennDiagram { vennTable :: Map [Bool] a } deriving (Eq,Ord,Show)---- | How many sets are in the Venn diagram-vennDiagramNumberOfSets :: VennDiagram a -> Int-vennDiagramNumberOfSets (VennDiagram table) = length $ fst $ Map.findMin table---- | How many zones are in the Venn diagram------ > vennDiagramNumberOfZones v == 2 ^ (vennDiagramNumberOfSets v)----vennDiagramNumberOfZones :: VennDiagram a -> Int-vennDiagramNumberOfZones venn = 2 ^ (vennDiagramNumberOfSets venn)---- | How many /nonempty/ zones are in the Venn diagram-vennDiagramNumberOfNonemptyZones :: VennDiagram Int -> Int-vennDiagramNumberOfNonemptyZones (VennDiagram table) = length $ filter (/=0) $ Map.elems table--unsafeMakeVennDiagram :: [([Bool],a)] -> VennDiagram a-unsafeMakeVennDiagram = VennDiagram . Map.fromList---- | We call venn diagram trivial if all the intersection zones has zero cardinality--- (that is, the original sets are all disjoint)-isTrivialVennDiagram :: VennDiagram Int -> Bool-isTrivialVennDiagram (VennDiagram table) = and [ c == 0 | (bs,c) <- Map.toList table , isIntersection bs ] where- isIntersection bs = case filter id bs of- [] -> False- [_] -> False- _ -> True--printVennDiagram :: Show a => VennDiagram a -> IO ()-printVennDiagram = putStrLn . prettyVennDiagram--prettyVennDiagram :: Show a => VennDiagram a -> String-prettyVennDiagram = unlines . asciiLines . asciiVennDiagram--asciiVennDiagram :: Show a => VennDiagram a -> ASCII-asciiVennDiagram (VennDiagram table) = asciiFromLines $ map f (Map.toList table) where- f (bs,a) = "{" ++ extendTo (length bs) [ if b then z else ' ' | (b,z) <- zip bs abc ] ++ "} -> " ++ show a- extendTo k str = str ++ replicate (k - length str) ' '- abc = ['A'..'Z']--instance Show a => DrawASCII (VennDiagram a) where- ascii = asciiVennDiagram---- | Given a Venn diagram of cardinalities, we compute the cardinalities of the--- original sets (note: this is slow!)-vennDiagramSetCardinalities :: VennDiagram Int -> [Int]-vennDiagramSetCardinalities (VennDiagram table) = go n list where- list = Map.toList table- n = length $ fst $ head list- go :: Int -> [([Bool],Int)] -> [Int]- go !0 _ = []- go !k xs = this : go (k-1) (map xtail xs) where- this = foldl' (+) 0 [ c | ((True:_) , c) <- xs ]- xtail (bs,c) = (tail bs,c)-------------------------------------------------------------------------------------- | Given the cardinalities of some finite sets, we list all possible--- Venn diagrams.------ Note: we don't include the empty zone in the tables, because it's always empty.------ Remark: if each sets is a singleton set, we get back set partitions:------ > > [ length $ enumerateVennDiagrams $ replicate k 1 | k<-[1..8] ]--- > [1,2,5,15,52,203,877,4140]--- >--- > > [ countSetPartitions k | k<-[1..8] ]--- > [1,2,5,15,52,203,877,4140]------ Maybe this could be called multiset-partitions?------ Example:------ > autoTabulate RowMajor (Right 6) $ map ascii $ enumerateVennDiagrams [2,3,3]----enumerateVennDiagrams :: [Int] -> [VennDiagram Int]-enumerateVennDiagrams dims = - case dims of- [] -> []- [d] -> venns1 d- (d:ds) -> concatMap (worker (length ds) d) $ enumerateVennDiagrams ds- where-- worker !n !d (VennDiagram table) = result where-- list = Map.toList table- falses = replicate n False-- comps k = compositions' (map snd list) k- result = - [ unsafeMakeVennDiagram $ - [ (False:tfs , m-c) | ((tfs,m),c) <- zip list comp ] ++- [ (True :tfs , c) | ((tfs,m),c) <- zip list comp ] ++- [ (True :falses , d-k) ]- | k <- [0..d]- , comp <- comps k- ]-- venns1 :: Int -> [VennDiagram Int]- venns1 p = [ theVenn ] where - theVenn = unsafeMakeVennDiagram [ ([True],p) ] ------------------------------------------------------------------------------------{----- | for testing only-venns2 :: Int -> Int -> [Venn Int]-venns2 p q = - [ mkVenn [ ([t,f],p-k) , ([f,t],q-k) , ([t,t],k) ]- | k <- [0..min p q] - ]- where- t = True- f = False--}
− Math/Combinat/Sign.hs
@@ -1,114 +0,0 @@---- | Signs--{-# LANGUAGE CPP, BangPatterns #-}-module Math.Combinat.Sign where------------------------------------------------------------------------------------import Data.Monoid---- Semigroup became a superclass of Monoid-#if MIN_VERSION_base(4,11,0) -import Data.Foldable-import Data.Semigroup-#endif--import System.Random------------------------------------------------------------------------------------data Sign- = Plus -- hmm, this way @Plus < Minus@, not sure about that- | Minus- deriving (Eq,Ord,Show,Read)-------------------------------------------------------------------------------------- Semigroup became a superclass of Monoid-#if MIN_VERSION_base(4,11,0) --instance Semigroup Sign where- (<>) = mulSign- sconcat = foldl1 mulSign--instance Monoid Sign where- mempty = Plus- mconcat = productOfSigns--#else--instance Monoid Sign where- mempty = Plus- mappend = mulSign- mconcat = productOfSigns--#endif------------------------------------------------------------------------------------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----------------------------------------------------------------------------------
− Math/Combinat/Tableaux.hs
@@ -1,242 +0,0 @@---- | 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.Integer-import Math.Combinat.Partitions.Integer.IntList ( _dualPartition )-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----------------------------------------------------------------------------------- -
− Math/Combinat/Tableaux/GelfandTsetlin.hs
@@ -1,341 +0,0 @@---- | 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 ----------------------------------------------------------------------------------
− Math/Combinat/Tableaux/GelfandTsetlin/Cone.hs
@@ -1,261 +0,0 @@---- 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----------------------------------------------------------------------------------
− Math/Combinat/Tableaux/LittlewoodRichardson.hs
@@ -1,399 +0,0 @@---- | 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 [] = []----------------------------------------------------------------------------------
− Math/Combinat/Tableaux/Skew.hs
@@ -1,224 +0,0 @@---- | 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.Integer.IntList ( _diffSequence )-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-----------------------------------------------------------------------------------
− Math/Combinat/Trees.hs
@@ -1,9 +0,0 @@--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-
− Math/Combinat/Trees/Binary.hs
@@ -1,492 +0,0 @@---- | 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 , [] )- findj _ _ = error "fasc4A_algorithm_P: fatal error shouldn't happen"- --- | 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_ ----------------------------------------------------------------------------------
− Math/Combinat/Trees/Binary.hs-boot
@@ -1,22 +0,0 @@---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----------------------------------------------------------------------------------
− Math/Combinat/Trees/Graphviz.hs
@@ -1,115 +0,0 @@---- | 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----------------------------------------------------------------------------------
− Math/Combinat/Trees/Nary.hs
@@ -1,430 +0,0 @@---- | N-ary trees.--{-# LANGUAGE FlexibleInstances, 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.Tree-import Data.List--import Control.Applicative----import Control.Monad.State-import Control.Monad.Trans.State-import Data.Traversable (traverse)--import Math.Combinat.Sets ( listTensor )-import Math.Combinat.Partitions.Multiset ( partitionMultiset )-import Math.Combinat.Compositions ( compositions )-import Math.Combinat.Numbers ( factorial, binomial )--import Math.Combinat.Trees.Graphviz ( Dot , graphvizDotForest , graphvizDotTree )--import Math.Combinat.Classes-import Math.Combinat.ASCII as ASCII-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)-----------------------------------------------------------------------------------
− Math/Combinat/Trees/Nary.hs-boot
@@ -1,16 +0,0 @@--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----------------------------------------------------------------------------------
− Math/Combinat/Tuples.hs
@@ -1,61 +0,0 @@---- | 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)---------------------------------------------------------
− Math/Combinat/TypeLevel.hs
@@ -1,117 +0,0 @@---- | 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)----------------------------------------------------------------------------------
+ README.md view
@@ -0,0 +1,31 @@+combinat - a Haskell combinatorics library+------------------------------------------++For the API docs, [check out Hackage](https://hackage.haskell.org/package/combinat).++This is a combinatorics library for Haskell. It contains functions +enumerating, counting, visualizing, manipulating, and sometimes randomly sampling +from many standard combinatorial objects, including:++* subsets+* compositions+* trees+* numbers:+ * natural numbers+ * prime numbers+ * formal power series+* permutations+* partitions:+ * integer partitions+ * set partitions, multiset partitions, non-crossing partitions+ * plane partitions+ * vector partitions+ * skew partitions, ribbons+* Young tableaux, Littlewood-Richardson coefficients+* lattice paths, Dyck paths+* groups:+ * permutation groups+ * braid groups+ * free groups, free products of cyclic groups+ * Thompson's group F+
combinat.cabal view
@@ -1,5 +1,5 @@ Name: combinat-Version: 0.2.10.0+Version: 0.2.10.1 Synopsis: Generate and manipulate various combinatorial objects. Description: A collection of functions to generate, count, manipulate and visualize all kinds of combinatorial objects like @@ -8,31 +8,35 @@ License: BSD3 License-file: LICENSE Author: Balazs Komuves-Copyright: (c) 2008-2021 Balazs Komuves+Copyright: (c) 2008-2023 Balazs Komuves Maintainer: bkomuves (plus) hackage (at) gmail (dot) com-Homepage: http://moire.be/haskell/+Homepage: https://github.com/bkomuves/combinat Stability: Experimental Category: Math-Tested-With: GHC == 8.6.5+Tested-With: GHC == 8.6.5, GHC == 9.4.7 Cabal-Version: 1.24 Build-Type: Simple extra-doc-files: svg/*.svg + README.md extra-source-files: svg/*.svg svg/src/gen_figures.hs source-repository head- type: darcs - location: https://hub.darcs.net/bkomuves/combinat+ type: git+ location: https://github.com/bkomuves/combinat -------------------------------------------------------------------------------- Library Build-Depends: base >= 4 && < 5, - array >= 0.5, containers, random, transformers,- compact-word-vectors >= 0.2.0.2+ array >= 0.5 && < 0.7, + containers >= 0.6 && < 0.9, + random >= 1.1 && < 1.4, + transformers >= 0.4.2 && < 0.8,+ compact-word-vectors >= 0.2.0.2 && < 0.4 Exposed-Modules: Math.Combinat Math.Combinat.Classes@@ -86,7 +90,7 @@ Default-Language: Haskell2010 - Hs-Source-Dirs: .+ Hs-Source-Dirs: src ghc-options: -fwarn-tabs -fno-warn-unused-matches -fno-warn-name-shadowing -fno-warn-unused-imports @@ -113,11 +117,19 @@ Tests.Numbers.Primes Tests.Numbers.Sequences - build-depends: base >= 4 && < 5, array >= 0.5, containers >= 0.5, random, transformers,- combinat, compact-word-vectors >= 0.2.0.2,- test-framework, - test-framework-quickcheck2, QuickCheck >= 2,- tasty, tasty-quickcheck, tasty-hunit+ build-depends: base >= 4 && < 5, + array >= 0.5 && < 0.7, + containers >= 0.6 && < 0.9, + random >= 1.1 && < 1.4, + transformers >= 0.4.2 && < 0.8,+ compact-word-vectors >= 0.2.0.2 && < 0.4,+ combinat, + test-framework > 0.8.1 && < 0.10, + test-framework-quickcheck2 >= 0.3 && < 0.5, + QuickCheck >= 2 && < 0.3,+ tasty >= 0.11 && < 1.7, + tasty-quickcheck >= 0.9 && < 1.0, + tasty-hunit >= 0.10 && < 1.0 Default-Language: Haskell2010 Default-Extensions: CPP, BangPatterns
+ src/Math/Combinat.hs view
@@ -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
+ src/Math/Combinat/ASCII.hs view
@@ -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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Classes.hs view
@@ -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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Compositions.hs view
@@ -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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Groups/Braid.hs view
@@ -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 perm = P.toPermutationUnsafeN n [ (n+1) - perm !!! (n-i) | i<-[0..n-1] ] where+ n = P.permutationSize perm++--------------------------------------------------------------------------------+-- * 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.identityPermutation 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 = P.uarrayToPermutationUnsafe (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 = runST action where+ n = P.permutationSize perm++ 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 = P.lookupPermutation perm phase -- (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)++--------------------------------------------------------------------------------+
+ src/Math/Combinat/Groups/Braid/NF.hs view
@@ -0,0 +1,536 @@++-- | 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.multiplyPermutation` (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.multiplyPermutation` 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.multiplyPermutation` 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.inversePermutation++-- | This satisfies+-- +-- > permutationFinishingSet p == permWordFinishingSet n (_permutationBraid p)+--+permutationFinishingSet :: Permutation -> [Int]+permutationFinishingSet perm+ = [ i | i<-[1..n-1] , perm !!! i > perm !!! (i+1) ] where n = P.permutationSize perm++-- | 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.multiplyPermutation s preal+ q' = P.multiplyPermutation 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+ ffs <- worker ps+ case ffs of+ (f:fs) -> return ((p:f):fs)+ _ -> error "Braid/NF/leftGreedyFactors/worker: fatal error; should not happen"+ 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 _ [] = []++-}++--------------------------------------------------------------------------------+
+ src/Math/Combinat/Groups/Free.hs view
@@ -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 ]++-}++--------------------------------------------------------------------------------++
+ src/Math/Combinat/Groups/Thompson/F.hs view
@@ -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]++--------------------------------------------------------------------------------++
+ src/Math/Combinat/Helper.hs view
@@ -0,0 +1,329 @@++-- | Miscellaneous helper functions used internally++{-# 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++interleave :: [a] -> [a] -> [a]+interleave (x:xs) (y:ys) = x : y : interleave xs ys+interleave [x] [] = x : []+interleave [] [] = []+interleave _ _ = error "interleave: shouldn't happen"++evens, odds :: [a] -> [a] +evens (x:xs) = x : odds xs+evens [] = []+odds (x:xs) = evens xs+odds [] = []++--------------------------------------------------------------------------------+-- * multiplication++-- | Product of list of integers, but in interleaved order (for a list of big numbers,+-- it should be faster than the linear order)+productInterleaved :: [Integer] -> Integer+productInterleaved = go where+ go [] = 1+ go [x] = x+ go [x,y] = x*y+ go list = go (evens list) * go (odds list)++-- | Faster implementation of @product [ i | i <- [a+1..b] ]@+productFromTo :: Integral a => a -> a -> Integer+productFromTo = go where+ go !a !b + | dif < 1 = 1+ | dif < 5 = product [ fromIntegral i | i<-[a+1..b] ]+ | otherwise = go a half * go half b+ where+ dif = b - a+ half = div (a+b+1) 2++-- | Faster implementation of product @[ i | i <- [a+1,a+3,..b] ]@+productFromToStride2 :: Integral a => a -> a -> Integer+productFromToStride2 = go where+ go !a !b + | dif < 1 = 1+ | dif < 9 = product [ fromIntegral i | i<-[a+1,a+3..b] ]+ | otherwise = go a half * go half b+ where+ dif = b - a+ half = a + 2*(div dif 4)++--------------------------------------------------------------------------------+-- * 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++reverseComparing :: Ord b => (a -> b) -> a -> a -> Ordering+reverseComparing f x y = compare (f y) (f x)++reverseCompare :: Ord a => a -> a -> Ordering+reverseCompare x y = compare y x -- 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++--------------------------------------------------------------------------------
+ src/Math/Combinat/LatticePaths.hs view
@@ -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++--------------------------------------------------------------------------------+
+ src/Math/Combinat/Numbers.hs view
@@ -0,0 +1,12 @@++module Math.Combinat.Numbers + ( module Math.Combinat.Numbers.Integers+ , module Math.Combinat.Numbers.Primes+ , module Math.Combinat.Numbers.Sequences+ ) + where++import Math.Combinat.Numbers.Integers+import Math.Combinat.Numbers.Primes+import Math.Combinat.Numbers.Sequences+
+ src/Math/Combinat/Numbers/Integers.hs view
@@ -0,0 +1,113 @@++-- | Operations on integers++module Math.Combinat.Numbers.Integers + ( -- * Integer logarithm+ integerLog2+ , ceilingLog2+ -- * Integer square root+ , isSquare+ , integerSquareRoot+ , ceilingSquareRoot+ , integerSquareRoot' + , integerSquareRootNewton'+ )+ where++--------------------------------------------------------------------------------++-- import Math.Combinat.Numbers++import Data.List ( group , sort )+import Data.Bits++import System.Random++--------------------------------------------------------------------------------+-- 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 + ]+-}++--------------------------------------------------------------------------------+
+ src/Math/Combinat/Numbers/Primes.hs view
@@ -0,0 +1,361 @@++-- | Prime numbers and related number theoretical stuff.++module Math.Combinat.Numbers.Primes + ( -- * Elementary number theory+ divides+ , divisors, squareFreeDivisors, squareFreeDivisors_ + , divisorSum , divisorSum'+ , moebiusMu , eulerTotient , liouvilleLambda+ -- * List of prime numbers+ , primes+ , primesSimple+ , primesTMWE+ -- * Prime factorization+ , factorize, factorizeNaive+ , productOfFactors+ , integerFactorsTrialDivision+ , groupIntegerFactors+ -- * Modulo @m@ arithmetic+ , powerMod+ -- * Prime testing+ , millerRabinPrimalityTest+ , isProbablyPrime+ , isVeryProbablyPrime+ )+ where++--------------------------------------------------------------------------------++import Data.List ( group , sort , foldl' )++import Math.Combinat.Sign+import Math.Combinat.Helper+import Math.Combinat.Numbers.Integers++-- import Math.Combinat.Sets ( sublists ) -- cyclic dependency...+import Math.Combinat.Tuples ( tuples' )++import Data.Bits++import System.Random++--------------------------------------------------------------------------------++-- | @d `divides` n@+divides :: Integer -> Integer -> Bool+divides d n = (mod n d == 0)++{-# SPECIALIZE moebiusMu :: Int -> Int #-}+{-# SPECIALIZE moebiusMu :: Integer -> Integer #-}+-- | The Moebius mu function+moebiusMu :: (Integral a, Num b) => a -> b+moebiusMu n + | any (>1) expos = 0+ | even (length primes) = 1+ | otherwise = -1+ where+ factors = groupIntegerFactors $ integerFactorsTrialDivision $ fromIntegral n+ (primes,expos) = unzip factors++{-# SPECIALIZE liouvilleLambda :: Int -> Int #-}+{-# SPECIALIZE liouvilleLambda :: Integer -> Integer #-}+-- | The Liouville lambda function+liouvilleLambda :: (Integral a, Num b) => a -> b+liouvilleLambda n = + if odd (foldl' (+) 0 $ map snd grps)+ then -1+ else 1+ where+ grps = groupIntegerFactors $ integerFactorsTrialDivision $ fromIntegral n++-- | Sum ofthe of the divisors+divisorSum :: Integer -> Integer+divisorSum n = foldl' (+) 0 [ d | d <- divisors n]++-- | Sum of @k@-th powers of the divisors+divisorSum' :: Int -> Integer -> Integer+divisorSum' k n = foldl' (+) 0 [ d^k | d <- divisors n]++-- | Euler's totient function+eulerTotient :: Integer -> Integer+eulerTotient n = div n prodp * prodp1 where+ grps = groupIntegerFactors $ integerFactorsTrialDivision n+ ps = map fst grps+ prodp = foldl' (*) 1 [ p | p <- ps ] + prodp1 = foldl' (*) 1 [ p-1 | p <- ps ] ++-- | Divisors of @n@ (note: the result is /not/ ordered!)+divisors :: Integer -> [Integer]+divisors n = [ f tup | tup <- tuples' expos ] where+ grps = groupIntegerFactors $ integerFactorsTrialDivision n+ (ps,expos) = unzip grps+ f es = foldl' (*) 1 $ zipWith (^) ps es++-- | List of square-free divisors together with their Mobius mu value+-- (note: the result is /not/ ordered!)+squareFreeDivisors :: Integer -> [(Integer,Sign)]+squareFreeDivisors n = map f (sublists primes) where+ grps = groupIntegerFactors $ integerFactorsTrialDivision n+ primes = map fst grps+ f ps = ( foldl' (*) 1 ps , if even (length ps) then Plus else Minus)++-- | List of square-free divisors +-- (note: the result is /not/ ordered!)+squareFreeDivisors_ :: Integer -> [Integer]+squareFreeDivisors_ n = map f (sublists primes) where+ grps = groupIntegerFactors $ integerFactorsTrialDivision n+ primes = map fst grps+ f ps = foldl' (*) 1 ps++-- | To avoid cyclic dependencies, I made a local copy of this...+sublists :: [a] -> [[a]]+sublists [] = [[]]+sublists (x:xs) = sublists xs ++ map (x:) (sublists xs) ++--------------------------------------------------------------------------------+-- 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++factorize :: Integer -> [(Integer,Int)]+factorize = factorizeNaive++factorizeNaive :: Integer -> [(Integer,Int)]+factorizeNaive = groupIntegerFactors . integerFactorsTrialDivision++productOfFactors :: [(Integer,Int)] -> Integer+productOfFactors = productInterleaved . map (uncurry pow) where+ pow _ 0 = 1+ pow p 1 = p+ pow 2 n = shiftL 1 n+ pow p 2 = p*p+ pow p n = if even n+ then (pow p (shiftR n 1))^2+ else p * (pow p (shiftR n 1))^2 ++-- | 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] ] +-}++--------------------------------------------------------------------------------+-- 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)++--------------------------------------------------------------------------------
+ src/Math/Combinat/Numbers/Sequences.hs view
@@ -0,0 +1,307 @@++-- | Some important number sequences. +-- +-- See the \"On-Line Encyclopedia of Integer Sequences\",+-- <https://oeis.org> .++{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}+module Math.Combinat.Numbers.Sequences where++--------------------------------------------------------------------------------++import Data.Array+import Data.Bits ( shiftL , shiftR , (.&.) )++import Math.Combinat.Helper +import Math.Combinat.Sign++import Math.Combinat.Numbers.Primes ( primes , factorize , productOfFactors )++import qualified Data.Map.Strict as Map -- used for factorialPrimeExponentsNaive++--------------------------------------------------------------------------------+-- * Factorial++-- | The factorial function (A000142).+factorial :: Integral a => a -> Integer+factorial = factorialSplit++-- | Faster implementation of the factorial function+factorialSplit :: Integral a => a -> Integer+factorialSplit n = productFromTo 1 n++-- | Naive implementation of factorial+factorialNaive :: Integral a => a -> Integer+factorialNaive n+ | n < 0 = error "factorialNaive: input should be nonnegative"+ | n == 0 = 1+ | otherwise = product [1..fromIntegral n]++-- | \"Swing factorial\" algorithm+factorialSwing :: Integral a => a -> Integer+factorialSwing n = productOfFactors (factorialPrimeExponents $ fromIntegral n) where++--------------------------------------------------------------------------------++-- | Prime factorization of the factorial (using the \"swing factorial\" algorithm)+factorialPrimeExponents :: Int -> [(Integer,Int)]+factorialPrimeExponents n = filter cond $ zip primes (factorialPrimeExponents_ n) where+ cond (_,!e) = e > 0++factorialPrimeExponentsNaive :: forall a. Integral a => a -> [(Integer,Int)]+factorialPrimeExponentsNaive n = result where+ fi = fromIntegral :: a -> Integer+ result = Map.toList + $ Map.unionsWith (+) + $ map Map.fromList + $ map factorize + $ map fi [1..n] ++factorialPrimeExponents_ :: Int -> [Int]+factorialPrimeExponents_ = go where+ go 0 = []+ go 1 = []+ go 2 = [1]+ go !n = longAdd half swing where+ half = map (flip shiftL 1) $ go (shiftR n 1)+ swing = swingFactorialExponents_ n++ longAdd :: [Int] -> [Int] -> [Int]+ longAdd xs [] = xs+ longAdd [] ys = ys+ longAdd (!x:xs) (!y:ys) = (x+y) : longAdd xs ys++-- | Prime factorizaiton of the \"swing factorial\" function)+swingFactorialExponents_ :: Int -> [Int]+swingFactorialExponents_ = go where+ go 0 = []+ go 1 = []+ go 2 = [1]+ go n = expo2 : map expo (tail ps) where++ nn = fromIntegral n :: Integer++ ps :: [Integer]+ ps = takeWhile (<=nn) primes ++ expo2 :: Int+ expo2 = go 0 (shiftR n 1) where+ go :: Int -> Int -> Int+ go !acc !r + | r < 1 = acc+ | otherwise = go acc' r' + where+ acc' = acc + (r .&. 1)+ r' = shiftR r 1++ expo :: Integer -> Int+ expo pp = go 0 (div n p) where+ p = fromInteger pp :: Int+ go :: Int -> Int -> Int+ go !acc !r + | r < 1 = acc+ | otherwise = go acc' r' + where+ acc' = acc + (r .&. 1)+ r' = div r p++--------------------------------------------------------------------------------++-- | The double factorial+doubleFactorial :: Integral a => a -> Integer+doubleFactorial = doubleFactorialSplit++-- | Faster implementation of the double factorial function+doubleFactorialSplit :: Integral a => a -> Integer+doubleFactorialSplit n+ | n < 0 = error "doubleFactorialSplit: input should be nonnegative"+ | n == 0 = 1+ | odd n = productFromToStride2 2 n+ | otherwise = let halfn = div n 2 + in shiftL (factorialSplit halfn) (fromIntegral halfn)++-- | Naive implementation of the double factorial (A006882).+doubleFactorialNaive :: Integral a => a -> Integer+doubleFactorialNaive n+ | n < 0 = error "doubleFactorialNaive: input should be nonnegative"+ | n == 0 = 1+ | odd n = product [1,3..fromIntegral n]+ | otherwise = product [2,4..fromIntegral n]++--------------------------------------------------------------------------------+-- * Binomial and multinomial++-- | Binomial numbers (A007318). Note: This is zero for @n<0@ or @k<0@; see also 'signedBinomial' below.+binomial :: Integral a => a -> a -> Integer+binomial = binomialSplit++-- | Faster implementation of binomial+binomialSplit :: Integral a => a -> a -> Integer+binomialSplit n k + | k > n = 0+ | k < 0 = 0+ | k > (n `div` 2) = binomialSplit n (n-k)+ | otherwise = (productFromTo (n-k) n) `div` (productFromTo 1 k)++-- | A007318. Note: This is zero for @n<0@ or @k<0@; see also 'signedBinomial' below.+binomialNaive :: Integral a => a -> a -> Integer+binomialNaive 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++--------------------------------------------------------------------------------+++
+ src/Math/Combinat/Numbers/Series.hs view
@@ -0,0 +1,434 @@++-- | 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, BangPatterns, 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)++-- | A different implementation, taken from:+--+-- M. Douglas McIlroy: Power Series, Power Serious +mulSeries :: Num a => [a] -> [a] -> [a]+mulSeries xs ys = go (xs ++ repeat 0) (ys ++ repeat 0) where+ go (f:fs) ggs@(g:gs) = f*g : (scaleSeries f gs) `addSeries` go fs ggs++-- | Multiplication of power series. This implementation is a synonym for 'convolve'+mulSeriesNaive :: Num a => [a] -> [a] -> [a]+mulSeriesNaive = 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++-- | Division of series.+--+-- Taken from: M. Douglas McIlroy: Power Series, Power Serious +divSeries :: (Eq a, Fractional a) => [a] -> [a] -> [a]+divSeries xs ys = go (xs ++ repeat 0) (ys ++ repeat 0) where+ go (0:fs) (0:gs) = go fs gs+ go (f:fs) ggs@(g:gs) = let q = f/g in q : go (fs `subSeries` scaleSeries q gs) ggs++-- | 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@.+--+-- This implementation is taken from+--+-- M. Douglas McIlroy: Power Series, Power Serious +composeSeries :: (Eq a, Num a) => [a] -> [a] -> [a]+composeSeries xs ys = go (xs ++ repeat 0) (ys ++ repeat 0) where+ go (f:fs) (0:gs) = f : mulSeries gs (go fs (0:gs))+ go (f:fs) (_:gs) = error "PowerSeries/composeSeries: we expect the the constant term of the inner series to be zero"++-- | @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 f g = composeSeries g f++-- | Naive implementation of 'composeSeries' (via 'substituteNaive')+composeSeriesNaive :: (Eq a, Num a) => [a] -> [a] -> [a]+composeSeriesNaive g f = substituteNaive f g++-- | Naive implementation of 'substitute'+substituteNaive :: (Eq a, Num a) => [a] -> [a] -> [a]+substituteNaive as_ bs_ = + case head as of+ 0 -> [ f n | n<-[0..] ]+ _ -> error "PowerSeries/substituteNaive: 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++-- | 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)+--+-- This implementation is taken from:+--+-- M. Douglas McIlroy: Power Series, Power Serious +lagrangeInversion :: (Eq a, Fractional a) => [a] -> [a]+lagrangeInversion xs = go (xs ++ repeat 0) where+ go (0:fs) = rs where rs = 0 : divSeries unitSeries (composeSeries fs rs)+ go (_:fs) = error "lagrangeInversion: the series should start with (0 + a1*x + a2*x^2 + ...) where a1 is non-zero"++-- | 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)+--+integralLagrangeInversionNaive :: (Eq a, Num a) => [a] -> [a]+integralLagrangeInversionNaive series_ = + case series of+ (0:1:rest) -> 0 : 1 : [ f n | n<-[1..] ]+ _ -> error "integralLagrangeInversionNaive: 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+ ] ++-- | Naive implementation of 'lagrangeInversion'+lagrangeInversionNaive :: (Eq a, Fractional a) => [a] -> [a]+lagrangeInversionNaive 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 "lagrangeInversionNaive: 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+ ] +++--------------------------------------------------------------------------------+-- * Differentiation and integration++differentiateSeries :: Num a => [a] -> [a]+differentiateSeries (y:ys) = go (1::Int) ys where+ go !n (x:xs) = fromIntegral n * x : go (n+1) xs+ go _ [] = []++integrateSeries :: Fractional a => [a] -> [a]+integrateSeries ys = 0 : go (1::Int) ys where+ go !n (x:xs) = x / (fromIntegral n) : go (n+1) xs+ go _ [] = []++--------------------------------------------------------------------------------+-- * 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)))++-- | Alternative implementation using differential equations.+--+-- Taken from: M. Douglas McIlroy: Power Series, Power Serious+cosSeries2, sinSeries2 :: Fractional a => [a]+cosSeries2 = unitSeries `subSeries` integrateSeries sinSeries2+sinSeries2 = integrateSeries cosSeries2++-- | 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+ +--------------------------------------------------------------------------------++
+ src/Math/Combinat/Partitions.hs view
@@ -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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Partitions/Integer.hs view
@@ -0,0 +1,459 @@++-- | 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 + ( -- module Math.Combinat.Partitions.Integer.Count+ module Math.Combinat.Partitions.Integer.Naive+ -- * Types and basic stuff+ , Partition+ -- * Conversion to\/from lists+ , fromPartition + , mkPartition + , toPartition + , toPartitionUnsafe + , isPartition + -- * Conversion to\/from exponent vectors+ , toExponentVector+ , fromExponentVector+ , dropTailingZeros+ -- * Union and sum+ , unionOfPartitions+ , sumOfPartitions+ -- * Generating partitions+ , partitions + , partitions'+ , allPartitions + , allPartitionsGrouped + , allPartitions' + , allPartitionsGrouped' + -- * Counting partitions+ , countPartitions+ , countPartitions'+ , countAllPartitions+ , countAllPartitions'+ , countPartitionsWithKParts + -- * Random partitions+ , randomPartition+ , randomPartitions+ -- * Dominating \/ dominated partitions+ , dominanceCompare+ , dominatedPartitions + , dominatingPartitions + -- * Conjugate lexicographic ordering+ , conjugateLexicographicCompare + , ConjLex (..) , fromConjLex + -- * Partitions with given number of parts+ , partitionsWithKParts+ -- * Partitions with only odd\/distinct parts+ , partitionsWithOddParts + , partitionsWithDistinctParts+ -- * Sub- and super-partitions of a given partition+ , subPartitions + , allSubPartitions + , superPartitions + -- * ASCII Ferrers diagrams+ , PartitionConvention(..)+ , asciiFerrersDiagram + , asciiFerrersDiagram'+ )+ 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++import Math.Combinat.Partitions.Integer.Naive hiding () -- this is for haddock!+import Math.Combinat.Partitions.Integer.IntList+import Math.Combinat.Partitions.Integer.Count++---------------------------------------------------------------------------------+-- * Conversion to\/from lists++fromPartition :: Partition -> [Int]+fromPartition (Partition_ part) = part+ +-- | Sorts the input, and cuts the nonpositive elements.+mkPartition :: [Int] -> Partition+mkPartition xs = toPartitionUnsafe $ sortBy (reverseCompare) $ filter (>0) xs++-- | 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"++-- | Assumes that the input is decreasing.+toPartitionUnsafe :: [Int] -> Partition+toPartitionUnsafe = 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++--------------------------------------------------------------------------------+-- * Conversion to\/from exponent vectors+ +-- | Converts a partition to an exponent vector.+--+-- For example, +--+-- > toExponentVector (Partition [4,4,2,2,2,1]) == [1,3,0,2]+--+-- meaning @(1^1,2^3,3^0,4^2)@.+--+toExponentVector :: Partition -> [Int]+toExponentVector part = fun 1 $ reverse $ group (fromPartition part) where+ fun _ [] = []+ fun !k gs@(this@(i:_):rest) + | k < i = replicate (i-k) 0 ++ fun i gs+ | otherwise = length this : fun (k+1) rest++fromExponentVector :: [Int] -> Partition+fromExponentVector expos = Partition $ concat $ reverse $ zipWith f [1..] expos where+ f !i !e = replicate e i++dropTailingZeros :: [Int] -> [Int]+dropTailingZeros = reverse . dropWhile (==0) . reverse++{-+-- alternative implementation+toExponentialVector2 :: Partition -> [Int]+toExponentialVector2 p = go 1 (toExponentialForm p) where+ go _ [] = []+ go !i ef@((j,e):rest) = if i<j + then 0 : go (i+1) ef+ else e : go (i+1) rest+-}++--------------------------------------------------------------------------------+-- * Union and sum++-- | This is simply the union of parts. For example +--+-- > Partition [4,2,1] `unionOfPartitions` Partition [4,3,1] == Partition [4,4,3,2,1,1]+--+-- Note: This is the dual of pointwise sum, 'sumOfPartitions'+--+unionOfPartitions :: Partition -> Partition -> Partition +unionOfPartitions (Partition_ xs) (Partition_ ys) = mkPartition (xs ++ ys)++-- | Pointwise sum of the parts. For example:+--+-- > Partition [3,2,1,1] `sumOfPartitions` Partition [4,3,1] == Partition [7,5,2,1]+--+-- Note: This is the dual of 'unionOfPartitions'+--+sumOfPartitions :: Partition -> Partition -> Partition +sumOfPartitions (Partition_ xs) (Partition_ ys) = Partition_ (longZipWith 0 0 (+) xs ys)++--------------------------------------------------------------------------------+-- * Generating partitions++-- | Partitions of @d@.+partitions :: Int -> [Partition]+partitions = map toPartitionUnsafe . _partitions++-- | 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 ++--------------------------------------------------------------------------------++-- | 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+++---------------------------------------------------------------------------------+-- * 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 of partitions counts 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++ cnt = countPartitions+ + 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)++--------------------------------------------------------------------------------+-- * Dominating \/ dominated partitions++-- | Dominance partial ordering as a partial ordering.+dominanceCompare :: Partition -> Partition -> Maybe Ordering+dominanceCompare p q + | p==q = Just EQ+ | p `dominates` q = Just GT+ | q `dominates` p = Just LT+ | otherwise = Nothing++-- | 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)++-- | 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)++--------------------------------------------------------------------------------+-- * Conjugate lexicographic ordering++conjugateLexicographicCompare :: Partition -> Partition -> Ordering+conjugateLexicographicCompare p q = compare (dualPartition q) (dualPartition p) ++newtype ConjLex = ConjLex Partition deriving (Eq,Show)++fromConjLex :: ConjLex -> Partition+fromConjLex (ConjLex p) = p++instance Ord ConjLex where+ compare (ConjLex p) (ConjLex q) = conjugateLexicographicCompare p q++-- {- CONJUGATE LEXICOGRAPHIC ordering is a refinement of dominance partial ordering -}+-- let test n = [ ConjLex p >= ConjLex q | p <- partitions n , q <-partitions n , p `dominates` q ]+-- and (test 20)++-- {- LEXICOGRAPHIC ordering is a refinement of dominance partial ordering -}+-- let test n = [ p >= q | p <- partitions n , q <-partitions n , p `dominates` q ]+-- and (test 20)++--------------------------------------------------------------------------------+-- * 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) ]++--------------------------------------------------------------------------------+-- * 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++-- | 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)++-- | All sub-partitions of a given partition+allSubPartitions :: Partition -> [Partition]+allSubPartitions (Partition_ ps) = map Partition_ (_allSubPartitions ps)++-- | 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 toPartitionUnsafe (_superPartitions d 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++--------------------------------------------------------------------------------+
+ src/Math/Combinat/Partitions/Integer/Compact.hs view
@@ -0,0 +1,355 @@++{- | Compact representation of integer partitions.++Partitions are conceptually nonincreasing sequences of /positive/ integers.++This implementation uses the @compact-word-vectors@ library internally to provide+a much more memory-efficient Partition type that the naive lists of integer.+This is very helpful when building large tables indexed by partitions, for example; +and hopefully quite a bit faster, too.++Note: This is an internal module, you are not supposed to import it directly.+It is also not fully ready to be used yet...++-}++{-# LANGUAGE BangPatterns, PatternSynonyms, ViewPatterns #-}+module Math.Combinat.Partitions.Integer.Compact where++--------------------------------------------------------------------------------++import Data.Bits+import Data.Word+import Data.Ord+import Data.List ( intercalate , group , sort , sortBy , foldl' , scanl' ) ++import Data.Vector.Compact.WordVec ( WordVec , Shape(..) )+import qualified Data.Vector.Compact.WordVec as V++import Math.Combinat.Compositions ( compositions' )++--------------------------------------------------------------------------------+-- * The compact partition data type++newtype Partition + = Partition WordVec + deriving Eq++instance Show Partition where+ showsPrec = showsPrecPartition++showsPrecPartition :: Int -> Partition -> ShowS+showsPrecPartition prec (Partition vec)+ = showParen (prec > 10) + $ showString "Partition"+ . showChar ' ' + . shows (V.toList vec)++instance Ord Partition where+ compare = cmpLexico+ +--------------------------------------------------------------------------------+-- * Pattern synonyms ++-- | Pattern sysnonyms allows us to use existing code with minimal modifications+pattern Nil :: Partition+pattern Nil <- (isEmpty -> True) where+ Nil = empty++pattern Cons :: Int -> Partition -> Partition+pattern Cons x xs <- (uncons -> Just (x,xs)) where+ Cons x xs = cons x xs++-- | Simulated newtype constructor +pattern Partition_ :: [Int] -> Partition+pattern Partition_ xs <- (toList -> xs) where+ Partition_ xs = fromDescList xs++pattern Head :: Int -> Partition +pattern Head h <- (height -> h)++pattern Tail :: Partition -> Partition+pattern Tail xs <- (partitionTail -> xs)++pattern Length :: Int -> Partition +pattern Length n <- (width -> n) ++--------------------------------------------------------------------------------+-- * Lexicographic comparison++-- | The lexicographic ordering+cmpLexico :: Partition -> Partition -> Ordering+cmpLexico (Partition vec1) (Partition vec2) = compare (V.toList vec1) (V.toList vec2)++--------------------------------------------------------------------------------+-- * Basic (de)constructrion++empty :: Partition+empty = Partition (V.empty)++isEmpty :: Partition -> Bool+isEmpty (Partition vec) = V.null vec++--------------------------------------------------------------------------------++singleton :: Int -> Partition+singleton x + | x > 0 = Partition (V.singleton $ i2w x)+ | x == 0 = empty+ | otherwise = error "Parittion/singleton: negative input"++--------------------------------------------------------------------------------++uncons :: Partition -> Maybe (Int,Partition)+uncons (Partition vec) = case V.uncons vec of+ Nothing -> Nothing+ Just (h,tl) -> Just (w2i h, Partition tl)++-- | @partitionTail p == snd (uncons p)@+partitionTail :: Partition -> Partition+partitionTail (Partition vec) = Partition (V.tail vec)++-------------------------------------------------------------------------------++-- | We assume that @x >= partitionHeight p@!+cons :: Int -> Partition -> Partition+cons !x (Partition !vec) + | V.null vec = Partition (if x > 0 then V.singleton y else V.empty) + | y >= h = Partition (V.cons y vec)+ | otherwise = error "Partition/cons: invalid element to cons"+ where + y = i2w x+ h = V.head vec++--------------------------------------------------------------------------------++-- | We assume that the element is not bigger than the last element!+snoc :: Partition -> Int -> Partition+snoc (Partition !vec) !x+ | x == 0 = Partition vec+ | V.null vec = Partition (V.singleton y)+ | y <= V.last vec = Partition (V.snoc vec y)+ | otherwise = error "Partition/snoc: invalid element to snoc"+ where+ y = i2w x++--------------------------------------------------------------------------------+-- * exponential form++toExponentialForm :: Partition -> [(Int,Int)]+toExponentialForm = map (\xs -> (head xs,length xs)) . group . toAscList++fromExponentialForm :: [(Int,Int)] -> Partition+fromExponentialForm = fromDescList . concatMap f . sortBy g where+ f (!i,!e) = replicate e i+ g (!i, _) (!j,_) = compare j i++--------------------------------------------------------------------------------+-- * Width and height of the bounding rectangle++-- | Width, or the number of parts+width :: Partition -> Int+width (Partition vec) = V.vecLen vec++-- | Height, or the first (that is, the largest) element+height :: Partition -> Int+height (Partition vec) = w2i (V.head vec)++-- | Width and height +widthHeight :: Partition -> (Int,Int)+widthHeight (Partition vec) = (V.vecLen vec , w2i (V.head vec))++--------------------------------------------------------------------------------+-- * Differential sequence++-- | From a non-increasing sequence @[a1,a2,..,an]@ this computes the sequence of differences+-- @[a1-a2,a2-a3,...,an-0]@+diffSequence :: Partition -> [Int]+diffSequence = go . toDescList where+ go (x:ys@(y:_)) = (x-y) : go ys + go [x] = [x]+ go [] = []++----------------------------------------++-- | From a non-increasing sequence @[a1,a2,..,an]@ this computes the reversed sequence of differences+-- @[ a[n]-0 , a[n-1]-a[n] , ... , a[2]-a[3] , a[1]-a[2] ] @+reverseDiffSequence :: Partition -> [Int]+reverseDiffSequence p = go (0 : toAscList p) where+ go (x:ys@(y:_)) = (y-x) : go ys + go [x] = []+ go [] = []++--------------------------------------------------------------------------------+-- * Dual partition++dualPartition :: Partition -> Partition+dualPartition compact@(Partition vec) + | V.null vec = Partition V.empty+ | otherwise = Partition (V.fromList' shape $ map i2w dual)+ where+ height = V.head vec+ len = V.vecLen vec+ shape = Shape (w2i height) (V.bitsNeededFor $ i2w len)+ dual = concat+ [ replicate d j+ | (j,d) <- zip (descendToOne len) (reverseDiffSequence compact)+ ]++--------------------------------------------------------------------------------+-- * Conversion to list++toList :: Partition -> [Int]+toList = toDescList++-- | returns a descending (non-increasing) list+toDescList :: Partition -> [Int]+toDescList (Partition vec) = map w2i (V.toList vec)++-- | Returns a reversed (ascending; non-decreasing) list+toAscList :: Partition -> [Int]+toAscList (Partition vec) = map w2i (V.toRevList vec)++--------------------------------------------------------------------------------+-- * Conversion from list++fromDescList :: [Int] -> Partition+fromDescList list = fromDescList' (length list) list++-- | We assume that the input is a non-increasing list of /positive/ integers!+fromDescList' + :: Int -- ^ length+ -> [Int] -- ^ the list+ -> Partition+fromDescList' !len !list = Partition (V.fromList' (Shape len bits) $ map i2w list) where+ bits = case list of+ [] -> 4+ (x:xs) -> V.bitsNeededFor (i2w x)++--------------------------------------------------------------------------------+-- * Partial orderings++-- @ |p `isSubPartitionOf` q@+isSubPartitionOf :: Partition -> Partition -> Bool+isSubPartitionOf p q = and $ zipWith (<=) (toList p) (toList q ++ repeat 0)++-- | @q `dominates` p@+dominates :: Partition -> Partition -> Bool+dominates (Partition vec_q) (Partition vec_p) = and $ zipWith (>=) (sums (qs ++ repeat 0)) (sums ps) where + sums = tail . scanl' (+) 0+ ps = V.toList vec_p+ qs = V.toList vec_q++--------------------------------------------------------------------------------+-- * Pieri rule++-- | Expands to product @s[lambda]*h[k]@ as a sum of @s[mu]@-s. See <https://en.wikipedia.org/wiki/Pieri's_formula>+pieriRule :: Partition -> Int -> [Partition]+pieriRule = error "Partitions/Integer/Compact: pieriRule not implemented yet"++{-+-- | Expands to product @s[lambda]*h[1] = s[lambda]*e[1]@ as a sum of @s[mu]@-s. See <https://en.wikipedia.org/wiki/Pieri's_formula>+pieriRuleSingleBox :: Partition -> [Partition]+pieriRuleSingleBox !compact = case compact of++ Nibble 0 -> [ singleton 1 ]++ Nibble w | h < 15 -> + [ Nibble (w + shiftL 1 (60-4*i)) | (i,d)<-zip [0..n-1] diffs1 , d>0 ] ++ [ snoc compact 1 ]++ Medium1 w | h < 255 -> + [ Medium1 (w + shiftL 1 (56-8*i)) | (i,d)<-zip [0..n-1] diffs1 , d>0 ] ++ [ snoc compact 1 ]++ Medium2 w1 w2 | h < 255 -> + let (diffs1a,diffs1b) = splitAt 8 diffs1 + in [ Medium2 (w1 + shiftL 1 (56-8*i)) w2 | (i,d)<-zip [0..7 ] diffs1a , d>0 ] +++ [ Medium2 w1 (w2 + shiftL 1 (56-8*i)) | (i,d)<-zip [0..n-9] diffs1b , d>0 ] +++ [ snoc compact 1 ]++ Medium3 w1 w2 w3 | h < 255 -> + let (diffs1a,tmp ) = splitAt 8 diffs1 + (diffs1b,diffs1c) = splitAt 8 tmp+ in [ Medium3 (w1 + shiftL 1 (56-8*i)) w2 w3 | (i,d)<-zip [0..7 ] diffs1a , d>0 ] +++ [ Medium3 w1 (w2 + shiftL 1 (56-8*i)) w3 | (i,d)<-zip [0..7 ] diffs1b , d>0 ] +++ [ Medium3 w1 w2 (w3 + shiftL 1 (56-8*i)) | (i,d)<-zip [0..n-17] diffs1c , d>0 ] +++ [ snoc compact 1 ]+ + _ -> genericSingleBox++ where+ (n,h) = widthHeight compact+ list = toDescList compact+ diffs1 = 1 : diffSequence compact++ genericSingleBox :: [Partition]+ genericSingleBox = map (fromDescList' n) (go list diffs1) ++ [ fromDescList' (n+1) (list ++ [1]) ] where+ go :: [Int] -> [Int] -> [[Int]]+ go (a:as) (d:ds) = if d > 0 then ((a+1):as) : map (a:) (go as ds) + else map (a:) (go as ds)+ go [] _ = []++-- | Expands to product @s[lambda]*h[k]@ as a sum of @s[mu]@-s. See <https://en.wikipedia.org/wiki/Pieri's_formula>+pieriRule :: Partition -> Int -> [Partition]+pieriRule !compact !k + | k < 0 = []+ | k == 0 = [ compact ]+ | k == 1 = pieriRuleSingleBox compact+ | h == 0 = [ singleton k ]+ | h + k <= 15 && n < 15 = case compact of { Nibble w -> + [ Nibble (w + encode c) | c <- comps ] }+ | otherwise = [ fromDescList' (n+b) xs | c <- comps , let (b,xs) = add c ] ++ where+ (n,h) = widthHeight compact+ list = toDescList compact+ bounds = k : {- map (min k) -} (diffSequence compact) + comps = compositions' bounds k++ add clist = go list clist where+ go (!p:ps) (!c:cs) = let (b,rest) = go ps cs in (b, (p+c):rest)+ go [] [c] = if c>0 then (1,[c]) else (0,[])+ go _ _ = error "Compact/pieriRule/add: shouldn't happen"++ encode :: [Int] -> Word64+ encode = go 60 where+ go !k [c] = if c==0 then 0 else shiftL (i2w c) k + 1+ go !k (c:cs) = shiftL (i2w c) k + go (k-4) cs+ go !k [] = error "Compact/pieriRule/encode: shouldn't happen"+-}++--------------------------------------------------------------------------------+-- * local (internally used) utility functions++{-# INLINE i2w #-}+i2w :: Int -> Word+i2w = fromIntegral++{-# INLINE w2i #-}+w2i :: Word -> Int+w2i = fromIntegral++{-# INLINE sum' #-}+sum' :: [Word] -> Word+sum' = foldl' (+) 0++{-# INLINE safeTail #-}+safeTail :: [Int] -> [Int]+safeTail xs = case xs of { [] -> [] ; _ -> tail xs }++{-# INLINE descendToZero #-}+descendToZero :: Int -> [Int]+descendToZero !n+ | n > 0 = n : descendToZero (n-1) + | n == 0 = [0]+ | n < 0 = []++{-# INLINE descendToOne #-}+descendToOne :: Int -> [Int]+descendToOne !n+ | n > 1 = n : descendToOne (n-1) + | n == 1 = [1]+ | n < 1 = []++--------------------------------------------------------------------------------++
+ src/Math/Combinat/Partitions/Integer/Count.hs view
@@ -0,0 +1,215 @@++-- | Counting partitions of integers.++{-# LANGUAGE CPP, BangPatterns, ScopedTypeVariables #-}+module Math.Combinat.Partitions.Integer.Count where++--------------------------------------------------------------------------------++import Data.List+import Control.Monad ( liftM , replicateM )++-- import Data.Map (Map)+-- import qualified Data.Map as Map++import Math.Combinat.Numbers ( factorial , binomial , multinomial )+import Math.Combinat.Numbers.Integers -- Primes+import Math.Combinat.Helper++import Data.Array+import System.Random++--------------------------------------------------------------------------------+-- * Infinite tables of integers++-- | A data structure which is essentially an infinite list of @Integer@-s,+-- but fast lookup (for reasonable small inputs)+newtype TableOfIntegers = TableOfIntegers [Array Int Integer]++lookupInteger :: TableOfIntegers -> Int -> Integer+lookupInteger (TableOfIntegers table) !n + | n >= 0 = (table !! k) ! r+ | n < 0 = 0+ where+ (k,r) = divMod n 1024++makeTableOfIntegers+ :: ((Int -> Integer) -> (Int -> Integer))+ -> TableOfIntegers+makeTableOfIntegers user = table where+ calc = user lkp+ lkp = lookupInteger table+ table = TableOfIntegers+ [ listArray (0,1023) (map calc [a..b]) + | k<-[0..] + , let a = 1024*k + , let b = 1024*(k+1) - 1 + ]++--------------------------------------------------------------------------------+-- * Counting partitions++-- | Number of partitions of @n@ (looking up a table built using Euler's algorithm)+countPartitions :: Int -> Integer+countPartitions = lookupInteger partitionCountTable ++-- | This uses the power series expansion of the infinite product. It is slower than the above.+countPartitionsInfiniteProduct :: Int -> Integer+countPartitionsInfiniteProduct k = partitionCountListInfiniteProduct !! k++-- | This uses 'countPartitions'', and is (very) slow+countPartitionsNaive :: Int -> Integer+countPartitionsNaive d = countPartitions' (d,d) d++--------------------------------------------------------------------------------++-- | This uses Euler's algorithm to compute p(n)+--+-- See eg.:+-- NEIL CALKIN, JIMENA DAVIS, KEVIN JAMES, ELIZABETH PEREZ, AND CHARLES SWANNACK+-- COMPUTING THE INTEGER PARTITION FUNCTION+-- <http://www.math.clemson.edu/~kevja/PAPERS/ComputingPartitions-MathComp.pdf>+--+partitionCountTable :: TableOfIntegers+partitionCountTable = table where++ table = makeTableOfIntegers fun++ fun lkp !n + | n > 1 = foldl' (+) 0 + [ (if even k then negate else id) + ( lkp (n - div (k*(3*k+1)) 2)+ + lkp (n - div (k*(3*k-1)) 2)+ )+ | k <- [1..limit n]+ ]+ | n < 0 = 0+ | n == 0 = 1+ | n == 1 = 1++ limit :: Int -> Int+ limit !n = fromInteger $ ceilingSquareRoot (1 + div (nn+nn+1) 3) where+ nn = fromIntegral n :: Integer++-- | An infinite list containing all @p(n)@, starting from @p(0)@.+partitionCountList :: [Integer]+partitionCountList = map countPartitions [0..]++--------------------------------------------------------------------------------++-- | 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 reasonably fast for small numbers.+--+-- > partitionCountListInfiniteProduct == map countPartitions [0..]+--+partitionCountListInfiniteProduct :: [Integer]+partitionCountListInfiniteProduct = 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 very slow.+--+partitionCountListNaive :: [Integer]+partitionCountListNaive = map countPartitionsNaive [0..]++--------------------------------------------------------------------------------+-- * Counting all partitions++countAllPartitions :: Int -> Integer+countAllPartitions d = sum' [ countPartitions i | i <- [0..d] ]++-- | Count all partitions fitting into a rectangle.+-- # = \\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++--------------------------------------------------------------------------------+-- * Counting fitting into a rectangle++-- | Number of of d, fitting into a given rectangle. Naive recursive algorithm.+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] ] ++--------------------------------------------------------------------------------+-- * Partitions with given number of parts++-- | Count partitions of @n@ into @k@ parts.+--+-- Naive recursive algorithm.+--+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) ]+-}++{-+-- > 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) ]+-}++--------------------------------------------------------------------------------
+ src/Math/Combinat/Partitions/Integer/IntList.hs view
@@ -0,0 +1,398 @@++-- | Partition functions working on lists of integers.+-- +-- It's not recommended to use this module directly.++{-# LANGUAGE CPP, BangPatterns, ScopedTypeVariables #-}+module Math.Combinat.Partitions.Integer.IntList where++--------------------------------------------------------------------------------++import Data.List+import Control.Monad ( liftM , replicateM )++import Math.Combinat.Numbers ( factorial , binomial , multinomial )+import Math.Combinat.Helper++import Data.Array+import System.Random++import Math.Combinat.Partitions.Integer.Count ( countPartitions )++--------------------------------------------------------------------------------+-- * Type and basic stuff++-- | Sorts the input, and cuts the nonpositive elements.+_mkPartition :: [Int] -> [Int]+_mkPartition xs = sortBy (reverseCompare) $ filter (>0) xs+ +-- | 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+++_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 [5,4,1] ==+-- > [ (1,1), (1,2), (1,3), (1,4), (1,5)+-- > , (2,1), (2,2), (2,3), (2,4)+-- > , (3,1)+-- > ]+--++_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 :: [Int] -> [(Int,Int)]+_toExponentialForm = reverse . map (\xs -> (head xs,length xs)) . group++_fromExponentialForm :: [(Int,Int)] -> [Int]+_fromExponentialForm = sortBy reverseCompare . go where+ go ((j,e):rest) = replicate e j ++ go rest+ go [] = [] ++---------------------------------------------------------------------------------+-- * Generating 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) ]++-- | All integer partitions up to a given degree (that is, all integer partitions whose sum is less or equal to @d@)+_allPartitions :: Int -> [[Int]]+_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 -> [[[Int]]]+_allPartitionsGrouped d = [ _partitions 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) ]++---------------------------------------------------------------------------------+-- * 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 -> ([Int], 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 -> ([[Int]], g)+_randomPartitions howmany n = runRand $ replicateM howmany (worker n []) where++ cnt = countPartitions+ + finish :: [(Int,Int)] -> [Int]+ 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 [Int]+ 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 :: [Int] -> [Int] -> Bool+_dominates qs 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 :: [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 :: [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+ -> [[Int]]+_partitionsWithKParts k n = 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) ]++--------------------------------------------------------------------------------+-- * Partitions with only odd\/distinct parts++-- | Partitions of @n@ with only odd parts+_partitionsWithOddParts :: Int -> [[Int]]+_partitionsWithOddParts d = (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 -> [[Int]]+_partitionsWithEvenParts d = (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 -> [[Int]]+_partitionsWithDistinctParts d = (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 :: [Int] -> [Int] -> Bool+_isSubPartitionOf ps qs = and $ zipWith (<=) ps (qs ++ repeat 0)++-- | This is provided for convenience\/completeness only, as:+--+-- > isSuperPartitionOf q p == isSubPartitionOf p q+--+_isSuperPartitionOf :: [Int] -> [Int] -> Bool+_isSuperPartitionOf qs 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 -> [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 :: [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 -> [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>+--+-- | 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 :: [Int] -> Int -> [[Int]] +_dualPieriRule lam n = map _dualPartition $ _pieriRule (_dualPartition lam) n++--------------------------------------------------------------------------------
+ src/Math/Combinat/Partitions/Integer/Naive.hs view
@@ -0,0 +1,214 @@++-- | Naive implementation of partitions of integers, encoded as list of @Int@-s.+--+-- Integer partitions are nonincreasing sequences of positive integers.+--+-- This is an internal module, you are not supposed to import it directly.+--+ ++{-# LANGUAGE CPP, BangPatterns, ScopedTypeVariables, PatternSynonyms, ViewPatterns #-}+module Math.Combinat.Partitions.Integer.Naive 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++import Math.Combinat.Partitions.Integer.IntList+import Math.Combinat.Partitions.Integer.Count ( countPartitions )++--------------------------------------------------------------------------------+-- * 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++---------------------------------------------------------------------------------++toList :: Partition -> [Int]+toList (Partition xs) = xs++fromList :: [Int] -> Partition +fromList = mkPartition where+ mkPartition xs = Partition $ sortBy (reverseCompare) $ filter (>0) xs++fromListUnsafe :: [Int] -> Partition+fromListUnsafe = Partition++---------------------------------------------------------------------------------++isEmptyPartition :: Partition -> Bool+isEmptyPartition (Partition p) = null p++emptyPartition :: Partition+emptyPartition = Partition []++instance CanBeEmpty Partition where+ empty = emptyPartition+ isEmpty = isEmptyPartition++-- | 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++-- | 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++--------------------------------------------------------------------------------+-- * Pattern synonyms ++-- | Pattern sysnonyms allows us to use existing code with minimal modifications+pattern Nil :: Partition+pattern Nil <- (isEmpty -> True) where+ Nil = empty++pattern Cons :: Int -> Partition -> Partition+pattern Cons x xs <- (unconsPartition -> Just (x,xs)) where+ Cons x (Partition xs) = Partition (x:xs)++-- | Simulated newtype constructor +pattern Partition_ :: [Int] -> Partition+pattern Partition_ xs = Partition xs++pattern Head :: Int -> Partition +pattern Head h <- (head . toDescList -> h)++pattern Tail :: Partition -> Partition+pattern Tail xs <- (Partition . tail . toDescList -> xs)++pattern Length :: Int -> Partition +pattern Length n <- (partitionWidth -> n) + +---------------------------------------------------------------------------------+-- * 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 . toDescList++fromExponentialForm :: [(Int,Int)] -> Partition+fromExponentialForm = Partition . _fromExponentialForm where++--------------------------------------------------------------------------------+-- * List-like operations++-- | From a sequence @[a1,a2,..,an]@ computes the sequence of differences+-- @[a1-a2,a2-a3,...,an-0]@+diffSequence :: Partition -> [Int]+diffSequence = go . toDescList where+ go (x:ys@(y:_)) = (x-y) : go ys + go [x] = [x]+ go [] = []++unconsPartition :: Partition -> Maybe (Int,Partition)+unconsPartition (Partition xs) = case xs of+ (y:ys) -> Just (y, Partition ys)+ [] -> Nothing++toDescList :: Partition -> [Int]+toDescList (Partition xs) = xs++---------------------------------------------------------------------------------+-- * 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++--------------------------------------------------------------------------------+-- * Containment partial ordering++-- | 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)+ +--------------------------------------------------------------------------------+-- * 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++-- | 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++--------------------------------------------------------------------------------++
+ src/Math/Combinat/Partitions/Multiset.hs view
@@ -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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Partitions/NonCrossing.hs view
@@ -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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Partitions/Plane.hs view
@@ -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 ]++--------------------------------------------------------------------------------
+ src/Math/Combinat/Partitions/Set.hs view
@@ -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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Partitions/Skew.hs view
@@ -0,0 +1,153 @@++-- | 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++-- | See "partitionElements"+skewPartitionElements :: SkewPartition -> [(Int, Int)]+skewPartitionElements (SkewPartition abs) = concat+ [ [ (i,j) | j <- [a+1 .. a+b] ]+ | (i,(a,b)) <- zip [1..] abs+ ]++--------------------------------------------------------------------------------+-- * 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 ++--------------------------------------------------------------------------------+-- connected components++{-+connectedComponents :: SkewPartition -> [((Int,Int),SkewPartition)]+connectedComponents = error "connectedComponents: not implemented yet"++isConnectedSkewPartition :: SkewPartition -> Bool+isConnectedSkewPartition skewp = length (connectedComponents skewp) == 1+-}++--------------------------------------------------------------------------------+-- * 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 ++--------------------------------------------------------------------------------+
+ src/Math/Combinat/Partitions/Skew/Ribbon.hs view
@@ -0,0 +1,364 @@++-- | Ribbons (also called border strips, skew hooks, skew rim hooks, etc...).+--+-- Ribbons are skew partitions that are 1) connected, 2) do not contain+-- 2x2 blocks. Intuitively, they are 1-box wide continuous strips on+-- the boundary.+--+-- An alternative definition that they are skew partitions whose projection+-- to the diagonal line is a continuous segment of width 1.++{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}+module Math.Combinat.Partitions.Skew.Ribbon where++--------------------------------------------------------------------------------++import Data.Array+import Data.List+import Data.Maybe++import qualified Data.Map as Map++import Math.Combinat.Sets+import Math.Combinat.Partitions.Integer+import Math.Combinat.Partitions.Integer.IntList ( _diffSequence )+import Math.Combinat.Partitions.Skew+import Math.Combinat.Tableaux+import Math.Combinat.Tableaux.LittlewoodRichardson+import Math.Combinat.Tableaux.GelfandTsetlin+import Math.Combinat.Helper++--------------------------------------------------------------------------------+-- * Corners (TODO: move to Partitions - but we also want to refactor that)++-- | The coordinates of the outer corners +outerCorners :: Partition -> [(Int,Int)]+outerCorners = outerCornerBoxes++-- | The coordinates of the inner corners, including the two on the two coordinate+-- axes. For the partition @[5,4,1]@ the result should be @[(0,5),(1,4),(2,1),(3,0)]@+extendedInnerCorners:: Partition -> [(Int,Int)]+extendedInnerCorners (Partition_ ps) = (0, head ps') : catMaybes mbCorners where+ ps' = ps ++ [0]+ mbCorners = zipWith3 f [1..] (tail ps') (_diffSequence ps') + f !y !x !k = if k > 0 then Just (y,x) else Nothing++-- | Sequence of all the (extended) corners+extendedCornerSequence :: Partition -> [(Int,Int)]+extendedCornerSequence (Partition_ ps) = {- if null ps then [(0,0)] else -} interleave inner outer where+ inner = (0, head ps') : [ (y,x) | (y,x,k) <- zip3 [1..] (tail ps') diff , k>0 ]+ outer = [ (y,x) | (y,x,k) <- zip3 [1..] ps' diff , k>0 ]+ diff = _diffSequence ps'+ ps' = ps ++ [0]++-- | The inner corner /boxes/ of the partition. Coordinates are counted from 1+-- (cf.the 'elements' function), and the first coordinate is the row, the second+-- the column (in English notation).+--+-- For the partition @[5,4,1]@ the result should be @[(1,4),(2,1)]@+--+-- > innerCornerBoxes lambda == (tail $ init $ extendedInnerCorners lambda)+--+innerCornerBoxes :: Partition -> [(Int,Int)]+innerCornerBoxes (Partition_ ps) = + case ps of+ [] -> []+ _ -> catMaybes mbCorners + where+ mbCorners = zipWith3 f [1..] (tail ps) (_diffSequence ps) + f !y !x !k = if k > 0 then Just (y,x) else Nothing++-- | The outer corner /boxes/ of the partition. Coordinates are counted from 1+-- (cf.the 'elements' function), and the first coordinate is the row, the second+-- the column (in English notation).+--+-- For the partition @[5,4,1]@ the result should be @[(1,5),(2,4),(3,1)]@+outerCornerBoxes :: Partition -> [(Int,Int)]+outerCornerBoxes (Partition_ ps) = catMaybes mbCorners where+ mbCorners = zipWith3 f [1..] ps (_diffSequence ps) + f !y !x !k = if k > 0 then Just (y,x) else Nothing++-- | The outer and inner corner boxes interleaved, so together they form +-- the turning points of the full border strip+cornerBoxSequence :: Partition -> [(Int,Int)]+cornerBoxSequence (Partition_ ps) = if null ps then [] else interleave outer inner where+ inner = [ (y,x) | (y,x,k) <- zip3 [1..] tailps diff , k>0 ]+ outer = [ (y,x) | (y,x,k) <- zip3 [1..] ps diff , k>0 ]+ diff = _diffSequence ps+ tailps = case ps of { [] -> [] ; _-> tail ps }++--------------------------------------------------------------------------------++-- | Naive (and very slow) implementation of @innerCornerBoxes@, for testing purposes+innerCornerBoxesNaive :: Partition -> [(Int,Int)]+innerCornerBoxesNaive part = filter f boxes where+ boxes = elements part+ f (y,x) = elem (y+1,x ) boxes+ && elem (y ,x+1) boxes+ && not (elem (y+1,x+1) boxes)++-- | Naive (and very slow) implementation of @outerCornerBoxes@, for testing purposes+outerCornerBoxesNaive :: Partition -> [(Int,Int)]+outerCornerBoxesNaive part = filter f boxes where+ boxes = elements part+ f (y,x) = not (elem (y+1,x ) boxes)+ && not (elem (y ,x+1) boxes)+ && not (elem (y+1,x+1) boxes)++--------------------------------------------------------------------------------+-- * Ribbon++-- | A skew partition is a a ribbon (or border strip) if and only if projected+-- to the diagonals the result is an interval.+isRibbon :: SkewPartition -> Bool+isRibbon skewp = go Nothing proj where+ proj = Map.toList + $ Map.fromListWith (+) [ (x-y , 1) | (y,x) <- skewPartitionElements skewp ]+ go Nothing [] = False+ go (Just _) [] = True+ go Nothing ((a,h):rest) = (h == 1) && go (Just a) rest + go (Just b) ((a,h):rest) = (h == 1) && (a == b+1) && go (Just a) rest++{-+-- | Naive (and slow) reference implementation of "isRibbon"+isRibbonNaive :: SkewPartition -> Bool+isRibbonNaive skewp = isConnectedSkewPartition skewp && no2x2 where+ boxes = skewPartitionElements skewp+ no2x2 = and + [ not ( elem (y+1,x ) boxes && + elem (y ,x+1) boxes && + elem (y+1,x+1) boxes ) -- no 2x2 blocks + | (y,x) <- boxes + ]+-}++toRibbon :: SkewPartition -> Maybe Ribbon+toRibbon skew = + if not (isRibbon skew)+ then Nothing+ else Just ribbon + where+ ribbon = Ribbon+ { rbShape = skew+ , rbLength = skewPartitionWeight skew+ , rbHeight = height+ , rbWidth = width+ }+ elems = skewPartitionElements skew+ height = (length $ group $ sort $ map fst elems) - 1 -- TODO: optimize these+ width = (length $ group $ sort $ map snd elems) - 1++-- | Border strips (or ribbons) are defined to be skew partitions which are +-- connected and do not contain 2x2 blocks.+-- +-- The /length/ of a border strip is the number of boxes it contains,+-- and its /height/ is defined to be one less than the number of rows+-- (in English notation) it occupies. The /width/ is defined symmetrically to +-- be one less than the number of columns it occupies.+--+data Ribbon = Ribbon+ { rbShape :: SkewPartition+ , rbLength :: Int+ , rbHeight :: Int+ , rbWidth :: Int+ }+ deriving (Eq,Ord,Show)++--------------------------------------------------------------------------------+-- * Inner border strips++-- | Ribbons (or border strips) are defined to be skew partitions which are +-- connected and do not contain 2x2 blocks. This function returns the+-- border strips whose outer partition is the given one.+innerRibbons :: Partition -> [Ribbon]+innerRibbons part@(Partition ps) = if null ps then [] else strips where++ strips = [ mkStrip i j + | i<-[1..n] , _canStartStrip (annArr!i)+ , j<-[i..n] , _canEndStrip (annArr!j)+ ]++ n = length annList+ annList = annotatedInnerBorderStrip part+ annArr = listArray (1,n) annList++ mkStrip !i1 !i2 = Ribbon shape len height width where+ ps' = ps ++ [0]+ shape = SkewPartition [ (p-k,k) | (i,p,q) <- zip3 [1..] ps (tail ps') , let k = indent i p q ] + indent !i !p !q + | i < y1 = 0+ | i > y2 = 0+ | i == y2 = p - x2 + 1 -- the order is important here !!!+ | otherwise = p - q + 1 -- because of the case y1 == y2 == i++ len = i2 - i1 + 1+ height = y2 - y1+ width = x1 - x2+ BorderBox _ _ y1 x1 = annArr ! i1+ BorderBox _ _ y2 x2 = annArr ! i2++-- | Inner border strips (or ribbons) of the given length+innerRibbonsOfLength :: Partition -> Int -> [Ribbon]+innerRibbonsOfLength part@(Partition ps) givenLength = if null ps then [] else strips where++ strips = [ mkStrip i j + | i<-[1..n] , _canStartStrip (annArr!i)+ , j<-[i..n] , _canEndStrip (annArr!j)+ , j-i+1 == givenLength+ ]++ n = length annList+ annList = annotatedInnerBorderStrip part+ annArr = listArray (1,n) annList++ mkStrip !i1 !i2 = Ribbon shape givenLength height width where+ ps' = ps ++ [0]+ shape = SkewPartition [ (p-k,k) | (i,p,q) <- zip3 [1..] ps (tail ps') , let k = indent i p q ] + indent !i !p !q + | i < y1 = 0+ | i > y2 = 0+ | i == y2 = p - x2 + 1 -- the order is important here !!!+ | otherwise = p - q + 1 -- because of the case y1 == y2 == i++ height = y2 - y1+ width = x1 - x2+ BorderBox _ _ y1 x1 = annArr ! i1+ BorderBox _ _ y2 x2 = annArr ! i2+++--------------------------------------------------------------------------------+-- * Outer border strips++-- | Hooks of length @n@ (TODO: move to the partition module)+listHooks :: Int -> [Partition]+listHooks 0 = []+listHooks 1 = [ Partition [1] ]+listHooks n = [ Partition (k : replicate (n-k) 1) | k<-[1..n] ]++-- | Outer border strips (or ribbons) of the given length+outerRibbonsOfLength :: Partition -> Int -> [Ribbon]+outerRibbonsOfLength part@(Partition ps) givenLength = result where++ result = if null ps + then [ Ribbon shape givenLength ht wd+ | p <- listHooks givenLength+ , let shape = mkSkewPartition (p,part)+ , let ht = partitionWidth p - 1 -- pretty inconsistent names here :(((+ , let wd = partitionHeight p - 1+ ]+ else strips ++ strips = [ mkStrip i j + | i<-[1..n] , _canStartStrip (annArr!i)+ , j<-[i..n] , _canEndStrip (annArr!j)+ , j-i+1 == givenLength+ ]+ + ysize = partitionWidth part+ xsize = partitionHeight part+ + annList = [ BorderBox True False 1 x | x <- reverse [xsize+2 .. xsize+givenLength ] ]+ ++ annList0 + ++ [ BorderBox False True y 1 | y <- [ysize+2 .. ysize+givenLength ] ]+ + n = length annList+ annList0 = annotatedOuterBorderStrip part+ annArr = listArray (1,n) annList++ mkStrip !i1 !i2 = Ribbon shape len height width where+ ps' = (-666) : ps ++ replicate (givenLength) 0+ shape = SkewPartition [ (p,k) | (i,p,q) <- zip3 [1..max ysize y2] (tail ps') ps' , let k = indent i p q ] + indent !i !p !q + | i < y1 = 0+ | i > y2 = 0+ | i == y1 = x1 - p -- the order is important here !!!+-- | i == y2 = x2 - p + | otherwise = q - p + 1 ++ len = i2 - i1 + 1+ height = y2 - y1+ width = x1 - x2+ BorderBox _ _ y1 x1 = annArr ! i1+ BorderBox _ _ y2 x2 = annArr ! i2++--------------------------------------------------------------------------------+-- * Naive implementations (for testing)++-- | Naive (and slow) implementation listing all inner border strips+innerRibbonsNaive :: Partition -> [Ribbon]+innerRibbonsNaive outer = list where+ list = [ Ribbon skew (len skew) (ht skew) (wt skew)+ | skew <- allSkewPartitionsWithOuterShape outer+ , isRibbon skew+ ]+ len skew = length (skewPartitionElements skew)+ ht skew = (length $ group $ sort $ map fst $ skewPartitionElements skew) - 1+ wt skew = (length $ group $ sort $ map snd $ skewPartitionElements skew) - 1+++-- | Naive (and slow) implementation listing all inner border strips of the given length+innerRibbonsOfLengthNaive :: Partition -> Int -> [Ribbon]+innerRibbonsOfLengthNaive outer givenLength = list where+ pweight = partitionWeight outer+ list = [ Ribbon skew (len skew) (ht skew) (wt skew)+ | skew <- skewPartitionsWithOuterShape outer givenLength+ , isRibbon skew+ ]+ len skew = length (skewPartitionElements skew)+ ht skew = (length $ group $ sort $ map fst $ skewPartitionElements skew) - 1+ wt skew = (length $ group $ sort $ map snd $ skewPartitionElements skew) - 1++-- | Naive (and slow) implementation listing all outer border strips of the given length+outerRibbonsOfLengthNaive :: Partition -> Int -> [Ribbon]+outerRibbonsOfLengthNaive inner givenLength = list where+ pweight = partitionWeight inner+ list = [ Ribbon skew (len skew) (ht skew) (wt skew)+ | skew <- skewPartitionsWithInnerShape inner givenLength+ , isRibbon skew+ ]+ len skew = length (skewPartitionElements skew)+ ht skew = (length $ group $ sort $ map fst $ skewPartitionElements skew) - 1+ wt skew = (length $ group $ sort $ map snd $ skewPartitionElements skew) - 1++--------------------------------------------------------------------------------+-- * Annotated borders++-- | A box on the border of a partition+data BorderBox = BorderBox+ { _canStartStrip :: !Bool+ , _canEndStrip :: !Bool+ , _yCoord :: !Int+ , _xCoord :: !Int+ }+ deriving Show+ +-- | The boxes of the full inner border strip, annotated with whether a border strip +-- can start or end there.+annotatedInnerBorderStrip :: Partition -> [BorderBox]+annotatedInnerBorderStrip partition = if isEmptyPartition partition then [] else list where+ list = goVert (head corners) (tail corners) + corners = extendedCornerSequence partition ++ goVert (y1,x ) ((y2,_ ):rest) = [ BorderBox True (y==y2) y x | y<-[y1+1..y2] ] ++ goHoriz (y2,x) rest+ goVert _ [] = [] ++ goHoriz (y ,x1) ((_, x2):rest) = case rest of+ [] -> [ BorderBox False True y x | x<-[x1-1,x1-2..x2+1] ]+ _ -> [ BorderBox False (x/=x2) y x | x<-[x1-1,x1-2..x2 ] ] ++ goVert (y,x2) rest++-- | The boxes of the full outer border strip, annotated with whether a border strip +-- can start or end there.+annotatedOuterBorderStrip :: Partition -> [BorderBox]+annotatedOuterBorderStrip partition = if isEmptyPartition partition then [] else list where+ list = goVert (head corners) (tail corners) + corners = extendedCornerSequence partition ++ goVert (y1,x ) ((y2,_ ):rest) = [ BorderBox (y==y1) (y/=y2) (y+1) (x+1) | y<-[y1..y2] ] ++ goHoriz (y2,x) rest+ goVert _ [] = [] ++ goHoriz (y ,x1) ((_, x2):rest) = case rest of+ [] -> [ BorderBox True (x==0) (y+1) (x+1) | x<-[x1-1,x1-2..x2 ] ]+ _ -> [ BorderBox True False (y+1) (x+1) | x<-[x1-1,x1-2..x2+1] ] ++ goVert (y,x2) rest+++--------------------------------------------------------------------------------
+ src/Math/Combinat/Partitions/Vector.hs view
@@ -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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Permutations.hs view
@@ -0,0 +1,969 @@++-- | 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+ , lookupPermutation , (!!!)+ , permutationArray+ , permutationUArray+ , uarrayToPermutationUnsafe+ , isPermutation+ , maybePermutation+ , toPermutation+ , toPermutationUnsafe+ , toPermutationUnsafeN+ , permutationSize+ -- * Disjoint cycles+ , DisjointCycles (..)+ , fromDisjointCycles+ , disjointCyclesUnsafe+ , permutationToDisjointCycles+ , disjointCyclesToPermutation+ , numberOfCycles+ , concatPermutations+ -- * 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+ , identityPermutation+ , inversePermutation+ , multiplyPermutation+ , productOfPermutations+ , productOfPermutations'+ -- * Action of the permutation group+ , permuteArray + , permuteList+ , permuteArrayLeft , permuteArrayRight+ , permuteListLeft , permuteListRight+ -- * Sorting+ , sortingPermutationAsc + , sortingPermutationDesc+ -- * 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 Data.Vector.Compact.WordVec ( WordVec )+import qualified Data.Vector.Compact.WordVec as V++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++--------------------------------------------------------------------------------+-- WordVec helpers++toUArray :: WordVec -> UArray Int Int+toUArray vec = listArray (1,n) (map fromIntegral $ V.toList vec) where n = V.vecLen vec++fromUArray :: UArray Int Int -> WordVec+fromUArray arr = fromPermListN n (map fromIntegral $ elems arr) where+ (1,n) = bounds arr++-- | maximum = length+fromPermListN :: Int -> [Int] -> WordVec+fromPermListN n perm = V.fromList' shape (map fromIntegral perm) where+ shape = V.Shape n bits+ bits = V.bitsNeededFor (fromIntegral n :: Word)++fromPermList :: [Int] -> WordVec+fromPermList perm = V.fromList (map fromIntegral perm)++(.!) :: WordVec -> Int -> Int+(.!) vec idx = fromIntegral (V.unsafeIndex (idx-1) vec)++_elems :: WordVec -> [Int]+_elems = map fromIntegral . V.toList++_assocs :: WordVec -> [(Int,Int)]+_assocs vec = zip [1..] (_elems vec)++_bound :: WordVec -> Int+_bound = V.vecLen++{- +-- the old internal representation (UArray Int Int)++_elems :: UArray Int Int -> [Int]+_elems = elems++_assocs :: UArray Int Int -> [(Int,Int)]+_assocs = elems++_bound :: UArray Int Int -> Int+_bound = snd . bounds+-}+++toPermN :: Int -> [Int] -> Permutation+toPermN n xs = Permutation (fromPermListN n xs)++--------------------------------------------------------------------------------+-- * Types++-- | A permutation. Internally it is an (compact) vector +-- of the integers @[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 WordVec 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) = toUArray ar++permutationArray :: Permutation -> Array Int Int+permutationArray (Permutation ar) = listArray (1,n) (_elems ar) where+ n = _bound ar++-- | Assumes that the input is a permutation of the numbers @[1..n]@.+toPermutationUnsafe :: [Int] -> Permutation+toPermutationUnsafe xs = Permutation (fromPermList xs) ++-- | This is faster than 'toPermutationUnsafe', but you need to supply @n@.+toPermutationUnsafeN :: Int -> [Int] -> Permutation+toPermutationUnsafeN n xs = Permutation (fromPermListN n xs) ++-- | Note: Indexing starts from 1.+uarrayToPermutationUnsafe :: UArray Int Int -> Permutation+uarrayToPermutationUnsafe = Permutation . fromUArray++-- | 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 (toPermutationUnsafe 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) = _bound ar++-- | Returns the image @sigma(k)@ of @k@ under the permutation @sigma@.+-- +-- Note: we don't check the bounds! It may even crash if you index out of bounds!+lookupPermutation :: Permutation -> Int -> Int+lookupPermutation (Permutation ar) idx = ar .! idx++-- infix 8 !!!++-- | Infix version of 'lookupPermutation'+(!!!) :: Permutation -> Int -> Int+(!!!) (Permutation ar) idx = ar .! idx++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+ n = _bound ar++-- | Given a permutation of @n@ and a permutation of @m@, we return+-- a permutation of @n+m@ resulting by putting them next to each other.+-- This should satisfy+--+-- > permuteList p1 xs ++ permuteList p2 ys == permuteList (concatPermutations p1 p2) (xs++ys)+--+concatPermutations :: Permutation -> Permutation -> Permutation +concatPermutations perm1 perm2 = toPermutationUnsafe list where+ n = permutationSize perm1+ list = fromPermutation perm1 ++ map (+n) (fromPermutation perm2)++--------------------------------------------------------------------------------+-- * 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 (inversePermutation 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 $ fromUArray 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++ n = _bound 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++ n = _bound 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 = _bound 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 = _bound 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 = _bound 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 = _bound 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 $ fromPermListN 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 n = _bound 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 $ fromPermListN 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 (fromUArray $ 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 (fromUArray $ 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 $ fromPermListN 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 $ fromPermListN n (n : [1..n-1])+ +--------------------------------------------------------------------------------+-- * Permutation groups++-- | Multiplies two permutations together: @p `multiplyPermutation` q@+-- means the permutation when we first apply @p@, and then @q@+-- (that is, the natural action is the /right/ action)+--+-- See also 'permuteArray' for our conventions. +--+multiplyPermutation :: Permutation -> Permutation -> Permutation+multiplyPermutation pi1@(Permutation perm1) pi2@(Permutation perm2) = + if (n==m) + then Permutation $ fromUArray result+ else error "multiplyPermutation: permutations of different sets" + where+ n = _bound perm1+ m = _bound perm2 + result = permuteArray pi2 (toUArray perm1)+ +infixr 7 `multiplyPermutation` ++-- | The inverse permutation.+inversePermutation :: Permutation -> Permutation +inversePermutation (Permutation perm1) = Permutation $ fromUArray result+ where+ result = array (1,n) $ map swap $ _assocs perm1+ n = _bound perm1+ +-- | The identity (or trivial) permutation.+identityPermutation :: Int -> Permutation +identityPermutation n = Permutation $ fromPermListN 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''.+productOfPermutations :: [Permutation] -> Permutation +productOfPermutations [] = error "productOfPermutations: empty list, we don't know size of the result"+productOfPermutations ps = foldl1' multiplyPermutation ps ++-- | Multiply together a (possibly empty) list of permutations, all of which has size @n@+productOfPermutations' :: Int -> [Permutation] -> Permutation +productOfPermutations' n [] = identityPermutation n+productOfPermutations' n ps@(p:_) = if n == permutationSize p + then foldl1' multiplyPermutation ps + else error "productOfPermutations': 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):+--+-- > permuteArray pi2 (permuteArray pi1 set) == permuteArray (pi1 `multiplyPermutation` pi2) set+--+-- Synonym to 'permuteArrayRight'+--+{-# SPECIALIZE permuteArray :: Permutation -> Array Int b -> Array Int b #-}+{-# SPECIALIZE permuteArray :: Permutation -> UArray Int Int -> UArray Int Int #-}+permuteArray :: IArray arr b => Permutation -> arr Int b -> arr Int b +permuteArray = permuteArrayRight++-- | Right action on lists. Synonym to 'permuteListRight'+--+permuteList :: Permutation -> [a] -> [a]+permuteList = permuteListRight+ +-- | The right (standard) action of permutations on sets. +-- +-- > permuteArrayRight pi2 (permuteArrayRight pi1 set) == permuteArrayRight (pi1 `multiplyPermutation` pi2) set+-- +-- The second argument should be an array with bounds @(1,n)@.+-- The function checks the array bounds.+--+{-# SPECIALIZE permuteArrayRight :: Permutation -> Array Int b -> Array Int b #-}+{-# SPECIALIZE permuteArrayRight :: Permutation -> UArray Int Int -> UArray Int Int #-}+permuteArrayRight :: IArray arr b => Permutation -> arr Int b -> arr Int b +permuteArrayRight pi@(Permutation perm) ar = + if (a==1) && (b==n) + then listArray (1,n) [ ar!(perm.!i) | i <- [1..n] ] + else error "permuteArrayRight: array bounds do not match"+ where+ n = _bound perm+ (a,b) = bounds ar ++-- | The right (standard) action on a list. The list should be of length @n@.+--+-- > fromPermutation perm == permuteListRight perm [1..n]+-- +permuteListRight :: forall a . Permutation -> [a] -> [a] +permuteListRight perm xs = elems $ permuteArrayRight perm $ arr where+ arr = listArray (1,n) xs :: Array Int a+ n = permutationSize perm++-- | The left (opposite) action of the permutation group.+--+-- > permuteArrayLeft pi2 (permuteArrayLeft pi1 set) == permuteArrayLeft (pi2 `multiplyPermutation` pi1) set+--+-- It is related to 'permuteLeftArray' via:+--+-- > permuteArrayLeft pi arr == permuteArrayRight (inversePermutation pi) arr+-- > permuteArrayRight pi arr == permuteArrayLeft (inversePermutation pi) arr+--+{-# SPECIALIZE permuteArrayLeft :: Permutation -> Array Int b -> Array Int b #-}+{-# SPECIALIZE permuteArrayLeft :: Permutation -> UArray Int Int -> UArray Int Int #-}+permuteArrayLeft :: IArray arr b => Permutation -> arr Int b -> arr Int b +permuteArrayLeft 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 "permuteArrayLeft: array bounds do not match"+ where+ n = _bound perm+ (a,b) = bounds ar ++-- | The left (opposite) action on a list. The list should be of length @n@.+--+-- > permuteListLeft perm set == permuteList (inversePermutation perm) set+-- > fromPermutation (inversePermutation perm) == permuteListLeft perm [1..n]+--+permuteListLeft :: forall a. Permutation -> [a] -> [a] +permuteListLeft perm xs = elems $ permuteArrayLeft perm $ arr where+ arr = listArray (1,n) xs :: Array Int a+ n = permutationSize perm++--------------------------------------------------------------------------------++-- | Given a list of things, we return a permutation which sorts them into+-- ascending order, that is:+--+-- > permuteList (sortingPermutationAsc xs) xs == sort xs+--+-- Note: if the things are not unique, then the sorting permutations is not+-- unique either; we just return one of them.+--+sortingPermutationAsc :: Ord a => [a] -> Permutation+sortingPermutationAsc xs = toPermutation (map fst sorted) where+ sorted = sortBy (comparing snd) $ zip [1..] xs++-- | Given a list of things, we return a permutation which sorts them into+-- descending order, that is:+--+-- > permuteList (sortingPermutationDesc xs) xs == reverse (sort xs)+--+-- Note: if the things are not unique, then the sorting permutations is not+-- unique either; we just return one of them.+--+sortingPermutationDesc :: Ord a => [a] -> Permutation+sortingPermutationDesc xs = toPermutation (map fst sorted) where+ sorted = sortBy (reverseComparing snd) $ zip [1..] xs++--------------------------------------------------------------------------------+-- * 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 (fromUArray 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"+ +--------------------------------------------------------------------------------++
+ src/Math/Combinat/RootSystems.hs view
@@ -0,0 +1,319 @@+ +-- | Naive (very inefficient) algorithm to generate the irreducible (Dynkin) root systems +-- +-- Based on <https://en.wikipedia.org/wiki/Root_system> + +{-# LANGUAGE BangPatterns, FlexibleInstances, TypeSynonymInstances, FlexibleContexts #-} +module Math.Combinat.RootSystems where + +-------------------------------------------------------------------------------- + +import Control.Monad + +import Data.Array + +import Data.Set (Set) +import qualified Data.Set as Set + +import Data.List +import Data.Ord + +import Math.Combinat.Numbers.Primes +import Math.Combinat.Sets + +-------------------------------------------------------------------------------- +-- * Half-integers + +-- | The type of half-integers (internally represented by their double) +-- +-- TODO: refactor this into its own module +newtype HalfInt + = HalfInt Int + deriving (Eq,Ord) + +half :: HalfInt +half = HalfInt 1 + +divByTwo :: Int -> HalfInt +divByTwo n = HalfInt n + +mulByTwo :: HalfInt -> Int +mulByTwo (HalfInt n) = n + +scaleBy :: Int -> HalfInt -> HalfInt +scaleBy k (HalfInt n) = HalfInt (k*n) + +instance Show HalfInt where + show (HalfInt n) = case divMod n 2 of + (k,0) -> show k + (_,1) -> show n ++ "/2" + +instance Num HalfInt where + fromInteger = HalfInt . (*2) . fromInteger + a + b = divByTwo $ mulByTwo a + mulByTwo b + a - b = divByTwo $ mulByTwo a - mulByTwo b + a * b = case divMod (mulByTwo a * mulByTwo b) 4 of + (k,0) -> HalfInt (2*k) + (k,2) -> HalfInt (2*k+1) + _ -> error "the result of multiplication is not a half-integer" + negate = divByTwo . negate . mulByTwo + signum = divByTwo . signum . mulByTwo + abs = divByTwo . abs . mulByTwo + +-------------------------------------------------------------------------------- +-- * Vectors of half-integers + +type HalfVec = [HalfInt] + +instance Num HalfVec where + fromInteger = error "HalfVec/fromInteger" + (+) = safeZip (+) + (-) = safeZip (-) + (*) = safeZip (*) + negate = map negate + abs = map abs + signum = map signum + +scaleVec :: Int -> HalfVec -> HalfVec +scaleVec k = map (scaleBy k) + +negateVec :: HalfVec -> HalfVec +negateVec = map negate + +-- dotProd :: HalfVec -> HalfVec +-- dotProd xs ys = foldl' (+) 0 $ safeZip (*) xs ys + +safeZip :: (a -> b -> c) -> [a] -> [b] -> [c] +safeZip f = go where + go (x:xs) (y:ys) = f x y : go xs ys + go [] [] = [] + go _ _ = error "safeZip: the lists do not have equal length" + +-------------------------------------------------------------------------------- +-- * Dynkin diagrams + +data Dynkin + = A !Int + | B !Int + | C !Int + | D !Int + | E6 | E7 | E8 + | F4 + | G2 + deriving (Eq,Show) + +-------------------------------------------------------------------------------- +-- * The roots of root systems + +-- | The ambient dimension of (our representation of the) system (length of the vector) +ambientDim :: Dynkin -> Int +ambientDim d = case d of + A n -> n+1 -- it's an n dimensional subspace of (n+1) dimensions + B n -> n + C n -> n + D n -> n + E6 -> 6 + E7 -> 8 -- sublattice of E8 ? + E8 -> 8 + F4 -> 4 + G2 -> 3 -- it's a 2 dimensional subspace of 3 dimensions + +simpleRootsOf :: Dynkin -> [HalfVec] +simpleRootsOf d = + + case d of + + A n -> [ e i - e (i+1) | i <- [1..n] ] + + B n -> [ e i - e (i+1) | i <- [1..n-1] ] ++ [e n] + + C n -> [ e i - e (i+1) | i <- [1..n-1] ] ++ [scaleVec 2 (e n)] + + D n -> [ e i - e (i+1) | i <- [1..n-1] ] ++ [e (n-1) + e n] + + E6 -> simpleRootsE6_123 + E7 -> simpleRootsE7_12 + E8 -> simpleRootsE8_even + + F4 -> [ [ 1,-1, 0, 0] + , [ 0, 1,-1, 0] + , [ 0, 0, 1, 0] + , [-h,-h,-h,-h] + ] + + G2 -> [ [ 1,-1, 0] + , [-1, 2,-1] + ] + + where + h = half + n = ambientDim d + + e :: Int -> HalfVec + e i = replicate (i-1) 0 ++ [1] ++ replicate (n-i) 0 + +positiveRootsOf :: Dynkin -> Set HalfVec +positiveRootsOf = positiveRoots . simpleRootsOf + +negativeRootsOf :: Dynkin -> Set HalfVec +negativeRootsOf = Set.map negate . positiveRootsOf + +allRootsOf :: Dynkin -> Set HalfVec +allRootsOf dynkin = Set.unions [ pos , neg ] where + simple = simpleRootsOf dynkin + pos = positiveRoots simple + neg = Set.map negate pos + +-------------------------------------------------------------------------------- +-- * Positive roots + +-- | Finds a vector, which is hopefully not orthognal to any root +-- (generated by the given simple roots), and has positive dot product with each of them. +findPositiveHyperplane :: [HalfVec] -> [Double] +findPositiveHyperplane vs = w where + n = length (head vs) + w0 = map (fromIntegral . mulByTwo) (foldl1 (+) vs) :: [Double] + w = zipWith (+) w0 perturb + perturb = map small $ map fromIntegral $ take n primes + small :: Double -> Double + small x = x / (10**10) + +positiveRoots :: [HalfVec] -> Set HalfVec +positiveRoots simples = Set.fromList pos where + roots = mirrorClosure simples + w = findPositiveHyperplane simples + pos = [ r | r <- Set.toList roots , dot4 r > 0 ] where + + dot4 :: HalfVec -> Double + dot4 a = foldl' (+) 0 $ safeZip (*) w $ map (fromIntegral . mulByTwo) a + +basisOfPositives :: Set HalfVec -> [HalfVec] +basisOfPositives set = Set.toList (Set.difference set set2) where + set2 = Set.fromList [ a + b | [a,b] <- choose 2 (Set.toList set) ] + + +-------------------------------------------------------------------------------- +-- * Operations on half-integer vectors + +-- | bracket b a = (a,b)/(a,a) +bracket :: HalfVec -> HalfVec -> HalfInt +bracket b a = + case divMod (2*a_dot_b) (a_dot_a) of + (n,0) -> divByTwo n + _ -> error "bracket: result is not a half-integer" + where + a_dot_b = foldl' (+) 0 $ safeZip (*) (map mulByTwo a) (map mulByTwo b) + a_dot_a = foldl' (+) 0 $ safeZip (*) (map mulByTwo a) (map mulByTwo a) + +-- | mirror b a = b - 2*(a,b)/(a,a) * a +mirror :: HalfVec -> HalfVec -> HalfVec +mirror b a = b - scaleVec (mulByTwo $ bracket b a) a + +-- | Cartan matrix of a list of (simple) roots +cartanMatrix :: [HalfVec] -> Array (Int,Int) Int +cartanMatrix list = array ((1,1),(n,n)) [ ((i,j), f i j) | i<-[1..n] , j<-[1..n] ] where + n = length list + arr = listArray (1,n) list + f !i !j = mulByTwo $ bracket (arr!j) (arr!i) + +printMatrix :: Show a => Array (Int,Int) a -> IO () +printMatrix arr = do + let ((1,1),(n,m)) = bounds arr + arr' = fmap show arr + let ks = [ 1 + maximum [ length (arr'!(i,j)) | i<-[1..n] ] | j<-[1..m] ] + forM_ [1..n] $ \i -> do + putStrLn $ flip concatMap [1..m] $ \j -> extendTo (ks!!(j-1)) $ arr' ! (i,j) + where + extendTo n s = replicate (n-length s) ' ' ++ s + +-------------------------------------------------------------------------------- +-- * Mirroring + +-- | We mirror stuff until there is no more things happening +-- (very naive algorithm, but seems to work) +mirrorClosure :: [HalfVec] -> Set HalfVec +mirrorClosure = go . Set.fromList where + + go set + | n' > n = go set' + | n'' > n = go set'' + | otherwise = set + where + n = Set.size set + n' = Set.size set' + n'' = Set.size set'' + set' = mirrorStep set + set'' = Set.union set (Set.map negateVec set) + +mirrorStep :: Set HalfVec -> Set HalfVec +mirrorStep old = Set.union old new where + new = Set.fromList [ mirror b a | [a,b] <- choose 2 $ Set.toList old ] + +-------------------------------------------------------------------------------- +-- * E6, E7 and E8 + +-- | This is a basis of E6 as the subset of the even E8 root system +-- where the first three coordinates agree (they are consolidated +-- into the first coordinate here) +simpleRootsE6_123:: [HalfVec] +simpleRootsE6_123 = roots where + h = half + roots = + [ [-h,-h,-h,-h,-h,-h,-h,-h] + , [ h, h, h, h, h, h,-h,-h] + , [ 0, 0, 0, 0,-1, 0, 1, 0] + , [ 0, 0, 0, 0, 0, 0,-1, 1] + , [-h,-h,-h, h, h, h, h,-h] + , [ 0, 0, 0,-1, 1, 0, 0, 0] + ] + +-- | This is a basis of E8 as the subset of the even E8 root system +-- where the first two coordinates agree (they are consolidated +-- into the first coordinate here) +simpleRootsE7_12:: [HalfVec] +simpleRootsE7_12 = roots where + h = half + roots = + [ [-h,-h,-h,-h,-h,-h,-h,-h] + , [ h, h, h, h, h, h,-h,-h] + , [ h, h,-h,-h,-h,-h, h, h] + , [-h,-h, h, h,-h, h, h,-h] + , [ 0, 0, 0,-1, 1, 0, 0, 0] + , [ 0, 0,-1, 1, 0, 0, 0, 0] + , [ 0, 0, 0, 0, 0, 0,-1, 1] + ] + +-- | This is a basis of E7 as the subset of the even E8 root system +-- for which the sum of coordinates sum to zero +simpleRootsE7_diag :: [HalfVec] +simpleRootsE7_diag = roots where + roots = [ e i - e (i+1) | i <-[2..7] ] ++ [[h,h,h,h,-h,-h,-h,-h]] + h = half + n = 8 + + e :: Int -> HalfVec + e i = replicate (i-1) 0 ++ [1] ++ replicate (n-i) 0 + +simpleRootsE8_even :: [HalfVec] +simpleRootsE8_even = roots where + roots = [v1,v2,v3,v4,v5,v7,v8,v6] + + [v1,v2,v3,v4,v5,v6,v7,v8] = roots0 + roots0 = [ e i - e (i+1) | i <-[1..6] ] ++ [ e 6 + e 7 , replicate 8 (-h) ] + + h = half + n = 8 + + e :: Int -> HalfVec + e i = replicate (i-1) 0 ++ [1] ++ replicate (n-i) 0 + +simpleRootsE8_odd :: [HalfVec] +simpleRootsE8_odd = roots where + roots = [ e i - e (i+1) | i <-[1..7] ] ++ [[-h,-h,-h,-h,-h , h,h,h]] + h = half + n = 8 + + e :: Int -> HalfVec + e i = replicate (i-1) 0 ++ [1] ++ replicate (n-i) 0 + +--------------------------------------------------------------------------------
+ src/Math/Combinat/Sets.hs view
@@ -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]++--------------------------------------------------------------------------------+
+ src/Math/Combinat/Sets/VennDiagrams.hs view
@@ -0,0 +1,150 @@++-- | Venn diagrams. See <https://en.wikipedia.org/wiki/Venn_diagram>+--+-- TODO: write a more efficient implementation (for example an array of size @2^n@)+--++{-# LANGUAGE BangPatterns #-}+module Math.Combinat.Sets.VennDiagrams where++--------------------------------------------------------------------------------++import Data.List++import GHC.TypeLits+import Data.Proxy++import qualified Data.Map as Map+import Data.Map (Map)++import Math.Combinat.Compositions+import Math.Combinat.ASCII++--------------------------------------------------------------------------------++-- | Venn diagrams of @n@ sets. Each possible zone is annotated with a value+-- of type @a@. A typical use case is to annotate with the cardinality of the+-- given zone.+--+-- Internally this is representated by a map from @[Bool]@, where @True@ means element +-- of the set, @False@ means not.+--+-- TODO: write a more efficient implementation (for example an array of size 2^n)+newtype VennDiagram a = VennDiagram { vennTable :: Map [Bool] a } deriving (Eq,Ord,Show)++-- | How many sets are in the Venn diagram+vennDiagramNumberOfSets :: VennDiagram a -> Int+vennDiagramNumberOfSets (VennDiagram table) = length $ fst $ Map.findMin table++-- | How many zones are in the Venn diagram+--+-- > vennDiagramNumberOfZones v == 2 ^ (vennDiagramNumberOfSets v)+--+vennDiagramNumberOfZones :: VennDiagram a -> Int+vennDiagramNumberOfZones venn = 2 ^ (vennDiagramNumberOfSets venn)++-- | How many /nonempty/ zones are in the Venn diagram+vennDiagramNumberOfNonemptyZones :: VennDiagram Int -> Int+vennDiagramNumberOfNonemptyZones (VennDiagram table) = length $ filter (/=0) $ Map.elems table++unsafeMakeVennDiagram :: [([Bool],a)] -> VennDiagram a+unsafeMakeVennDiagram = VennDiagram . Map.fromList++-- | We call venn diagram trivial if all the intersection zones has zero cardinality+-- (that is, the original sets are all disjoint)+isTrivialVennDiagram :: VennDiagram Int -> Bool+isTrivialVennDiagram (VennDiagram table) = and [ c == 0 | (bs,c) <- Map.toList table , isIntersection bs ] where+ isIntersection bs = case filter id bs of+ [] -> False+ [_] -> False+ _ -> True++printVennDiagram :: Show a => VennDiagram a -> IO ()+printVennDiagram = putStrLn . prettyVennDiagram++prettyVennDiagram :: Show a => VennDiagram a -> String+prettyVennDiagram = unlines . asciiLines . asciiVennDiagram++asciiVennDiagram :: Show a => VennDiagram a -> ASCII+asciiVennDiagram (VennDiagram table) = asciiFromLines $ map f (Map.toList table) where+ f (bs,a) = "{" ++ extendTo (length bs) [ if b then z else ' ' | (b,z) <- zip bs abc ] ++ "} -> " ++ show a+ extendTo k str = str ++ replicate (k - length str) ' '+ abc = ['A'..'Z']++instance Show a => DrawASCII (VennDiagram a) where+ ascii = asciiVennDiagram++-- | Given a Venn diagram of cardinalities, we compute the cardinalities of the+-- original sets (note: this is slow!)+vennDiagramSetCardinalities :: VennDiagram Int -> [Int]+vennDiagramSetCardinalities (VennDiagram table) = go n list where+ list = Map.toList table+ n = length $ fst $ head list+ go :: Int -> [([Bool],Int)] -> [Int]+ go !0 _ = []+ go !k xs = this : go (k-1) (map xtail xs) where+ this = foldl' (+) 0 [ c | ((True:_) , c) <- xs ]+ xtail (bs,c) = (tail bs,c)++--------------------------------------------------------------------------------++-- | Given the cardinalities of some finite sets, we list all possible+-- Venn diagrams.+--+-- Note: we don't include the empty zone in the tables, because it's always empty.+--+-- Remark: if each sets is a singleton set, we get back set partitions:+--+-- > > [ length $ enumerateVennDiagrams $ replicate k 1 | k<-[1..8] ]+-- > [1,2,5,15,52,203,877,4140]+-- >+-- > > [ countSetPartitions k | k<-[1..8] ]+-- > [1,2,5,15,52,203,877,4140]+--+-- Maybe this could be called multiset-partitions?+--+-- Example:+--+-- > autoTabulate RowMajor (Right 6) $ map ascii $ enumerateVennDiagrams [2,3,3]+--+enumerateVennDiagrams :: [Int] -> [VennDiagram Int]+enumerateVennDiagrams dims = + case dims of+ [] -> []+ [d] -> venns1 d+ (d:ds) -> concatMap (worker (length ds) d) $ enumerateVennDiagrams ds+ where++ worker !n !d (VennDiagram table) = result where++ list = Map.toList table+ falses = replicate n False++ comps k = compositions' (map snd list) k+ result = + [ unsafeMakeVennDiagram $ + [ (False:tfs , m-c) | ((tfs,m),c) <- zip list comp ] +++ [ (True :tfs , c) | ((tfs,m),c) <- zip list comp ] +++ [ (True :falses , d-k) ]+ | k <- [0..d]+ , comp <- comps k+ ]++ venns1 :: Int -> [VennDiagram Int]+ venns1 p = [ theVenn ] where + theVenn = unsafeMakeVennDiagram [ ([True],p) ] ++--------------------------------------------------------------------------------++{-++-- | for testing only+venns2 :: Int -> Int -> [Venn Int]+venns2 p q = + [ mkVenn [ ([t,f],p-k) , ([f,t],q-k) , ([t,t],k) ]+ | k <- [0..min p q] + ]+ where+ t = True+ f = False+-}
+ src/Math/Combinat/Sign.hs view
@@ -0,0 +1,114 @@++-- | Signs++{-# LANGUAGE CPP, BangPatterns #-}+module Math.Combinat.Sign where++--------------------------------------------------------------------------------++import Data.Monoid++-- Semigroup became a superclass of Monoid+#if MIN_VERSION_base(4,11,0) +import Data.Foldable+import Data.Semigroup+#endif++import System.Random++--------------------------------------------------------------------------------++data Sign+ = Plus -- hmm, this way @Plus < Minus@, not sure about that+ | Minus+ deriving (Eq,Ord,Show,Read)++--------------------------------------------------------------------------------++-- Semigroup became a superclass of Monoid+#if MIN_VERSION_base(4,11,0) ++instance Semigroup Sign where+ (<>) = mulSign+ sconcat = foldl1 mulSign++instance Monoid Sign where+ mempty = Plus+ mconcat = productOfSigns++#else++instance Monoid Sign where+ mempty = Plus+ mappend = mulSign+ mconcat = productOfSigns++#endif++--------------------------------------------------------------------------------++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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Tableaux.hs view
@@ -0,0 +1,242 @@++-- | 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.Integer+import Math.Combinat.Partitions.Integer.IntList ( _dualPartition )+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++--------------------------------------------------------------------------------+ +
+ src/Math/Combinat/Tableaux/GelfandTsetlin.hs view
@@ -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 ++--------------------------------------------------------------------------------
+ src/Math/Combinat/Tableaux/GelfandTsetlin/Cone.hs view
@@ -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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Tableaux/LittlewoodRichardson.hs view
@@ -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 [] = []++--------------------------------------------------------------------------------
+ src/Math/Combinat/Tableaux/Skew.hs view
@@ -0,0 +1,224 @@++-- | 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.Integer.IntList ( _diffSequence )+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++--------------------------------------------------------------------------------+
+ src/Math/Combinat/Trees.hs view
@@ -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+
+ src/Math/Combinat/Trees/Binary.hs view
@@ -0,0 +1,492 @@++-- | 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 , [] )+ findj _ _ = error "fasc4A_algorithm_P: fatal error shouldn't happen"+ +-- | 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_ ++--------------------------------------------------------------------------------
+ src/Math/Combinat/Trees/Binary.hs-boot view
@@ -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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Trees/Graphviz.hs view
@@ -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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Trees/Nary.hs view
@@ -0,0 +1,430 @@++-- | N-ary trees.++{-# LANGUAGE FlexibleInstances, 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.Tree+import Data.List++import Control.Applicative++--import Control.Monad.State+import Control.Monad.Trans.State+import Data.Traversable (traverse)++import Math.Combinat.Sets ( listTensor )+import Math.Combinat.Partitions.Multiset ( partitionMultiset )+import Math.Combinat.Compositions ( compositions )+import Math.Combinat.Numbers ( factorial, binomial )++import Math.Combinat.Trees.Graphviz ( Dot , graphvizDotForest , graphvizDotTree )++import Math.Combinat.Classes+import Math.Combinat.ASCII as ASCII+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)++--------------------------------------------------------------------------------+
+ src/Math/Combinat/Trees/Nary.hs-boot view
@@ -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++--------------------------------------------------------------------------------
+ src/Math/Combinat/Tuples.hs view
@@ -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)++-------------------------------------------------------
+ src/Math/Combinat/TypeLevel.hs view
@@ -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)++--------------------------------------------------------------------------------