{"id":23651515,"url":"https://github.com/athanclark/cassowary-haskell","last_synced_at":"2025-09-09T19:25:01.154Z","repository":{"id":34153177,"uuid":"37992067","full_name":"athanclark/cassowary-haskell","owner":"athanclark","description":"A haskell implementation of the Cassowary linear programming solver.","archived":false,"fork":false,"pushed_at":"2020-09-19T20:05:48.000Z","size":8045,"stargazers_count":23,"open_issues_count":2,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-05-20T01:13:58.174Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/athanclark.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-06-24T15:10:19.000Z","updated_at":"2024-11-11T15:34:04.000Z","dependencies_parsed_at":"2022-09-02T14:01:41.436Z","dependency_job_id":null,"html_url":"https://github.com/athanclark/cassowary-haskell","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/athanclark/cassowary-haskell","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/athanclark%2Fcassowary-haskell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/athanclark%2Fcassowary-haskell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/athanclark%2Fcassowary-haskell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/athanclark%2Fcassowary-haskell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/athanclark","download_url":"https://codeload.github.com/athanclark/cassowary-haskell/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/athanclark%2Fcassowary-haskell/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274348649,"owners_count":25268972,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-09T02:00:10.223Z","response_time":80,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-28T16:38:08.679Z","updated_at":"2025-09-09T19:25:01.123Z","avatar_url":"https://github.com/athanclark.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"cassowary-haskell\n=================\n\n[![Build Status](https://travis-ci.org/athanclark/cassowary-haskell.svg?branch=master)](https://travis-ci.org/athanclark/cassowary-haskell)\n\n\u003e An implementation of the Cassowary linear constraint solver, in Haskell\n\n## Installation\n\n```bash\ngit clone https://github.com/athanclark/cassowary-haskell.git\ncd cassowary-haskell\ncabal sandbox init \u0026\u0026 cabal install\n```\n\n### How to run tests\n\n```bash\ncabal install --enable-tests \u0026\u0026 cabal test --show-details=always\n```\n\n## Bird's Eye View\n\nLinear Program Solvers are fairly complex, but in short it is something like a\ncompiler for linear equations - \"[standard form](https://en.wikipedia.org/wiki/Linear_equation#General_.28or_standard.29_form)\"\nbecomes our grammar / AST for the language, and the act of compilation is the\n\"constraint solving\" technique we employ. In this instance, we are using the\n[Cassowary](https://constraints.cs.washington.edu/solvers/cassowary-tochi.pdf)\nLP solver.\n\nThere are a few components to the system:\n\n- [The Grammar](#the-grammar) for Linear Equations\n- [The Simplex Method](#the-simplex-method)\n- [Handling Unrestricted Variables](#unrestricted-variables)\n- [Handling Weighted Constraints](#weights)\n\n### The Grammar\n\nThere is a user-facing syntax to be used as a DSL when creating equations, and\nan internal grammar representing linear equations in standard form. At both\nlayers, linearity of the equations is guaranteed by design.\n\n#### User-Facing API\n\nThere are a few operators to consider when writing equations - `.+.`, `.*.`,\n`.==.`, `.\u003c=.` and `.\u003e=.`. To create a variable, you need to wrap your string name\nin a `EVar` constructor, and sometimes you will need to coerce numeric literals\nto `(8 :: Rational)`. Also, sometimes you may need to wrap numbers with `ELit`.\nOtherwise, you write your equations like normal:\n\n`5x - 3y + 2z = 0` becomes\n\n```haskell\nequ1 = (5 :: Double) .*. EVar \"x\"\n   .+. (3 :: Double) .*. EVar \"y\"\n   .+. (2 :: Double) .*. EVar \"z\"\n  .==. ELit 0\n```\n\nThis will create a value of type `IneqExpr` (expressions representing inequalities).\nTo see what this looks like in standard form, use `standardForm`:\n\n```haskell\nequ1std :: IneqStdForm\nequ1std = standardForm equ1\n\nλ\u003e equ1std\n➥ EquStd [LinVar 5 \"x\", LinVar 3 \"y\", LinVar 2 \"z\"] 0\n```\n\nAn equation (via `EquStd`) consists of:\n\n- A list of variables with their coefficients (`LinVar`), implicitly added together\n- A constant value (in this case, `0`)\n\n#### Standard Form\n\nA linear equation in standard form is when all variables and constant values\nare separated between their inequality operator. Our example above is already in standard\nform, but for demonstration here is another:\n\n`3x + 4y - 1 \u003c= 3z + 2` becomes\n\n`3x + 4y - 3z \u003c= 3`\n\nThe internal grammatical data structure sees this as a list of variables with their\ncoefficients summed together, with the constant - a structure isomorphic to `([(String, Rational)], Rational)`\n(where `LinVar` is isomorphic to a tuple `(,)`, and `EquStd` is also isomorphic to a\ntuple `(,)`)\nfor each inequality operator. This makes everything much more simple to work with\nin the compiler.\n\n\u003e Our implementation actually uses `Map String Rational` for log(n) lookups, and\n\u003e deletes `0` coefficients to reduce space.\n\n### The Simplex Method\n\nThis is where we compile. The first thing we need to understand is a constraint set /\ntableau. This is where we store all the equations - we could be primitive and just make\na list of equations:\n\n```haskell\nnewtype LameTableau = LameTableau [IneqStdForm]\n```\n\nbut this neglegts what simplex actually does to solve equations - it refines the set of basic-normal\nform variables in the constraint set successively, through pivots.\n\n#### Basic Normal Form\n\nAn equation with a variable in BNF should be the **only** equation\nwith that variable - \"basic\" variables uniquely identify equations in the set.\nSimplex successively solves the system of equations by (efficiently) refactoring the constraint set,\nmaking new basic variables with each pivot. In effect, simplex is isolating _definitions_ for those\nbasic variables.\n\nAt the same time, it's successively finding a more optimal basic-feasible solution - a solution where\neach basic variable is assigned the constant in their corrosponding definition; ie: all other variables\nin the definition are set to `0`. This is how (primal) simplex\nfinds the optimal (maximum) solution for a constraint set: It refactors the set, creating\nbasic variables (via Bland's rule), then takes the simplest substitution - each constant as the value for the\nbasic variables, and all others become zero. This actually \"walks\" the feasible region's corners\non each pivot, and does indeed find the most optimal solution as the most simple one.\n\nWe refine the tableau data structure to optimize for this basic feasible solution, accommodating\na separate Map for equations in basic-normal form:\n\n```haskell\ndata Tableau = Tableau (Map LinVarName IneqStdForm) [IneqStdForm]\n```\n\nEach pivot then moves an equation from the list of constraints to the Map (if it can).\n\n#### Slack Variables\n\nSimplex can only work on equality constraints (`.==.`) - we need a way to turn an inequality into an\nequality, soundly. That's what \"slack\" variables are used for:\n\n`3x + 2y \u003c= 20` becomes\n\n`3x + 2y + s1 == 20` where `s1 \u003e= 0`\n\nWe do this with a simple recursive function over the constraint list - a stateful\nmonad that makes sure no duplicate names are used.\n\n#### Dual Simplex\n\nCassowary aims to _minimize_ changes, while primal simplex tries to _maximize_ variables\nin a constraint set. Therefore, we need to take the dual approach to simplex, which\nis identical to the primal simplex enacted on the transpose of the tableau.\n\nWe instead create a dual Bland-rule, which chooses the next basic variable and definition.\nThis saves us the costly overhead of a transpose.\n\n### Unrestricted Variables\n\nIn Cassowary, we expect to reduce constraint sets for equations involving\n_unrestricted_ variables - variables that can be positive or negative. The simplex\nmethod, by default, only works on positive variables, so we need to account for this.\n\nWe can't use simplex on unrestricted variables, only on positive variables of the\nform `\u003e= 0`. So, we make a mapping `x = err_x_+ - err_x_-`, where `err_x_+, err_x_- \u003e= 0`.\nThis means we can take any number, and separate it into it's positive and negative components.\nWhen we use these equations in Cassowary, they're seen as the _change_ in the values\nfor each constraint. We want to minimize these as much as possible, and can be seen\nas error metrics. We then substitute `err_x_+ - err_x_-` for `x` in every equation.\nNow, we can use the simplex method on these restricted equations.\n\n### Weights\n\nBland's rule is the technique the simplex method uses when deciding which variable should be chosen\nas the next basic variable to pivot on. Basically, it's theory is that the varaible in the objective\nfunction with the \"most negative\" effect should be made as the next quantity to isolate.\n\nHowever, we want weights for our constraints, choosing stronger constraints over\nweaker ones, in a method we declare.\n\nAt the user-level, we do this with a natural-number analogue - `0` represents required constraints,\nand more positive represents added weakness to the equation. We can add this weight\nin a constraint-by-constraint basis:\n\n```haskell\nequ1weighted = addWeight 3 equ1std\n```\n\nHowever, this means we need to encode the \"weight\" meaning in each constraint, and each variable.\nWe do this by translating the natural numbers into non-empty\ninductive lists full of coefficients for each weight, making the list `n` long. When adding weight\nto a constraint, we generate `0`s until `n`, which holds\nthe original coefficient. So, the result of `addWeight 3 equ1std` would be\n\n```haskell\nequ1weighted = EquStd [ (\"x\", [0,0,0,5])\n                      , (\"y\", [0,0,0,3])\n                      , (\"z\", [0,0,0,2])\n                      ] 0\n```\n\nThe unfortunate side-effect of this design is the resulting arithmetic operators `+`,`-`,`*`, and `/`.\nAddition and substitution are pretty simple - you take the union of the coefficients, and combining their\ncontents with `+` or `-` when they collide. The more difficult area is multiplication and division -\nwe can multiply a `Rational * [Rational]` pretty easily via distribution / `fmap`, but division `Rational / [Rational]`\nis unintuitive - we actually take the sum of all coefficients to obtain a `Rational / Rational`.\n\n## Conclusion\n\nThese are the more important conecpts to understand before diving into the code. Please let me know if\nyou need clarification!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fathanclark%2Fcassowary-haskell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fathanclark%2Fcassowary-haskell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fathanclark%2Fcassowary-haskell/lists"}