{- |

Common crossover operators for genetic algorithms.

-}

module Moo.GeneticAlgorithm.Crossover
  (
  -- ** Discrete operators
    onePointCrossover
  , twoPointCrossover
  , uniformCrossover
  , noCrossover
  -- ** Application
  , doCrossovers
  , doNCrossovers
) where

import Moo.GeneticAlgorithm.Random
import Moo.GeneticAlgorithm.Types
import Moo.GeneticAlgorithm.Utilities

import Control.Monad (liftM)

-- | Crossover two lists in exactly @n@ random points.
nPointCrossover :: Int -> ([a], [a]) -> Rand ([a], [a])
nPointCrossover n (xs,ys)
    | n <= 0 = return (xs,ys)
    | otherwise =
  let len = min (length xs) (length ys)
  in  do
    pos <- getRandomR (0, len-n)
    let (hxs, txs) = splitAt pos xs
    let (hys, tys) = splitAt pos ys
    (rxs, rys) <- nPointCrossover (n-1) (tys, txs) -- FIXME: not tail recursive
    return (hxs ++ rxs, hys ++ rys)

-- |Select a random point in two genomes, and swap them beyond this point.
-- Apply with probability @p@.
onePointCrossover :: Double -> CrossoverOp a
onePointCrossover _ []  = return ([],[])
onePointCrossover _ [celibate] = return ([],[celibate])
onePointCrossover p (g1:g2:rest) = do
  (h1,h2) <- withProbability p (nPointCrossover 1) (g1, g2)
  return ([h1,h2], rest)

-- |Select two random points in two genomes, and swap everything in between.
-- Apply with probability @p@.
twoPointCrossover :: Double -> CrossoverOp a
twoPointCrossover _ []  = return ([], [])
twoPointCrossover _ [celibate] = return ([],[celibate])
twoPointCrossover p (g1:g2:rest) = do
  (h1,h2) <- withProbability p (nPointCrossover 2) (g1,g2)
  return ([h1,h2], rest)

-- |Swap individual bits of two genomes with probability @p@.
uniformCrossover :: Double -> CrossoverOp a
uniformCrossover _ []  = return ([], [])
uniformCrossover _ [celibate] = return ([],[celibate])
uniformCrossover p (g1:g2:rest) = do
  (h1, h2) <- unzip `liftM` mapM swap (zip g1 g2)
  return ([h1,h2], rest)
  where
    swap = withProbability p (\(a,b) -> return (b,a))