• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

msakai / haskell-MIP / 212

14 Jan 2025 03:47AM UTC coverage: 58.751% (-0.04%) from 58.79%
212

push

github

web-flow
Merge pull request #50 from msakai/fix-lp-indicator

Fix handling of right hand side of indicator in LP files

1 of 5 new or added lines in 1 file covered. (20.0%)

1 existing line in 1 file now uncovered.

1138 of 1937 relevant lines covered (58.75%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.36
/MIP/src/Numeric/Optimization/MIP/LPFile.hs
1
{-# OPTIONS_GHC -Wall -fno-warn-unused-do-bind #-}
2
{-# OPTIONS_HADDOCK show-extensions #-}
3
{-# LANGUAGE BangPatterns #-}
4
{-# LANGUAGE ConstraintKinds #-}
5
{-# LANGUAGE FlexibleContexts #-}
6
{-# LANGUAGE OverloadedStrings #-}
7
{-# LANGUAGE ScopedTypeVariables #-}
8
{-# LANGUAGE TypeFamilies #-}
9
{-# LANGUAGE TypeOperators #-}
10
-----------------------------------------------------------------------------
11
-- |
12
-- Module      :  Numeric.Optimization.MIP.LPFile
13
-- Copyright   :  (c) Masahiro Sakai 2011-2014
14
-- License     :  BSD-style
15
--
16
-- Maintainer  :  masahiro.sakai@gmail.com
17
-- Stability   :  provisional
18
-- Portability :  non-portable
19
--
20
-- A CPLEX @.lp@ format parser library.
21
--
22
-- References:
23
--
24
-- * <http://publib.boulder.ibm.com/infocenter/cosinfoc/v12r2/index.jsp?topic=/ilog.odms.cplex.help/Content/Optimization/Documentation/CPLEX/_pubskel/CPLEX880.html>
25
--
26
-- * <http://www.gurobi.com/doc/45/refman/node589.html>
27
--
28
-- * <http://lpsolve.sourceforge.net/5.5/CPLEX-format.htm>
29
--
30
-----------------------------------------------------------------------------
31
module Numeric.Optimization.MIP.LPFile
32
  ( parseString
33
  , parseFile
34
  , ParseError
35
  , parser
36
  , render
37
  ) where
38

39
import Control.Applicative hiding (many)
40
import Control.Exception (throwIO)
41
import Control.Monad
42
import Control.Monad.Writer
43
import Control.Monad.ST
44
import Data.Char
45
import Data.Default.Class
46
import Data.List
47
import Data.Maybe
48
import Data.Scientific (Scientific, floatingOrInteger)
49
import Data.Map (Map)
50
import qualified Data.Map as Map
51
import Data.Set (Set)
52
import qualified Data.Set as Set
53
import Data.STRef
54
import Data.String
55
import qualified Data.Text as T
56
import qualified Data.Text.Lazy as TL
57
import Data.Text.Lazy.Builder (Builder)
58
import qualified Data.Text.Lazy.Builder as B
59
import qualified Data.Text.Lazy.Builder.Int as B
60
import qualified Data.Text.Lazy.Builder.Scientific as B
61
import qualified Data.Text.Lazy.IO as TLIO
62
import Data.OptDir
63
import System.IO
64
import Text.Megaparsec hiding (label, skipManyTill, ParseError)
65
import Text.Megaparsec.Char hiding (string', char')
66
import qualified Text.Megaparsec.Char.Lexer as P
67

68
import qualified Numeric.Optimization.MIP.Base as MIP
69
import Numeric.Optimization.MIP.FileUtils (ParseError)
70
import Numeric.Optimization.MIP.Internal.Util (combineMaybe)
71

72
-- ---------------------------------------------------------------------------
73

74
type C e s m = (MonadParsec e s m, Token s ~ Char, IsString (Tokens s))
75

76
-- | Parse a string containing LP file data.
77
-- The source name is only used in error messages and may be the empty string.
78
parseString :: (Stream s, Token s ~ Char, IsString (Tokens s)) => MIP.FileOptions -> String -> s -> Either (ParseError s) (MIP.Problem Scientific)
79
parseString _ = parse (parser <* eof)
1✔
80

81
-- | Parse a file containing LP file data.
82
parseFile :: MIP.FileOptions -> FilePath -> IO (MIP.Problem Scientific)
83
parseFile opt fname = do
1✔
84
  h <- openFile fname ReadMode
1✔
85
  case MIP.optFileEncoding opt of
1✔
86
    Nothing -> return ()
×
87
    Just enc -> hSetEncoding h enc
×
88
  ret <- parse (parser <* eof) fname <$> TLIO.hGetContents h
×
89
  case ret of
1✔
90
    Left e -> throwIO (e :: ParseError TL.Text)
×
91
    Right a -> return a
×
92

93
-- ---------------------------------------------------------------------------
94

95
anyChar :: C e s m => m Char
96
anyChar = anySingle
1✔
97

98
char' :: C e s m => Char -> m Char
99
char' c = (char c <|> char (toUpper c)) <?> show c
1✔
100

101
string' :: C e s m => String -> m ()
102
string' s = mapM_ char' s <?> show s
×
103

104
sep :: C e s m => m ()
105
sep = skipMany ((comment >> return ()) <|> (spaceChar >> return ()))
×
106

107
comment :: C e s m => m ()
108
comment = do
1✔
109
  char '\\'
1✔
110
  skipManyTill anyChar (try eol)
1✔
111

112
tok :: C e s m => m a -> m a
113
tok p = do
1✔
114
  x <- p
1✔
115
  sep
1✔
116
  return x
1✔
117

118
ident :: C e s m => m String
119
ident = tok $ do
1✔
120
  x <- letterChar <|> oneOf syms1
1✔
121
  xs <- many (alphaNumChar <|> oneOf syms2)
1✔
122
  let s = x:xs
1✔
123
  guard $ map toLower s `Set.notMember` reserved
1✔
124
  return s
1✔
125
  where
126
    syms1 = "!\"#$%&()/,;?@_`'{}|~"
1✔
127
    syms2 = '.' : syms1
1✔
128

129
variable :: C e s m => m MIP.Var
130
variable = liftM fromString ident
1✔
131

132
label :: C e s m => m MIP.Label
133
label = do
1✔
134
  name <- ident
1✔
135
  tok $ char ':'
1✔
136
  return $! T.pack name
1✔
137

138
reserved :: Set String
139
reserved = Set.fromList
1✔
140
  [ "bound", "bounds"
1✔
141
  , "gen", "general", "generals"
1✔
142
  , "bin", "binary", "binaries"
1✔
143
  , "semi", "semi-continuous", "semis"
1✔
144
  , "sos"
1✔
145
  , "end"
1✔
146
  , "subject"
1✔
147
  ]
148

149
-- ---------------------------------------------------------------------------
150

151
-- | LP file parser
152
parser :: (MonadParsec e s m, Token s ~ Char, IsString (Tokens s)) => m (MIP.Problem Scientific)
153
parser = do
1✔
154
  name <- optional $ try $ do
1✔
155
    space
1✔
156
    string' "\\* Problem: "
1✔
157
    liftM fromString $ manyTill anyChar (try (string " *\\" >> eol))
×
158
  sep
1✔
159
  obj <- problem
1✔
160

161
  cs <- liftM concat $ many $ msum $
1✔
162
    [ liftM (map Left) constraintSection
1✔
163
    , liftM (map Left) lazyConstraintsSection
×
164
    , liftM (map Right) userCutsSection
×
165
    ]
166

167
  bnds <- option Map.empty (try boundsSection)
×
168
  exvs <- many (liftM Left generalSection <|> liftM Right binarySection)
×
169
  let ints = Set.fromList $ concat [x | Left  x <- exvs]
1✔
170
      bins = Set.fromList $ concat [x | Right x <- exvs]
×
171
  bnds2 <- return $ Map.unionWith MIP.intersectBounds
×
172
            bnds (Map.fromAscList [(v, (MIP.Finite 0, MIP.Finite 1)) | v <- Set.toAscList bins])
×
173
  scs <- liftM Set.fromList $ option [] (try semiSection)
1✔
174

175
  ss <- option [] (try sosSection)
1✔
176
  end
1✔
177
  let vs = Set.unions $ map MIP.vars cs ++
1✔
178
           [ Map.keysSet bnds2
1✔
179
           , ints
1✔
180
           , bins
1✔
181
           , scs
1✔
182
           , MIP.vars obj
1✔
183
           , MIP.vars ss
1✔
184
           ]
185
      isInt v  = v `Set.member` ints || v `Set.member` bins
1✔
186
      isSemi v = v `Set.member` scs
1✔
187
  return $
1✔
188
    MIP.Problem
1✔
189
    { MIP.name              = name
1✔
190
    , MIP.objectiveFunction = obj
1✔
191
    , MIP.constraints       = [c | Left c <- cs]
1✔
192
    , MIP.userCuts          = [c | Right c <- cs]
×
193
    , MIP.sosConstraints    = ss
1✔
194
    , MIP.varType           = Map.fromAscList
1✔
195
       [ ( v
1✔
196
         , if isInt v then
1✔
197
             if isSemi v then MIP.SemiIntegerVariable
×
198
             else MIP.IntegerVariable
1✔
199
           else
200
             if isSemi v then MIP.SemiContinuousVariable
×
201
             else MIP.ContinuousVariable
1✔
202
         )
203
       | v <- Set.toAscList vs ]
1✔
204
    , MIP.varBounds         = Map.fromAscList [ (v, Map.findWithDefault MIP.defaultBounds v bnds2) | v <- Set.toAscList vs]
1✔
205
    }
206

207
problem :: C e s m => m (MIP.ObjectiveFunction Scientific)
208
problem = do
1✔
209
  flag <-  (try minimize >> return OptMin)
1✔
210
       <|> (try maximize >> return OptMax)
1✔
211
  name <- optional (try label)
1✔
212
  obj <- expr
1✔
213
  return def{ MIP.objLabel = name, MIP.objDir = flag, MIP.objExpr = obj }
1✔
214

215
minimize, maximize :: C e s m => m ()
216
minimize = tok $ string' "min" >> optional (string' "imize") >> return ()
×
217
maximize = tok $ string' "max" >> optional (string' "imize") >> return ()
×
218

219
end :: C e s m => m ()
220
end = tok $ string' "end"
1✔
221

222
-- ---------------------------------------------------------------------------
223

224
constraintSection :: C e s m => m [MIP.Constraint Scientific]
225
constraintSection = subjectTo >> many (try (constraint False))
1✔
226

227
subjectTo :: C e s m => m ()
228
subjectTo = msum
1✔
229
  [ try $ tok (string' "subject") >> tok (string' "to")
1✔
230
  , try $ tok (string' "such") >> tok (string' "that")
×
231
  , try $ tok (string' "st")
1✔
232
  , try $ tok (string' "s") >> optional (tok (char '.')) >> tok (string' "t")
×
233
        >> tok (char '.') >> return ()
×
234
  ]
235

236
constraint :: C e s m => Bool -> m (MIP.Constraint Scientific)
237
constraint isLazy = do
1✔
238
  name <- optional (try label)
1✔
239
  g <- optional $ try indicator
1✔
240

241
  -- It seems that CPLEX allows empty lhs, but GLPK rejects it.
242
  e <- expr
1✔
243
  op <- relOp
1✔
244
  s <- option 1 sign
1✔
245
  rhs <- liftM (s*) number
1✔
246

247
  let (lb,ub) =
248
        case op of
1✔
249
          MIP.Le -> (MIP.NegInf, MIP.Finite rhs)
1✔
250
          MIP.Ge -> (MIP.Finite rhs, MIP.PosInf)
1✔
251
          MIP.Eql -> (MIP.Finite rhs, MIP.Finite rhs)
1✔
252

253
  return $ MIP.Constraint
1✔
254
    { MIP.constrLabel     = name
1✔
255
    , MIP.constrIndicator = g
1✔
256
    , MIP.constrExpr      = e
1✔
257
    , MIP.constrLB        = lb
1✔
258
    , MIP.constrUB        = ub
1✔
259
    , MIP.constrIsLazy    = isLazy
1✔
260
    }
261

262
relOp :: C e s m => m MIP.RelOp
263
relOp = tok $ msum
1✔
264
  [ char '<' >> optional (char '=') >> return MIP.Le
1✔
265
  , char '>' >> optional (char '=') >> return MIP.Ge
1✔
266
  , char '=' >> msum [ char '<' >> return MIP.Le
×
267
                     , char '>' >> return MIP.Ge
×
268
                     , return MIP.Eql
1✔
269
                     ]
270
  ]
271

272
indicator :: C e s m => m (MIP.Var, Scientific)
273
indicator = do
1✔
274
  var <- variable
1✔
275
  tok (char '=')
1✔
276
  val <- number  -- numbers other than 0 or 1 should be error?
1✔
277
  tok $ string "->"
1✔
278
  return (var, val)
×
279

280
lazyConstraintsSection :: C e s m => m [MIP.Constraint Scientific]
281
lazyConstraintsSection = do
1✔
282
  tok $ string' "lazy"
1✔
283
  tok $ string' "constraints"
×
284
  many $ try $ constraint True
×
285

286
userCutsSection :: C e s m => m [MIP.Constraint Scientific]
287
userCutsSection = do
1✔
288
  tok $ string' "user"
1✔
289
  tok $ string' "cuts"
×
290
  many $ try $ constraint False
×
291

292
type Bounds2 c = (Maybe (MIP.BoundExpr c), Maybe (MIP.BoundExpr c))
293

294
boundsSection :: C e s m => m (Map MIP.Var (MIP.Bounds Scientific))
295
boundsSection = do
1✔
296
  tok $ string' "bound" >> optional (char' 's')
1✔
297
  liftM (Map.map g . Map.fromListWith f) $ many (try bound)
×
298
  where
299
    f (lb1,ub1) (lb2,ub2) = (combineMaybe max lb1 lb2, combineMaybe min ub1 ub2)
×
300
    g (lb, ub) = ( fromMaybe MIP.defaultLB lb
×
301
                 , fromMaybe MIP.defaultUB ub
1✔
302
                 )
303

304
bound :: C e s m => m (MIP.Var, Bounds2 Scientific)
305
bound = msum
1✔
306
  [ try $ do
1✔
307
      v <- try variable
1✔
308
      msum
1✔
309
        [ do
1✔
310
            op <- relOp
1✔
311
            b <- boundExpr
1✔
312
            return
1✔
313
              ( v
×
314
              , case op of
×
315
                  MIP.Le -> (Nothing, Just b)
×
316
                  MIP.Ge -> (Just b, Nothing)
×
317
                  MIP.Eql -> (Just b, Just b)
×
318
              )
319
        , do
×
320
            tok $ string' "free"
×
321
            return (v, (Just MIP.NegInf, Just MIP.PosInf))
×
322
        ]
323
  , do
1✔
324
      b1 <- liftM Just boundExpr
1✔
325
      op1 <- relOp
1✔
326
      guard $ op1 == MIP.Le
1✔
327
      v <- variable
1✔
328
      b2 <- option Nothing $ do
1✔
329
        op2 <- relOp
1✔
330
        guard $ op2 == MIP.Le
1✔
331
        liftM Just boundExpr
1✔
332
      return (v, (b1, b2))
1✔
333
  ]
334

335
boundExpr :: C e s m => m (MIP.BoundExpr Scientific)
336
boundExpr = msum
1✔
337
  [ try (tok (char '+') >> inf >> return MIP.PosInf)
1✔
338
  , try (tok (char '-') >> inf >> return MIP.NegInf)
1✔
339
  , do
1✔
340
      s <- option 1 sign
1✔
341
      x <- number
1✔
342
      return $ MIP.Finite (s*x)
1✔
343
  ]
344

345
inf :: C e s m => m ()
346
inf = tok (string "inf" >> optional (string "inity")) >> return ()
×
347

348
-- ---------------------------------------------------------------------------
349

350
generalSection :: C e s m => m [MIP.Var]
351
generalSection = do
1✔
352
  tok $ string' "gen" >> optional (string' "eral" >> optional (string' "s"))
1✔
353
  many (try variable)
1✔
354

355
binarySection :: C e s m => m [MIP.Var]
356
binarySection = do
1✔
357
  tok $ string' "bin" >> optional (string' "ar" >> (string' "y" <|> string' "ies"))
1✔
358
  many (try variable)
1✔
359

360
semiSection :: C e s m => m [MIP.Var]
361
semiSection = do
1✔
362
  tok $ string' "semi" >> optional (string' "-continuous" <|> string' "s")
×
363
  many (try variable)
×
364

365
sosSection :: C e s m => m [MIP.SOSConstraint Scientific]
366
sosSection = do
1✔
367
  tok $ string' "sos"
1✔
368
  many $ try $ do
×
369
    (l,t) <- try (do{ l <- label; t <- typ; return (Just l, t) })
×
370
          <|> (do{ t <- typ; return (Nothing, t) })
×
371
    xs <- many $ try $ do
×
372
      v <- variable
×
373
      tok $ char ':'
×
374
      w <- number
×
375
      return (v,w)
×
376
    return $ MIP.SOSConstraint l t xs
×
377
  where
378
    typ = do
×
379
      t <- tok $ (char' 's' >> ((char '1' >> return MIP.S1) <|> (char '2' >> return MIP.S2)))
×
380
      tok (string "::")
×
381
      return t
×
382

383
-- ---------------------------------------------------------------------------
384

385
expr :: forall e s m. C e s m => m (MIP.Expr Scientific)
386
expr = try expr1 <|> return 0
×
387
  where
388
    expr1 :: m (MIP.Expr Scientific)
389
    expr1 = do
1✔
390
      t <- term True
1✔
391
      ts <- many (term False)
1✔
392
      return $ foldr (+) 0 (t : ts)
1✔
393

394
sign :: (C e s m, Num a) => m a
395
sign = tok ((char '+' >> return 1) <|> (char '-' >> return (-1)))
1✔
396

397
term :: C e s m => Bool -> m (MIP.Expr Scientific)
398
term flag = do
1✔
399
  s <- if flag then optional sign else liftM Just sign
1✔
400
  c <- optional number
1✔
401
  e <- liftM MIP.varExpr variable <|> qexpr
1✔
402
  return $ case combineMaybe (*) s c of
1✔
403
    Nothing -> e
1✔
404
    Just d -> MIP.constExpr d * e
1✔
405

406
qexpr :: C e s m => m (MIP.Expr Scientific)
407
qexpr = do
1✔
408
  tok (char '[')
1✔
409
  t <- qterm True
1✔
410
  ts <- many (qterm False)
1✔
411
  let e = MIP.Expr (t:ts)
×
412
  tok (char ']')
1✔
413
  -- Gurobi allows ommiting "/2"
414
  (do mapM_ (tok . char) ("/2" :: String) -- Explicit type signature is necessary because the type of mapM_ in GHC-7.10 is generalized for arbitrary Foldable
1✔
415
      return $ MIP.constExpr (1/2) * e)
×
416
   <|> return e
×
417

418
qterm :: C e s m => Bool -> m (MIP.Term Scientific)
419
qterm flag = do
1✔
420
  s <- if flag then optional sign else liftM Just sign
×
421
  c <- optional number
1✔
422
  es <- do
1✔
423
    e <- qfactor
1✔
424
    es <- many (tok (char '*') >> qfactor)
1✔
425
    return $ e ++ concat es
×
426
  return $ case combineMaybe (*) s c of
×
427
    Nothing -> MIP.Term 1 es
×
428
    Just d -> MIP.Term d es
×
429

430
qfactor :: C e s m => m [MIP.Var]
431
qfactor = do
1✔
432
  v <- variable
1✔
433
  msum [ tok (char '^') >> tok (char '2') >> return [v,v]
×
434
       , return [v]
×
435
       ]
436

437
number :: forall e s m. C e s m => m Scientific
438
number = tok $ P.signed sep P.scientific
×
439

440
skipManyTill :: Alternative m => m a -> m end -> m ()
441
skipManyTill p end' = scan
1✔
442
  where
443
    scan = (end' *> pure ()) <|> (p *> scan)
×
444

445
-- ---------------------------------------------------------------------------
446

447
type M a = Writer Builder a
448

449
execM :: M a -> TL.Text
450
execM m = B.toLazyText $ execWriter m
1✔
451

452
writeString :: T.Text -> M ()
453
writeString s = tell $ B.fromText s
1✔
454

455
writeChar :: Char -> M ()
456
writeChar c = tell $ B.singleton c
1✔
457

458
-- ---------------------------------------------------------------------------
459

460
-- | Render a problem into a 'TL.Text' containing LP file data.
461
render :: MIP.FileOptions -> MIP.Problem Scientific -> Either String TL.Text
462
render _ mip = Right $ execM $ render' $ normalize mip
1✔
463

464
writeVar :: MIP.Var -> M ()
465
writeVar (MIP.Var v) = writeString v
1✔
466

467
render' :: MIP.Problem Scientific -> M ()
468
render' mip = do
1✔
469
  case MIP.name mip of
1✔
470
    Just name -> writeString $ "\\* Problem: " <> name <> " *\\\n"
×
471
    Nothing -> return ()
×
472

473
  let obj = MIP.objectiveFunction mip
1✔
474

475
  writeString $
1✔
476
    case MIP.objDir obj of
1✔
477
      OptMin -> "MINIMIZE"
1✔
478
      OptMax -> "MAXIMIZE"
1✔
479
  writeChar '\n'
1✔
480

481
  renderLabel (MIP.objLabel obj)
1✔
482
  renderExpr True (MIP.objExpr obj)
×
483
  writeChar '\n'
1✔
484

485
  writeString "SUBJECT TO\n"
1✔
486
  forM_ (MIP.constraints mip) $ \c -> do
1✔
487
    unless (MIP.constrIsLazy c) $ do
1✔
488
      renderConstraint c
1✔
489
      writeChar '\n'
1✔
490

491
  let lcs = [c | c <- MIP.constraints mip, MIP.constrIsLazy c]
×
492
  unless (null lcs) $ do
×
493
    writeString "LAZY CONSTRAINTS\n"
×
494
    forM_ lcs $ \c -> do
×
495
      renderConstraint c
×
496
      writeChar '\n'
×
497

498
  let cuts = [c | c <- MIP.userCuts mip]
×
499
  unless (null cuts) $ do
×
500
    writeString "USER CUTS\n"
×
501
    forM_ cuts $ \c -> do
×
502
      renderConstraint c
×
503
      writeChar '\n'
×
504

505
  let ivs = MIP.integerVariables mip `Set.union` MIP.semiIntegerVariables mip
1✔
506
      (bins,gens) = Set.partition (\v -> MIP.getBounds mip v == (MIP.Finite 0, MIP.Finite 1)) ivs
1✔
507
      scs = MIP.semiContinuousVariables mip `Set.union` MIP.semiIntegerVariables mip
1✔
508

509
  writeString "BOUNDS\n"
1✔
510
  forM_ (Map.toAscList (MIP.varBounds mip)) $ \(v, (lb,ub)) -> do
1✔
511
    unless (v `Set.member` bins) $ do
1✔
512
      renderBoundExpr lb
1✔
513
      writeString " <= "
1✔
514
      writeVar v
1✔
515
      writeString " <= "
1✔
516
      renderBoundExpr ub
1✔
517
      writeChar '\n'
1✔
518

519
  unless (Set.null gens) $ do
1✔
520
    writeString "GENERALS\n"
1✔
521
    renderVariableList $ Set.toList gens
1✔
522

523
  unless (Set.null bins) $ do
×
524
    writeString "BINARIES\n"
×
525
    renderVariableList $ Set.toList bins
×
526

527
  unless (Set.null scs) $ do
×
528
    writeString "SEMI-CONTINUOUS\n"
×
529
    renderVariableList $ Set.toList scs
×
530

531
  unless (null (MIP.sosConstraints mip)) $ do
×
532
    writeString "SOS\n"
×
533
    forM_ (MIP.sosConstraints mip) $ \(MIP.SOSConstraint l typ xs) -> do
×
534
      renderLabel l
×
535
      writeString $ fromString $ show typ
×
536
      writeString " ::"
×
537
      forM_ xs $ \(v, r) -> do
×
538
        writeString "  "
×
539
        writeVar v
×
540
        writeString " : "
×
541
        tell $ B.scientificBuilder r
×
542
      writeChar '\n'
×
543

544
  writeString "END\n"
1✔
545

546
-- FIXME: Gurobi は quadratic term が最後に一つある形式でないとダメっぽい
547
renderExpr :: Bool -> MIP.Expr Scientific -> M ()
548
renderExpr isObj e = fill 80 (ts1 ++ ts2)
1✔
549
  where
550
    (ts,qts) = partition isLin (MIP.terms e)
1✔
551
    isLin (MIP.Term _ [])  = True
×
552
    isLin (MIP.Term _ [_]) = True
1✔
553
    isLin _ = False
×
554

555
    ts1 = map f ts
1✔
556
    ts2
1✔
557
      | null qts  = []
×
558
      | otherwise =
×
559
        -- マイナスで始めるとSCIP 2.1.1 は「cannot have '-' in front of quadratic part ('[')」というエラーを出す
560
        -- SCIP-3.1.0 does not allow spaces between '/' and '2'.
561
        ["+ ["] ++ map g qts ++ [if isObj then "] /2" else "]"]
×
562

563
    f :: MIP.Term Scientific -> T.Text
564
    f (MIP.Term c [])  = showConstTerm c
×
565
    f (MIP.Term c [v]) = showCoeff c <> MIP.varName v
1✔
566
    f _ = error "should not happen"
×
567

568
    g :: MIP.Term Scientific -> T.Text
569
    g (MIP.Term c vs) =
×
570
      (if isObj then showCoeff (2*c) else showCoeff c) <>
×
571
      mconcat (intersperse " * " (map MIP.varName vs))
×
572

573
showValue :: Scientific -> T.Text
574
showValue = fromString . show
1✔
575

576
showCoeff :: Scientific -> T.Text
577
showCoeff c =
1✔
578
  if c' == 1
1✔
579
    then s
1✔
580
    else s <> showValue c' <> " "
1✔
581
  where
582
    c' = abs c
1✔
583
    s = if c >= 0 then "+ " else "- "
1✔
584

585
showConstTerm :: Scientific -> T.Text
586
showConstTerm c = s <> showValue (abs c)
×
587
  where
588
    s = if c >= 0 then "+ " else "- "
×
589

590
renderLabel :: Maybe MIP.Label -> M ()
591
renderLabel l =
1✔
592
  case l of
1✔
593
    Nothing -> return ()
×
594
    Just s -> writeString s >> writeString ": "
1✔
595

596
renderOp :: MIP.RelOp -> M ()
597
renderOp MIP.Le = writeString "<="
1✔
598
renderOp MIP.Ge = writeString ">="
1✔
599
renderOp MIP.Eql = writeString "="
1✔
600

601
renderConstraint :: MIP.Constraint Scientific -> M ()
602
renderConstraint c@MIP.Constraint{ MIP.constrExpr = e, MIP.constrLB = lb, MIP.constrUB = ub } = do
1✔
603
  renderLabel (MIP.constrLabel c)
1✔
604
  case MIP.constrIndicator c of
1✔
605
    Nothing -> return ()
×
606
    Just (v,vval) -> do
×
607
      writeVar v
×
608
      writeString " = "
×
NEW
609
      tell $
×
NEW
610
        case floatingOrInteger vval of
×
NEW
611
          Right (i :: Integer) -> B.decimal i
×
NEW
612
          Left (_ :: Double) -> B.scientificBuilder vval  -- should be error?
×
UNCOV
613
      writeString " -> "
×
614

615
  renderExpr False e
×
616
  writeChar ' '
1✔
617
  let (op, val) =
618
        case (lb, ub) of
1✔
619
          (MIP.NegInf, MIP.Finite x) -> (MIP.Le, x)
1✔
620
          (MIP.Finite x, MIP.PosInf) -> (MIP.Ge, x)
1✔
621
          (MIP.Finite x1, MIP.Finite x2) | x1==x2 -> (MIP.Eql, x1)
×
622
          _ -> error "Numeric.Optimization.MIP.LPFile.renderConstraint: should not happen"
×
623
  renderOp op
1✔
624
  writeChar ' '
1✔
625
  tell $ B.scientificBuilder val
1✔
626

627
renderBoundExpr :: MIP.BoundExpr Scientific -> M ()
628
renderBoundExpr (MIP.Finite r) = tell $ B.scientificBuilder r
1✔
629
renderBoundExpr MIP.NegInf = writeString "-inf"
1✔
630
renderBoundExpr MIP.PosInf = writeString "+inf"
1✔
631

632
renderVariableList :: [MIP.Var] -> M ()
633
renderVariableList vs = fill 80 (map MIP.varName vs) >> writeChar '\n'
1✔
634

635
fill :: Int -> [T.Text] -> M ()
636
fill width str = go str 0
1✔
637
  where
638
    go [] _ = return ()
×
639
    go (x:xs) 0 = writeString x >> go xs (T.length x)
1✔
640
    go (x:xs) w =
641
      if w + 1 + T.length x <= width
×
642
        then writeChar ' ' >> writeString x >> go xs (w + 1 + T.length x)
1✔
643
        else writeChar '\n' >> go (x:xs) 0
×
644

645
-- ---------------------------------------------------------------------------
646

647
{-
648
compileExpr :: Expr -> Maybe (Map Var Scientific)
649
compileExpr e = do
650
  xs <- forM e $ \(Term c vs) ->
651
    case vs of
652
      [v] -> return (v, c)
653
      _ -> mzero
654
  return (Map.fromList xs)
655
-}
656

657
-- ---------------------------------------------------------------------------
658

659
normalize :: (Eq r, Num r) => MIP.Problem r -> MIP.Problem r
660
normalize = removeEmptyExpr . removeRangeConstraints
1✔
661

662
removeRangeConstraints :: (Eq r, Num r) => MIP.Problem r -> MIP.Problem r
663
removeRangeConstraints prob = runST $ do
1✔
664
  vsRef <- newSTRef $ MIP.variables prob
×
665
  cntRef <- newSTRef (0::Int)
×
666
  newvsRef <- newSTRef []
1✔
667

668
  let gensym = do
×
669
        vs <- readSTRef vsRef
×
670
        let loop !c = do
×
671
              let v = fromString ("~r_" ++ show c)
×
672
              if v `Set.member` vs then
×
673
                loop (c+1)
×
674
              else do
×
675
                writeSTRef cntRef $! c+1
×
676
                modifySTRef vsRef (Set.insert v)
×
677
                return v
×
678
        loop =<< readSTRef cntRef
×
679

680
  cs2 <- forM (MIP.constraints prob) $ \c -> do
1✔
681
    case (MIP.constrLB c, MIP.constrUB c) of
1✔
682
      (MIP.NegInf, MIP.Finite _) -> return c
1✔
683
      (MIP.Finite _, MIP.PosInf) -> return c
1✔
684
      (MIP.Finite x1, MIP.Finite x2) | x1 == x2 -> return c
×
685
      (lb, ub) -> do
×
686
        v <- gensym
×
687
        modifySTRef newvsRef ((v, (lb,ub)) :)
×
688
        return $
×
689
          c
×
690
          { MIP.constrExpr = MIP.constrExpr c - MIP.varExpr v
×
691
          , MIP.constrLB = MIP.Finite 0
×
692
          , MIP.constrUB = MIP.Finite 0
×
693
          }
694

695
  newvs <- liftM reverse $ readSTRef newvsRef
1✔
696
  return $
1✔
697
    prob
1✔
698
    { MIP.constraints = cs2
1✔
699
    , MIP.varType = MIP.varType prob `Map.union` Map.fromList [(v, MIP.ContinuousVariable) | (v,_) <- newvs]
×
700
    , MIP.varBounds = MIP.varBounds prob `Map.union` (Map.fromList newvs)
1✔
701
    }
702

703
removeEmptyExpr :: Num r => MIP.Problem r -> MIP.Problem r
704
removeEmptyExpr prob =
1✔
705
  prob
1✔
706
  { MIP.objectiveFunction = obj{ MIP.objExpr = convertExpr (MIP.objExpr obj) }
1✔
707
  , MIP.constraints = map convertConstr $ MIP.constraints prob
1✔
708
  , MIP.userCuts    = map convertConstr $ MIP.userCuts prob
×
709
  }
710
  where
711
    obj = MIP.objectiveFunction prob
1✔
712

713
    convertExpr (MIP.Expr []) = MIP.Expr [MIP.Term 0 [fromString "x0"]]
1✔
714
    convertExpr e = e
1✔
715

716
    convertConstr constr =
1✔
717
      constr
1✔
718
      { MIP.constrExpr = convertExpr $ MIP.constrExpr constr
1✔
719
      }
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc