-- | This module contains code related to fitness evaluation.  The
--   main purpose of the code is to both evaluate fitnesses of individuals
--   and to sort individuals by fitness.  These are intended to all be
--   higher order functions that assume nothing about the purpose of the
--   individuals or the types of inputs being used for fitness testing.
--   The only assumption made currently is that the outputs for test cases
--   are floating point numbers.  That likely should change for general
--   purpose usage.
--
--   mjsottile\@computer.org
--
module GEP.Fitness
    ( FitnessFunction
    , TestCase
    , TestDict
    , TestOuts
    , fitness_tester
    , fitness_filter
    , sortByFitness
    ) where

import GEP.Types
import Data.Function
import Data.List (sortBy)

-- | Fitness function type
type FitnessFunction a b = a -> TestCase b -> Double -> Double -> Double

-- | A test case maps a list of terminals to float values
type TestCase a = SymTable a

-- | A test dictionary is a set of test cases
type TestDict a = [TestCase a]

-- | The set of outputs expected for each entry in the test dictionary
type TestOuts = [Double]

--
-- Sort a list of pairs by first element of each pair.
--
pairSort :: (Ord a) => [(a,b)] -> [(a,b)]
pairSort = sortBy (compare `on` fst)

--
-- |
--  Fitness evaluator for generic individuals.  This needs to go away
--  and use a more general approach like evaluateFitness above.
-- 
fitness_tester :: a                   -- ^ Expressed individual
               -> FitnessFunction a b -- ^ Fitness function
               -> TestDict b          -- ^ List of symbol tables for test cases
               -> TestOuts            -- ^ List of expected outputs for test cases
               -> Double              -- ^ Range of selection.  M in original
                                      --   GEP paper equations for fitness.
               -> Double              -- ^ Fitness value for given individual
fitness_tester who ffun inputDict outputs m = 
  sum tests
  where 
    tests = map (\(x,y) -> ffun who x y m) 
                (zip inputDict outputs)

-- |
--  Given a list of fitness values and a corresponding list of individuals,
--  return a list of tuples pairing the fitness value with the individuals for
--  only those individuals that have a valid fitness value.  This means those
--  that are +/- infinity or NaN are removed.
--
fitness_filter :: [Double]               -- ^ Fitness values
               -> [Chromosome]           -- ^ Individuals
               -> [(Double, Chromosome)] -- ^ Paired fitness/individuals after 
                                         --   filtering
fitness_filter fitnesses pop =
    filter (\(i,_) -> not ((isNaN i) || (isInfinite i))) 
           (zip fitnesses pop)

-- |
--  Sort a set of individuals with fitness values by their fitness
--
sortByFitness :: [(Double, Chromosome)] -> [(Double, Chromosome)]
sortByFitness xs = reverse (pairSort xs)