packages feed

highs-lp-0.0: src/Numeric/HiGHS/LP/Private.hs

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Numeric.HiGHS.LP.Private where

import qualified Numeric.HiGHS.LP.FFI as Highs
import Numeric.HiGHS.LP.Enumeration (ModelStatus, modelStatusFromC)
import Numeric.HiGHS.LP.FFI (Highs, HighsInt)
import Numeric.LinearProgramming.Common
         (Term(..), Bound(..), Inequality(Inequality),
          Bounds, Constraints, Direction(..))

import qualified Data.Array.Comfort.Boxed as BoxedArray
import qualified Data.Array.Comfort.Storable.Unchecked.Monadic as ArrayMonadic
import qualified Data.Array.Comfort.Storable.Unchecked as ArrayUnchecked
import qualified Data.Array.Comfort.Storable as Array
import qualified Data.Array.Comfort.Shape as Shape
import qualified Data.Traversable as Trav
import Data.Array.Comfort.Storable (Array)
import Data.Foldable (for_)
import Data.Tuple.HT (mapPair)

import qualified Control.Monad.Trans.Reader as MR
import qualified Control.Monad.Trans.Cont as MC
import qualified Control.Applicative.HT as AppHT
import qualified Control.Functor.HT as FuncHT
import Control.Monad (guard, when)
import Control.Applicative (liftA2, liftA3)

import qualified Foreign.Marshal.Array.Guarded as ForeignArray
import Foreign.Storable (pokeElemOff, peekElemOff)
import Foreign.ForeignPtr (withForeignPtr)
import Foreign.Ptr (Ptr)
import Foreign.C.String (withCString)
import Foreign.C.Types (CDouble)



withBuffer :: Array sh a -> MC.ContT r IO (Ptr a)
withBuffer arr =
   MC.ContT $ withForeignPtr (ArrayUnchecked.buffer arr)

runContT :: MC.ContT a IO a -> IO a
runContT act = MC.runContT act return



positiveInfinity, negativeInfinity :: CDouble
positiveInfinity =  1/0
negativeInfinity = -1/0

prepareBounds :: Inequality a -> (a, (CDouble, CDouble))
prepareBounds (Inequality x bnd) =
   (,) x $
   case bnd of
      LessEqual up    -> (negativeInfinity, realToFrac up)
      GreaterEqual lo -> (realToFrac lo,    positiveInfinity)
      Between lo up   -> (realToFrac lo,    realToFrac up)
      Equal y         -> (realToFrac y,     realToFrac y)
      Free            -> (negativeInfinity, positiveInfinity)

prepareColumnBoundsArrays ::
   (Shape.Indexed sh, Shape.Index sh ~ ix) =>
   sh -> Bounds ix -> (Array sh CDouble, Array sh CDouble)
prepareColumnBoundsArrays shape =
   mapPair (Array.fromBoxed, Array.fromBoxed) .
   FuncHT.unzip .
   BoxedArray.fromAssociations (0, positiveInfinity) shape .
   map prepareBounds


type ShapeInt = Shape.ZeroBased Int

prepareRowBoundsArrays ::
   Bounds ix -> (Array ShapeInt CDouble, Array ShapeInt CDouble)
prepareRowBoundsArrays constrs =
   let shape = Shape.ZeroBased $ length constrs in
   mapPair (Array.fromList shape, Array.fromList shape) $
   unzip $ map (snd . prepareBounds) constrs

storeBounds ::
   (Array sh CDouble, Array sh CDouble) ->
   MC.ContT r IO (Ptr CDouble, Ptr CDouble)
storeBounds = AppHT.mapPair (withBuffer, withBuffer)


prepareConstraints ::
   (Shape.Indexed sh, Shape.Index sh ~ ix) =>
   sh -> Constraints Double ix ->
   (Array ShapeInt CDouble, Array ShapeInt HighsInt, Array ShapeInt HighsInt)
prepareConstraints shape constrs =
   let {-
       Highs.passLp returns Warning when there are zero coefficients.
       I think zero coefficients are reasonably ok.
       -}
       constrsNonZero = map (fmap (filter (\(Term c _x) -> c/=0))) constrs
       rowStarts =
         Array.vectorFromList $ scanl (+) 0 $
         map (\(Inequality terms _bnd) -> fromIntegral $ length terms)
            constrsNonZero
       shapeOffset = Shape.offset shape
       coefficients =
         concatMap (\(Inequality terms _bnd) -> terms) constrsNonZero
       indexArr =
         Array.vectorFromList $
         map (\(Term _ ix) -> fromIntegral $ shapeOffset ix) coefficients
       coefficientArr =
         Array.vectorFromList $
         map (\(Term c _) -> realToFrac c) coefficients
   in (coefficientArr, indexArr, rowStarts)

storeConstraints ::
   (Array ShapeInt CDouble, Array ShapeInt HighsInt, Array ShapeInt HighsInt) ->
   MC.ContT r IO (Ptr CDouble, Ptr HighsInt, Ptr HighsInt)
storeConstraints (coefficients, indices, rowStarts) =
   liftA3 (,,)
      (withBuffer coefficients)
      (withBuffer indices)
      (withBuffer rowStarts)


objectiveSense :: Direction -> Highs.ObjSense
objectiveSense dir =
   case dir of
      Minimize -> Highs.objSenseMinimize
      Maximize -> Highs.objSenseMaximize


setBoolOptionValue :: Ptr Highs -> String -> Highs.Bool -> IO ()
setBoolOptionValue model key b =
   checkStatus $ withCString key $ \cstr ->
      Highs.setBoolOptionValue model cstr b

setIntOptionValue :: Ptr Highs -> String -> HighsInt -> IO ()
setIntOptionValue model key n =
   checkStatus $ withCString key $ \cstr ->
      Highs.setIntOptionValue model cstr n

setDoubleOptionValue :: Ptr Highs -> String -> CDouble -> IO ()
setDoubleOptionValue model key x =
   checkStatus $ withCString key $ \cstr ->
      Highs.setDoubleOptionValue model cstr x

setStringOptionValue :: Ptr Highs -> String -> String -> IO ()
setStringOptionValue model key value =
   checkStatus $
      withCString key $ \keyPtr ->
      withCString value $ \valuePtr ->
      Highs.setStringOptionValue model keyPtr valuePtr



newtype Method = Method {deMethod :: String}
   deriving (Show)

simplex, choose, ipm :: Method
simplex = Method "simplex"
choose = Method "choose"
ipm = Method "ipm"

setMethod :: Ptr Highs -> Method -> IO ()
setMethod model (Method method) =
   setStringOptionValue model "solver" method

checkStatus :: IO Highs.Status -> IO ()
checkStatus act = do
   status <- act
   when (status == Highs.statusError) $ fail "Highs function failed"


newtype Query sh a = Query (MR.ReaderT (sh, Ptr Highs) IO a)
   deriving (Functor, Applicative, Monad)

getResult :: (Shape.C sh) => Query sh (Double, Array sh Double)
getResult = liftA2 (,) getObjectiveValue getOptimalVector

getObjectiveValue :: Query sh Double
getObjectiveValue =
   Query $ MR.ReaderT $ fmap realToFrac . Highs.getObjectiveValue . snd

doubleFromCDoubleBuffer :: Int -> Ptr CDouble -> Ptr Double -> IO ()
doubleFromCDoubleBuffer n srcPtr dstPtr =
   for_ (take n [0..]) $ \k ->
      pokeElemOff dstPtr k . realToFrac =<< peekElemOff srcPtr k

getOptimalVector :: (Shape.C sh) => Query sh (Array sh Double)
getOptimalVector =
   Query $ MR.ReaderT $ \(shape,model) ->
   ArrayMonadic.unsafeCreateWithSize shape $ \numCols arrPtr ->
      (fmap fromIntegral (Highs.getNumRow model) >>= ) $ \numRows ->
      ForeignArray.alloca numCols $ \colValuePtr ->
      ForeignArray.alloca numCols $ \colDualPtr  ->
      ForeignArray.alloca numRows $ \rowValuePtr ->
      ForeignArray.alloca numRows $ \rowDualPtr  -> do

      checkStatus $
         Highs.getSolution model colValuePtr colDualPtr rowValuePtr rowDualPtr

      doubleFromCDoubleBuffer numCols colValuePtr arrPtr

getSolutionVectors ::
   (Shape.C sh) =>
   Query sh
      ((Array sh Double, Array sh Double),
       (Array ShapeInt Double, Array ShapeInt Double))
getSolutionVectors =
   Query $ MR.ReaderT $ \(shape,model) ->
   fmap (\(colPrimal,(colDual,row2)) -> ((colPrimal,colDual),row2)) $
   ArrayMonadic.unsafeCreateWithSizeAndResult shape $ \numCols colValueArr ->
   ArrayMonadic.unsafeCreateWithSizeAndResult shape $ \_numCols colDualArr ->
   (fmap fromIntegral (Highs.getNumRow model) >>= ) $ \numRows ->
   let constraintsShape = Shape.ZeroBased numRows in
   ArrayMonadic.unsafeCreateWithSizeAndResult constraintsShape $
      \_numRows rowValueArr ->
   ArrayMonadic.unsafeCreate constraintsShape $ \rowDualArr ->
      ForeignArray.alloca numCols $ \colValuePtr ->
      ForeignArray.alloca numCols $ \colDualPtr  ->
      ForeignArray.alloca numRows $ \rowValuePtr ->
      ForeignArray.alloca numRows $ \rowDualPtr  -> do

      checkStatus $
         Highs.getSolution model colValuePtr colDualPtr rowValuePtr rowDualPtr

      doubleFromCDoubleBuffer numCols colValuePtr colValueArr
      doubleFromCDoubleBuffer numCols colDualPtr colDualArr
      doubleFromCDoubleBuffer numRows rowValuePtr rowValueArr
      doubleFromCDoubleBuffer numRows rowDualPtr rowDualArr

getBasisStatus ::
   (Shape.C sh) =>
   Query sh (Array sh Highs.BasisStatus, Array ShapeInt Highs.BasisStatus)
getBasisStatus =
   Query $ MR.ReaderT $ \(shape,model) ->
   ArrayMonadic.unsafeCreateWithSizeAndResult shape $ \_numCols colStatusPtr ->
   (fmap fromIntegral (Highs.getNumRow model) >>= ) $ \numRows ->
   ArrayMonadic.unsafeCreate (Shape.ZeroBased numRows) $ \rowStatusPtr ->
      checkStatus $ Highs.getBasis model colStatusPtr rowStatusPtr



type Result sh = (ModelStatus, Maybe (Double, Array sh Double))

examineStatus :: (Shape.C sh) =>
   Query sh a -> sh -> Ptr Highs -> Highs.Status -> IO (ModelStatus, Maybe a)
examineStatus (Query query) shape model status =
   liftA2 (,) (fmap modelStatusFromC $ Highs.getModelStatus model) $
   Trav.for (guard (status /= Highs.statusError)) $ \() ->
      MR.runReaderT query (shape,model)