{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE TupleSections #-} -- | Linear expressions of variables. module Math.Programming.LinExpr where import Data.List (foldl', sortOn) -- | A linear expression. -- -- Linear expressions contain symbolic variables of type @b@ and -- numeric coefficients of type @a@. Often @a@ will be 'Double', and -- @b@ will be whatever variable type your linear program uses. data LinExpr a b = LinExpr ![(a, b)] !a deriving (Eq, Read, Show, Functor, Foldable, Traversable) instance Num a => Semigroup (LinExpr a b) where (<>) = (.+.) instance Num a => Monoid (LinExpr a b) where mempty = con 0 -- | Construct a term in a linear expression by multiplying a constant -- by a variable. (*.) :: Num a => a -> b -> LinExpr a b (*.) x y = LinExpr [(x, y)] 0 infixl 7 .* -- | Construct a term in a linear expression by multiplying a variable -- by a constant. (.*) :: Num a => b -> a -> LinExpr a b (.*) = flip (*.) infixl 7 *. -- | Construct a term in a linear expression by dividing a variable by -- a constant. (./) :: Fractional a => b -> a -> LinExpr a b (./) x y = x .* (1 / y) infixl 7 ./ -- | Multiplication of linear expressions by a constant. scale :: Num a => a -> LinExpr a b -> LinExpr a b scale coef (LinExpr terms constant) = LinExpr terms' constant' where terms' = [(c * coef, x) | (c, x) <- terms] constant' = constant * coef -- | Addition of linear expressions. (.+.) :: Num a => LinExpr a b -> LinExpr a b -> LinExpr a b (.+.) (LinExpr terms constant) (LinExpr terms' constant') = LinExpr (terms <> terms') (constant + constant') infixl 6 .+. -- | The difference of linear expressions. (.-.) :: Num a => LinExpr a b -> LinExpr a b -> LinExpr a b (.-.) x y = x .+. scale (-1) y infixl 6 .-. -- | A linear expression with a single variable term. var :: Num a => b -> LinExpr a b var x = LinExpr [(1, x)] 0 -- | A linear expression with only a constant term. con :: a -> LinExpr a b con = LinExpr [] -- | The sum of variable terms with coefficients of unity. vsum :: Num a => [b] -> LinExpr a b vsum = flip LinExpr 0 . fmap (1,) -- | The sum of linear expressions. esum :: Num a => Foldable t => t (LinExpr a b) -> LinExpr a b esum = foldl' (.+.) mempty -- | Reduce an expression to its value. eval :: Num a => LinExpr a a -> a eval (LinExpr terms constant) = constant + sum' (map (uncurry (*)) terms) where sum' = foldl' (+) 0 -- | Simplify an expression by grouping like terms. simplify :: (Num a, Ord b) => LinExpr a b -> LinExpr a b simplify (LinExpr terms constant) = LinExpr (foldr f [] (sortOn snd terms)) constant where f (c, x) [] = [(c, x)] f (c, x) ((c', x') : xs) = if x == x' then (c + c', x) : xs else (c, x) : (c', x') : xs