{-# LANGUAGE DerivingVia, GeneralizedNewtypeDeriving, StandaloneDeriving #-}
module Math.ParetoFront (
Comparison(..),
Debatable(..),
Front,
Strata,
singleton,
stratum,
getStrata,
getFront,
quota,
nestedFold
) where
import Data.Foldable
import Data.List (partition, transpose)
import Data.Ord (Down(..))
import Data.Semigroup(Arg(..), Max(..), Min(..))
data Comparison =
Dominated |
WeakTie |
StrongTie |
Dominates
deriving(Ord,Eq,Show,Read)
instance Semigroup Comparison where
WeakTie <> b = b
a <> WeakTie = a
Dominates <> Dominates = Dominates
Dominated <> Dominated = Dominated
_ <> _ = StrongTie
instance Monoid Comparison where
mempty = WeakTie
class Debatable a where
weigh :: a -> a -> Comparison
instance Ord a => Debatable (Min a) where
weigh (Min a) (Min b) = case compare a b of
LT -> Dominates
GT -> Dominated
EQ -> WeakTie
deriving via Min Int instance Debatable Int
deriving via Min Integer instance Debatable Integer
deriving via Min Double instance Debatable Double
deriving via Min Float instance Debatable Float
deriving via Min (Down a) instance Ord a => Debatable (Max a)
instance Debatable a => Debatable (Down a) where
weigh (Down a) (Down b) = case weigh a b of
Dominates -> Dominated
Dominated -> Dominates
r -> r
instance Debatable a => Debatable (Arg a b) where
weigh (Arg a _) (Arg b _) = weigh a b
instance (Debatable a, Debatable b) => Debatable (a,b) where
weigh ~(a1,a2) ~(b1,b2) = weigh a1 b1 <> weigh a2 b2
instance (Debatable a, Debatable b, Debatable c) => Debatable (a,b,c) where
weigh ~(a1,a2,a3) ~(b1,b2,b3) = weigh a1 b1 <> weigh a2 b2 <> weigh a3 b3
instance (Debatable a, Debatable b, Debatable c, Debatable d) =>
Debatable (a,b,c,d) where
weigh ~(a1,a2,a3,a4) ~(b1,b2,b3,b4) = weigh a1 b1 <>
weigh a2 b2 <>
weigh a3 b3 <>
weigh a4 b4
newtype Front a = Front [a] deriving (Foldable, Show)
newtype Strata a = Strata [Front a] deriving (Show)
instance Foldable Strata where
foldMap f (Strata l) = foldMap (foldMap f) l
singleton :: a -> Front a
singleton a = Front [a]
stratum :: a -> Strata a
stratum a = Strata [singleton a]
fuse :: Debatable a => Front a -> Front a -> (Front a, Front a, Front a)
fuse (Front a) (Front b) = let
m = map (flip map b . weigh) a
m' = transpose m
s = map (not . any (== Dominated)) m
s' = map (not . any (== Dominates)) m'
(f1, t1) = partition snd $ zip a s
(f2, t2) = partition snd $ zip b s'
in (Front $ map fst (f1 ++ f2), Front $ map fst t1, Front $ map fst t2)
instance Debatable a => Semigroup (Front a) where
a <> b = let
(r, _, _) = fuse a b
in r
instance Debatable a => Monoid (Front a) where
mempty = Front []
instance Debatable a => Semigroup (Strata a) where
a <> b = mconcat [a,b]
instance Debatable a => Monoid (Strata a) where
mempty = Strata []
mconcat = Strata . rebuild . transpose . map getStrata where
rebuild [] = []
rebuild ([] : r) = rebuild r
rebuild ([x] : r) = x : rebuild r
rebuild ((x : y : s) : r) = let
(f, p, q) = fuse x y
r' = push p $ push q r
in rebuild ((f: s): r')
push (Front []) r = r
push q [] = [[q]]
push q (s : r) = ((q:s) : r)
getFront :: Front a -> [a]
getFront (Front l) = l
getStrata :: Strata a -> [Front a]
getStrata (Strata l) = l
quota :: Int -> Strata a -> Strata a
quota _ (Strata []) = Strata []
quota n (Strata (a:r))
| n > 0 = let
Strata r' = quota (n - length (getFront a)) (Strata r)
in Strata (a : r')
| otherwise = Strata []
nestedFold :: (Monoid m, Monoid n) => (a -> m) -> (m -> n) -> Strata a -> n
nestedFold f g (Strata l) = foldMap (g . foldMap f) l