/CIS552-final-project/submission/Chrom.hs

https://bitbucket.org/patyoon/haskell-genetic-algorithm · Haskell · 312 lines · 223 code · 37 blank · 52 comment · 45 complexity · e50075395660fe04db72947265394784 MD5 · raw file

  1. -- by Partick Yoon yeyoon and Mark Smyda msmyda
  2. {-# OPTIONS -Wall -fwarn-tabs -fno-warn-type-defaults -fno-warn-orphans #-}
  3. {-# LANGUAGE FlexibleInstances, ScopedTypeVariables, NamedFieldPuns,
  4. TupleSections, FlexibleContexts #-}
  5. module Chrom (Chrom(..),
  6. target,
  7. charTarget,
  8. toBinTarget,
  9. xover,
  10. xoverGetOne,
  11. prop_xoverGetOne,
  12. prop_xoverGetTwo,
  13. prop_xoverTwoPivot,
  14. xoverRandomPick,
  15. mutate,
  16. prop_mutateRand,
  17. prop_mutateSwap,
  18. fitness,
  19. test_fitness,
  20. genRandom,
  21. prop_genRandom,
  22. ) where
  23. import Control.Applicative
  24. import Control.Monad.State
  25. import Data.List (nub, (\\), findIndex)
  26. import Test.QuickCheck
  27. import Data.Function (on)
  28. import Control.Monad.Random
  29. import Test.QuickCheck.Monadic hiding (stop)
  30. import Test.HUnit hiding (assert)
  31. import Helper
  32. -- | The type of Gene is a string of ASCII characters or
  33. -- its binary representation.
  34. data Chrom = Chrom { gene :: String,
  35. gen :: Int }
  36. deriving (Eq, Ord, Show)
  37. -- | QuickCheck Arbitrary instance for Chrom
  38. instance Arbitrary (Chrom) where
  39. arbitrary = do gene <- replicateM (length (gene target))
  40. $ (arbitrary :: Gen Char)
  41. return $ Chrom gene 0
  42. -- target :: Bool -> Chrom
  43. -- target option
  44. -- option = charTarget
  45. -- otherwise = toBinTar
  46. -- | Default target chromosome
  47. target :: Chrom
  48. target = Chrom "Haskell is fun." (-1)
  49. -- | Pre-defined target chromosome with target string.
  50. charTarget :: Chrom
  51. charTarget = Chrom "Haskell is fun." (-1)
  52. -- | Convert to binary version of the target.
  53. toBinTarget :: Chrom -> Chrom
  54. toBinTarget targ = Chrom (concat binChars) (-1)
  55. where binChars = map char2bin (gene targ)
  56. -- | Crossever function selector
  57. xover :: RandomGen g => Int -> Chrom -> Chrom -> Rand g (Chrom, Chrom)
  58. xover option
  59. | option == 1 = xoverGetTwo
  60. | option == 3 = xoverTwoPivot
  61. | option == 4 = xoverRandomPick
  62. | otherwise = error "Invalid Option for Crossover"
  63. -- | Function for doing crossever of two chromosomes
  64. -- Here we select a random pivot for the crossoever.
  65. -- For the first child, select the characters with index i < pivot from the
  66. -- first parent, then the remaining characters from the second parent with
  67. -- index i such that pivot <= i < length.
  68. -- For the second child, we select the characters that are not included in
  69. -- the first child. (Using the same pivot point)
  70. -- This is crossover function that returns only one child created.
  71. xoverGetOne :: RandomGen g => Chrom -> Chrom -> Rand g Chrom
  72. xoverGetOne ch1 ch2 = do pivot <- getRandomR (0, length (gene ch1) - 1)
  73. let child = take pivot (gene ch1) ++
  74. drop pivot (gene ch2)
  75. return $ Chrom child ((gen ch1)+1)
  76. -- | QuickCheck property for crossover
  77. prop_xoverGetOne :: PropertyM IO ()
  78. prop_xoverGetOne = run (evalRandIO $ twoTupleM genRandom >>= makeTuple)
  79. >>= assert . check
  80. where check :: (Chrom, Chrom, Chrom) -> Bool
  81. check (p1,p2,c) = length (gene c) == length (gene p1) &&
  82. (and . map (\(_, y, z) -> y == z) .
  83. dropWhile (\(x, _, z) -> x == z)
  84. $ zip3 (gene p1) (gene p2) (gene c))
  85. makeTuple :: RandomGen g => (Chrom, Chrom) ->
  86. Rand g (Chrom, Chrom, Chrom)
  87. makeTuple (p1,p2) = (p1,p2,) <$> xoverGetOne p1 p2
  88. -- | Mate function that returns both children results from crossover
  89. xoverGetTwo :: RandomGen g => Chrom -> Chrom ->
  90. Rand g (Chrom, Chrom)
  91. xoverGetTwo ch1 ch2 = do pivot <- getRandomR (0, length (gene ch1) - 1)
  92. let child1 = take pivot (gene ch1) ++
  93. drop pivot (gene ch2)
  94. let child2 = take pivot (gene ch2) ++
  95. drop pivot (gene ch1)
  96. return $ (Chrom child1 ((gen ch1)+1),
  97. Chrom child2 ((gen ch1)+1))
  98. -- | QuickCheck property for xoverChTwo
  99. prop_xoverGetTwo :: PropertyM IO ()
  100. prop_xoverGetTwo = run (evalRandIO $ twoTupleM genRandom >>= makeTuple)
  101. >>= assert . check
  102. where check :: (Chrom, Chrom, (Chrom, Chrom)) -> Bool
  103. check (p1, p2, (c1, c2)) = length (gene c1) == length (gene p1) &&
  104. length (gene c2) == length (gene p1) &&
  105. checkAllFromParent p1 p2 c1 &&
  106. checkAllFromParent p2 p1 c2 &&
  107. checkRebuildParent p1 p2 c1 c2
  108. makeTuple :: RandomGen g => (Chrom, Chrom) ->
  109. Rand g (Chrom, Chrom, (Chrom, Chrom))
  110. makeTuple (p1,p2) = (p1,p2,) <$> xoverGetTwo p1 p2
  111. -- | Check whether the p1 is equal to String c1[:pivot] ++ c2[pivot+1:]
  112. -- and p2 is equal to String c2[pivot+1:] ++ c2[:pivot]
  113. checkRebuildParent :: Chrom -> Chrom -> Chrom -> Chrom -> Bool
  114. checkRebuildParent p1 p2 c1 c2 = gene p1 == take pivot (gene c1) ++
  115. drop pivot (gene c2) &&
  116. gene p2 == take pivot (gene c2)
  117. ++ drop pivot (gene c1)
  118. where pivot = case findIndex (\(x,y) -> x/=y)
  119. $ zip (gene p1) (gene c1) of
  120. Just x -> x
  121. Nothing -> (length . gene) p1
  122. -- | Check if all characters of a child is from either one of its parents.
  123. checkAllFromParent :: Chrom -> Chrom -> Chrom -> Bool
  124. checkAllFromParent p1 p2 c = (and . map (\(_, y, z) -> y == z) .
  125. dropWhile (\(x, _, z) -> x == z)
  126. $ zip3 (gene p1) (gene p2) (gene c))
  127. -- | Crossoever function with two random crossever points.
  128. xoverTwoPivot :: RandomGen g => Chrom -> Chrom ->
  129. Rand g (Chrom, Chrom)
  130. xoverTwoPivot ch1 ch2 =
  131. do pivot1 <- getRandomR (0, length (gene ch1) - 2)
  132. pivot2 <- getRandomR (pivot1 + 1, length (gene ch1) - 1)
  133. let child1 = (take pivot1 (gene ch1)) ++
  134. ((drop pivot1) . (take pivot2)) (gene ch2) ++
  135. (drop pivot2 (gene ch1))
  136. let child2 = (take pivot1 (gene ch2)) ++
  137. ((drop pivot1) . (take pivot2)) (gene ch1) ++
  138. (drop pivot2 (gene ch2))
  139. return $ (Chrom child1 ((gen ch1) + 1),
  140. Chrom child2 ((gen ch1) + 1))
  141. -- | QuickCheck property for xoverChTwo
  142. prop_xoverTwoPivot :: PropertyM IO ()
  143. prop_xoverTwoPivot = run (evalRandIO $ twoTupleM genRandom >>= makeTuple)
  144. >>= assert . check
  145. where check :: (Chrom, Chrom, (Chrom, Chrom)) -> Bool
  146. check (p1, p2, (c1, c2)) = length (gene c1) == length (gene p1) &&
  147. length (gene c2) == length (gene p1) &&
  148. checkAllFromPTwoPivot p1 p2 c1 &&
  149. checkAllFromPTwoPivot p2 p1 c2 &&
  150. ckRebuildPaTwoP p1 p2 c1 c2
  151. makeTuple :: RandomGen g => (Chrom, Chrom) ->
  152. Rand g (Chrom, Chrom, (Chrom, Chrom))
  153. makeTuple (p1,p2) = (p1,p2,) <$> xoverTwoPivot p1 p2
  154. -- | Check if all characters of a child is from either one of its parents.
  155. checkAllFromPTwoPivot :: Chrom -> Chrom -> Chrom -> Bool
  156. checkAllFromPTwoPivot p1 p2 c = (and . map (\(x, _, z) -> x == z) .
  157. dropWhile (\(_, y, z) -> y == z) .
  158. dropWhile (\(x, _, z) -> x == z)
  159. $ zip3 (gene p1) (gene p2) (gene c))
  160. -- | Check whether p1 = c1[:pivot1] ++ c2[pivot1+1:pivot2] ++ c1[pivot2+1:]
  161. -- and p2 = c2[:pivot1] ++ c1[pivot1+1:pivot2] ++ c2[pivot2+1:]
  162. ckRebuildPaTwoP :: Chrom -> Chrom -> Chrom -> Chrom -> Bool
  163. ckRebuildPaTwoP p1 p2 c1 c2 = gene p1 == take pivot1 (gene c1) ++
  164. ((drop pivot1) . (take pivot2)) (gene c2) ++
  165. drop pivot2 (gene c1) &&
  166. gene p2 == take pivot1 (gene c2) ++
  167. ((drop pivot1) . (take pivot2)) (gene c1) ++
  168. drop pivot2 (gene c2)
  169. where pivot1 = case findIndex (\(x,y) -> x/=y)
  170. $ zip (gene p1) (gene c1) of
  171. Just x -> x
  172. Nothing -> (length . gene) p1
  173. pivot2 = case findIndex (\(x,y) -> x/=y)
  174. $ zip ((reverse . gene) p1)
  175. ((reverse . gene) c1) of
  176. Just x -> (length . gene) p1 - x
  177. Nothing -> 0
  178. -- | Crossoever function with random multiple crossever points.
  179. -- child1 is created randomly at each point from either parent1 or
  180. -- parent2, and child2 has untaken genes.
  181. xoverRandomPick :: RandomGen g => Chrom -> Chrom ->
  182. Rand g (Chrom, Chrom)
  183. xoverRandomPick c1 c2 = do r :: [Int] <- replicateM (length (gene target))
  184. (getRandomR (0,1))
  185. let combined = zip3 (gene c1) (gene c2) r
  186. let child1 = map (\(f,s,n) ->
  187. if n == 0 then f else s) combined
  188. let child2 = map (\(f,s,n) ->
  189. if n == 1 then f else s) combined
  190. return $ (Chrom child1 ((gen c1) + 1),
  191. Chrom child2 ((gen c1) + 1))
  192. -- | QuickCheck property for xoverRandomPick
  193. prop_xoverRandomPick :: PropertyM IO ()
  194. prop_xoverRandomPick = run (evalRandIO $ twoTupleM genRandom >>= makeTuple)
  195. >>= assert . check
  196. where check :: (Chrom, Chrom, (Chrom, Chrom)) -> Bool
  197. check (p1, p2, (c1, c2)) = length (gene c1) == length (gene p1) &&
  198. length (gene c2) == length (gene p1) &&
  199. checkFromEitherOfTwoPa p1 p2 c1 c2
  200. makeTuple :: RandomGen g => (Chrom, Chrom) ->
  201. Rand g (Chrom, Chrom, (Chrom, Chrom))
  202. makeTuple (p1,p2) = (p1,p2,) <$> xoverRandomPick p1 p2
  203. checkFromEitherOfTwoPa :: Chrom -> Chrom -> Chrom -> Chrom -> Bool
  204. checkFromEitherOfTwoPa p1 p2 c1 c2 = undefined
  205. -- | get mutuation character
  206. getMutchar :: RandomGen g => Rand g Char
  207. getMutchar = getRandomR (' ', 'z')
  208. -- getMutchar = getRandomR ('0', '1')
  209. -- | Selector function for mutate function.
  210. mutate :: RandomGen g => Int -> Chrom -> Rand g Chrom
  211. mutate option
  212. | option == 1 = mutateRand
  213. | option == 2 = mutateSwap
  214. | otherwise = error "Invalid Option"
  215. -- | Mutate the string at random position with random character.
  216. mutateRand :: RandomGen g => Chrom -> Rand g Chrom
  217. mutateRand ch = do index <- getRandomR (0, length (gene ch) - 1)
  218. let (split1, split2) = splitAt index (gene ch)
  219. mutChar <- getMutchar
  220. return $ Chrom (split1 ++ (mutChar : tail split2)) (gen ch)
  221. -- | QuickCheck Property for mutateRandCh function
  222. prop_mutateRand :: PropertyM IO ()
  223. prop_mutateRand = run (evalRandIO $ genRandom >>= makeMutTuple) >>= assert . check
  224. where check :: (Chrom, Chrom) -> Bool
  225. check (orig, mut) = length (nub (gene orig)
  226. \\ nub (gene mut)) <= 1 &&
  227. length (gene orig)
  228. == length (gene mut)
  229. makeMutTuple :: RandomGen g => Chrom ->
  230. Rand g (Chrom, Chrom)
  231. makeMutTuple orig = (orig,) <$> mutateRand orig
  232. -- | Mutate by randomly swapping two characters
  233. mutateSwap :: RandomGen g => Chrom -> Rand g Chrom
  234. mutateSwap ch = do idx1 <- getRandomR (0, length (gene ch) - 2)
  235. idx2 <- getRandomR (idx1 + 1, length (gene ch) - 1)
  236. return $ Chrom (swap idx1 idx2 (gene ch)) (gen ch)
  237. -- | QuickCheck Property for mutateChSwap function
  238. prop_mutateSwap :: PropertyM IO ()
  239. prop_mutateSwap = run (evalRandIO $ genRandom >>= makeMutTuple)
  240. >>= assert . check
  241. where check :: (Chrom, Chrom) -> Bool
  242. check (orig, mut) = (nub (gene orig) \\
  243. nub (gene mut) == []) &&
  244. length (gene mut) == length (gene orig)
  245. makeMutTuple orig = (orig,) <$> mutateSwap orig
  246. -- | Fitness function. Works only for chromosomes with the same length.
  247. -- If the chromosome is longer and have the target as prefix e.g.
  248. -- "Haskell is fun..." it will score zero.
  249. fitness :: Chrom -> Int
  250. fitness ch = sum $ map abs (zipWith ((-) `on` fromEnum)
  251. (gene target) (gene ch))
  252. -- | unit test for fitness
  253. test_fitness :: Test
  254. test_fitness = test [ fitness (Chrom "Haskell is fun." 0) ~=? 0,
  255. fitness (Chrom "Python is better" 0) ~=? 419,
  256. fitness (Chrom "Java is messy" 0) ~=? 212,
  257. fitness (Chrom "Haskell is fun?" 0) ~=? 17 ]
  258. -- | Generate a random chromosome with the same length as the target.
  259. genRandom :: RandomGen g => Rand g Chrom
  260. genRandom = do genes <- replicateM (length (gene target))
  261. $ getRandomChar
  262. return $ Chrom genes 0
  263. getRandomChar :: RandomGen g => Rand g Char
  264. getRandomChar = getRandomR (' ', 'z')
  265. -- getRandomR ('0', '1')
  266. -- | QuickCheck Property for genRandom
  267. prop_genRandom :: PropertyM IO ()
  268. prop_genRandom = run (evalRandIO genRandom) >>= assert . check
  269. where check :: Chrom -> Bool
  270. check ch = and $ map ($ ch) [all (between (fromEnum ' ')
  271. (fromEnum 'z') . fromEnum) . gene,
  272. (== length (gene target)) . length . gene,
  273. (>= 0) . fitness]
  274. between :: Int -> Int -> Int -> Bool
  275. between l r x = l <= x && x <= r