Skip to content

Commit 8a9e15b

Browse files
committed
wip
1 parent 38b55a2 commit 8a9e15b

12 files changed

Lines changed: 1310 additions & 1238 deletions

File tree

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ The `Linear.Simplex.Solver.TwoPhase` module contain both phases of the two-phase
1111
Phase one is implemented by `findFeasibleSolution`:
1212

1313
```haskell
14-
findFeasibleSolution :: [PolyConstraint] -> Maybe (DictionaryForm, [Integer], [Integer], Integer)
14+
findFeasibleSolution :: [StandardConstraint] -> Maybe (DictionaryForm, [Integer], [Integer], Integer)
1515
```
1616

17-
`findFeasibleSolution` takes a list of `PolyConstraint`s.
18-
The `PolyConstraint` type, as well as other custom types required by this library, are defined in the `Linear.Simplex.Types` module.
19-
`PolyConstraint` is defined as:
17+
`findFeasibleSolution` takes a list of `StandardConstraint`s.
18+
The `StandardConstraint` type, as well as other custom types required by this library, are defined in the `Linear.Simplex.Types` module.
19+
`StandardConstraint` is defined as:
2020

2121
```haskell
22-
data PolyConstraint =
22+
data StandardConstraint =
2323
LEQ Vars Rational |
2424
GEQ Vars Rational |
2525
EQ Vars Rational deriving (Show, Eq);
@@ -34,11 +34,11 @@ type Vars = [(Integer, Rational)]
3434
A `Vars` is treated as a list of `Integer` variables mapped to their `Rational` coefficients, with an implicit `+` between each element in the list.
3535
For example: `[(1, 2), (2, (-3)), (1, 3)]` is equivalent to `(2x1 + (-3x2) + 3x1)`.
3636

37-
And a `PolyConstraint` is an inequality/equality where the LHS is a `Vars` and the RHS is a `Rational`.
37+
And a `StandardConstraint` is an inequality/equality where the LHS is a `Vars` and the RHS is a `Rational`.
3838
For example: `LEQ [(1, 2), (2, (-3)), (1, 3)] 60` is equivalent to `(2x1 + (-3x2) + 3x1) <= 60`.
3939

40-
Passing a `[PolyConstraint]` to `findFeasibleSolution` will return a feasible solution if it exists as well as a list of slack variables, artificial variables, and a variable that can be safely used to represent the objective for phase two.
41-
`Nothing` is returned if the given `[PolyConstraint]` is infeasible.
40+
Passing a `[StandardConstraint]` to `findFeasibleSolution` will return a feasible solution if it exists as well as a list of slack variables, artificial variables, and a variable that can be safely used to represent the objective for phase two.
41+
`Nothing` is returned if the given `[StandardConstraint]` is infeasible.
4242
The feasible system is returned as the type `DictionaryForm`:
4343

4444
```haskell
@@ -70,7 +70,7 @@ If a variable is not in this list, the variable is equal to 0.
7070
It has the type:
7171

7272
```haskell
73-
twoPhaseSimplex :: ObjectiveFunction -> [PolyConstraint] -> Maybe (Integer, [(Integer, Rational)])
73+
twoPhaseSimplex :: ObjectiveFunction -> [StandardConstraint] -> Maybe (Integer, [(Integer, Rational)])
7474
```
7575

7676
The return type is the same as that of `optimizeFeasibleSystem`
@@ -93,7 +93,7 @@ This implementation assumes that the user only provides positive `Integer` varia
9393
## Example
9494

9595
```haskell
96-
exampleFunction :: (ObjectiveFunction, [PolyConstraint])
96+
exampleFunction :: (ObjectiveFunction, [StandardConstraint])
9797
exampleFunction =
9898
(
9999
Max [(1, 3), (2, 5)], -- 3x1 + 5x2
@@ -122,7 +122,7 @@ Just
122122
```
123123

124124
There are many more examples in test/TestFunctions.hs.
125-
You may use `prettyShowVarConstMap`, `prettyShowPolyConstraint`, and `prettyShowObjectiveFunction` to convert these tests into a more human-readable format.
125+
You may use `prettyShowVarConstMap`, `prettyShowStandardConstraint`, and `prettyShowObjectiveFunction` to convert these tests into a more human-readable format.
126126

127127
## Issues
128128

package.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ dependencies:
2727
- monad-logger >= 0.3.40 && < 0.4
2828
- text >= 2.0.2 && < 2.1
2929
- time
30+
- hspec
31+
- QuickCheck
3032

3133
default-extensions:
3234
DataKinds
3335
DeriveFunctor
3436
DeriveGeneric
37+
DerivingStrategies
3538
DisambiguateRecordFields
3639
DuplicateRecordFields
3740
FlexibleContexts
@@ -50,7 +53,8 @@ library:
5053

5154
tests:
5255
simplex-haskell-test:
53-
main: Spec.hs
54-
source-dirs: test
56+
defaults: hspec/hspec@main
5557
dependencies:
5658
- simplex-method
59+
- hspec
60+
- QuickCheck

simplex-method.cabal

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cabal-version: 1.12
22

3-
-- This file has been generated from package.yaml by hpack version 0.35.1.
3+
-- This file has been generated from package.yaml by hpack version 0.36.0.
44
--
55
-- see: https://github.com/sol/hpack
66

@@ -27,20 +27,24 @@ source-repository head
2727

2828
library
2929
exposed-modules:
30+
Linear.Simplex.DeriveBounds
3031
Linear.Simplex.Prettify
3132
Linear.Simplex.Solver.TwoPhase
33+
Linear.Simplex.Standardize
3234
Linear.Simplex.Types
3335
Linear.Simplex.Util
3436
other-modules:
3537
Paths_simplex_method
3638
hs-source-dirs:
3739
src
3840
default-extensions:
39-
DataKinds DeriveFunctor DeriveGeneric DisambiguateRecordFields DuplicateRecordFields FlexibleContexts LambdaCase OverloadedLabels OverloadedRecordDot OverloadedStrings RecordWildCards TemplateHaskell TupleSections TypeApplications NamedFieldPuns
41+
DataKinds DeriveFunctor DeriveGeneric DerivingStrategies DisambiguateRecordFields DuplicateRecordFields FlexibleContexts LambdaCase OverloadedLabels OverloadedRecordDot OverloadedStrings RecordWildCards TemplateHaskell TupleSections TypeApplications NamedFieldPuns
4042
build-depends:
41-
base >=4.14 && <5
43+
QuickCheck
44+
, base >=4.14 && <5
4245
, containers >=0.6.5.1 && <0.7
4346
, generic-lens >=2.2.0 && <2.3
47+
, hspec
4448
, lens >=5.2.2 && <5.3
4549
, monad-logger >=0.3.40 && <0.4
4650
, text >=2.0.2 && <2.1
@@ -51,19 +55,22 @@ test-suite simplex-haskell-test
5155
type: exitcode-stdio-1.0
5256
main-is: Spec.hs
5357
other-modules:
54-
TestFunctions
58+
Linear.Simplex.TypesSpec
5559
Paths_simplex_method
5660
hs-source-dirs:
5761
test
5862
default-extensions:
59-
DataKinds DeriveFunctor DeriveGeneric DisambiguateRecordFields DuplicateRecordFields FlexibleContexts LambdaCase OverloadedLabels OverloadedRecordDot OverloadedStrings RecordWildCards TemplateHaskell TupleSections TypeApplications NamedFieldPuns
63+
DataKinds DeriveFunctor DeriveGeneric DerivingStrategies DisambiguateRecordFields DuplicateRecordFields FlexibleContexts LambdaCase OverloadedLabels OverloadedRecordDot OverloadedStrings RecordWildCards TemplateHaskell TupleSections TypeApplications NamedFieldPuns
6064
build-depends:
61-
base >=4.14 && <5
65+
QuickCheck
66+
, base >=4.14 && <5
6267
, containers >=0.6.5.1 && <0.7
6368
, generic-lens >=2.2.0 && <2.3
69+
, hspec
6470
, lens >=5.2.2 && <5.3
6571
, monad-logger >=0.3.40 && <0.4
6672
, simplex-method
6773
, text >=2.0.2 && <2.1
6874
, time
6975
default-language: Haskell2010
76+
build-tool-depends: hspec-discover:hspec-discover == 2.*

src/Linear/Simplex/DeriveBounds.hs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
module Linear.Simplex.DeriveBounds where
2+
3+
import Prelude hiding (EQ)
4+
5+
import Control.Applicative (liftA2)
6+
import Control.Lens hiding (Const)
7+
import Data.Generics.Labels ()
8+
import Data.List (sort)
9+
import GHC.Generics (Generic)
10+
11+
import Linear.Simplex.Types
12+
13+
import Data.Map (Map)
14+
import qualified Data.Map as Map
15+
import Data.Maybe (isNothing, fromMaybe)
16+
17+
-- | Update the bounds for a variable in the map via intersection
18+
updateBounds :: Var -> Bounds -> Map Var Bounds -> Map Var Bounds
19+
updateBounds var newBounds boundsMap =
20+
let existingBounds = Map.findWithDefault (Bounds Nothing Nothing) var boundsMap
21+
updatedBounds = combineBounds newBounds existingBounds
22+
in Map.insert var updatedBounds boundsMap
23+
24+
-- Intersection of two bounds
25+
combineBounds :: Bounds -> Bounds -> Bounds
26+
combineBounds newBounds existingBounds = do
27+
let newLowerBound =
28+
case (newBounds.lowerBound, existingBounds.lowerBound) of
29+
(Just newLowerBound, Just existingLowerBound) -> Just $ max newLowerBound existingLowerBound
30+
(_, Just existingLowerBound) -> Just existingLowerBound
31+
(Just newLowerBound, _) -> Just newLowerBound
32+
(_, _) -> Nothing
33+
let newUpperBound =
34+
case (newBounds.upperBound, existingBounds.upperBound) of
35+
(Just newUpperBound, Just existingUpperBound) -> Just $ max newUpperBound existingUpperBound
36+
(_, Just existingUpperBound) -> Just existingUpperBound
37+
(Just newUpperBound, _) -> Just newUpperBound
38+
(_, _) -> Nothing
39+
Bounds newLowerBound newUpperBound
40+
41+
-- Helper to recursively analyze expressions and derive bounds
42+
-- deriveBoundsFromExpr :: Expr -> Bounds -> (Var -> Bounds -> Map Var Bounds -> Map Var Bounds)
43+
-- deriveBoundsFromExpr (Var v) bounds accMap = updateBounds v bounds accMap
44+
-- deriveBoundsFromExpr (Const c) _ accMap = accMap
45+
-- deriveBoundsFromExpr (e1 :+ e2) bounds accMap =
46+
-- deriveBoundsFromExpr e1 bounds $ deriveBoundsFromExpr e2 bounds accMap
47+
-- deriveBoundsFromExpr (e1 :-: e2) bounds accMap =
48+
-- deriveBoundsFromExpr e1 bounds $ deriveBoundsFromExpr e2 bounds accMap
49+
-- deriveBoundsFromExpr (e1 :*: e2) bounds accMap =
50+
-- deriveBoundsFromExpr e1 bounds $ deriveBoundsFromExpr e2 bounds accMap
51+
-- deriveBoundsFromExpr (e1 :/: e2) bounds accMap =
52+
-- deriveBoundsFromExpr e1 bounds $ deriveBoundsFromExpr e2 bounds accMap
53+
54+
-- Function to derive bounds from a single constraint for a variable
55+
deriveVarBoundsFromConstraint :: Constraint -> Map Var Bounds -> Map Var Bounds
56+
-- deriveVarBoundsFromConstraint (Var v :<=: Const u) accMap = updateBounds v (Bounds Nothing (Just u)) accMap
57+
-- deriveVarBoundsFromConstraint (Var v :>=: Const l) accMap = updateBounds v (Bounds (Just l) Nothing) accMap
58+
-- deriveVarBoundsFromConstraint (Var v :==: Const c) accMap = updateBounds v (Bounds (Just c) (Just c)) accMap
59+
deriveVarBoundsFromConstraint _ accMap = accMap -- Ignore non-constant expressions
60+
61+
-- Function to derive bounds for all variables in the constraints list
62+
deriveBounds :: [Constraint] -> Map Var Bounds
63+
deriveBounds constraints = foldr deriveVarBoundsFromConstraint Map.empty constraints
64+
65+
-- data Bounds = Bounds
66+
-- { lowerBound :: Maybe SimplexNum
67+
-- , upperBound :: Maybe SimplexNum
68+
-- }
69+
-- deriving (Show, Read, Eq, Generic)
70+
71+
validateBounds :: Map Var Bounds -> Bool
72+
validateBounds boundsMap = all soundBounds $ Map.toList boundsMap
73+
where
74+
soundBounds (_, Bounds lowerBound upperBound) =
75+
case (lowerBound, upperBound) of
76+
(Just l, Just u) -> l <= u
77+
(_, _) -> True
78+
79+
80+
type AuxVarMap = Map.Map Var (Var, Var)
81+
82+
splitNegativeVars :: Map Var Bounds -> (Map Var Bounds, AuxVarMap)
83+
splitNegativeVars boundsMap =
84+
let (newBoundsMap, auxMap, _) = Map.foldrWithKey splitVar (Map.empty, Map.empty, Map.size boundsMap + 1) boundsMap
85+
in (newBoundsMap, auxMap)
86+
where
87+
splitVar var (Bounds lowerBound upperBound) (newBoundsMap, auxMap, nextVar) =
88+
if fromMaybe (-1) lowerBound < 0
89+
then let var1 = nextVar
90+
var2 = nextVar + 1
91+
newBounds = Bounds (Just 0) Nothing
92+
in (Map.insert var1 newBounds $ Map.insert var2 newBounds newBoundsMap,
93+
Map.insert var (var1, var2) auxMap,
94+
nextVar + 2)
95+
else (Map.insert var (Bounds lowerBound upperBound) newBoundsMap, auxMap, nextVar)
96+
97+
-- PLAN:
98+
99+
-- Accept systems with any kind of constraints (<=, >=, ==)
100+
-- Identify all variables in the system and their bounds
101+
-- For any variable with negative/unbounded lower bound, split it into two variables with lower bound 0
102+
-- So, say x has lower bound -1, then we split it into x1 and x2, where x1 >= 0 and x2 >= 0
103+
-- Then substitute x with x1 - x2 in all constraints
104+
-- All variables now have non-negative lower bounds
105+
-- Now, we proceed with the remaining transformations
106+
-- Slack variables are introduced for all constraints
107+
-- Artificial variables are introduced for all constraints with equality
108+
-- and so on until we have a system in the standard form
109+
110+
-- Maybe have a type for the standard system? It would be a list of linear equalities with a constant on the RHS
111+
-- All variables >= 0 can be assumed, doesn't need to be in the type
112+
-- The objective function can be considered separate, so not part of the standard system type?

src/Linear/Simplex/Prettify.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ prettyShowVarConstMap = aux . M.toList
3232
where
3333
r' = if denominator r == 1 then show (numerator r) else show (numerator r) ++ " / " ++ show (numerator r)
3434

35-
-- | Convert a 'PolyConstraint' into a human-readable 'String'
36-
prettyShowPolyConstraint :: PolyConstraint -> String
37-
prettyShowPolyConstraint (LEQ vcm r) = prettyShowVarConstMap vcm ++ " <= " ++ show r
38-
prettyShowPolyConstraint (GEQ vcm r) = prettyShowVarConstMap vcm ++ " >= " ++ show r
39-
prettyShowPolyConstraint (Linear.Simplex.Types.EQ vcm r) = prettyShowVarConstMap vcm ++ " == " ++ show r
35+
-- | Convert a 'StandardConstraint' into a human-readable 'String'
36+
-- prettyShowStandardConstraint :: StandardConstraint -> String
37+
-- prettyShowStandardConstraint (LEQ vcm r) = prettyShowVarConstMap vcm ++ " <= " ++ show r
38+
-- prettyShowStandardConstraint (GEQ vcm r) = prettyShowVarConstMap vcm ++ " >= " ++ show r
39+
-- prettyShowStandardConstraint (Linear.Simplex.Types.EQ vcm r) = prettyShowVarConstMap vcm ++ " == " ++ show r
4040

4141
-- | Convert an 'ObjectiveFunction' into a human-readable 'String'
4242
prettyShowObjectiveFunction :: ObjectiveFunction -> String

0 commit comments

Comments
 (0)