hanalyze-0.1.0.0: bench/haskell/BenchMO.hs
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-full-laziness -fno-cse #-}
-- | Multi-objective optimization benchmarks (B4).
--
-- Runs NSGA-II from 'Hanalyze.Optim.NSGA' on ZDT1/2/3 (m=2, d=30) and DTLZ1/2
-- (m=3, d=10). Reports median wall time and the hypervolume of the
-- final approximation set against a reference point.
module Main where
import qualified Hanalyze.Optim.NSGA as NSGA
import qualified System.Random.MWC as MWC
import Data.List (sort)
import Control.Monad (forM)
import BenchUtil
-- ---------------------------------------------------------------------------
-- Test problems (m = number of objectives, d = dimension)
-- ---------------------------------------------------------------------------
data MOProblem = MOProblem
{ mpName :: String
, mpDim :: Int
, mpObjs :: Int
, mpFunc :: [Double] -> [Double]
, mpBounds :: [(Double, Double)]
, mpRefPoint :: [Double] -- ^ reference for hypervolume
}
zdt1, zdt2, zdt3 :: MOProblem
zdt1 = MOProblem "ZDT1" 30 2 fn (replicate 30 (0,1)) [1.1, 1.1]
where
fn xs =
let f1 = head xs
g = 1 + 9 * sum (tail xs) / fromIntegral (length xs - 1)
f2 = g * (1 - sqrt (f1 / g))
in [f1, f2]
zdt2 = MOProblem "ZDT2" 30 2 fn (replicate 30 (0,1)) [1.1, 1.1]
where
fn xs =
let f1 = head xs
g = 1 + 9 * sum (tail xs) / fromIntegral (length xs - 1)
f2 = g * (1 - (f1 / g) ** 2)
in [f1, f2]
zdt3 = MOProblem "ZDT3" 30 2 fn (replicate 30 (0,1)) [1.1, 1.1]
where
fn xs =
let f1 = head xs
g = 1 + 9 * sum (tail xs) / fromIntegral (length xs - 1)
f2 = g * (1 - sqrt (f1 / g) - (f1 / g) * sin (10 * pi * f1))
in [f1, f2]
dtlz2_3 :: MOProblem
dtlz2_3 = MOProblem "DTLZ2_3" 10 3 fn (replicate 10 (0, 1)) [1.5, 1.5, 1.5]
where
fn xs =
let m = 3
k = length xs - m + 1
x_m = drop (m - 1) xs
g = sum [(xi - 0.5) ** 2 | xi <- x_m]
fAt i = (1 + g)
* product [ cos (xs!!j * pi / 2) | j <- [0 .. m - i - 2] ]
* (if i == 0 then 1 else sin (xs!!(m - i - 1) * pi / 2))
in [fAt i | i <- [0 .. m - 1]]
problems :: [MOProblem]
problems = [zdt1, zdt2, zdt3, dtlz2_3]
-- ---------------------------------------------------------------------------
-- Driver
-- ---------------------------------------------------------------------------
main :: IO ()
main = do
rows <- fmap concat $ forM problems $ \p -> do
-- 1 seed: run NSGA once; export the Pareto set so that the Python
-- aggregator can score HV/IGD via pymoo (uniform metric for both
-- sides).
(ms, sols) <- runOne p
let pts = map NSGA.solObjectives sols
writePareto p pts
return [ BenchRow "haskell" "mo"
(mpName p ++ "/NSGA-II") ms 0 (fromIntegral (length pts))
("Pareto written to bench/results/haskell/mo_pareto_"
++ mpName p ++ ".csv") ]
writeRows "bench/results/haskell/mo.csv" rows
putStrLn $ "wrote " ++ show (length rows)
++ " rows → bench/results/haskell/mo.csv"
writePareto :: MOProblem -> [[Double]] -> IO ()
writePareto p pts = do
let path = "bench/results/haskell/mo_pareto_" ++ mpName p ++ ".csv"
hdr = unwords ["f" ++ show i | i <- [0 .. mpObjs p - 1]]
writeFile path (replaceSpaces hdr ++ "\n"
++ unlines [ commas (map show row) | row <- pts ])
where
replaceSpaces = map (\c -> if c == ' ' then ',' else c)
commas [] = ""
commas [x] = x
commas (x:xs) = x ++ "," ++ commas xs
{-# NOINLINE runOne #-}
runOne :: MOProblem -> IO (Double, [NSGA.Solution])
runOne p = do
gen <- MWC.createSystemRandom
let cfg = NSGA.defaultNSGAConfig
{ NSGA.nsgaPopSize = 100
, NSGA.nsgaGenerations = 100 -- pymoo と同条件 (N4 で per-gen も近接)
}
(ms, sols) <- timeitIO 1
(\xs -> sum [head (NSGA.solObjectives s) | s <- xs])
(\_ -> NSGA.nsga2 cfg (mpFunc p) (mpBounds p) gen)
return (ms, sols)
-- ---------------------------------------------------------------------------
median :: Ord a => [a] -> a
median xs = sort xs !! (length xs `div` 2)