packages feed

hmep-0.1.0: app/Main.hs

module Main where

{-
  | = Example application: trigonometry cheating

  Find the trigonometric expression of cos(x) through sin(x)
  using our automatic programming method.
-}

import qualified Data.Vector as V
import           Data.List ( foldl' )
import           Control.Monad ( foldM )
import           Math.Probable.Random  -- From `probable` package
                 ( vectorOf
                 , double
                 )

import           AI.MEP

config = defaultConfig {
  -- Functions available to genetically produced programs
  c'ops = V.fromList [
       ('*', (*)),
       ('+', (+)),
       ('/', (/)),
       ('-', (-)),
       ('s', \x _ -> sin x)
     ]
  -- Chromosome length
  , c'length = 50
  }

-- | Absolute value distance between two scalar values
dist :: Double -> Double -> Double
dist x y = if isNaN x || isNaN y
  -- Large distance
  then 10000
  else abs $ x - y

-- Could be optimized
sum' :: Num a => [V.Vector a] -> V.Vector a
sum' xss = foldl' (V.zipWith (+)) base xss
  where
    len = V.length $ head xss
    base = V.replicate len 0

main :: IO ()
main = do
  -- A vector of 50 random numbers between 0 and 1 (including 1)
  xs <- runRandIO (vectorOf 50 double)

  -- Scale the values to the interval of (-pi, pi]
  let xs' = V.map ((2*pi *). subtract 0.5) xs
      -- Target function f to approximate
      function x = (cos x)^2
      -- Pairs (x, f(x))
      dataset = V.map (\x -> (x, function x)) xs'

  -- Randomly create a population of chromosomes
  pop <- runRandIO $ initialize config

  let -- The loss function which depends on the dataset
      loss evalf = (V.singleton i', loss')
        where
          (xs, ys) = unzip $ V.toList dataset
          -- Distances resulting from multiple expression evaluation
          dss = zipWith (\x y -> V.map (dist y). evalf. V.singleton $ x) xs ys
          -- Cumulative distances for each index
          dcumul = sum' dss
          -- Select index minimizing cumulative distances
          i' = V.minIndex dcumul
          -- The loss value with respect to the index of the best expression
          loss' = dcumul V.! i'

  -- Evaluate the initial population
  let popEvaluated = evaluateGeneration loss pop

  putStrLn $ "Average loss in the initial population " ++ show (avgLoss popEvaluated)

  -- Declare how to produce the new generation
  let nextGeneration = evolve config loss (mutation3 config) crossover binaryTournament

  -- Specify the I/O loop, which logs every 5 generation
      runIO pop i = do
        newPop <- runRandIO $ foldM (\xg _ -> nextGeneration xg) pop [1..generations]
        putStrLn $ "Population " ++ show (i * generations) ++ ": average loss " ++ show (avgLoss newPop)
        return newPop
          where generations = 5

  -- Final generation
  final <- foldM runIO popEvaluated [1..20]
  let best = last final
  print best
  putStrLn "Interpreted expression:"
  putStrLn $ generateCode best