Skip to content

Commit d053637

Browse files
authored
Initial refactor - much better types (#9)
* Initial refactor - much better types + Use types which are way clearer + Some docs + Use lens where they make things easier + New types required quite a significant factor of the codebase + Cleanup + Further refactoring/polishing + I want type names to reflect those typically used when talking about simplex methods + But first, I need to learn what the actual terms are + I'd also like to simplify a lot of these functions + The refactor has made it easier for me to think about these functions, which makes it easier to simplify them * Run formatter * `FeasibleSystem` instances * Re-add stack * (wip) fix pivoting issues * Switch CI to stack * Use stack to build haddocks * Run formatter * Matrix test windows and macos * Remove ghc 8.10 from CI * Make CI fail when tests fail * Run formatter * Fix broken tests + test10 was broken due to an LLM sneakily adding extra constraints and changing objective + testQuickCheck1/2 were broken because the constraints were built using `Map.fromList` with duplicate keys + only one of the key values were used, others were ignored * polishing * Lens getters -> RecordDot getters * Add logging, improve docs, more tests, handle edge cases + Control.Monad.Logger used for logging + Documented various functions + Handled some edge cases which shouldn't be possible (and log warnings/errors when we reach these edge cases) * fixme * Improve logging * Fourmolu upgrade: limit lines to 120 chars * Upgrade fourmolu action, specify fourmolu version * Bump package version * Bump lts * some helper functions * Rename Linaer.Simplex.Simplex to Linear.Simplex.Solver.TwoPhase + I like this name better + Allows for alternative solvers * Fix caching + I was using the cabal plan as a key, now I use stack files/cabal generated files * Update lts * Diagnose caching issues * Try fixing caching * Remove windows from CI + Don't want to spend effor fixing windows caching * Update workflow step labels * Update stack yaml lock * Add windows + caching to ci * Save .stack-work for windows too * Only save when cache is not hit * Update ChangeLog * Update copyright dates
1 parent 72c8201 commit d053637

File tree

16 files changed

+1529
-898
lines changed

16 files changed

+1529
-898
lines changed

.github/workflows/haskell.yml

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,17 @@ jobs:
1717

1818
steps:
1919
- uses: actions/checkout@v3
20-
- uses: haskell-actions/run-fourmolu@v7
20+
- uses: haskell-actions/run-fourmolu@v9
21+
with:
22+
version: "0.14.0.0"
2123
build:
2224
name: GHC ${{ matrix.ghc-version }} on ${{ matrix.os }}
2325
runs-on: ${{ matrix.os }}
2426
strategy:
2527
fail-fast: false
2628
matrix:
27-
os: [ubuntu-latest]
28-
ghc-version: ['9.6', '9.4', '9.2', '9.0', '8.10']
29-
30-
include:
31-
- os: windows-latest
32-
ghc-version: '9.6'
33-
- os: macos-latest
34-
ghc-version: '9.6'
29+
os: [windows-latest, macos-latest, ubuntu-latest]
30+
ghc-version: ['9.6', '9.4', '9.2', '9.0']
3531

3632
steps:
3733
- uses: actions/checkout@v3
@@ -41,54 +37,94 @@ jobs:
4137
id: setup
4238
with:
4339
ghc-version: ${{ matrix.ghc-version }}
44-
# Defaults, added for clarity:
45-
cabal-version: 'latest'
46-
cabal-update: true
40+
enable-stack: true
4741

48-
- name: Installed minor versions of GHC and Cabal
42+
- name: Installed minor versions of GHC, Cabal, and Stack
4943
shell: bash
5044
run: |
5145
GHC_VERSION=$(ghc --numeric-version)
5246
CABAL_VERSION=$(cabal --numeric-version)
47+
STACK_VERSION=$(stack --numeric-version)
5348
echo "GHC_VERSION=${GHC_VERSION}" >> "${GITHUB_ENV}"
5449
echo "CABAL_VERSION=${CABAL_VERSION}" >> "${GITHUB_ENV}"
50+
echo "STACK_VERSION=${STACK_VERSION}" >> "${GITHUB_ENV}"
5551
5652
- name: Configure the build
5753
run: |
58-
cabal configure --enable-tests --enable-benchmarks --disable-documentation
59-
cabal build --dry-run
54+
# cabal configure --enable-tests --enable-benchmarks --disable-documentation
55+
# cabal build --dry-run
56+
stack build --test --bench --no-haddock --dry-run
6057
# The last step generates dist-newstyle/cache/plan.json for the cache key.
6158

62-
- name: Restore cached dependencies
59+
- name: Restore .stack-work cache
60+
uses: actions/cache/restore@v3
61+
id: cache-restore-stack-work
62+
with:
63+
path: .stack-work
64+
key: ${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-stack-${{ env.STACK_VERSION }}-stack-work-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}-${{ hashFiles('**/*.hs') }}
65+
restore-keys: |
66+
${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-stack-${{ env.STACK_VERSION }}-stack-work-
67+
68+
- name: Restore ~/.stack cache (Unix)
69+
uses: actions/cache/restore@v3
70+
id: cache-restore-stack-global-unix
71+
if: runner.os == 'Linux' || runner.os == 'macOS'
72+
with:
73+
path: ~/.stack
74+
key: ${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-stack-${{ env.STACK_VERSION }}-stack-global-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}
75+
restore-keys: |
76+
${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-stack-${{ env.STACK_VERSION }}-stack-global-
77+
78+
- name: Restore %APPDATA%\stack, %LOCALAPPDATA%\Programs\stack cache (Windows)
6379
uses: actions/cache/restore@v3
64-
id: cache
80+
id: cache-restore-stack-global-windows
81+
if: runner.os == 'Windows'
6582
with:
66-
path: ${{ steps.setup.outputs.cabal-store }}
67-
key: ${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-cabal-${{ env.CABAL_VERSION }}-plan-${{ hashFiles('**/plan.json') }}
83+
path: |
84+
~\AppData\Roaming\stack
85+
~\AppData\Local\Programs\stack
86+
key: ${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-stack-${{ env.STACK_VERSION }}-stack-global-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}
6887
restore-keys: |
69-
${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-cabal-${{ env.CABAL_VERSION }}-
88+
${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-stack-${{ env.STACK_VERSION }}-stack-global-
89+
90+
- name: Build dependencies
91+
run: stack build --only-dependencies
7092

71-
- name: Install dependencies
72-
run: cabal build all --only-dependencies
93+
- name: Build the package
94+
run: stack build
7395

74-
# Cache dependencies already here, so that we do not have to rebuild them should the subsequent steps fail.
75-
- name: Save cached dependencies
96+
- name: Save .stack-work cache
7697
uses: actions/cache/save@v3
77-
# Caches are immutable, trying to save with the same key would error.
78-
if: ${{ !steps.cache.outputs.cache-hit
79-
|| steps.cache.outputs.cache-primary-key != steps.cache.outputs.cache-matched-key }}
98+
id: cache-save-stack-work
99+
if: steps.cache-restore-stack-work.outputs.cache-hit != 'true'
80100
with:
81-
path: ${{ steps.setup.outputs.cabal-store }}
82-
key: ${{ steps.cache.outputs.cache-primary-key }}
83-
84-
- name: Build
85-
run: cabal build all
101+
path: .stack-work
102+
key: ${{ steps.cache-restore-stack-work.outputs.cache-primary-key }}
103+
104+
- name: Save %APPDATA%\stack, %LOCALAPPDATA%\Programs\stack cache (Windows)
105+
uses: actions/cache/save@v3
106+
if: runner.os == 'Windows'
107+
&& steps.cache-restore-stack-global-windows.outputs.cache-hit != 'true'
108+
with:
109+
path: |
110+
~\AppData\Roaming\stack
111+
~\AppData\Local\Programs\stack
112+
key: ${{ steps.cache-restore-stack-global-windows.outputs.cache-primary-key }}
113+
114+
- name: Save ~/.stack cache (Unix)
115+
uses: actions/cache/save@v3
116+
id: cache-save-stack-global
117+
if: (runner.os == 'Linux' || runner.os == 'macOS')
118+
&& steps.cache-restore-stack-global-unix.outputs.cache-hit != 'true'
119+
with:
120+
path: ~/.stack
121+
key: ${{ steps.cache-restore-stack-global-unix.outputs.cache-primary-key }}
86122

87123
- name: Run tests
88-
run: cabal test all
124+
run: stack test
89125

90126
- name: Check cabal file
91127
run: cabal check
92128

93129
- name: Build documentation
94-
run: cabal haddock all
130+
run: stack haddock

ChangeLog.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,18 @@
22

33
## Unreleased changes
44

5+
## [v0.2.0.0](https://github.com/rasheedja/LPPaver/tree/v0.2.0.0)
6+
57
- Setup CI
68
- Use fourmolu formatter
7-
- Switch to Cabal
9+
- Add better types
10+
- Use lens
11+
- Use RecordDot syntax
12+
- Add logging
13+
- Improve Docs
14+
- More Tests
15+
- Bump Stackage LTS
16+
- Rename Linear.Simplex.Simplex -> Linear.Simplex.TwoPhase.Simplex
817

918
## [v0.1.0.0](https://github.com/rasheedja/LPPaver/tree/v0.1.0.0)
1019

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright Junaid Rasheed (c) 2020-2022
1+
Copyright Junaid Rasheed (c) 2020-2023
22

33
All rights reserved.
44

README.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
## Quick Overview
66

7-
The `Linear.Simplex.Simplex` module contain both phases of the simplex method.
7+
The `Linear.Simplex.Solver.TwoPhase` module contain both phases of the two-phase simplex method.
88

99
### Phase One
1010

@@ -20,46 +20,46 @@ The `PolyConstraint` type, as well as other custom types required by this librar
2020

2121
```haskell
2222
data PolyConstraint =
23-
LEQ VarConstMap Rational |
24-
GEQ VarConstMap Rational |
25-
EQ VarConstMap Rational deriving (Show, Eq);
23+
LEQ Vars Rational |
24+
GEQ Vars Rational |
25+
EQ Vars Rational deriving (Show, Eq);
2626
```
2727

28-
And `VarConstMap` is defined as:
28+
And `Vars` is defined as:
2929

3030
```haskell
31-
type VarConstMap = [(Integer, Rational)]
31+
type Vars = [(Integer, Rational)]
3232
```
3333

34-
A `VarConstMap` is treated as a list of `Integer` variables mapped to their `Rational` coefficients, with an implicit `+` between each element in the list.
34+
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 `VarConstMap` and the RHS is a `Rational`.
37+
And a `PolyConstraint` 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

4040
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.
4141
`Nothing` is returned if the given `[PolyConstraint]` is infeasible.
4242
The feasible system is returned as the type `DictionaryForm`:
4343

4444
```haskell
45-
type DictionaryForm = [(Integer, VarConstMap)]
45+
type DictionaryForm = [(Integer, Vars)]
4646
```
4747

48-
`DictionaryForm` can be thought of as a list of equations, where the `Integer` represents a basic variable on the LHS that is equal to the RHS represented as a `VarConstMap`. In this `VarConstMap`, the `Integer` -1 is used internally to represent a `Rational` number.
48+
`DictionaryForm` can be thought of as a list of equations, where the `Integer` represents a basic variable on the LHS that is equal to the RHS represented as a `Vars`. In this `Vars`, the `Integer` -1 is used internally to represent a `Rational` number.
4949

5050
### Phase Two
5151

5252
`optimizeFeasibleSystem` performs phase two of the simplex method, and has the type:
5353

5454
```haskell
55-
data ObjectiveFunction = Max VarConstMap | Min VarConstMap deriving (Show, Eq)
55+
data ObjectiveFunction = Max Vars | Min Vars deriving (Show, Eq)
5656

5757
optimizeFeasibleSystem :: ObjectiveFunction -> DictionaryForm -> [Integer] -> [Integer] -> Integer -> Maybe (Integer, [(Integer, Rational)])
5858
```
5959

6060
We first pass an `ObjectiveFunction`.
6161
Then we give a feasible system in `DictionaryForm`, a list of slack variables, a list of artificial variables, and a variable to represent the objective.
62-
`optimizeFeasibleSystem` Maximizes/Minimizes the linear equation represented as a `VarConstMap` in the given `ObjectiveFunction`.
62+
`optimizeFeasibleSystem` Maximizes/Minimizes the linear equation represented as a `Vars` in the given `ObjectiveFunction`.
6363
The first item of the returned pair is the `Integer` variable representing the objective.
6464
The second item is a list of `Integer` variables mapped to their optimized values.
6565
If a variable is not in this list, the variable is equal to 0.
@@ -87,7 +87,7 @@ There are similar functions for `DictionaryForm` as well as other custom types i
8787

8888
## Usage notes
8989

90-
You must only use positive `Integer` variables in a `VarConstMap`.
90+
You must only use positive `Integer` variables in a `Vars`.
9191
This implementation assumes that the user only provides positive `Integer` variables; the `Integer` -1, for example, is sometimes used to represent a `Rational` number.
9292

9393
## Example

fourmolu.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
indentation: 2
2-
column-limit: none
2+
column-limit: 120
33
function-arrows: trailing
44
comma-style: leading
5-
import-export-style: diff-friendly
5+
import-export-style: leading
66
indent-wheres: true
77
record-brace-space: true
88
newlines-between-decls: 1
9-
haddock-style: multi-line
10-
haddock-style-module:
11-
let-style: auto
12-
in-style: right-align
9+
haddock-style: single-line
10+
haddock-style-module: single-line
11+
let-style: inline
12+
in-style: left-align
1313
single-constraint-parens: always
1414
unicode: never
1515
respectful: true

package.yaml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: simplex-method
2+
version: 0.2.0.0
3+
github: "rasheedja/simplex-method"
4+
license: BSD3
5+
author: "Junaid Rasheed"
6+
maintainer: "[email protected]"
7+
copyright: "BSD-3"
8+
9+
extra-source-files:
10+
- README.md
11+
- ChangeLog.md
12+
13+
# Metadata used when publishing your package
14+
synopsis: Implementation of the two-phase simplex method in exact rational arithmetic
15+
category: Math, Maths, Mathematics, Optimisation, Optimization, Linear Programming
16+
17+
# To avoid duplicated efforts in documentation and dealing with the
18+
# complications of embedding Haddock markup inside cabal files, it is
19+
# common to point users to the README.md file.
20+
description: Please see the README on GitHub at <https://github.com/rasheedja/simplex-method#readme>
21+
22+
dependencies:
23+
- base >= 4.14 && < 5
24+
- containers >= 0.6.5.1 && < 0.7
25+
- generic-lens >= 2.2.0 && < 2.3
26+
- lens >= 5.2.2 && < 5.3
27+
- monad-logger >= 0.3.40 && < 0.4
28+
- text >= 2.0.2 && < 2.1
29+
- time
30+
31+
default-extensions:
32+
DataKinds
33+
DeriveFunctor
34+
DeriveGeneric
35+
DisambiguateRecordFields
36+
DuplicateRecordFields
37+
FlexibleContexts
38+
LambdaCase
39+
OverloadedLabels
40+
OverloadedRecordDot
41+
OverloadedStrings
42+
RecordWildCards
43+
TemplateHaskell
44+
TupleSections
45+
TypeApplications
46+
NamedFieldPuns
47+
48+
library:
49+
source-dirs: src
50+
51+
tests:
52+
simplex-haskell-test:
53+
main: Spec.hs
54+
source-dirs: test
55+
dependencies:
56+
- simplex-method

0 commit comments

Comments
 (0)