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

msakai / haskell-MIP / 344

04 Jan 2026 03:39PM UTC coverage: 76.98% (+0.3%) from 76.66%
344

push

github

web-flow
Merge cc7d6067e into 9f1c6930b

1565 of 2033 relevant lines covered (76.98%)

0.77 hits per line

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

85.35
/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.Either (lefts, rights)
47
import Data.List
48
import Data.Maybe
49
import Data.Scientific (Scientific, floatingOrInteger)
50
import Data.Map (Map)
51
import qualified Data.Map as Map
52
import Data.Set (Set)
53
import qualified Data.Set as Set
54
import Data.STRef
55
import Data.String
56
import qualified Data.Text as T
57
import qualified Data.Text.Lazy as TL
58
import Data.Text.Lazy.Builder (Builder)
59
import qualified Data.Text.Lazy.Builder as B
60
import qualified Data.Text.Lazy.Builder.Int as B
61
import qualified Data.Text.Lazy.Builder.Scientific as B
62
import Data.OptDir
63
import System.IO
64
import Text.Megaparsec hiding (label, skipManyTill, ParseError)
65
import Text.Megaparsec.Char hiding (string', char', newline)
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, readTextFile)
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
--
78
-- The source name is only used in error messages and may be the empty string.
79
parseString :: (Stream s, Token s ~ Char, IsString (Tokens s)) => MIP.FileOptions -> String -> s -> Either (ParseError s) (MIP.Problem Scientific)
80
parseString _ = parse (parser <* eof)
1✔
81

82
-- | Parse a file containing LP file data.
83
parseFile :: MIP.FileOptions -> FilePath -> IO (MIP.Problem Scientific)
84
parseFile opt fname = do
1✔
85
  s <- readTextFile opt fname
1✔
86
  case parse (parser <* eof) fname s of
×
87
    Left e -> throwIO (e :: ParseError TL.Text)
×
88
    Right a -> return a
1✔
89

90
-- ---------------------------------------------------------------------------
91

92
anyChar :: C e s m => m Char
93
anyChar = anySingle
1✔
94

95
char' :: C e s m => Char -> m Char
96
char' c = (char c <|> char (toUpper c)) <?> show c
1✔
97

98
string' :: C e s m => String -> m ()
99
string' s = mapM_ char' s <?> show s
×
100

101
sep :: C e s m => m ()
102
sep = skipMany (void comment <|> void spaceChar)
1✔
103

104
comment :: C e s m => m ()
105
comment = do
1✔
106
  char '\\'
1✔
107
  skipManyTill anyChar (try eol)
1✔
108

109
tok :: C e s m => m a -> m a
110
tok p = do
1✔
111
  x <- p
1✔
112
  sep
1✔
113
  return x
1✔
114

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

126
variable :: C e s m => m MIP.Var
127
variable = liftM fromString ident
1✔
128

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

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

146
-- ---------------------------------------------------------------------------
147

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

158
  cs <- liftM concat $ many $ msum $
1✔
159
    [ liftM (map Left) constraintSection
1✔
160
    , liftM (map Left) lazyConstraintsSection
1✔
161
    , liftM (map Right) userCutsSection
1✔
162
    ]
163

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

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

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

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

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

220
-- ---------------------------------------------------------------------------
221

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

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

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

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

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

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

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

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

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

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

290
type Bounds2 c = (Maybe (MIP.BoundExpr c), Maybe (MIP.BoundExpr c))
291

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

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

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

343
inf :: C e s m => m ()
344
inf = void (tok (string "inf" >> optional (string "inity")))
1✔
345

346
-- ---------------------------------------------------------------------------
347

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

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

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

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

381
-- ---------------------------------------------------------------------------
382

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

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

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

404
qexpr :: C e s m => m (MIP.Expr Scientific)
405
qexpr = do
1✔
406
  tok (char '[')
1✔
407
  t <- qterm True
1✔
408
  ts <- many (qterm False)
1✔
409
  let e = MIP.Expr (t:ts)
1✔
410
  tok (char ']')
1✔
411
  -- Gurobi allows ommiting "/2"
412
  (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✔
413
      return $ MIP.constExpr (1/2) * e)
1✔
414
   <|> return e
1✔
415

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

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

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

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

443
-- ---------------------------------------------------------------------------
444

445
type M a = Writer Builder a
446

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

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

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

456
-- ---------------------------------------------------------------------------
457

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

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

465
render' :: MIP.FileOptions -> MIP.Problem Scientific -> M ()
466
render' opt mip = do
1✔
467
  let newline =
1✔
468
        case fromMaybe LF (MIP.optNewline opt) of
1✔
469
          LF -> "\n"
1✔
470
          CRLF -> "\r\n"
1✔
471
      writeStringLn s = do
1✔
472
        writeString s
1✔
473
        writeString newline
1✔
474

475
  case MIP.name mip of
1✔
476
    Just name -> do
1✔
477
      writeString $ "\\* Problem: " <> name <> " *\\"
1✔
478
      writeString newline
1✔
479
    Nothing -> return ()
×
480

481
  let obj = MIP.objectiveFunction mip
1✔
482

483
  writeString $
1✔
484
    case MIP.objDir obj of
1✔
485
      OptMin -> "MINIMIZE"
1✔
486
      OptMax -> "MAXIMIZE"
1✔
487
  writeString newline
1✔
488

489
  renderLabel (MIP.objLabel obj)
1✔
490
  renderExpr newline True (MIP.objExpr obj)
×
491
  writeString newline
1✔
492

493
  writeStringLn "SUBJECT TO"
1✔
494
  forM_ (MIP.constraints mip) $ \c -> do
1✔
495
    unless (MIP.constrIsLazy c) $ do
1✔
496
      renderConstraint newline c
×
497
      writeString newline
1✔
498

499
  let lcs = [c | c <- MIP.constraints mip, MIP.constrIsLazy c]
1✔
500
  unless (null lcs) $ do
1✔
501
    writeStringLn "LAZY CONSTRAINTS"
1✔
502
    forM_ lcs $ \c -> do
1✔
503
      renderConstraint newline c
×
504
      writeString newline
1✔
505

506
  let cuts = MIP.userCuts mip
1✔
507
  unless (null cuts) $ do
1✔
508
    writeStringLn "USER CUTS"
1✔
509
    forM_ cuts $ \c -> do
1✔
510
      renderConstraint newline c
×
511
      writeString newline
1✔
512

513
  let ivs = MIP.integerVariables mip `Set.union` MIP.semiIntegerVariables mip
1✔
514
      (bins,gens) = Set.partition (\v -> MIP.getBounds mip v == (MIP.Finite 0, MIP.Finite 1)) ivs
1✔
515
      scs = MIP.semiContinuousVariables mip `Set.union` MIP.semiIntegerVariables mip
1✔
516

517
  writeStringLn "BOUNDS"
1✔
518
  forM_ (Map.toAscList (MIP.varBounds mip)) $ \(v, (lb,ub)) -> do
1✔
519
    unless (v `Set.member` bins) $ do
1✔
520
      renderBoundExpr lb
1✔
521
      writeString " <= "
1✔
522
      writeVar v
1✔
523
      writeString " <= "
1✔
524
      renderBoundExpr ub
1✔
525
      writeString newline
1✔
526

527
  unless (Set.null gens) $ do
1✔
528
    writeStringLn "GENERALS"
1✔
529
    renderVariableList newline $ Set.toList gens
1✔
530

531
  unless (Set.null bins) $ do
1✔
532
    writeStringLn "BINARIES"
1✔
533
    renderVariableList newline $ Set.toList bins
1✔
534

535
  unless (Set.null scs) $ do
1✔
536
    writeStringLn "SEMI-CONTINUOUS"
1✔
537
    renderVariableList newline $ Set.toList scs
1✔
538

539
  unless (null (MIP.sosConstraints mip)) $ do
1✔
540
    writeStringLn "SOS"
1✔
541
    forM_ (MIP.sosConstraints mip) $ \(MIP.SOSConstraint l typ xs) -> do
1✔
542
      renderLabel l
1✔
543
      writeString $ case typ of
1✔
544
        MIP.SOS1 -> "S1"
1✔
545
        MIP.SOS2 -> "S2"
1✔
546
      writeString " ::"
1✔
547
      forM_ xs $ \(v, r) -> do
1✔
548
        writeString "  "
1✔
549
        writeVar v
1✔
550
        writeString " : "
1✔
551
        tell $ B.scientificBuilder r
1✔
552
      writeString newline
1✔
553

554
  writeStringLn "END"
1✔
555

556
-- FIXME: Gurobi は quadratic term が最後に一つある形式でないとダメっぽい
557
renderExpr :: T.Text -> Bool -> MIP.Expr Scientific -> M ()
558
renderExpr newline isObj e = fill newline 80 (ts1 ++ ts2)
×
559
  where
560
    (ts,qts) = partition isLin (MIP.terms e)
1✔
561
    isLin (MIP.Term _ [])  = True
×
562
    isLin (MIP.Term _ [_]) = True
1✔
563
    isLin _ = False
1✔
564

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

573
    f :: MIP.Term Scientific -> T.Text
574
    f (MIP.Term c [])  = showConstTerm c
×
575
    f (MIP.Term c [v]) = showCoeff c <> MIP.varName v
1✔
576
    f _ = error "should not happen"
×
577

578
    g :: MIP.Term Scientific -> T.Text
579
    g (MIP.Term c vs) =
1✔
580
      (if isObj then showCoeff (2*c) else showCoeff c) <>
1✔
581
      mconcat (intersperse " * " (map MIP.varName vs))
1✔
582

583
showValue :: Scientific -> T.Text
584
showValue = fromString . show
1✔
585

586
showCoeff :: Scientific -> T.Text
587
showCoeff c =
1✔
588
  if c' == 1
1✔
589
    then s
1✔
590
    else s <> showValue c' <> " "
1✔
591
  where
592
    c' = abs c
1✔
593
    s = if c >= 0 then "+ " else "- "
1✔
594

595
showConstTerm :: Scientific -> T.Text
596
showConstTerm c = s <> showValue (abs c)
×
597
  where
598
    s = if c >= 0 then "+ " else "- "
×
599

600
renderLabel :: Maybe MIP.Label -> M ()
601
renderLabel l =
1✔
602
  case l of
1✔
603
    Nothing -> return ()
×
604
    Just s -> writeString s >> writeString ": "
1✔
605

606
renderOp :: MIP.RelOp -> M ()
607
renderOp MIP.Le = writeString "<="
1✔
608
renderOp MIP.Ge = writeString ">="
1✔
609
renderOp MIP.Eql = writeString "="
1✔
610

611
renderConstraint :: T.Text -> MIP.Constraint Scientific -> M ()
612
renderConstraint newline c@MIP.Constraint{ MIP.constrExpr = e, MIP.constrLB = lb, MIP.constrUB = ub } = do
1✔
613
  renderLabel (MIP.constrLabel c)
1✔
614
  case MIP.constrIndicator c of
1✔
615
    Nothing -> return ()
×
616
    Just (v,vval) -> do
1✔
617
      writeVar v
1✔
618
      writeString " = "
1✔
619
      tell $
1✔
620
        case floatingOrInteger vval of
1✔
621
          Right (i :: Integer) -> B.decimal i
1✔
622
          Left (_ :: Double) -> B.scientificBuilder vval  -- should be error?
×
623
      writeString " -> "
1✔
624

625
  renderExpr newline False e
×
626
  writeChar ' '
1✔
627
  let (op, val) =
628
        case (lb, ub) of
1✔
629
          (MIP.NegInf, MIP.Finite x) -> (MIP.Le, x)
1✔
630
          (MIP.Finite x, MIP.PosInf) -> (MIP.Ge, x)
1✔
631
          (MIP.Finite x1, MIP.Finite x2) | x1==x2 -> (MIP.Eql, x1)
×
632
          _ -> error "Numeric.Optimization.MIP.LPFile.renderConstraint: should not happen"
×
633
  renderOp op
1✔
634
  writeChar ' '
1✔
635
  tell $ B.scientificBuilder val
1✔
636

637
renderBoundExpr :: MIP.BoundExpr Scientific -> M ()
638
renderBoundExpr (MIP.Finite r) = tell $ B.scientificBuilder r
1✔
639
renderBoundExpr MIP.NegInf = writeString "-inf"
1✔
640
renderBoundExpr MIP.PosInf = writeString "+inf"
1✔
641

642
renderVariableList :: T.Text -> [MIP.Var] -> M ()
643
renderVariableList newline vs = fill newline 80 (map MIP.varName vs) >> writeString newline
×
644

645
fill :: T.Text -> Int -> [T.Text] -> M ()
646
fill newline width str = go str 0
1✔
647
  where
648
    go [] _ = return ()
×
649
    go (x:xs) 0 = writeString x >> go xs (T.length x)
1✔
650
    go (x:xs) w =
651
      if w + 1 + T.length x <= width
×
652
        then writeChar ' ' >> writeString x >> go xs (w + 1 + T.length x)
1✔
653
        else writeString newline >> go (x:xs) 0
×
654

655
-- ---------------------------------------------------------------------------
656

657
{-
658
compileExpr :: Expr -> Maybe (Map Var Scientific)
659
compileExpr e = do
660
  xs <- forM e $ \(Term c vs) ->
661
    case vs of
662
      [v] -> return (v, c)
663
      _ -> mzero
664
  return (Map.fromList xs)
665
-}
666

667
-- ---------------------------------------------------------------------------
668

669
normalize :: (Eq r, Num r) => MIP.Problem r -> MIP.Problem r
670
normalize = removeEmptyExpr . removeRangeConstraints
1✔
671

672
removeRangeConstraints :: (Eq r, Num r) => MIP.Problem r -> MIP.Problem r
673
removeRangeConstraints prob = runST $ do
1✔
674
  vsRef <- newSTRef $ MIP.variables prob
×
675
  cntRef <- newSTRef (0::Int)
×
676
  newvsRef <- newSTRef []
1✔
677

678
  let gensym = do
×
679
        vs <- readSTRef vsRef
×
680
        let loop !c = do
×
681
              let v = fromString ("~r_" ++ show c)
×
682
              if v `Set.member` vs then
×
683
                loop (c+1)
×
684
              else do
×
685
                writeSTRef cntRef $! c+1
×
686
                modifySTRef vsRef (Set.insert v)
×
687
                return v
×
688
        loop =<< readSTRef cntRef
×
689

690
  cs2 <- forM (MIP.constraints prob) $ \c -> do
1✔
691
    case (MIP.constrLB c, MIP.constrUB c) of
1✔
692
      (MIP.NegInf, MIP.Finite _) -> return c
1✔
693
      (MIP.Finite _, MIP.PosInf) -> return c
1✔
694
      (MIP.Finite x1, MIP.Finite x2) | x1 == x2 -> return c
×
695
      (lb, ub) -> do
×
696
        v <- gensym
×
697
        modifySTRef newvsRef ((v, (lb,ub)) :)
×
698
        return $
×
699
          c
×
700
          { MIP.constrExpr = MIP.constrExpr c - MIP.varExpr v
×
701
          , MIP.constrLB = MIP.Finite 0
×
702
          , MIP.constrUB = MIP.Finite 0
×
703
          }
704

705
  newvs <- liftM reverse $ readSTRef newvsRef
1✔
706
  return $
1✔
707
    prob
1✔
708
    { MIP.constraints = cs2
1✔
709
    , MIP.varDomains = MIP.varDomains prob `Map.union` Map.fromList [(v, (MIP.ContinuousVariable, bs)) | (v,bs) <- newvs]
×
710
    }
711

712
removeEmptyExpr :: Num r => MIP.Problem r -> MIP.Problem r
713
removeEmptyExpr prob =
1✔
714
  prob
1✔
715
  { MIP.objectiveFunction = obj{ MIP.objExpr = convertExpr (MIP.objExpr obj) }
1✔
716
  , MIP.constraints = map convertConstr $ MIP.constraints prob
1✔
717
  , MIP.userCuts    = map convertConstr $ MIP.userCuts prob
1✔
718
  }
719
  where
720
    obj = MIP.objectiveFunction prob
1✔
721

722
    convertExpr (MIP.Expr []) = MIP.Expr [MIP.Term 0 [fromString "x0"]]
1✔
723
    convertExpr e = e
1✔
724

725
    convertConstr constr =
1✔
726
      constr
1✔
727
      { MIP.constrExpr = convertExpr $ MIP.constrExpr constr
1✔
728
      }
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