cplex-hs-0.3.0.0: src/LSolver/Backend/Cplex.hs
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE RecordWildCards #-}
module LSolver.Backend.Cplex(solLP, standardBounds, solMIP, defaultCallBacks, getCallBackLp, getIncCallBackXs, getCallBackXs,
addCallBackCut, UserCutCallBack, CutCallBackM, UserIncumbentCallBack, IncumbentCallBackM, CallBacks(..)) where
import Data.Ix as I
import qualified Data.Vector as V
import qualified Data.Sequence as S
import Data.Foldable as F
import CPLEX.Bindings
import CPLEX.Param
import CPLEX.Core hiding (Bound)
--import Foreign.C (CInt)
import LSolver.Bindings
import qualified Data.Vector.Storable as VS
import Foreign.Ptr
import Foreign.ForeignPtr(newForeignPtr_)
import Foreign.Storable
import Foreign.C
import Control.Monad
import Control.Monad.Reader
import qualified Data.Map as M
import Data.List (sortBy)
import Data.Ord (comparing)
data CallBacks = ActiveCallBacks {cutcb :: Maybe UserCutCallBack, inccb :: Maybe UserIncumbentCallBack,
lazycb :: Maybe UserCutCallBack}
defaultCallBacks = ActiveCallBacks {cutcb = Nothing, inccb = Nothing, lazycb = Nothing}
type ParamValues = [(CPX_PARAM, Int)]
data CutCallBackArgs = CutCallBackArgs {env :: CpxEnv, cbdata :: Ptr (), wherefrom :: CInt, cbhandle :: Ptr (), userdata :: Ptr Int}
type CutCallBackM a = (ReaderT CutCallBackArgs IO a)
type UserCutCallBack = CutCallBackM Int
data IncumbentCallBackArgs = IncumbentCallBackArgs {envi :: CpxEnv, cbdatai :: Ptr (), wherefromi :: CInt, cbhandlei :: Ptr (),
objVal :: CDouble, xs :: Ptr CDouble, isfeas :: Ptr Int , useraction :: Ptr Int}
type IncumbentCallBackM a = (ReaderT IncumbentCallBackArgs IO a)
type UserIncumbentCallBack = Double -> VS.Vector Double -> IncumbentCallBackM Bool
incumbentcallback :: UserIncumbentCallBack -> CIncumbentCallback
incumbentcallback usercb env' cbdata wherefrom cbhandle objVal xs isfeas useraction = do
let env = CpxEnv env'
let oval = realToFrac objVal
foreignPtr <- newForeignPtr_ xs
lp <- getCallbackLP env cbdata (fromIntegral wherefrom)
xs' <- case lp of
Right lp' -> do
colCount <- getNumCols env lp'
return $ VS.map realToFrac $ VS.unsafeFromForeignPtr0 foreignPtr colCount
Left _ -> return VS.empty
isFeas <- runReaderT (usercb oval xs') $ IncumbentCallBackArgs env cbdata wherefrom cbhandle objVal xs isfeas useraction
poke isfeas (if isFeas then 1 else 0)
return 0
cutcallback :: UserCutCallBack -> CCutCallback
cutcallback usercb env' cbdata wherefrom cbhandle ptrUser = do
let env = CpxEnv env'
runReaderT usercb $ CutCallBackArgs env cbdata wherefrom cbhandle ptrUser
lazycallback :: UserCutCallBack -> CCutCallback
lazycallback usercb env' cbdata wherefrom cbhandle ptrUser = do
let env = CpxEnv env'
runReaderT usercb $ CutCallBackArgs env cbdata wherefrom cbhandle ptrUser
getCallBackLp :: CutCallBackM CpxLp
getCallBackLp = do
CutCallBackArgs{..} <- ask
res <- liftIO $ getCallbackLP env cbdata (fromIntegral wherefrom)
case res of
Right lp -> return lp
_ -> error "I cant get the LP for some reason"
getIncCallBackXs :: IncumbentCallBackM (V.Vector Double)
getIncCallBackXs = do
IncumbentCallBackArgs{..} <- ask
lp <- liftIO $ getCallbackLP envi cbdatai (fromIntegral wherefromi)
xs' <- case lp of
Right lp' -> do
colCount <- liftIO $ getNumCols envi lp'
(stat, xsVS) <- liftIO $ getCallbackNodeX envi cbdatai (fromIntegral wherefromi) 0 (colCount-1)
return $ VS.convert xsVS
Left msg -> return $ V.empty
return xs'
getCallBackXs :: CutCallBackM (V.Vector Double)
getCallBackXs = do
CutCallBackArgs{..} <- ask
lp <- liftIO $ getCallbackLP env cbdata (fromIntegral wherefrom)
xs' <- case lp of
Right lp' -> do
colCount <- liftIO $ getNumCols env lp'
(stat, xsVS) <- liftIO $ getCallbackNodeX env cbdata (fromIntegral wherefrom) 0 (colCount-1)
return $ VS.convert xsVS
Left msg -> return $ V.empty
return xs'
addCallBackCut :: Bound [Variable Int] -> CutCallBackM (Maybe String)
addCallBackCut st = do
CutCallBackArgs{..} <- ask
let (cnstrs,rhs) = toForm st
liftIO $ addCutFromCallback env cbdata wherefrom (fromIntegral $ length cnstrs) rhs cnstrs CPX_USECUT_FORCE
where
toForm (vars :< b) = (map (\(v :# i) -> (Col i, v)) vars, L b)
toForm (vars :> b) = (map (\(v :# i) -> (Col i, v)) vars, G b)
toForm (vars := b) = (map (\(v :# i) -> (Col i, v)) vars, E b)
standardBounds :: (Enum t, Num a) => (t, t) -> [(t, Maybe a, Maybe a1)]
standardBounds (i,j) = map (\i' -> (i', Just 0, Nothing)) [i..j]
toBounds :: (Num a, Ix t) => [(t, Maybe a, Maybe a1)] -> (t, t) -> [(Maybe a, Maybe a1)]
toBounds bounds varRange = F.toList $ aux bounds def
where
def = S.fromList [k | _ <- I.range varRange, let k = (Just 0, Nothing)]
aux [] s = s
aux ((b,lb,ub):bs) s = aux bs (S.update (I.index varRange b) (lb,ub) s)
toConstraints :: Ix a => Constraints a -> (a, a) -> ([(Row, Col, Double)], V.Vector Sense)
toConstraints constraints varRange = let (st, rhs) = toStandard constraints 0 [] [] varRange
in (st, V.fromList rhs)
toStandard :: Ix a => Constraints a -> Int -> [(Row, Col, Double)] -> [Sense] -> (a, a)
-> ([(Row, Col, Double)], [Sense])
toStandard (Constraints []) _ accSt accRhs _ = (reverse $ accSt, reverse $ accRhs)
toStandard (Constraints (b:bs)) rowI accSt accRhs varRange = case b of
vars :< boundVal -> addRow vars L boundVal
vars := boundVal -> addRow vars E boundVal
vars :> boundVal -> addRow vars G boundVal
where addRow vars s boundVal = toStandard (Constraints bs) (rowI+1) (generateRow vars ++ accSt)
((s boundVal) : accRhs) varRange
generateRow [] = []
generateRow ((v :# i):vs) = (Row rowI, Col $ I.index varRange i, v):generateRow vs
toObj :: Optimization Int -> (ObjSense, V.Vector Double)
toObj (Maximize dd) = (CPX_MAX, varsToVector dd)
toObj (Minimize dd) = (CPX_MIN, varsToVector dd)
-- This assumes that all elements are in !, zero pad boys
varsToVector :: [Variable Int] -> V.Vector Double
varsToVector vs = V.fromList $ map snd $ sortBy (comparing fst) $ map (\(c :# i) -> (i,c)) vs
solLP :: (Eq a, Ord a) => LinearProblem a -> ParamValues -> IO (LPSolution a)
solLP (LP objective_ constraints_ bounds_) params = withEnv $ \env -> do
--setIntParam env CPX_PARAM_SCRIND cpx_ON
--setIntParam env CPX_PARAM_DATACHECK cpx_ON
mapM_ (\(p,v) -> setIntParam env p (fromIntegral v)) params
withLp env "testprob" $ \lp -> do
let
dic = generateVarDic constraints_
revDic = M.fromList $ map (\(a,b) -> (b,a)) $ M.toList dic
objective = tokenizeObj objective_ dic
constraints = tokenizeConstraints constraints_ dic
bounds = tokenizeBounds bounds_ dic
varRange = (0,M.size dic)
(objsen, obj) = toObj objective
(cnstrs,rhs) = toConstraints constraints varRange
xbnds = toBounds bounds varRange
statusLp <- copyLp env lp objsen obj rhs cnstrs (V.fromList xbnds)
case statusLp of
Nothing -> return ()
Just msg -> error $ "CPXcopylp error: " ++ msg
statusOpt <- qpopt env lp
case statusOpt of
Nothing -> return ()
Just msg -> error $ "CPXqpopt error: " ++ msg
statusSol <- getSolution env lp
case statusSol of
Left msg -> error $ "CPXsolution error: " ++ msg
Right sol -> do
let vars = V.toList $ VS.convert $ solX sol
let m = M.fromList $ zip (map (revDic M.!) [0..length vars - 1]) vars
return $ LPSolution (solStat sol == CPX_STAT_OPTIMAL) (solObj sol) ( m ) (VS.convert $ solPi sol)
solMIP :: (Ord a, Eq a) => MixedIntegerProblem a -> ParamValues -> CallBacks -> IO (MIPSolution a)
solMIP (MILP objective_ constraints_ bounds_ types_ ) params (ActiveCallBacks {..}) = withEnv $ \env -> do
-- setIntParam env CPX_PARAM_SCRIND 1
-- setIntParam env CPX_PARAM_DATACHECK 1
mapM_ (\(p,v) -> setIntParam env p (fromIntegral v)) params
withLp env "clu" $ \lp -> do
let
dic = generateVarDic constraints_
revDic = M.fromList $ map (\(a,b) -> (b,a)) $ M.toList dic
objective = tokenizeObj objective_ dic
constraints = tokenizeConstraints constraints_ dic
bounds = tokenizeBounds bounds_ dic
types = tokenizeTypes types_ dic
varRange = (0,M.size dic)
(varA, varB) = varRange
varCount = varB - varA + 1
(objsen, obj) = toObj objective
(cnstrs,rhs) = toConstraints constraints varRange
xbnds = toBounds bounds varRange
types' = V.fromList (replicate varCount CPX_CONTINUOUS) V.// (map (\(a,t) -> (a, typeToCPX t)) types)
statusLp <- copyMip env lp objsen obj rhs cnstrs (V.fromList xbnds) types'
case statusLp of
Nothing -> return ()
Just msg -> error $ "CPXcopyMIP error: " ++ msg
case inccb of
Just cb -> do statusIncCB <- setIncumbentCallback env (incumbentcallback cb)
case statusIncCB of
Nothing -> return ()
Just msg -> error $ "CPXIncumbentCallBackSet Error: " ++ msg
Nothing -> return ()
case lazycb of
Just cb -> do statusLazyCB <- setLazyConstraintCallback env (lazycallback cb)
case statusLazyCB of
Nothing -> return ()
Just msg -> error $ "CPXLazyConstraintCallBackSet Error: " ++ msg
Nothing -> return ()
case cutcb of
Just cb -> do statusCutCB <- setCutCallback env (cutcallback cb)
case statusCutCB of
Nothing -> return ()
Just msg -> error $ "CPXCutCallBackSet Error: " ++ msg
Nothing -> return ()
statusOpt <- mipopt env lp
case statusOpt of
Nothing -> return ()
Just msg -> error $ "CPXmipopt error: " ++ msg
statusSol <- getMIPSolution env lp
case statusSol of
Left msg -> error $ "CPXsolution error: " ++ msg
Right sol -> do -- I call this imperative do notation
let vars = V.toList $ VS.convert $ solX sol
let m = M.fromList $ zip (map (revDic M.!) [0..length vars - 1]) vars
return $ MIPSolution (solStat sol == CPXMIP_OPTIMAL) (solObj sol) m
typeToCPX :: LSolver.Bindings.Type -> CPLEX.Core.Type
typeToCPX (TInteger) = CPX_INTEGER
typeToCPX (TContinuous) = CPX_CONTINUOUS
typeToCPX (TBinary) = CPX_BINARY
generateVarDic :: (Eq a, Ord a) => Constraints a -> M.Map a Int
generateVarDic (Constraints bounds) = foldr addBoundToDic M.empty bounds
where
addToDic (d :# v) m = M.insertWith (\new old -> old) v (M.size m) m
addBoundToDic (vs :< _ ) m = foldr addToDic m vs
addBoundToDic (vs :> _ ) m = foldr addToDic m vs
addBoundToDic (vs := _ ) m = foldr addToDic m vs
-- Change variables in bound to have ID
tokenizeConstraints :: (Ord a, Eq a) => Constraints a -> M.Map a Int -> Constraints Int
tokenizeConstraints (Constraints bounds) dic = Constraints $ map b2b bounds
where
v2v (d :# v) = d :# (dic M.! v)
b2b (vs :< d) = map v2v vs :< d
b2b (vs :> d) = map v2v vs :> d
b2b (vs := d) = map v2v vs := d
tokenizeObj :: (Ord a, Eq a) => Optimization a -> M.Map a Int -> Optimization Int
tokenizeObj obj dic = o2o obj
where
v2v (d :# v) = d :# (dic M.! v)
o2o (Minimize vs) = Minimize $ map v2v vs
o2o (Maximize vs) = Maximize $ map v2v vs
tokenizeBounds :: (Ord a, Eq a) => [(a,b,b)] -> M.Map a Int -> [(Int,b,b)]
tokenizeBounds xs dic = map (\(a,b,c) -> (dic M.! a, b, c) ) xs
tokenizeTypes :: (Ord a, Eq a) => [(a,b)] -> M.Map a Int -> [(Int, b)]
tokenizeTypes xs dic = map (\(a,b) -> (dic M.! a, b) ) xs