• 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

57.04
/MIP/src/Numeric/Optimization/MIP/Base.hs
1
{-# OPTIONS_GHC -Wall #-}
2
{-# OPTIONS_HADDOCK show-extensions #-}
3
{-# LANGUAGE CPP #-}
4
{-# LANGUAGE FlexibleInstances #-}
5
{-# LANGUAGE MultiParamTypeClasses #-}
6
{-# LANGUAGE PatternSynonyms #-}
7
{-# LANGUAGE TypeFamilies #-}
8
{-# LANGUAGE ViewPatterns #-}
9
-----------------------------------------------------------------------------
10
-- |
11
-- Module      :  Numeric.Optimization.MIP.Base
12
-- Copyright   :  (c) Masahiro Sakai 2011-2019
13
-- License     :  BSD-style
14
--
15
-- Maintainer  :  masahiro.sakai@gmail.com
16
-- Stability   :  provisional
17
-- Portability :  non-portable
18
--
19
-- Mixed-Integer Programming Problems with some commmonly used extensions
20
--
21
-----------------------------------------------------------------------------
22
module Numeric.Optimization.MIP.Base
23
  (
24
  -- * Mixed-Integer Programming (MIP) problem specification
25

26
  -- ** MIP problems
27
    Problem (..)
28

29
  -- *** Set of variables
30
  , variables
31
  , continuousVariables
32
  , integerVariables
33
  , binaryVariables
34
  , semiContinuousVariables
35
  , semiIntegerVariables
36

37
  -- *** Variable's attributes
38
  , varTypes
39
  , varType
40
  , getVarType
41
  , varBounds
42
  , getBounds
43

44
  -- ** Variables
45
  , Var (Var', Var)
46
  , varName
47
  , toVar
48
  , fromVar
49

50
  -- *** Variable domain
51
  , Domain
52

53
  -- *** Variable types
54
  , VarType (..)
55

56
  -- *** Variable bounds
57
  , BoundExpr
58
  , Extended (..)
59
  , Bounds
60
  , defaultBounds
61
  , defaultLB
62
  , defaultUB
63

64
  -- ** Labels
65
  , Label
66

67
  -- ** Expressions
68
  , Expr (Expr)
69
  , varExpr
70
  , constExpr
71
  , terms
72
  , Term (..)
73

74
  -- ** Objective function
75
  , OptDir (..)
76
  , ObjectiveFunction (..)
77

78
  -- ** Constraints
79

80
  -- *** Linear (or Quadratic or Polynomial) constraints
81
  , Constraint (..)
82
  , constrBounds
83
  , (.==.)
84
  , (.<=.)
85
  , (.>=.)
86
  , RelOp (..)
87

88
  -- *** SOS constraints
89
  , SOSType (SOS1, SOS2, S1, S2)
90
  , SOSConstraint (..)
91

92
  -- * Solutions
93
  , Solution (..)
94
  , Status (..)
95
  , meetStatus
96

97
  -- * Evaluation
98
  , Tol (..)
99
  , zeroTol
100
  , Eval (..)
101
  , isInDomain
102
  , isIntegral
103
  , isInBounds
104

105
  -- * File I/O
106
  , FileOptions (..)
107
  , WriteSetting (..)
108

109
  -- * Utilities
110
  , Default (..)
111
  , Variables (..)
112
  , intersectBounds
113
  , isAscii
114
  ) where
115

116
#if !MIN_VERSION_lattices(2,0,0)
117
import Algebra.Lattice
118
#endif
119
import Algebra.PartialOrd
120
import Control.Arrow ((***), second)
121
import Control.Monad
122
#if !MIN_VERSION_text(2,0,2)
123
import qualified Data.Char as Char
124
#endif
125
import Data.Default.Class
126
import Data.Foldable (toList)
127
import Data.Hashable
128
import Data.List (sortBy)
129
import Data.Map (Map)
130
import qualified Data.Map as Map
131
import Data.Maybe (catMaybes, fromMaybe, isJust)
132
import Data.Ord (comparing)
133
import Data.Sequence (Seq)
134
import qualified Data.Sequence as Seq
135
import Data.Set (Set)
136
import qualified Data.Set as Set
137
import Data.Interned (intern, unintern)
138
import Data.Interned.Text
139
import Data.ExtendedReal
140
import Data.OptDir
141
import Data.String
142
import qualified Data.Text as T
143
import System.IO (Newline (..), TextEncoding)
144

145
infix 4 .<=., .>=., .==.
146

147
-- ---------------------------------------------------------------------------
148

149
-- | A problem instance.
150
data Problem c
151
  = Problem
152
  { name :: Maybe T.Text
1✔
153
    -- ^ Problem name
154
  , objectiveFunction :: ObjectiveFunction c
1✔
155
    -- ^ Objective functions of the problem
156
  , constraints :: [Constraint c]
1✔
157
    -- ^ Constraints of the problem
158
    --
159
    -- Indicator constraints and lazy constraints are included in this list.
160
  , sosConstraints :: [SOSConstraint c]
1✔
161
    -- ^ Special ordered sets
162
  , userCuts :: [Constraint c]
1✔
163
    -- ^ User cuts
164
  , varDomains :: Map Var (Domain c)
1✔
165
    -- ^ Variable domains
166
  }
167
  deriving (Show, Eq, Ord)
×
168

169
instance Default (Problem c) where
170
  def = Problem
1✔
171
        { name = Nothing
×
172
        , objectiveFunction = def
1✔
173
        , constraints = []
1✔
174
        , sosConstraints = []
1✔
175
        , userCuts = []
×
176
        , varDomains = Map.empty
×
177
        }
178

179
instance Functor Problem where
×
180
  fmap f prob =
×
181
    prob
×
182
    { objectiveFunction = fmap f (objectiveFunction prob)
×
183
    , constraints       = map (fmap f) (constraints prob)
×
184
    , sosConstraints    = map (fmap f) (sosConstraints prob)
×
185
    , userCuts          = map (fmap f) (userCuts prob)
×
186
    , varDomains        = fmap (second (fmap f *** fmap f)) (varDomains prob)
×
187
    }
188

189
-- | Types of variables.
190
--
191
-- This is equivalent to:
192
--
193
-- @
194
-- 'fmap' 'fst' . 'varDomains'
195
-- @
196
varTypes :: Problem c -> Map Var VarType
197
varTypes = fmap fst . varDomains
×
198

199
{-# DEPRECATED varType "Use varTypes instead" #-}
200
-- | Types of variables.
201
--
202
-- Deprecated alias of 'varTypes'.
203
varType :: Problem c -> Map Var VarType
204
varType = varTypes
×
205

206
-- | Bounds of variables.
207
--
208
-- This is equivalent to:
209
--
210
-- @
211
-- 'fmap' 'snd' . 'varDomains'
212
-- @
213
varBounds :: Problem c -> Map Var (Bounds c)
214
varBounds = fmap snd . varDomains
1✔
215

216
-- | Label used for naming various elements of t'Problem'.
217
type Label = T.Text
218

219
-- ---------------------------------------------------------------------------
220

221
-- | Variables used in a t'Problem'.
222
newtype Var = Var' InternedText
223
  deriving Eq
1✔
224

225
pattern Var :: T.Text -> Var
226
pattern Var s <- Var' (unintern -> s) where
1✔
227
  Var s = Var' (intern s)
1✔
228

229
{-# COMPLETE Var #-}
230

231
instance IsString Var where
232
  fromString = Var' . fromString
1✔
233

234
instance Ord Var where
×
235
  compare (Var' a) (Var' b)
1✔
236
    | a == b = EQ
1✔
237
    | otherwise = compare (unintern a) (unintern b)
×
238

239
instance Show Var where
×
240
  showsPrec d (Var x) = showsPrec d x
×
241

242
instance Hashable Var where
×
243
#if MIN_VERSION_intern(0,9,3)
244
  hashWithSalt salt (Var' x) = hashWithSalt salt x
×
245
#else
246
  hashWithSalt salt (Var' x) = hashWithSalt salt (internedTextId x)
247
#endif
248

249
-- | Variable's name.
250
varName :: Var -> T.Text
251
varName (Var s) = s
1✔
252

253
{-# DEPRECATED toVar "Use fromString function or Var pattern instead" #-}
254
-- | Convert a string into a variable.
255
toVar :: String -> Var
256
toVar = fromString
×
257

258
{-# DEPRECATED fromVar "Use varName function or Var pattern instead" #-}
259
-- | Convert a variable into a string.
260
fromVar :: Var -> String
261
fromVar (Var s) = T.unpack s
×
262

263

264
-- | Domain of a variable consists of variable type ('VarType') and bounds ('Bounds').
265
--
266
-- @since 0.2.1.0
267
type Domain c = (VarType, Bounds c)
268

269
-- | Variable types.
270
--
271
-- Variables can take values depending on their types and their bounds ('Bounds').
272
data VarType
273
  = ContinuousVariable     -- ^ can take values from \(\{x \in \mathbb{R} \mid L \le x \le U\}\)
274
  | IntegerVariable        -- ^ can take values from \(\{x \in \mathbb{Z} \mid L \le x \le U\}\)
275
  | SemiContinuousVariable -- ^ can take values from \(\{0\} \cup \{x \in \mathbb{R} \mid L \le x \le U\}\)
276
  | SemiIntegerVariable    -- ^ can take values from \(\{0\} \cup \{x \in \mathbb{Z} \mid L \le x \le U\}\)
277
  deriving (Eq, Ord, Show)
×
278

279
instance Default VarType where
280
  def = ContinuousVariable
×
281

282
-- | Look up variable type.
283
getVarType :: Problem c -> Var -> VarType
284
getVarType mip v =
1✔
285
  case Map.lookup v (varDomains mip) of
1✔
286
    Just (vt, _) -> vt
1✔
287
    Nothing -> def
×
288

289
-- | Type for representing lower/upper bound of variables.
290
type BoundExpr c = Extended c
291

292
-- | Type for representing lower/upper bound of variables.
293
type Bounds c = (BoundExpr c, BoundExpr c)
294

295
-- | Default bounds.
296
defaultBounds :: Num c => Bounds c
297
defaultBounds = (defaultLB, defaultUB)
1✔
298

299
-- | Default lower bound (0).
300
defaultLB :: Num c => BoundExpr c
301
defaultLB = Finite 0
1✔
302

303
-- | Default upper bound (+∞).
304
defaultUB :: BoundExpr c
305
defaultUB = PosInf
1✔
306

307
-- | Look up bounds for a variable.
308
getBounds :: Num c => Problem c -> Var -> Bounds c
309
getBounds mip v =
1✔
310
  case Map.lookup v (varDomains mip) of
1✔
311
    Just (_, bs) -> bs
1✔
312
    Nothing -> defaultBounds
×
313

314
-- | Intersection of two 'Bounds'.
315
intersectBounds :: Ord c => Bounds c -> Bounds c -> Bounds c
316
intersectBounds (lb1,ub1) (lb2,ub2) = (max lb1 lb2, min ub1 ub2)
×
317

318
-- ---------------------------------------------------------------------------
319

320
-- | Arithmetic expressions.
321
--
322
-- Essentialy an expression is a sequence of t'Term's.
323
newtype Expr c = Expr' (Seq (Term c))
324
  deriving (Eq, Ord)
×
325

326
pattern Expr :: [Term c] -> Expr c
327
pattern Expr ts <- Expr' (toList -> ts) where
1✔
328
  Expr ts = Expr' (Seq.fromList ts)
1✔
329

330
{-# COMPLETE Expr #-}
331

332
instance Show c => Show (Expr c) where
×
333
  showsPrec d (Expr ts) = showParen (d > app_prec) $
×
334
    showString "Expr " . showsPrec (app_prec+1) ts
×
335
    where
336
      app_prec = 10
×
337

338
-- | Variable expression.
339
varExpr :: Num c => Var -> Expr c
340
varExpr v = Expr' $ Seq.singleton $ Term 1 [v]
1✔
341

342
-- | Constant expression.
343
constExpr :: (Eq c, Num c) => c -> Expr c
344
constExpr 0 = Expr' Seq.empty
1✔
345
constExpr c = Expr' $ Seq.singleton $ Term c []
1✔
346

347
-- | Terms of an expression.
348
terms :: Expr c -> [Term c]
349
terms (Expr ts) = ts
1✔
350

351
instance Num c => Num (Expr c) where
1✔
352
  Expr' e1 + Expr' e2 = Expr' (e1 <> e2)
1✔
353
  Expr e1 * Expr e2 = Expr [Term (c1*c2) (vs1 ++ vs2) | Term c1 vs1 <- e1, Term c2 vs2 <- e2]
1✔
354
  negate (Expr' e) = Expr' $ fmap (\(Term c vs) -> Term (-c) vs) e
1✔
355
  abs = id
×
356
  signum _ = 1
×
357
  fromInteger 0 = Expr' Seq.empty
1✔
358
  fromInteger c = Expr' $ Seq.singleton $ Term (fromInteger c) []
1✔
359

360
instance Functor Expr where
×
361
  fmap f (Expr' ts) = Expr' $ fmap (fmap f) ts
×
362

363
-- | Split an expression into an expression without constant term and a constant.
364
splitConst :: Num c => Expr c -> (Expr c, c)
365
splitConst (Expr' ts) = (e2, c2)
1✔
366
  where
367
    p (Term _ (_:_)) = True
1✔
368
    p _ = False
×
369
    e2 = Expr' $ Seq.filter p ts
1✔
370
    c2 = sum [c | Term c [] <- toList ts]
×
371

372
-- | Terms.
373
data Term c = Term c [Var]
374
  deriving (Eq, Ord, Show)
×
375

376
instance Functor Term where
×
377
  fmap f (Term c vs) = Term (f c) vs
×
378

379
-- ---------------------------------------------------------------------------
380

381
-- | Objective function.
382
data ObjectiveFunction c
383
  = ObjectiveFunction
384
  { objLabel :: Maybe Label
1✔
385
  , objDir :: OptDir
1✔
386
  , objExpr :: Expr c
1✔
387
  }
388
  deriving (Eq, Ord, Show)
×
389

390
instance Default (ObjectiveFunction c) where
391
  def =
1✔
392
    ObjectiveFunction
1✔
393
    { objLabel = Nothing
×
394
    , objDir = OptMin
×
395
    , objExpr = Expr []
1✔
396
    }
397

398
instance Functor ObjectiveFunction where
×
399
  fmap f obj = obj{ objExpr = fmap f (objExpr obj) }
×
400

401
-- ---------------------------------------------------------------------------
402

403
-- | Constraint.
404
--
405
-- In the most general case, it is of the form @x = v → L ≤ e ≤ U@.
406
data Constraint c
407
  = Constraint
408
  { constrLabel     :: Maybe Label
1✔
409
    -- ^ name of the constraint
410
  , constrIndicator :: Maybe (Var, c)
1✔
411
    -- ^ @x = v@ (v is either 0 or 1)
412
  , constrExpr      :: Expr c
1✔
413
    -- ^ expression @e@
414
  , constrLB        :: BoundExpr c
1✔
415
    -- ^ lower bound @L@
416
  , constrUB        :: BoundExpr c
1✔
417
    -- ^ upper bound @U@
418
  , constrIsLazy    :: Bool
1✔
419
    -- ^ if it is set to @True@, solver can delay adding the constraint until the constraint is violated.
420
  }
421
  deriving (Eq, Ord, Show)
×
422

423
-- | Lower- and Upper- bounds of a t'Constraint'.
424
--
425
-- @since 0.2.1.0
426
constrBounds :: Constraint c -> Bounds c
427
constrBounds c = (constrLB c, constrUB c)
×
428

429
-- | Equality constraint.
430
(.==.) :: Num c => Expr c -> Expr c -> Constraint c
431
lhs .==. rhs =
×
432
  case splitConst (lhs - rhs) of
×
433
    (e, c) -> def{ constrExpr = e, constrLB = Finite (- c), constrUB = Finite (- c) }
×
434

435
-- | Inequality constraint (≤).
436
(.<=.) :: Num c => Expr c -> Expr c -> Constraint c
437
lhs .<=. rhs =
1✔
438
  case splitConst (lhs - rhs) of
1✔
439
    (e, c) -> def{ constrExpr = e, constrUB = Finite (- c) }
1✔
440

441
-- | Inequality constraint (≥).
442
(.>=.) :: Num c => Expr c -> Expr c -> Constraint c
443
lhs .>=. rhs =
×
444
  case splitConst (lhs - rhs) of
×
445
    (e, c) -> def{ constrExpr = e, constrLB = Finite (- c) }
×
446

447
instance Default (Constraint c) where
448
  def = Constraint
1✔
449
        { constrLabel = Nothing
×
450
        , constrIndicator = Nothing
1✔
451
        , constrExpr = Expr []
×
452
        , constrLB = NegInf
1✔
453
        , constrUB = PosInf
×
454
        , constrIsLazy = False
×
455
        }
456

457
instance Functor Constraint where
×
458
  fmap f c =
×
459
    c
×
460
    { constrIndicator = fmap (second f) (constrIndicator c)
×
461
    , constrExpr = fmap f (constrExpr c)
×
462
    , constrLB = fmap f (constrLB c)
×
463
    , constrUB = fmap f (constrUB c)
×
464
    }
465

466
-- | Relational operators.
467
data RelOp
468
  = Le  -- ^ (≤)
469
  | Ge  -- ^ (≥)
470
  | Eql -- ^ (=)
471
  deriving (Eq, Ord, Enum, Show)
×
472

473
-- ---------------------------------------------------------------------------
474

475
-- | Types of SOS (special ordered sets) constraints.
476
data SOSType
477
  = SOS1 -- ^ Type 1 SOS constraint
478
  | SOS2 -- ^ Type 2 SOS constraint
479
  deriving (Eq, Ord, Enum, Bounded, Show, Read)
×
480

481
-- {-# DEPRECATED S1 "Use SOS1 instead" #-}
482
-- | Alias of 'SOS1'.
483
pattern S1 :: SOSType
484
pattern S1 = SOS1
1✔
485

486
-- {-# DEPRECATED S2 "Use SOS2 instead" #-}
487
-- | Alias of 'SOS2'.
488
pattern S2 :: SOSType
489
pattern S2 = SOS2
1✔
490

491
{-# COMPLETE S1, S2 #-}
492

493
-- | SOS (special ordered sets) constraints.
494
data SOSConstraint c
495
  = SOSConstraint
496
  { sosLabel :: Maybe Label
1✔
497
  , sosType  :: SOSType
1✔
498
  , sosBody  :: [(Var, c)]
1✔
499
  }
500
  deriving (Eq, Ord, Show)
×
501

502
instance Functor SOSConstraint where
×
503
  fmap f c = c{ sosBody = map (second f) (sosBody c) }
×
504

505
instance Default (SOSConstraint c) where
506
  def = SOSConstraint
1✔
507
        { sosLabel = Nothing
×
508
        , sosType = SOS1
×
509
        , sosBody = []
×
510
        }
511

512
-- ---------------------------------------------------------------------------
513

514
-- | MIP status with the following partial order:
515
--
516
-- <<doc-images/MIP-Status-diagram.png>>
517
data Status
518
  = StatusUnknown
519
  | StatusFeasible
520
  | StatusOptimal
521
  | StatusInfeasibleOrUnbounded
522
  | StatusInfeasible
523
  | StatusUnbounded
524
  deriving (Eq, Ord, Enum, Bounded, Show)
×
525

526
instance PartialOrd Status where
×
527
  leq a b = (a,b) `Set.member` rel
1✔
528
    where
529
      rel = unsafeLfpFrom rel0 $ \r ->
1✔
530
        Set.union r (Set.fromList [(x,z) | (x,y) <- Set.toList r, (y',z) <- Set.toList r, y == y'])
1✔
531
      rel0 = Set.fromList $
1✔
532
        [(x,x) | x <- [minBound .. maxBound]] ++
1✔
533
        [ (StatusUnknown, StatusFeasible)
1✔
534
        , (StatusUnknown, StatusInfeasibleOrUnbounded)
1✔
535
        , (StatusFeasible, StatusOptimal)
1✔
536
        , (StatusFeasible, StatusUnbounded)
1✔
537
        , (StatusInfeasibleOrUnbounded, StatusUnbounded)
1✔
538
        , (StatusInfeasibleOrUnbounded, StatusInfeasible)
1✔
539
        ]
540

541
-- | /meet/ (greatest lower bound) operator of the partial order of 'Status' type.
542
--
543
-- If the version of @lattices@ is \<2, then @MeetSemiLattice@ instance can also be used.
544
meetStatus :: Status -> Status -> Status
545
StatusUnknown `meetStatus` _b = StatusUnknown
1✔
546
StatusFeasible `meetStatus` b
547
  | StatusFeasible `leq` b = StatusFeasible
1✔
548
  | otherwise = StatusUnknown
×
549
StatusOptimal `meetStatus` StatusOptimal = StatusOptimal
1✔
550
StatusOptimal `meetStatus` b
551
  | StatusFeasible `leq` b = StatusFeasible
1✔
552
  | otherwise = StatusUnknown
×
553
StatusInfeasibleOrUnbounded `meetStatus` b
554
  | StatusInfeasibleOrUnbounded `leq` b = StatusInfeasibleOrUnbounded
1✔
555
  | otherwise = StatusUnknown
×
556
StatusInfeasible `meetStatus` StatusInfeasible = StatusInfeasible
1✔
557
StatusInfeasible `meetStatus` b
558
  | StatusInfeasibleOrUnbounded `leq` b = StatusInfeasibleOrUnbounded
1✔
559
  | otherwise = StatusUnknown
×
560
StatusUnbounded `meetStatus` StatusUnbounded = StatusUnbounded
1✔
561
StatusUnbounded `meetStatus` b
562
  | StatusFeasible `leq` b = StatusFeasible
1✔
563
  | StatusInfeasibleOrUnbounded `leq` b = StatusInfeasibleOrUnbounded
1✔
564
  | otherwise = StatusUnknown
×
565

566
#if !MIN_VERSION_lattices(2,0,0)
567

568
instance MeetSemiLattice Status where
569
  meet = meetStatus
570

571
#endif
572

573

574
-- | Type for representing a solution of MIP problem.
575
data Solution r
576
  = Solution
577
  { solStatus :: Status
1✔
578
    -- ^ status
579
  , solObjectiveValue :: Maybe r
1✔
580
    -- ^ value of the objective function
581
  , solVariables :: Map Var r
1✔
582
    -- ^ variable assignments
583
  }
584
  deriving (Eq, Ord, Show)
×
585

586
instance Functor Solution where
×
587
  fmap f (Solution status obj vs) = Solution status (fmap f obj) (fmap f vs)
1✔
588

589
instance Default (Solution r) where
590
  def = Solution
1✔
591
        { solStatus = StatusUnknown
×
592
        , solObjectiveValue = Nothing
×
593
        , solVariables = Map.empty
×
594
        }
595

596
-- ---------------------------------------------------------------------------
597

598
-- | Tolerance for evaluating solutions against t'Problem'.
599
data Tol r
600
  = Tol
601
  { integralityTol :: r
1✔
602
    -- ^ If a value of integer variable is within this amount from its nearest
603
    -- integer, it is considered feasible.
604
  , feasibilityTol :: r
1✔
605
    -- ^ If the amount of violation of constraints is within this amount, it is
606
    -- considered feasible.
607
  , optimalityTol :: r
1✔
608
    -- ^ Feasiblity tolerance of dual constraints.
609
  }
610
  deriving (Eq, Ord, Show)
×
611

612
-- | Defautl is @1e-6@ for the feasibility and optimality tolerances, and @1e-5@ for the integrality tolerance.
613
instance Fractional r => Default (Tol r) where
614
  def =
1✔
615
    Tol
1✔
616
    { integralityTol = 1e-5
1✔
617
    , feasibilityTol = 1e-6
1✔
618
    , optimalityTol = 1e-6
1✔
619
    }
620

621
-- | t'Tol' value with all tolerances are zero.
622
zeroTol :: Fractional r => Tol r
623
zeroTol =
×
624
  Tol
×
625
  { integralityTol = 1e-5
×
626
  , feasibilityTol = 1e-6
×
627
  , optimalityTol = 1e-6
×
628
  }
629

630
-- | Type class for evaluation various elements of t'Problem' under
631
-- the given variable assignments.
632
class Eval r a where
633
  -- | Result type of 'eval'
634
  type Evaluated r a
635

636
  -- | Evaluate a value of type @a@ under given assignments and the tolerance
637
  eval :: Tol r -> Map Var r -> a -> Evaluated r a
638

639
instance Num r => Eval r Var where
640
  type Evaluated r Var = r
641
  eval _tol sol v = fromMaybe 0 (Map.lookup v sol)
×
642

643
instance Num r => Eval r (Term r) where
644
  type Evaluated r (Term r) = r
645
  eval tol sol (Term c vs) = product (c : [eval tol sol v | v <- vs])
×
646

647
instance Num r => Eval r (Expr r) where
648
  type Evaluated r (Expr r) = r
649
  eval tol sol expr = sum [eval tol sol t | t <- terms expr]
×
650

651
instance Num r => Eval r (ObjectiveFunction r) where
652
  type Evaluated r (ObjectiveFunction r) = r
653
  eval tol sol obj = eval tol sol (objExpr obj)
×
654

655
instance (Num r, Ord r) => Eval r (Constraint r) where
656
  type Evaluated r (Constraint r) = Bool
657
  eval tol sol constr =
1✔
658
    not (evalIndicator (constrIndicator constr)) ||
1✔
659
    isInBounds tol (constrLB constr, constrUB constr) (eval tol sol (constrExpr constr))
×
660
    where
661
      evalIndicator Nothing = True
1✔
662
      evalIndicator (Just (v, val')) = isInBounds tol (Finite val', Finite val') (eval tol sol v)
×
663

664
instance (Num r, Ord r) => Eval r (SOSConstraint r) where
665
  type Evaluated r (SOSConstraint r) = Bool
666
  eval tol sol sos =
1✔
667
    case sosType sos of
1✔
668
      SOS1 -> length [() | val <- body, val] <= 1
×
669
      SOS2 -> f body
1✔
670
    where
671
      body = map (not . isInBounds tol (0, 0) . eval tol sol . fst) $ sortBy (comparing snd) (sosBody sos)
×
672
      f [] = True
×
673
      f [_] = True
1✔
674
      f (x1 : x2 : xs)
675
        | x1 = all not xs
1✔
676
        | otherwise = f (x2 : xs)
×
677

678
instance (RealFrac r) => Eval r (Problem r) where
679
  type Evaluated r (Problem r) = Maybe r
680
  eval tol sol prob = do
1✔
681
    forM_ (Map.toList (varDomains prob)) $ \(v, dom) -> do
1✔
682
      guard $ isInDomain tol dom (eval tol sol v)
×
683
    forM_ (constraints prob) $ \constr -> do
×
684
      guard $ eval tol sol constr
×
685
    forM_ (sosConstraints prob) $ \constr -> do
×
686
      guard $ eval tol sol constr
×
687
    return $ eval tol sol (objectiveFunction prob)
×
688

689
-- | Under the given tolerance, is the value included in the domain?
690
--
691
-- @since 0.2.1.0
692
isInDomain :: RealFrac r => Tol r -> Domain r -> r -> Bool
693
isInDomain tol (vt, bounds) x = isJust $ do
1✔
694
  case vt of
1✔
695
    ContinuousVariable -> return ()
×
696
    SemiContinuousVariable -> return ()
×
697
    IntegerVariable -> guard $ isIntegral tol x
1✔
698
    SemiIntegerVariable -> guard $ isIntegral tol x
1✔
699
  case vt of
1✔
700
    ContinuousVariable -> guard $ isInBounds tol bounds x
1✔
701
    IntegerVariable -> guard $ isInBounds tol bounds x
1✔
702
    SemiIntegerVariable -> guard $ isInBounds tol (0,0) x || isInBounds tol bounds x
1✔
703
    SemiContinuousVariable -> guard $ isInBounds tol (0,0) x || isInBounds tol bounds x
1✔
704

705
-- | Under the given tolerance, is the value integral?
706
--
707
-- @since 0.2.1.0
708
isIntegral :: RealFrac r => Tol r -> r -> Bool
709
isIntegral tol x = abs (x - fromIntegral (floor (x + 0.5) :: Integer)) <= integralityTol tol
1✔
710

711
-- | Under the given tolerance, is the value within the bounds?
712
--
713
-- @since 0.2.1.0
714
isInBounds :: (Num r, Ord r) => Tol r -> Bounds r -> r -> Bool
715
isInBounds tol (lb, ub) x =
1✔
716
  lb - Finite (feasibilityTol tol) <= Finite x &&
1✔
717
  Finite x <= ub + Finite (feasibilityTol tol)
1✔
718

719
-- ---------------------------------------------------------------------------
720

721
-- | Type class for types that contain variables.
722
class Variables a where
723
  vars :: a -> Set Var
724

725
instance Variables a => Variables [a] where
726
  vars = Set.unions . map vars
1✔
727

728
instance (Variables a, Variables b) => Variables (Either a b) where
729
  vars (Left a)  = vars a
1✔
730
  vars (Right b) = vars b
1✔
731

732
instance Variables (Problem c) where
733
  vars = variables
1✔
734

735
instance Variables (Expr c) where
736
  vars (Expr e) = vars e
1✔
737

738
instance Variables (Term c) where
739
  vars (Term _ xs) = Set.fromList xs
1✔
740

741
instance Variables Var where
742
  vars = Set.singleton
×
743

744
instance Variables (ObjectiveFunction c) where
745
  vars ObjectiveFunction{ objExpr = e } = vars e
1✔
746

747
instance Variables (Constraint c) where
748
  vars Constraint{ constrIndicator = ind, constrExpr = e } = Set.union (vars e) vs2
1✔
749
    where
750
      vs2 = maybe Set.empty (Set.singleton . fst) ind
1✔
751

752
instance Variables (SOSConstraint c) where
753
  vars SOSConstraint{ sosBody = xs } = Set.fromList (map fst xs)
1✔
754

755
-- ---------------------------------------------------------------------------
756

757
-- | Set of variables of a t'Problem'.
758
variables :: Problem c -> Set Var
759
variables mip = Map.keysSet $ varDomains mip
1✔
760

761
-- | Set of continuous variables of a t'Problem'.
762
continuousVariables :: Problem c -> Set Var
763
continuousVariables mip = Map.keysSet $ Map.filter ((ContinuousVariable ==) . fst) (varDomains mip)
×
764

765
-- | Set of integer variables of a t'Problem'.
766
integerVariables :: Problem c -> Set Var
767
integerVariables mip = Map.keysSet $ Map.filter ((IntegerVariable ==) . fst) (varDomains mip)
1✔
768

769
-- | Set of binary variables (integers variables with lower bound 0 and upper bound 1) of a t'Problem'.
770
binaryVariables :: (Num c, Eq c) => Problem c -> Set Var
771
binaryVariables mip = Map.keysSet $ Map.filter p (varDomains mip)
×
772
  where
773
    p (IntegerVariable, (Finite 0, Finite 1)) = True
×
774
    p (_, _) = False
×
775

776
-- | Set of semi-continuous variables of a t'Problem'.
777
semiContinuousVariables :: Problem c -> Set Var
778
semiContinuousVariables mip = Map.keysSet $ Map.filter ((SemiContinuousVariable ==) . fst) (varDomains mip)
1✔
779

780
-- | Set of semi-integer variables of a t'Problem'.
781
semiIntegerVariables :: Problem c -> Set Var
782
semiIntegerVariables mip = Map.keysSet $ Map.filter ((SemiIntegerVariable ==) . fst) (varDomains mip)
1✔
783

784
-- ---------------------------------------------------------------------------
785

786
-- | Options for reading/writing problem files.
787
data FileOptions
788
  = FileOptions
789
  { optFileEncoding :: Maybe TextEncoding
1✔
790
    -- ^ Text encoding used for file I/O
791
  , optNewline :: Maybe Newline
1✔
792
    -- ^ 'Newline' used for 'T.Text' data generation and writing to file.
793
    --
794
    -- If 'Nothing' is specified, 'LF' is used for text data generation
795
    -- assuming that newline conversion will be performed on I/O, and
796
    -- 'nativeNewline' is used for file writing.
797
    --
798
    -- (Default: 'Nothing')
799
  , optMPSWriteObjSense :: WriteSetting
1✔
800
    -- ^ @OBJSENSE@ section in MPS file is an extention of MPS file
801
    -- format for specifying the direction of the objective function
802
    -- in MPS file. But not all solvers support it (e.g. GLPK-4.48
803
    -- does not support it).
804
    --
805
    -- This option controls whether the @OBJSENSE@ sections is written.
806
    -- If 'WriteIfNotDefault' is used, @OBJSENSE@ is written when the
807
    -- objective is maximization and @OBJSENSE@ is not written
808
    -- when the objective is minimization.
809
    --
810
    -- (Default: 'WriteIfNotDefault')
811
  , optMPSWriteObjName :: Bool
1✔
812
    -- ^ @OBJNAME@ section is an extention of MPS file format for
813
    -- selecting an objective function from among the free rows within
814
    -- a MPS file. Not all solver support it (e.g. GLPK-4.48
815
    -- does not support it).
816
    --
817
    -- This option controls whether the @OBJNAME@ section is written.
818
    --
819
    -- (Default: 'True')
820
  } deriving (Show)
×
821

822
instance Default FileOptions where
823
  def =
1✔
824
    FileOptions
1✔
825
    { optFileEncoding = Nothing
1✔
826
    , optNewline = Nothing
1✔
827
    , optMPSWriteObjSense = WriteIfNotDefault
1✔
828
    , optMPSWriteObjName = True
1✔
829
    }
830

831
-- | Options for writing a particular data to files.
832
data WriteSetting
833
  = WriteAlways
834
    -- ^ Always write the data.
835
  | WriteIfNotDefault
836
    -- ^ Write the data only if it is not the default value.
837
  | WriteNever
838
    -- ^ Never write the data.
839
  deriving (Eq, Ord, Enum, Bounded, Show, Read)
×
840

841
-- | Checks if all variable names and labels in the problem are ASCII.
842
isAscii :: Problem c -> Bool
843
isAscii prob = and
1✔
844
  [ all p $ catMaybes [name prob, objLabel (objectiveFunction prob)]
1✔
845
  , all p $ catMaybes $ map constrLabel $ constraints prob
1✔
846
  , all p $ catMaybes $ map constrLabel $ userCuts prob
1✔
847
  , all p $ catMaybes $ map sosLabel $ sosConstraints prob
1✔
848
  , all (p . varName) $ Map.keys (varDomains prob)
1✔
849
  ]
850
  where
851
#if MIN_VERSION_text(2,0,2)
852
    p = T.isAscii
1✔
853
#else
854
    p = T.all Char.isAscii
855
#endif
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