Skip to content

Commit

Permalink
Initial refactor - much better types (#9)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rasheedja authored Nov 25, 2023
1 parent 72c8201 commit d053637
Show file tree
Hide file tree
Showing 16 changed files with 1,529 additions and 898 deletions.
104 changes: 70 additions & 34 deletions .github/workflows/haskell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,17 @@ jobs:

steps:
- uses: actions/checkout@v3
- uses: haskell-actions/run-fourmolu@v7
- uses: haskell-actions/run-fourmolu@v9
with:
version: "0.14.0.0"
build:
name: GHC ${{ matrix.ghc-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
ghc-version: ['9.6', '9.4', '9.2', '9.0', '8.10']

include:
- os: windows-latest
ghc-version: '9.6'
- os: macos-latest
ghc-version: '9.6'
os: [windows-latest, macos-latest, ubuntu-latest]
ghc-version: ['9.6', '9.4', '9.2', '9.0']

steps:
- uses: actions/checkout@v3
Expand All @@ -41,54 +37,94 @@ jobs:
id: setup
with:
ghc-version: ${{ matrix.ghc-version }}
# Defaults, added for clarity:
cabal-version: 'latest'
cabal-update: true
enable-stack: true

- name: Installed minor versions of GHC and Cabal
- name: Installed minor versions of GHC, Cabal, and Stack
shell: bash
run: |
GHC_VERSION=$(ghc --numeric-version)
CABAL_VERSION=$(cabal --numeric-version)
STACK_VERSION=$(stack --numeric-version)
echo "GHC_VERSION=${GHC_VERSION}" >> "${GITHUB_ENV}"
echo "CABAL_VERSION=${CABAL_VERSION}" >> "${GITHUB_ENV}"
echo "STACK_VERSION=${STACK_VERSION}" >> "${GITHUB_ENV}"
- name: Configure the build
run: |
cabal configure --enable-tests --enable-benchmarks --disable-documentation
cabal build --dry-run
# cabal configure --enable-tests --enable-benchmarks --disable-documentation
# cabal build --dry-run
stack build --test --bench --no-haddock --dry-run
# The last step generates dist-newstyle/cache/plan.json for the cache key.

- name: Restore cached dependencies
- name: Restore .stack-work cache
uses: actions/cache/restore@v3
id: cache-restore-stack-work
with:
path: .stack-work
key: ${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-stack-${{ env.STACK_VERSION }}-stack-work-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}-${{ hashFiles('**/*.hs') }}
restore-keys: |
${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-stack-${{ env.STACK_VERSION }}-stack-work-
- name: Restore ~/.stack cache (Unix)
uses: actions/cache/restore@v3
id: cache-restore-stack-global-unix
if: runner.os == 'Linux' || runner.os == 'macOS'
with:
path: ~/.stack
key: ${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-stack-${{ env.STACK_VERSION }}-stack-global-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}
restore-keys: |
${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-stack-${{ env.STACK_VERSION }}-stack-global-
- name: Restore %APPDATA%\stack, %LOCALAPPDATA%\Programs\stack cache (Windows)
uses: actions/cache/restore@v3
id: cache
id: cache-restore-stack-global-windows
if: runner.os == 'Windows'
with:
path: ${{ steps.setup.outputs.cabal-store }}
key: ${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-cabal-${{ env.CABAL_VERSION }}-plan-${{ hashFiles('**/plan.json') }}
path: |
~\AppData\Roaming\stack
~\AppData\Local\Programs\stack
key: ${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-stack-${{ env.STACK_VERSION }}-stack-global-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}
restore-keys: |
${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-cabal-${{ env.CABAL_VERSION }}-
${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-stack-${{ env.STACK_VERSION }}-stack-global-
- name: Build dependencies
run: stack build --only-dependencies

- name: Install dependencies
run: cabal build all --only-dependencies
- name: Build the package
run: stack build

# Cache dependencies already here, so that we do not have to rebuild them should the subsequent steps fail.
- name: Save cached dependencies
- name: Save .stack-work cache
uses: actions/cache/save@v3
# Caches are immutable, trying to save with the same key would error.
if: ${{ !steps.cache.outputs.cache-hit
|| steps.cache.outputs.cache-primary-key != steps.cache.outputs.cache-matched-key }}
id: cache-save-stack-work
if: steps.cache-restore-stack-work.outputs.cache-hit != 'true'
with:
path: ${{ steps.setup.outputs.cabal-store }}
key: ${{ steps.cache.outputs.cache-primary-key }}

- name: Build
run: cabal build all
path: .stack-work
key: ${{ steps.cache-restore-stack-work.outputs.cache-primary-key }}

- name: Save %APPDATA%\stack, %LOCALAPPDATA%\Programs\stack cache (Windows)
uses: actions/cache/save@v3
if: runner.os == 'Windows'
&& steps.cache-restore-stack-global-windows.outputs.cache-hit != 'true'
with:
path: |
~\AppData\Roaming\stack
~\AppData\Local\Programs\stack
key: ${{ steps.cache-restore-stack-global-windows.outputs.cache-primary-key }}

- name: Save ~/.stack cache (Unix)
uses: actions/cache/save@v3
id: cache-save-stack-global
if: (runner.os == 'Linux' || runner.os == 'macOS')
&& steps.cache-restore-stack-global-unix.outputs.cache-hit != 'true'
with:
path: ~/.stack
key: ${{ steps.cache-restore-stack-global-unix.outputs.cache-primary-key }}

- name: Run tests
run: cabal test all
run: stack test

- name: Check cabal file
run: cabal check

- name: Build documentation
run: cabal haddock all
run: stack haddock
11 changes: 10 additions & 1 deletion ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@

## Unreleased changes

## [v0.2.0.0](https://github.com/rasheedja/LPPaver/tree/v0.2.0.0)

- Setup CI
- Use fourmolu formatter
- Switch to Cabal
- Add better types
- Use lens
- Use RecordDot syntax
- Add logging
- Improve Docs
- More Tests
- Bump Stackage LTS
- Rename Linear.Simplex.Simplex -> Linear.Simplex.TwoPhase.Simplex

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

Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright Junaid Rasheed (c) 2020-2022
Copyright Junaid Rasheed (c) 2020-2023

All rights reserved.

Expand Down
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Quick Overview

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

### Phase One

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

```haskell
data PolyConstraint =
LEQ VarConstMap Rational |
GEQ VarConstMap Rational |
EQ VarConstMap Rational deriving (Show, Eq);
LEQ Vars Rational |
GEQ Vars Rational |
EQ Vars Rational deriving (Show, Eq);
```

And `VarConstMap` is defined as:
And `Vars` is defined as:

```haskell
type VarConstMap = [(Integer, Rational)]
type Vars = [(Integer, Rational)]
```

A `VarConstMap` is treated as a list of `Integer` variables mapped to their `Rational` coefficients, with an implicit `+` between each element in the list.
A `Vars` is treated as a list of `Integer` variables mapped to their `Rational` coefficients, with an implicit `+` between each element in the list.
For example: `[(1, 2), (2, (-3)), (1, 3)]` is equivalent to `(2x1 + (-3x2) + 3x1)`.

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

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

```haskell
type DictionaryForm = [(Integer, VarConstMap)]
type DictionaryForm = [(Integer, Vars)]
```

`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.
`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.

### Phase Two

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

```haskell
data ObjectiveFunction = Max VarConstMap | Min VarConstMap deriving (Show, Eq)
data ObjectiveFunction = Max Vars | Min Vars deriving (Show, Eq)

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

We first pass an `ObjectiveFunction`.
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.
`optimizeFeasibleSystem` Maximizes/Minimizes the linear equation represented as a `VarConstMap` in the given `ObjectiveFunction`.
`optimizeFeasibleSystem` Maximizes/Minimizes the linear equation represented as a `Vars` in the given `ObjectiveFunction`.
The first item of the returned pair is the `Integer` variable representing the objective.
The second item is a list of `Integer` variables mapped to their optimized values.
If a variable is not in this list, the variable is equal to 0.
Expand Down Expand Up @@ -87,7 +87,7 @@ There are similar functions for `DictionaryForm` as well as other custom types i

## Usage notes

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

## Example
Expand Down
12 changes: 6 additions & 6 deletions fourmolu.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
indentation: 2
column-limit: none
column-limit: 120
function-arrows: trailing
comma-style: leading
import-export-style: diff-friendly
import-export-style: leading
indent-wheres: true
record-brace-space: true
newlines-between-decls: 1
haddock-style: multi-line
haddock-style-module:
let-style: auto
in-style: right-align
haddock-style: single-line
haddock-style-module: single-line
let-style: inline
in-style: left-align
single-constraint-parens: always
unicode: never
respectful: true
Expand Down
56 changes: 56 additions & 0 deletions package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: simplex-method
version: 0.2.0.0
github: "rasheedja/simplex-method"
license: BSD3
author: "Junaid Rasheed"
maintainer: "[email protected]"
copyright: "BSD-3"

extra-source-files:
- README.md
- ChangeLog.md

# Metadata used when publishing your package
synopsis: Implementation of the two-phase simplex method in exact rational arithmetic
category: Math, Maths, Mathematics, Optimisation, Optimization, Linear Programming

# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description: Please see the README on GitHub at <https://github.com/rasheedja/simplex-method#readme>

dependencies:
- base >= 4.14 && < 5
- containers >= 0.6.5.1 && < 0.7
- generic-lens >= 2.2.0 && < 2.3
- lens >= 5.2.2 && < 5.3
- monad-logger >= 0.3.40 && < 0.4
- text >= 2.0.2 && < 2.1
- time

default-extensions:
DataKinds
DeriveFunctor
DeriveGeneric
DisambiguateRecordFields
DuplicateRecordFields
FlexibleContexts
LambdaCase
OverloadedLabels
OverloadedRecordDot
OverloadedStrings
RecordWildCards
TemplateHaskell
TupleSections
TypeApplications
NamedFieldPuns

library:
source-dirs: src

tests:
simplex-haskell-test:
main: Spec.hs
source-dirs: test
dependencies:
- simplex-method
Loading

0 comments on commit d053637

Please sign in to comment.