{-# 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)]