array-forth-0.2.1.4: src/Language/ArrayForth/Synthesis.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Language.ArrayForth.Synthesis where
import Control.Arrow (first)
import Control.Monad.Random (Random, random, randomR)
import Data.Function (on)
import Data.Functor ((<$>))
import Data.List (elemIndices, genericLength, (\\))
import Data.Monoid (Monoid (..))
import Language.ArrayForth.Distance
import Language.ArrayForth.Interpreter
import Language.ArrayForth.Opcode
import Language.ArrayForth.Program
import Language.ArrayForth.State
import Language.Synthesis.Distribution (Distr (..), mix,
negativeInfinity, randInt,
uniform)
import Language.Synthesis.Mutations hiding (mix)
import qualified Language.Synthesis.Mutations as M
import Language.Synthesis.Synthesis (Score (..))
import Text.Printf
-- | A score type that contains a correctness value and a performance
-- value.
data DefaultScore = DefaultScore Double Double deriving (Ord, Eq)
instance Score DefaultScore where
toScore (DefaultScore correctness performance) = correctness + 0.1 * performance
instance Show DefaultScore where show (DefaultScore a b) = printf "<%.2f, %.2f>" a b
instance Monoid DefaultScore where
mempty = DefaultScore 0 0
DefaultScore c₁ p₁ `mappend` DefaultScore c₂ p₂ = DefaultScore (c₁ + c₂) (p₁ + p₂)
-- | Creates an evaluation function from a spec, a set of inputs and a
-- function for comparing program traces.
trace :: Monoid score => Program -> [State] -> (Trace -> Trace -> score) -> Program -> score
trace spec inputs score program = mconcat $ zipWith score specs throttled
where specs = stepProgram . load spec <$> inputs
results = stepProgram . load program <$> inputs
throttled = zipWith go specs results
where go spec' trace' = either id id $ throttle (length spec') trace'
-- | Using a given correctness measure, produce a score also
-- containing performance.
withPerformance :: Score s => (Trace -> Trace -> s) -> (Trace -> Trace -> DefaultScore)
withPerformance score spec result = DefaultScore (toScore $ score spec res) performance
where res = either id id $ throttle (length spec) result
performance = case throttle (length spec) result of
Right res' -> (countTime spec - countTime res') / 10
Left res' -> countTime spec - countTime res' - 1e10
-- | Given a specification program and some inputs, evaluate a program
-- against the specification for both performance and
-- correctness. Normalize the score based on the number of test cases.
evaluate :: Program -> [State] -> (State -> State -> Distance) -> Program -> DefaultScore
evaluate spec inputs distance =
normalize . trace spec inputs (withPerformance (distance `on` last))
where normalize (DefaultScore c p) = DefaultScore (c / len) (p / len)
len = genericLength inputs
-- I need this so that I can get a distribution over Forth words.
instance Random F18Word where
randomR (start, end) gen =
first fromInteger $ randomR (fromIntegral start, fromIntegral end) gen
random = randomR (0, maxBound)
-- | The default distribution of instructions. For now, we do not
-- support any sort of jumps. All the other possible instructions
-- along with constant numbers and unused slots are equally
-- likely. The numeric value of constants is currently a uniform
-- distribution over 18-bit words.
defaultOps :: Distr Instruction
defaultOps = mix [(constants, 1.0), (uniform [Unused], 1.0),
(uniform instrs, genericLength instrs)]
where instrs = map Opcode $ filter (not . isJump) opcodes \\ [Unext, Nop]
constants = let Distr {..} = randInt (0, maxBound)
logProb (Number n) = logProbability n
logProb _ = negativeInfinity in
Distr { sample = Number <$> sample
, logProbability = logProb }
pairs :: [(Instruction, Instruction)]
pairs = map (\ (a, b) -> (Opcode a, Opcode b))
[ (SetA, ReadA)
, (Push, Pop)
, (Over, Drop) ]
removePairs :: Distr Instruction -> Mutation Program
removePairs instrDistr program =
mix [(mutateInstructionsAt instrDistr is program, 1.0) | is <- findPairs program]
where findPairs program' = do (a, b) <- pairs
indexA <- elemIndices a program'
indexB <- elemIndices b program'
return [indexA, indexB]
-- | The default mutations to try. For now, this will either change an
-- instruction or swap two instructions in the program, with equal
-- probability.
defaultMutations :: Mutation Program
defaultMutations = M.mix [(mutateInstruction defaultOps, 1), (swapInstructions, 1)]