|
1 | 1 | {-# LANGUAGE LambdaCase #-} |
2 | 2 |
|
3 | | -{-| |
4 | | -Module : Linear.Simplex.Util |
5 | | -Description : Helper functions |
6 | | -Copyright : (c) Junaid Rasheed, 2020-2022 |
7 | | -License : BSD-3 |
8 | | - |
9 | | -Stability : experimental |
10 | | -
|
11 | | -Helper functions for performing the two-phase simplex method. |
12 | | --} |
| 3 | +-- | |
| 4 | +-- Module : Linear.Simplex.Util |
| 5 | +-- Description : Helper functions |
| 6 | +-- Copyright : (c) Junaid Rasheed, 2020-2022 |
| 7 | +-- License : BSD-3 |
| 8 | + |
| 9 | +-- Stability : experimental |
| 10 | +-- |
| 11 | +-- Helper functions for performing the two-phase simplex method. |
13 | 12 | module Linear.Simplex.Util where |
14 | 13 |
|
15 | | -import Prelude hiding (EQ); |
16 | | -import Linear.Simplex.Types |
17 | | -import Data.List |
18 | 14 | import Data.Bifunctor |
| 15 | +import Data.List |
| 16 | +import Linear.Simplex.Types |
| 17 | +import Prelude hiding (EQ) |
19 | 18 |
|
20 | | --- |Is the given 'ObjectiveFunction' to be 'Max'imized? |
| 19 | +-- | Is the given 'ObjectiveFunction' to be 'Max'imized? |
21 | 20 | isMax :: ObjectiveFunction -> Bool |
22 | 21 | isMax (Max _) = True |
23 | 22 | isMax (Min _) = False |
24 | 23 |
|
25 | | --- |Extract the objective ('VarConstMap') from an 'ObjectiveFunction' |
| 24 | +-- | Extract the objective ('VarConstMap') from an 'ObjectiveFunction' |
26 | 25 | getObjective :: ObjectiveFunction -> VarConstMap |
27 | 26 | getObjective (Max o) = o |
28 | 27 | getObjective (Min o) = o |
29 | 28 |
|
30 | | --- |Simplifies a system of 'PolyConstraint's by first calling 'simplifyPolyConstraint', |
31 | | --- then reducing 'LEQ' and 'GEQ' with same LHS and RHS (and other similar situations) into 'EQ', |
32 | | --- and finally removing duplicate elements using 'nub'. |
| 29 | +-- | Simplifies a system of 'PolyConstraint's by first calling 'simplifyPolyConstraint', |
| 30 | +-- then reducing 'LEQ' and 'GEQ' with same LHS and RHS (and other similar situations) into 'EQ', |
| 31 | +-- and finally removing duplicate elements using 'nub'. |
33 | 32 | simplifySystem :: [PolyConstraint] -> [PolyConstraint] |
34 | 33 | simplifySystem = nub . reduceSystem . map simplifyPolyConstraint |
35 | 34 | where |
36 | 35 | reduceSystem :: [PolyConstraint] -> [PolyConstraint] |
37 | 36 | reduceSystem [] = [] |
38 | 37 | -- Reduce LEQ with matching GEQ and EQ into EQ |
39 | 38 | reduceSystem ((LEQ lhs rhs) : pcs) = |
40 | | - let |
41 | | - matchingConstraints = |
42 | | - filter |
43 | | - (\case |
44 | | - GEQ lhs' rhs' -> lhs == lhs' && rhs == rhs' |
45 | | - EQ lhs' rhs' -> lhs == lhs' && rhs == rhs' |
46 | | - _ -> False |
47 | | - ) |
48 | | - pcs |
49 | | - in |
50 | | - if null matchingConstraints |
51 | | - then LEQ lhs rhs : reduceSystem pcs |
52 | | - else EQ lhs rhs : reduceSystem (pcs \\ matchingConstraints) |
| 39 | + let matchingConstraints = |
| 40 | + filter |
| 41 | + ( \case |
| 42 | + GEQ lhs' rhs' -> lhs == lhs' && rhs == rhs' |
| 43 | + EQ lhs' rhs' -> lhs == lhs' && rhs == rhs' |
| 44 | + _ -> False |
| 45 | + ) |
| 46 | + pcs |
| 47 | + in if null matchingConstraints |
| 48 | + then LEQ lhs rhs : reduceSystem pcs |
| 49 | + else EQ lhs rhs : reduceSystem (pcs \\ matchingConstraints) |
53 | 50 | -- Reduce GEQ with matching LEQ and EQ into EQ |
54 | 51 | reduceSystem ((GEQ lhs rhs) : pcs) = |
55 | | - let |
56 | | - matchingConstraints = |
57 | | - filter |
58 | | - (\case |
59 | | - LEQ lhs' rhs' -> lhs == lhs' && rhs == rhs' |
60 | | - EQ lhs' rhs' -> lhs == lhs' && rhs == rhs' |
61 | | - _ -> False |
62 | | - ) |
63 | | - pcs |
64 | | - in |
65 | | - if null matchingConstraints |
66 | | - then GEQ lhs rhs : reduceSystem pcs |
67 | | - else EQ lhs rhs : reduceSystem (pcs \\ matchingConstraints) |
| 52 | + let matchingConstraints = |
| 53 | + filter |
| 54 | + ( \case |
| 55 | + LEQ lhs' rhs' -> lhs == lhs' && rhs == rhs' |
| 56 | + EQ lhs' rhs' -> lhs == lhs' && rhs == rhs' |
| 57 | + _ -> False |
| 58 | + ) |
| 59 | + pcs |
| 60 | + in if null matchingConstraints |
| 61 | + then GEQ lhs rhs : reduceSystem pcs |
| 62 | + else EQ lhs rhs : reduceSystem (pcs \\ matchingConstraints) |
68 | 63 | -- Reduce EQ with matching LEQ and GEQ into EQ |
69 | 64 | reduceSystem ((EQ lhs rhs) : pcs) = |
70 | | - let |
71 | | - matchingConstraints = |
72 | | - filter |
73 | | - (\case |
74 | | - LEQ lhs' rhs' -> lhs == lhs' && rhs == rhs' |
75 | | - GEQ lhs' rhs' -> lhs == lhs' && rhs == rhs' |
76 | | - _ -> False |
77 | | - ) |
78 | | - pcs |
79 | | - in |
80 | | - if null matchingConstraints |
81 | | - then EQ lhs rhs : reduceSystem pcs |
82 | | - else EQ lhs rhs : reduceSystem (pcs \\ matchingConstraints) |
| 65 | + let matchingConstraints = |
| 66 | + filter |
| 67 | + ( \case |
| 68 | + LEQ lhs' rhs' -> lhs == lhs' && rhs == rhs' |
| 69 | + GEQ lhs' rhs' -> lhs == lhs' && rhs == rhs' |
| 70 | + _ -> False |
| 71 | + ) |
| 72 | + pcs |
| 73 | + in if null matchingConstraints |
| 74 | + then EQ lhs rhs : reduceSystem pcs |
| 75 | + else EQ lhs rhs : reduceSystem (pcs \\ matchingConstraints) |
83 | 76 |
|
84 | | --- |Simplify an 'ObjectiveFunction' by first 'sort'ing and then calling 'foldSumVarConstMap' on the 'VarConstMap'. |
| 77 | +-- | Simplify an 'ObjectiveFunction' by first 'sort'ing and then calling 'foldSumVarConstMap' on the 'VarConstMap'. |
85 | 78 | simplifyObjectiveFunction :: ObjectiveFunction -> ObjectiveFunction |
86 | 79 | simplifyObjectiveFunction (Max varConstMap) = Max (foldSumVarConstMap (sort varConstMap)) |
87 | 80 | simplifyObjectiveFunction (Min varConstMap) = Min (foldSumVarConstMap (sort varConstMap)) |
88 | 81 |
|
89 | | --- |Simplify a 'PolyConstraint' by first 'sort'ing and then calling 'foldSumVarConstMap' on the 'VarConstMap'. |
| 82 | +-- | Simplify a 'PolyConstraint' by first 'sort'ing and then calling 'foldSumVarConstMap' on the 'VarConstMap'. |
90 | 83 | simplifyPolyConstraint :: PolyConstraint -> PolyConstraint |
91 | 84 | simplifyPolyConstraint (LEQ varConstMap rhs) = LEQ (foldSumVarConstMap (sort varConstMap)) rhs |
92 | 85 | simplifyPolyConstraint (GEQ varConstMap rhs) = GEQ (foldSumVarConstMap (sort varConstMap)) rhs |
93 | | -simplifyPolyConstraint (EQ varConstMap rhs) = EQ (foldSumVarConstMap (sort varConstMap)) rhs |
| 86 | +simplifyPolyConstraint (EQ varConstMap rhs) = EQ (foldSumVarConstMap (sort varConstMap)) rhs |
94 | 87 |
|
95 | | --- |Add a sorted list of 'VarConstMap's, folding where the variables are equal |
| 88 | +-- | Add a sorted list of 'VarConstMap's, folding where the variables are equal |
96 | 89 | foldSumVarConstMap :: [(Integer, Rational)] -> [(Integer, Rational)] |
97 | | -foldSumVarConstMap [] = [] |
98 | | -foldSumVarConstMap [(v, c)] = [(v, c)] |
| 90 | +foldSumVarConstMap [] = [] |
| 91 | +foldSumVarConstMap [(v, c)] = [(v, c)] |
99 | 92 | foldSumVarConstMap ((v1, c1) : (v2, c2) : vcm) = |
100 | 93 | if v1 == v2 |
101 | | - then |
| 94 | + then |
102 | 95 | let newC = c1 + c2 |
103 | | - in |
104 | | - if newC == 0 |
105 | | - then foldSumVarConstMap vcm |
106 | | - else foldSumVarConstMap $ (v1, c1 + c2) : vcm |
| 96 | + in if newC == 0 |
| 97 | + then foldSumVarConstMap vcm |
| 98 | + else foldSumVarConstMap $ (v1, c1 + c2) : vcm |
107 | 99 | else (v1, c1) : foldSumVarConstMap ((v2, c2) : vcm) |
108 | 100 |
|
109 | | --- |Get a map of the value of every 'Integer' variable in a 'Tableau' |
| 101 | +-- | Get a map of the value of every 'Integer' variable in a 'Tableau' |
110 | 102 | displayTableauResults :: Tableau -> [(Integer, Rational)] |
111 | 103 | displayTableauResults = map (\(basicVar, (_, rhs)) -> (basicVar, rhs)) |
112 | 104 |
|
113 | | --- |Get a map of the value of every 'Integer' variable in a 'DictionaryForm' |
| 105 | +-- | Get a map of the value of every 'Integer' variable in a 'DictionaryForm' |
114 | 106 | displayDictionaryResults :: DictionaryForm -> [(Integer, Rational)] |
115 | | -displayDictionaryResults dict = displayTableauResults$ dictionaryFormToTableau dict |
| 107 | +displayDictionaryResults dict = displayTableauResults $ dictionaryFormToTableau dict |
116 | 108 |
|
117 | | --- |Map the given 'Integer' variable to the given 'ObjectiveFunction', for entering into 'DictionaryForm'. |
| 109 | +-- | Map the given 'Integer' variable to the given 'ObjectiveFunction', for entering into 'DictionaryForm'. |
118 | 110 | createObjectiveDict :: ObjectiveFunction -> Integer -> (Integer, VarConstMap) |
119 | 111 | createObjectiveDict (Max obj) objectiveVar = (objectiveVar, obj) |
120 | 112 | createObjectiveDict (Min obj) objectiveVar = (objectiveVar, map (second negate) obj) |
121 | 113 |
|
122 | | --- |Converts a 'Tableau' to 'DictionaryForm'. |
123 | | --- We do this by isolating the basic variable on the LHS, ending up with all non basic variables and a 'Rational' constant on the RHS. |
124 | | --- (-1) is used to represent the rational constant. |
| 114 | +-- | Converts a 'Tableau' to 'DictionaryForm'. |
| 115 | +-- We do this by isolating the basic variable on the LHS, ending up with all non basic variables and a 'Rational' constant on the RHS. |
| 116 | +-- (-1) is used to represent the rational constant. |
125 | 117 | tableauInDictionaryForm :: Tableau -> DictionaryForm |
126 | | -tableauInDictionaryForm [] = [] |
127 | | -tableauInDictionaryForm ((basicVar, (vcm, r)) : rows) = |
| 118 | +tableauInDictionaryForm [] = [] |
| 119 | +tableauInDictionaryForm ((basicVar, (vcm, r)) : rows) = |
128 | 120 | (basicVar, (-1, r / basicCoeff) : map (\(v, c) -> (v, negate c / basicCoeff)) nonBasicVars) : tableauInDictionaryForm rows |
129 | 121 | where |
130 | 122 | basicCoeff = if null basicVars then 1 else snd $ head basicVars |
131 | 123 | (basicVars, nonBasicVars) = partition (\(v, _) -> v == basicVar) vcm |
132 | 124 |
|
133 | | --- |Converts a 'DictionaryForm' to a 'Tableau'. |
134 | | --- This is done by moving all non-basic variables from the right to the left. |
135 | | --- The rational constant (represented by the 'Integer' variable -1) stays on the right. |
136 | | --- The basic variables will have a coefficient of 1 in the 'Tableau'. |
| 125 | +-- | Converts a 'DictionaryForm' to a 'Tableau'. |
| 126 | +-- This is done by moving all non-basic variables from the right to the left. |
| 127 | +-- The rational constant (represented by the 'Integer' variable -1) stays on the right. |
| 128 | +-- The basic variables will have a coefficient of 1 in the 'Tableau'. |
137 | 129 | dictionaryFormToTableau :: DictionaryForm -> Tableau |
138 | 130 | dictionaryFormToTableau [] = [] |
139 | | -dictionaryFormToTableau ((basicVar, row) : rows) = |
140 | | - (basicVar, ((basicVar, 1) : map (second negate) nonBasicVars, r)) : dictionaryFormToTableau rows |
| 131 | +dictionaryFormToTableau ((basicVar, row) : rows) = |
| 132 | + (basicVar, ((basicVar, 1) : map (second negate) nonBasicVars, r)) : dictionaryFormToTableau rows |
141 | 133 | where |
142 | | - (rationalConstant, nonBasicVars) = partition (\(v,_) -> v == (-1)) row |
| 134 | + (rationalConstant, nonBasicVars) = partition (\(v, _) -> v == (-1)) row |
143 | 135 | r = if null rationalConstant then 0 else (snd . head) rationalConstant -- If there is no rational constant found in the right side, the rational constant is 0. |
144 | 136 |
|
145 | | --- |If this function is given 'Nothing', return 'Nothing'. |
146 | | --- Otherwise, we 'lookup' the 'Integer' given in the first item of the pair in the map given in the second item of the pair. |
147 | | --- This is typically used to extract the value of the 'ObjectiveFunction' after calling 'Linear.Simplex.Simplex.twoPhaseSimplex'. |
| 137 | +-- | If this function is given 'Nothing', return 'Nothing'. |
| 138 | +-- Otherwise, we 'lookup' the 'Integer' given in the first item of the pair in the map given in the second item of the pair. |
| 139 | +-- This is typically used to extract the value of the 'ObjectiveFunction' after calling 'Linear.Simplex.Simplex.twoPhaseSimplex'. |
148 | 140 | extractObjectiveValue :: Maybe (Integer, [(Integer, Rational)]) -> Maybe Rational |
149 | | -extractObjectiveValue Nothing = Nothing |
| 141 | +extractObjectiveValue Nothing = Nothing |
150 | 142 | extractObjectiveValue (Just (objVar, results)) = |
151 | 143 | case lookup objVar results of |
152 | 144 | Nothing -> error "Objective not found in results when extracting objective value" |
|
0 commit comments