packages feed

vertexenum-1.0.0.0: src/Geometry/VertexEnum/Internal.hs

module Geometry.VertexEnum.Internal
  ( normalizeConstraints
  , varsOfConstraint
  , feasiblePoint
  , findSigns
  , iPoint )
  where
import           Prelude                hiding         ( EQ )
import           Control.Monad.Logger                  (
                                                         runStdoutLoggingT
                                                       , filterLogger
                                                       )
import           Data.IntMap.Strict                    ( IntMap, mergeWithKey )
import qualified Data.IntMap.Strict                    as IM
import qualified Data.Map.Strict                       as DM
import           Data.Maybe                            ( fromJust, isJust )
import           Data.List                             ( nub, union )
import           Data.List.Extra                       ( unsnoc )
import           Geometry.VertexEnum.Constraint        ( Constraint (..), Sense (..) )
import           Geometry.VertexEnum.LinearCombination ( LinearCombination (..), VarIndex )
import           Linear.Simplex.Solver.TwoPhase        (
                                                         twoPhaseSimplex
                                                       , findFeasibleSolution
                                                       )
import           Linear.Simplex.Types                  (
                                                         Result ( .. )
                                                       , PolyConstraint ( .. )
                                                       , ObjectiveFunction ( .. )
                                                       )
import           Linear.Simplex.Util                   (
                                                         simplifySystem
                                                       )

normalizeLinearCombination :: 
  Num a => [VarIndex] -> LinearCombination a -> IntMap a
normalizeLinearCombination vars (LinearCombination lc) =
  IM.union lc (IM.fromList [(i,0) | i <- vars `union` [0]])

varsOfLinearCombo :: LinearCombination a -> [VarIndex]
varsOfLinearCombo (LinearCombination imap) = IM.keys imap

varsOfConstraint :: Constraint a -> [VarIndex]
varsOfConstraint (Constraint left _ right) =
  varsOfLinearCombo left `union` varsOfLinearCombo right

normalizeConstraint :: Real a => [VarIndex] -> Constraint a -> [a]
normalizeConstraint vars (Constraint left sense right) =
  if sense == Lt
    then xs ++ [x]
    else map negate xs ++ [-x]
  where
    lhs' = normalizeLinearCombination vars left
    rhs' = normalizeLinearCombination vars right
    coefs = IM.elems $ mergeWithKey (\_ a b -> Just (a-b)) id id lhs' rhs'
    (x, xs) = case coefs of
      (xx:xxs) -> (xx, xxs)
      []       -> (0, [])

normalizeConstraints :: Real a => [Constraint a] -> [[a]]
normalizeConstraints constraints = 
  map (normalizeConstraint vars) constraints
  where
    vars = nub $ concatMap varsOfConstraint constraints

negateIf :: Bool -> Rational -> Rational
negateIf test x = if test then -x else x

inequality :: [Bool] -> [Rational] -> PolyConstraint
inequality toNegate row = 
  LEQ { 
        lhs = DM.fromList (zip [0 ..] (1 : coeffs')), rhs = -bound
      }
  where
    (coeffs, bound) = fromJust $ unsnoc row
    coeffs' = zipWith negateIf toNegate coeffs

inequalities :: [[Rational]] -> [Bool] -> [PolyConstraint]
inequalities normConstraints toNegate = 
  simplifySystem $ map (inequality toNegate) normConstraints

-- iPoint does not necessarily return the optimal interior point, because 
-- this point possibly corresponds to another [Bool] combination; it just 
-- returns a feasible point
iPoint :: [[Rational]] -> [Bool] -> IO [Double]
iPoint halfspacesMatrix toNegate = do
  maybeResult <- runStdoutLoggingT $ filterLogger (\_ _ -> False) $ 
                  twoPhaseSimplex objFunc polyConstraints
  return $ case maybeResult of
    Just (Result var varLitMap) -> 
      let sol = DM.delete 0 $ DM.delete var varLitMap
          nvars = length toNegate
          sol' = DM.union sol (DM.fromList (zip [1 .. nvars] (repeat 0)))
      in 
      map fromRational 
        (
          zipWith negateIf toNegate (DM.elems sol')
        )
    Nothing -> error "iPoint: should not happen."
  where
    polyConstraints = inequalities halfspacesMatrix toNegate
    objFunc = Max {
        objective = DM.singleton 0 1
      } 

feasiblePoint :: [[Rational]] -> [Bool] -> IO Bool
feasiblePoint halfspacesMatrix toNegate = do
  maybeFS <- runStdoutLoggingT $ filterLogger (\_ _ -> False) $ 
                  findFeasibleSolution polyConstraints
  return $ isJust maybeFS
  where
    polyConstraints = simplifySystem $ map ineq halfspacesMatrix
    ineq row = 
      LEQ { 
            lhs = DM.fromList (zip [1 ..] coeffs'), rhs = -bound
          } 
      where
        (coeffs, bound) = fromJust $ unsnoc row
        coeffs' = zipWith negateIf toNegate coeffs

findSigns :: [[Rational]] -> IO [Bool]
findSigns halfspacesMatrix = do 
  go 0
  where
    nvars = length (halfspacesMatrix !! 0) - 1
    combinations = sequence $ replicate nvars [False, True]
    ncombinations = length combinations
    go i 
      | i == ncombinations = do 
          return []
      | otherwise          = do 
          let combo = combinations !! i
          test <- feasiblePoint halfspacesMatrix combo
          if test 
            then do
              return $ combo
            else do
              go (i+1)