├── .gitignore ├── images └── egg-power.jpg ├── .github └── workflows │ ├── TagBot.yml │ └── CompatHelper.yml ├── Project.toml ├── example ├── simple-example.jl └── complicated-example.jl ├── .travis.yml ├── LICENSE ├── beedocker └── Dockerfile ├── test └── runtests.jl ├── README.md └── src └── BeeEncoder.jl /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /Manifest.toml 3 | /dev/ 4 | README.html 5 | *.cov 6 | -------------------------------------------------------------------------------- /images/egg-power.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpsanders/BeeEncoder.jl/master/images/egg-power.jpg -------------------------------------------------------------------------------- /.github/workflows/TagBot.yml: -------------------------------------------------------------------------------- 1 | name: TagBot 2 | on: 3 | schedule: 4 | - cron: 0 0 * * * 5 | jobs: 6 | TagBot: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: JuliaRegistries/TagBot@v1 10 | with: 11 | token: ${{ secrets.GITHUB_TOKEN }} 12 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "BeeEncoder" 2 | uuid = "88bd2ba4-b024-4779-b1f7-c7f148753aae" 3 | authors = ["Xing Shi Cai "] 4 | version = "0.1.0" 5 | 6 | [deps] 7 | Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" 8 | 9 | [compat] 10 | Suppressor = "0.2.0" 11 | julia = "1" 12 | 13 | [extras] 14 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 15 | 16 | [targets] 17 | test = ["Test"] 18 | -------------------------------------------------------------------------------- /example/simple-example.jl: -------------------------------------------------------------------------------- 1 | # A simple example to use `BeeEncoder.jl` 2 | 3 | using BeeEncoder 4 | 5 | @beeint x 0 5 6 | @beeint y -4 9 7 | @beeint z -5 10 8 | 9 | @constrain x + y == z 10 | 11 | @beeint w 0 10 12 | 13 | xl = @beebool x[1:4] 14 | 15 | @constrain xl[1] == -xl[2] 16 | @constrain xl[2] == true 17 | 18 | @constrain sum([-xl[1], xl[2], -xl[3], xl[4]]) == w 19 | 20 | BeeEncoder.render() 21 | -------------------------------------------------------------------------------- /example/complicated-example.jl: -------------------------------------------------------------------------------- 1 | # A very complicated example to use `BEE.jl` 2 | # 3 | # We will *prove* the following fact 4 | 5 | using BEE 6 | 7 | @beeint x 0 5 8 | @beeint y -4 9 9 | @beeint z -5 10 10 | 11 | x + y == z 12 | 13 | @beeint w 0 10 14 | 15 | xl = [beebool("x$i") for i=1:4] 16 | 17 | xl[1] == -xl[2] 18 | xl[2] == true 19 | 20 | sum([-xl[1], xl[2], -xl[3], xl[4]]) == w 21 | 22 | BEE.render() 23 | -------------------------------------------------------------------------------- /.github/workflows/CompatHelper.yml: -------------------------------------------------------------------------------- 1 | name: CompatHelper 2 | 3 | on: 4 | schedule: 5 | - cron: '00 00 * * *' 6 | 7 | jobs: 8 | CompatHelper: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Pkg.add("CompatHelper") 12 | run: julia -e 'using Pkg; Pkg.add("CompatHelper")' 13 | - name: CompatHelper.main() 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | run: julia -e 'using CompatHelper; CompatHelper.main()' 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Documentation: http://docs.travis-ci.com/user/languages/julia/ 2 | language: julia 3 | os: 4 | - linux 5 | - osx 6 | julia: 7 | - 1.4.0 8 | - nightly 9 | matrix: 10 | allow_failures: 11 | - julia: nightly 12 | fast_finish: true 13 | notifications: 14 | email: false 15 | branches: 16 | except: 17 | - dev 18 | after_success: 19 | - julia -e 'using Pkg; cd(Pkg.dir("BEE")); Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())' 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Xing Shi Cai 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /beedocker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y software-properties-common wget unzip build-essential && \ 5 | apt-add-repository ppa:swi-prolog/stable && \ 6 | apt-get update && \ 7 | apt-get install -y swi-prolog-nox && \ 8 | wget -q https://julialang-s3.julialang.org/bin/linux/x64/1.4/julia-1.4.1-linux-x86_64.tar.gz && \ 9 | tar -xzf julia-1.4.1-linux-x86_64.tar.gz && \ 10 | rm -f julia-1.4.1-linux-x86_64.tar.gz && \ 11 | echo "export PATH=\"\$PATH:`realpath julia-1.4.1/bin`\"" >> /root/.bashrc && \ 12 | wget -q http://amit.metodi.me/research/bee/bee20170615.zip && \ 13 | unzip -q -d bee bee20170615.zip && \ 14 | rm -f bee20170615.zip && \ 15 | (cd ./bee/satsolver_src && CPATH="/usr/lib/swi-prolog/include/" make satSolvers) && \ 16 | (cd ./bee/beeSolver/ && make) && \ 17 | echo "export PATH=\"\$PATH:`realpath bee`\"" >> /root/.bashrc && \ 18 | ./julia-1.4.1/bin/julia -e "using Pkg; Pkg.add(PackageSpec(url=\"https://github.com/newptcai/BeeEncoder.jl\"))" && \ 19 | apt-get autoremove -y software-properties-common wget unzip build-essential vim && \ 20 | apt-get clean && \ 21 | rm -rf /var/lib/apt/lists/* 22 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using BeeEncoder 2 | using Suppressor 3 | using Test 4 | 5 | @testset "BeeEncoder.jl" begin 6 | @testset "simple" begin 7 | BeeEncoder.reset() 8 | 9 | example="""new_int(w, 0, 10) 10 | new_int(x, 0, 5) 11 | new_int(z, -5, 10) 12 | new_int(y, -4, 9) 13 | new_bool(x1) 14 | new_bool(x4) 15 | new_bool(x2) 16 | new_bool(x3) 17 | int_plus(x, y, z) 18 | bool_eq(x1, -x2) 19 | bool_eq(x2, true) 20 | bool_array_sum_eq([-x1, x2, -x3, x4], w) 21 | solve satisfy 22 | """ 23 | ret = @capture_out include("../example/simple-example.jl") 24 | @test ret == example 25 | 26 | @test "new_int(w, 0, 10)\n" == capture_render(w) 27 | 28 | c = x+y == z 29 | @test "int_plus(x, y, z)\n" == capture_render(c) 30 | 31 | c = xl[1] == -xl[2] 32 | @test "bool_eq(x1, -x2)\n" == capture_render(c) 33 | 34 | BeeEncoder.reset() 35 | @constrain xl[1] == -xl[2] 36 | @test "bool_eq(x1, -x2)\nsolve satisfy\n" == capture_render() 37 | 38 | BeeEncoder.reset() 39 | constrain(xl[1] == -xl[2]) 40 | @test "bool_eq(x1, -x2)\nsolve satisfy\n" == capture_render() 41 | end 42 | 43 | @testset "Declaring Variable" begin 44 | BeeEncoder.reset() 45 | 46 | @beebool x1 47 | @test "new_bool(x1)\n" == capture_render(x1) 48 | 49 | x2 = beebool("x2") 50 | @test "new_bool(x2)\n" == capture_render(x2) 51 | 52 | x3 = beebool("x3") 53 | @test "new_bool(x3)\n" == capture_render(x3) 54 | 55 | @test hasbool("x3") 56 | @test !hasbool("x4") 57 | 58 | x5 = fetchbool("x5") 59 | @test isa(x5, BeeBool) 60 | @test x5.name == "x5" 61 | @test x5 === fetchbool("x5") 62 | 63 | @test x5 === getbool("x5") 64 | @test_throws KeyError getbool("x6") 65 | 66 | xx1 = fetchbool("xx") 67 | xx2 = fetchbool("xx") 68 | 69 | @test isequal(xx1, xx2) 70 | 71 | yl = @beebool y1 y2 y3 72 | for i in 1:3 73 | @test "new_bool(y$i)\n" == capture_render(yl[i]) 74 | end 75 | 76 | zl = @beebool z[1:10] 77 | for i in 1:10 78 | @test "new_bool(z$i)\n" == capture_render(zl[i]) 79 | end 80 | 81 | z1 = fetchbool("z1") 82 | @test z1 in zl 83 | 84 | (a, b, cl, d) = @beebool a b c[1:10] d 85 | for i in 1:10 86 | @test "new_bool(c$i)\n" == capture_render(cl[i]) 87 | end 88 | 89 | BeeEncoder.reset() 90 | 91 | @beeint xx 3 55 92 | 93 | @test isequal(xx, xx) 94 | 95 | @test "new_int(xx, 3, 55)\n" == capture_render(xx) 96 | 97 | il = @beeint i[1:10] 3 7 98 | for i in 1:10 99 | @test "new_int(i$i, 3, 7)\n" == capture_render(il[i]) 100 | end 101 | 102 | @test "[i1, i2, i3]" == capture_render(il[1:3]) 103 | 104 | i1 = getint("i1") 105 | @test i1 in il 106 | 107 | @test hasint("i4") 108 | 109 | @test !hasint("i12") 110 | 111 | @test xx === getint("xx") 112 | @test_throws KeyError getint("x6") 113 | end 114 | 115 | @testset "Boolean statements" begin 116 | BeeEncoder.reset() 117 | 118 | @beebool x1 119 | @beebool x2 120 | x3 = BeeBool("x3") 121 | 122 | c = x1 == x2 123 | @test "bool_eq(x1, x2)\n" == capture_render(c) 124 | 125 | c = -x1 == x2 126 | @test "bool_eq(-x1, x2)\n" == capture_render(c) 127 | 128 | c = true == and([x1, -x2, x3]) 129 | @test "bool_array_and([x1, -x2, x3])\n" == capture_render(c) 130 | 131 | c = and([x1, x2]) == -x3 132 | @test "bool_array_and_reif([x1, x2], -x3)\n" == capture_render(c) 133 | 134 | c = -x3 == BeeEncoder.xor(-x1, x2) 135 | @test "bool_xor_reif(-x1, x2, -x3)\n" == capture_render(c) 136 | end 137 | 138 | @testset "Integer statements" begin 139 | BeeEncoder.reset() 140 | 141 | @beeint x1 3 7 142 | @beeint x2 4 6 143 | x3 = beeint("x3", 10, 15) 144 | 145 | c = (x1 < x2) == true 146 | @test "int_lt(x1, x2)\n" == capture_render(c) 147 | 148 | c = true == (x1 < x2) 149 | @test "int_lt(x1, x2)\n" == capture_render(c) 150 | 151 | c = false == (x1 < x2) 152 | @test "int_lt_reif(x1, x2, false)\n" == capture_render(c) 153 | 154 | c = (x1 < x2) == false 155 | @test "int_lt_reif(x1, x2, false)\n" == capture_render(c) 156 | 157 | c = alldiff([x1, x2, x3]) == true 158 | @test "int_array_allDiff([x1, x2, x3])\n" == capture_render(c) 159 | 160 | c = true == alldiff([x1, x2, x3]) 161 | @test "int_array_allDiff([x1, x2, x3])\n" == capture_render(c) 162 | 163 | c = alldiff([x1, x2]) == x3 164 | @test "int_array_allDiff_reif([x1, x2], x3)\n" == capture_render(c) 165 | 166 | c = x3 == alldiff([x1, x2]) 167 | @test "int_array_allDiff_reif([x1, x2], x3)\n" == capture_render(c) 168 | 169 | c = x1 * x2 == x3 170 | @test "int_times(x1, x2, x3)\n" == capture_render(c) 171 | 172 | c = x3 == x1 * x2 173 | @test "int_times(x1, x2, x3)\n" == capture_render(c) 174 | 175 | c = x1 + x2 == 33 176 | @test "int_plus(x1, x2, 33)\n" == capture_render(c) 177 | 178 | c = 33 == x1 + x2 179 | @test "int_plus(x1, x2, 33)\n" == capture_render(c) 180 | 181 | c = max([x1, x2]) == x3 182 | @test "int_array_max([x1, x2], x3)\n" == capture_render(c) 183 | 184 | c = x3 == max([x1, x2]) 185 | @test "int_array_max([x1, x2], x3)\n" == capture_render(c) 186 | end 187 | 188 | @testset "Cardinality" begin 189 | BeeEncoder.reset() 190 | 191 | xl = [beebool("x$i") for i in 1:3] 192 | 193 | il = [beeint("i$i", 3, 5) for i in 1:3] 194 | 195 | c = sum(il) == il[1] 196 | @test "int_array_sum_eq([i1, i2, i3], i1)\n" == capture_render(c) 197 | 198 | c = sum(xl) >= il[1] 199 | @test "bool_array_sum_geq([x1, x2, x3], i1)\n" == capture_render(c) 200 | 201 | c = sum(xl) > il[1] 202 | @test "bool_array_sum_gt([x1, x2, x3], i1)\n" == capture_render(c) 203 | end 204 | 205 | @testset "Boolean arrays relation" begin 206 | BeeEncoder.reset() 207 | 208 | xl = [beebool("x$i") for i in 1:3] 209 | 210 | il = [beebool("i$i") for i in 1:3] 211 | 212 | @beebool b 213 | 214 | c = (xl == il) == true 215 | @test "bool_arrays_eq([x1, x2, x3], [i1, i2, i3])\n" == capture_render(c) 216 | 217 | c = (xl != il) == true 218 | @test "bool_arrays_neq([x1, x2, x3], [i1, i2, i3])\n" == capture_render(c) 219 | 220 | c = (xl <= il) == true 221 | @test "bool_arrays_lex([x1, x2, x3], [i1, i2, i3])\n" == capture_render(c) 222 | 223 | c = (xl < il) == true 224 | @test "bool_arrays_lexLt([x1, x2, x3], [i1, i2, i3])\n" == capture_render(c) 225 | 226 | c = (xl <= il) == b 227 | @test "bool_arrays_lex_reif([x1, x2, x3], [i1, i2, i3], b)\n" == capture_render(c) 228 | 229 | c = (xl < il) == b 230 | @test "bool_arrays_lexLt_reif([x1, x2, x3], [i1, i2, i3], b)\n" == capture_render(c) 231 | end 232 | end 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Using `BEE` and `BeeEncoder.jl` 🐝️ to solve SAT problems 2 | 3 | [![Build Status](https://travis-ci.org/newptcai/BeeEncoder.jl.svg?branch=master)](https://travis-ci.org/newptcai/BeeEncoder.jl) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 5 | 6 | *This package was originally named [`BEE.jl`](https://github.com/newptcai/BEE.jl). The name was changed so it can be registered in Julia's package repository.* 7 | 8 | ## The beauty of brute force 🤜️ 9 | 10 |

11 | Brute force 12 |

13 | 14 | 15 | Modern [SAT](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem) solver are often capable 16 | of handling problems with *HUGE* size. They have been successfully applied to many combinatorics 17 | problems. Communications ACM has an article titled [The Science of Brute 18 | Force](https://cacm.acm.org/magazines/2017/8/219606-the-science-of-brute-force/fulltext) on how the 19 | [Boolean Pythagorean Triples problem](https://www.cs.utexas.edu/~marijn/publications/ptn.pdf) was 20 | solved with an SAT solver. Another well-known example is [Paul Erdős Discrepancy 21 | Conjecture](https://www.quantamagazine.org/terence-taos-answer-to-the-erdos-discrepancy-problem-20151001/), 22 | which was [initially attacked with the help of computer](https://arxiv.org/pdf/1402.2184.pdf). 23 | 24 | Thus it is perhaps beneficial 🥦️ for anyone who is interested in combinatorics 🀄️ to learn how to 25 | harness the beautiful brute force 🔨 of SAT solvers. Doing experiments with SAT solver can search much 26 | bigger space than pencil and paper. New patterns can be spotted 👁️. Conjectures can be proved or 27 | disapproved 🎉️. 28 | 29 | However, combinatorial problems are often difficult to encode into CNF formulas, which can only 30 | contain boolean variables. So integers must be represented by such boolean variables with some 31 | encoding scheme. Doing so manually can be very tedious 😑️. 32 | 33 | Of course you can use solvers which go beyond CNF. For example Microsoft has a 34 | [`Z3`](https://github.com/Z3Prover/z3) theorem proved. You can solve many more types of problems 35 | with it. But if the size of your problem matters, pure CNF solver is still way much faster 🚀️. 36 | 37 | ## What is `BEE` 🐝️ 38 | 39 | 40 | One project that tries to ease using SAT solvers is [`BEE` (Ben-Gurion 41 | University Equi-propagation Encoder)](http://amit.metodi.me/research/bee/), which 42 | 43 | > ... is a 44 | > compiler which enables to encode finite domain constraint problems to CNF. During compilation, `BEE` 45 | > applies optimizations which include equi-propagation (see paper), partial-evaluation, and a careful 46 | > selection of encoding techniques per constraint, depending on various parameters of the constraint. 47 | 48 | From my experiments, `BEE` has a good balance of expressive power and performance. 49 | 50 | ## Many ways to use `BEE` 🤔️ 51 | 52 | `BEE` is written in [`Prolog`](https://en.wikipedia.org/wiki/Prolog). So you either have to learn 53 | `Prolog`, or you can 54 | 1. encode your problem in a syntax defined by `BEE`, 55 | 2. use a program `BumbleBEE` that comes with the package to solve it directly with `BEE` 56 | 3. or use `BumbleBEE` to compile your problem to a [DIMACS CNF file](https://people.sc.fsu.edu/~jburkardt/data/cnf/cnf.html), which can be solved by the numerous 57 | SAT solvers out there. 58 | 59 | My choice is to use [Julia](https://julialang.org/) to convert combinatorics problems into 60 | `BumbleBEE` code and this is why I wrote the package [`BeeEncoder.jl`](https://github.com/newptcai/BeeEncoder.jl). 61 | 62 | Here's my workflow for smaller problems 63 | 64 | ```shell 65 | Julia code --(BeeEncoder.jl)--> BEE code --(BumbleBEE)--> solution/unsatisfiable 66 | ``` 67 | 68 | When the problem is getting bigger, I try 69 | 70 | ```shell 71 | Julia code --(BeeEncoder.jl)--> BEE code -- (BumbleBEE)--> CNF --(SAT Solver) 72 | | 73 | +-------------------------+--------------------------------+ 74 | | | 75 | v v 76 | unsatisfiable CNF solution --(BumbleSol)--> BEE solution 77 | ``` 78 | 79 | In the rest of this article, I will mostly describe how to use `BEE` 😀️. You do not need to know any 80 | Julia to understand this part. I will only briefly mention what `BeeEncoder.jl` does by the 81 | end. 82 | 83 | ## `BEE` and SAT solver for beginners 84 | 85 | ### Docker image 86 | 87 | The easiest way to try `BEE` and `BeeEncoder.jl` is to use this [docker 88 | image](https://hub.docker.com/r/newptcai/beeencoder) with everything you need. 89 | If you have [docker](https://www.docker.com/) install, simply type in a terminal 90 | ```shell 91 | docker pull newptcai/beeencoder 92 | docker run -it newptcai/beeencoder 93 | ``` 94 | This will download and start a bash shell within the image. You will find `BEE` install in the 95 | folder `/bee`. To check it works, run 96 | ```shell 97 | cd bee && ./BumbleBEE beeSolver/bExamples/ex_sat.bee 98 | ``` 99 | `BeeEncoder.jl` is also included in this image. You can start Julia REPL and use it immediately. 100 | 101 | The drawback of this method is that the image is quite large (about 600MB). This is unavoidable if we 102 | use docker. Julia itself needs about 400MB, and Prolog costs another 100MB. 😑️ 103 | 104 | ### Compiling and running `BEE` 105 | 106 | I ran into some difficulties when I tried to compile [2017 version of 107 | `BEE`](http://amit.metodi.me/research/bee/bee20170615.zip). Here is how to do it correctly on 108 | Ubuntu. Other Linux system should work in similar ways. 109 | 110 | First install [SWI-Prolog](https://www.swi-prolog.org/build/PPA.txt). You can do this in a terminal 111 | by typing 112 | ```shell 113 | sudo apt-add-repository ppa:swi-prolog/stable 114 | sudo apt-get update 115 | sudo apt-get install swi-prolog-nox 116 | ``` 117 | Download `BEE` using the link above and unzip it somewhere on your computer. 118 | In a terminal, change directory to 119 | ```shell 120 | cd /path-to-downloaded-file/bee20170615/satsolver_src 121 | ``` 122 | Compile sat solvers coming with `BEE` by 123 | ```shell 124 | env CPATH="/usr/lib/swi-prolog/include/" make satSolvers 125 | ``` 126 | If compilation is successful, you should be able to excute 127 | ```shell 128 | cd ../satsolver && ls 129 | ``` 130 | and see the following output 131 | ```shell 132 | pl-glucose4.so pl-glucose.so pl-minisat.so satsolver.pl 133 | ``` 134 | Next we compile `BumbleBEE` by 135 | ```shell 136 | cd ../beeSolver/ && make 137 | ``` 138 | If you succeed, you will be able to find `BumbleBEE` and `BumbleSol` one directory above by 139 | ```shell 140 | cd .. && ls 141 | ``` 142 | And you should see these files 143 | ```shell 144 | bApplications beeSolver BumbleSol pl-satsolver.so satsolver 145 | beeCompiler BumbleBEE Constraints.pdf README.txt satsolver_src 146 | ``` 147 | ### Using `BumbleBEE` 148 | 149 | We can now give `BEE` a try 😁️. You can find examples of `BumbleBEE` problems in the folder 150 | `beeSolver/bExamples`. A very simple one is the following 151 | `ex_sat.bee`. 152 | ```shell 153 | new_int(x,0,5) 154 | new_int(y,-4,9) 155 | new_int(z,-5,10) 156 | int_plus(x,y,z) 157 | new_int(w,0,10) 158 | new_bool(x1) 159 | new_bool(x2) 160 | new_bool(x3) 161 | new_bool(x4) 162 | bool_eq(x1,-x2) 163 | bool_eq(x2,true) 164 | bool_array_sum_eq([-x1,x2,-x3,x4],w) 165 | solve satisfy 166 | ``` 167 | It defines 4 integer variables `x, y, z, w` in various range and 4 boolean variables `x1, x2, x3, x4`. 168 | Then it adds various constraints on these variables, for example, `x+y==z` and `x1==x2`. For the 169 | syntax, check the [document](http://amit.metodi.me/research/bee/Constraints.pdf). 170 | 171 | ### Solving problem directly 172 | 173 | We can solve problem directly with `BumbleBEE` by 174 | ```shell 175 | ./BumbleBEE beeSolver/bExamples/ex_sat.bee 176 | ``` 177 | And the solution should be 178 | ```shell 179 | (base) xing@MAT-WL-xinca341:bee20170615$ ./BumbleBEE beeSolver/bExamples/ex_sat.bee 180 | % \'''/ // BumbleBEE / \_/ \_/ \ 181 | % -(|||)(') (15/06/2017) \_/ \_/ \_/ 182 | % ^^^ by Amit Metodi / \_/ \_/ \ 183 | % 184 | % reading BEE file ... done 185 | % load pl-satSolver ... % SWI-Prolog interface to Glucose v4.0 ... OK 186 | % encoding BEE model ... done 187 | % solving CNF (satisfy) ... 188 | x = 0 189 | y = -4 190 | z = -4 191 | w = 3 192 | x1 = false 193 | x2 = true 194 | x3 = false 195 | x4 = false 196 | ---------- 197 | ``` 198 | You can check that all the constraints are satisfied. 199 | 200 | ⚠️ But here is a caveat -- you must run `BumbleBEE` with the current 201 | directory `PWD` set to be where the file 202 | `BumbleBEE` is. You cannot use any other directory 🤦. For example if you try 203 | ```shell 204 | cd .. && bee20170615/BumbleBEE bee20170615/beeSolver/bExamples/ex_sat.bee 205 | ``` 206 | You will only get error messages. 207 | 208 | ### Convert the problem to CNF 209 | 210 | As I mentioned earlier, you can also compile your problem into CNF DIMACS format. For example 211 | ```shell 212 | ./BumbleBEE beeSolver/bExamples/ex_sat.bee -dimacs ./ex_sat.cnf ./ex_sat.map 213 | ``` 214 | will create two files `ex_sat.cnf` and `ex_sat.map`. The top few lines of 215 | `ex_sat.cnf` looks like this 216 | ```shell 217 | c DIMACS File generated by BumbleBEE 218 | p cnf 37 189 219 | 1 0 220 | -6 5 0 221 | -5 4 0 222 | -4 3 0 223 | -3 2 0 224 | -19 18 0 225 | -18 17 0 226 | -17 16 0 227 | ``` 228 | A little bit explanation for the first 4 lines 229 | 230 | 1. A line with `c` at the beginning is a comment. 231 | 2. The line with `p` says that this is a CNF formula with `37` variables and `189` clauses. 232 | 3. `1 0` is a clause which says that variable `1` must be true. `0` is symbol to end a 233 | clause. 234 | 4. `-6 5` means either the negate of variable `6` is true or variable `5` is true ... 235 | 236 | As you can see, with integers are needed, even a toy problem needs a large numbers of 237 | boolean variables. This is why efficient coding of integers are critical. And this is where `BEE` 238 | helps. 239 | 240 | Now you can try your favourite SAT solver on the problem. I often use 241 | [`CryptoMiniSat`](https://www.msoos.org/cryptominisat5/). Assuming that you have it on your `PATH`, you 242 | can now use 243 | ```shell 244 | cryptominisat5 ex_sat.cnf > ex_sat.sol 245 | ``` 246 | to solve the problem and save the solution into a file `ex_sat.sol`. Most of `ex_sat.sol` are 247 | comments except the last 3 lines 248 | ```shell 249 | s SATISFIABLE 250 | v 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 251 | v -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 34 -35 -36 -37 0 252 | ``` 253 | It says the problem is satisfiable and one solution is given. A number in the line starting with an `v` 254 | means a variables. Without a `-` sign in front of it, a variable is assigned the value `true` 255 | otherwise it is assigned `false`. 256 | 257 | ⚠️ To get back to a solution to `BEE` variables, we use `BumbleSol`, which is 258 | at the same folder as `BumbleBEE`. But `BumbleSol` needs bit help 😑️. Remove the starting `s` and `v` 259 | in the `ex_sat.sol` to make it like this 260 | ```shell 261 | SATISFIABLE 262 | 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 263 | -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 34 -35 -36 -37 0 264 | ``` 265 | Then we can run 266 | ```shell 267 | ./BumbleSol ex_sat.map ex_sat.sol 268 | ``` 269 | and get 270 | ```shell 271 | % \'''/ // BumbleBEE Solution Reader / \_/ \_/ \ 272 | % -(|||)(') (04/06/2016) \_/ \_/ \_/ 273 | % ^^^ by Amit Metodi / \_/ \_/ \ 274 | % 275 | % reading Dimacs solution file ... done 276 | % reading and decoding BEE map file ... 277 | x = 0 278 | y = -4 279 | z = -4 280 | w = 2 281 | x1 = false 282 | x2 = true 283 | x3 = false 284 | x4 = false 285 | ---------- 286 | ========== 287 | ``` 288 | 289 | That's it! Now you know how to use `BEE` 🐝️! Have fan with your problem 🤣️. 290 | 291 | ### Choice of SAT solver 292 | 293 | Some top-level SAT solvers are 294 | 295 | * [CaDical](https://github.com/arminbiere/cadical) -- Winner of [2019 SAT 296 | Race](http://sat-race-2019.ciirc.cvut.cz/). Tends to be 297 | fastest in dealing with solvable problems. 298 | * [Lingeling, Plingeling and Treengeling](http://fmv.jku.at/lingeling/) -- Good at parallelization. 299 | * [Painless](https://www.lrde.epita.fr/wiki/Painless) -- Uses a divide and conquer strategy for 300 | parallelization. 301 | * MapleLCMDiscChronoBT-DL -- Winner of 2019 SAT Race for unsatisfiable problem. But I have not 302 | found any documents of it. 303 | 304 | My experience is that all these SAT solvers have similar performance. It is always more important to 305 | try to encode your problem better. 306 | 307 | ## How to use `BeeEncoder.jl` 308 | 309 | When your problems becomes bigger, you don't want to write all `BEE` code manually. Here's what 310 | `BeeEncoder.jl` may help. You can write your problem in Julia, and `BeeEncoder.jl` will convert it to `BEE` syntax. 311 | Here's how to do the example above with `BeeEncoder.jl` 312 | 313 | First install `BeeEncoder.jl` by typing this in `Julia REPL`. 314 | ```Julia 315 | using Pkg; Pkg.add("BeeEncoder") 316 | ``` 317 | Then run the following code in Julia REPL 318 | ```Julia 319 | using BeeEncoder 320 | 321 | @beeint x 0 5 322 | @beeint y -4 9 323 | @beeint z -5 10 324 | 325 | @constrain x + y == z 326 | 327 | @beeint w 0 10 328 | 329 | xl = @beebool x[1:4] 330 | 331 | @constrain xl[1] == -xl[2] 332 | @constrain xl[2] == true 333 | 334 | @constrain sum([-xl[1], xl[2], -xl[3], xl[4]]) == w 335 | 336 | BEE.render() 337 | ``` 338 | You will get output like this 339 | ```Julia 340 | new_int(w, 0, 10) 341 | new_int(x, 0, 5) 342 | new_int(z, -5, 10) 343 | new_int(y, -4, 9) 344 | new_bool(x1) 345 | new_bool(x4) 346 | new_bool(x2) 347 | new_bool(x3) 348 | int_plus(x, y, z) 349 | bool_eq(x1, -x2) 350 | bool_eq(x2, true) 351 | bool_array_sum_eq(([-x1, x2, -x3, x4], w)) 352 | solve satisfy 353 | ``` 354 | Exactly as above. 355 | You can solve this into a file and solve it with `BumbleBEE` as I described before. 356 | 357 | If you have `BEE` installed and `BumbleBEE` can be found through your `PATH` environment variable, then you can run 358 | `BEE.solve()` directly in Julia and get the solution, like this. 359 | ```Julia 360 | julia> output = solve(); 361 | % SWI-Prolog interface to Glucose v4.0 ... OK 362 | % \'''/ // BumbleBEE / \_/ \_/ \ 363 | % -(|||)(') (15/06/2017) \_/ \_/ \_/ 364 | % ^^^ by Amit Metodi / \_/ \_/ \ 365 | % 366 | % reading BEE file ... done 367 | % load pl-satSolver ... % encoding BEE model ... done 368 | % solving CNF (satisfy) ... 369 | w = 2 370 | x = 0 371 | z = -4 372 | y = -4 373 | x1 = false 374 | x4 = false 375 | x2 = true 376 | x3 = true 377 | ---------- 378 | ========== 379 | ``` 380 | And if you check `output`, you will it is a dictionary containing the solution. 381 | ```Julia 382 | julia> out 383 | BEE solution: 384 | * Satisfiable: true 385 | * Integer variables: Dict("w" => 2,"x" => 0,"z" => -4,"y" => -4) 386 | * Boolean variables: Dict{String,Bool}("x1" => 0,"x4" => 0,"x2" => 1,"x3" => 1) 387 | ``` 388 | To reset the model, use `reset()`. 389 | 390 | ## Acknowledgement 🙏️ 391 | 392 | I want to thank all the generous ❤️ people who have spend their time to create these amazing SAT 393 | solvers and made them freely available to everyone. 394 | 395 | By writing this module, I have learn quite a great deal of Julia and its convenient meta-programming 396 | features. I want to thank everyone 💁 on GitHub and [Julia Slack channel](https://slackinvite.julialang.org/) who has helped me, in 397 | particular Alex Arslan, [David Sanders](https://github.com/dpsanders), Syx Pek, and [Jeffrey 398 | Sarnoff](https://github.com/JeffreySarnoff). 399 | 400 | I also want to thank my dear friend [Yelena Yuditsky](https://sites.google.com/view/yuditsky/home) for 401 | giving me a problem to solve so that I have the motivation to do all this. 402 | -------------------------------------------------------------------------------- /src/BeeEncoder.jl: -------------------------------------------------------------------------------- 1 | module BeeEncoder 2 | 3 | import Base: -, +, *, /, mod, max, min, ==, <, <=, !=, >, >=, sum, show, convert, isequal, in 4 | 5 | using Suppressor 6 | 7 | export BeeInt, 8 | BeeBool, BeeModel, BeeSolution, GBL_MODEL, 9 | 10 | @beeint, @beebool, beeint, beebool, 11 | 12 | boolnum, intnum, hasvar, hasbool, hasint, fetchbool, getbool, getint, 13 | 14 | render, reset, solve, 15 | 16 | constrain, @constrain, and, or, iff, alldiff, 17 | 18 | capture_render 19 | 20 | # ------------------------------------------------------------- 21 | # abstract types 22 | # ------------------------------------------------------------- 23 | 24 | "An object that can be rendered to BEE syntax" 25 | abstract type BeeObject end 26 | "A symbol in BEE sytanx. Can be either a variable or a value." 27 | abstract type BeeSymbol <: BeeObject end 28 | abstract type BeeBoolean <: BeeSymbol end 29 | abstract type BeeInteger <: BeeSymbol end 30 | 31 | BB = Union{BeeBoolean, Bool} 32 | ZZ = Union{BeeInteger, Int} 33 | 34 | "Reload `isequal` to allow using `BeeSymbol` comparing them for `Dict`." 35 | isequal(v1::BeeSymbol, v2::BeeSymbol) = v1 === v2 36 | 37 | "Reload `in` to allow checking if a `BeeSymbol` is in an array" 38 | function in(v::T, vs::Array{T,1}) where T <: BeeSymbol 39 | for s in vs 40 | if v === s 41 | return true 42 | end 43 | end 44 | false 45 | end 46 | 47 | # ------------------------------------------------------------- 48 | # BEE integer variable 49 | # ------------------------------------------------------------- 50 | 51 | "An integer variable in BEE syntax" 52 | struct BeeInt <: BeeInteger 53 | name::String 54 | lo::Int 55 | hi::Int 56 | function BeeInt(model, name, lo, hi) 57 | if lo > hi 58 | error("$lo > $hi") 59 | end 60 | if hasvar(model, name) 61 | error("Variable $name has already been defined in $model") 62 | end 63 | var = new(name, lo, hi) 64 | model.intdict[name] = var 65 | end 66 | end 67 | 68 | BeeInt(name::String, lo::Int, hi::Int) = BeeInt(GBL_MODEL, name, lo, hi) 69 | BeeInt(name::Symbol, lo, hi) = BeeInt(string(name), lo, hi) 70 | 71 | macro beeint(name, lo, hi) 72 | q = Expr(:block) 73 | if isa(name, Symbol) 74 | ex = :($(esc(name)) = beeint($(string(name)), $lo, $hi)) 75 | push!(q.args, ex) 76 | push!(q.args, escvar(name)) # return all of the variables we created 77 | elseif isa(name, Expr) && name.head == :ref 78 | vhead = name.args[1] 79 | vlist = [] 80 | for i in eval(name.args[2]) 81 | vhead = name.args[1] 82 | vstr = "$vhead$i" 83 | vsym = Symbol(vstr) 84 | ex = :($(esc(vsym)) = beeint($(vstr), $lo, $hi)) 85 | push!(vlist, vsym) 86 | push!(q.args, ex) 87 | end 88 | push!(q.args, escvar(vlist)) 89 | end 90 | q 91 | end 92 | 93 | "Create an integer varaible called `name` in `GBL_MODEL`" 94 | beeint(name, lo, hi) = beeint(GBL_MODEL, name, lo, hi) 95 | 96 | "Create an integer varaible called `name` in `GBL_MODEL`" 97 | beeint(model, name, lo, hi) = BeeInt(model, name, lo, hi) 98 | 99 | render(io::IO, var::BeeInt) = print(io, "new_int($var, $(var.lo), $(var.hi))\n") 100 | 101 | 102 | # ------------------------------------------------------------- 103 | # BEE boolean variable 104 | # ------------------------------------------------------------- 105 | 106 | "A boolean variable in BEE syntax" 107 | struct BeeBool <: BeeBoolean 108 | name::String 109 | function BeeBool(model, name) 110 | if hasvar(model, name) 111 | error("Variable $name has already been defined in $model") 112 | end 113 | var = new(name) 114 | model.booldict[name] = var 115 | end 116 | end 117 | BeeBool(name::String) = BeeBool(GBL_MODEL, name) 118 | BeeBool(name::Symbol) = BeeBool(string(name)) 119 | 120 | function escvar(var) 121 | if isa(var, Symbol) 122 | vs = esc(var) 123 | else 124 | vs = Expr(:vect, [esc(v) for v in var]...) 125 | end 126 | vs 127 | end 128 | 129 | macro beebool(namelist...) 130 | q = Expr(:block) 131 | varlist = [] # list of boolean variables to create 132 | for name in namelist 133 | if isa(name, Symbol) 134 | push!(varlist, name) 135 | ex = :($(esc(name)) = beebool($(string(name)))) 136 | push!(q.args, ex) 137 | elseif isa(name, Expr) && name.head == :ref 138 | vhead = name.args[1] 139 | vlist = [] 140 | for i in eval(name.args[2]) 141 | vhead = name.args[1] 142 | vstr = "$vhead$i" 143 | vsym = Symbol(vstr) 144 | ex = :($(esc(vsym)) = beebool($(vstr))) 145 | push!(vlist, vsym) 146 | push!(q.args, ex) 147 | end 148 | push!(varlist, vlist) 149 | end 150 | end 151 | if length(varlist) > 1 152 | ret = Expr(:tuple, map(escvar, varlist)...) 153 | else 154 | ret = escvar(varlist[1]) 155 | end 156 | push!(q.args, ret) # return all of the variables we created 157 | q 158 | end 159 | 160 | "Create a boolean varaible called `name` in `GBL_MODEL`" 161 | beebool(name) = beebool(GBL_MODEL, name) 162 | 163 | "Create a boolean varaible called `name` in `model`" 164 | beebool(model, name) = BeeBool(model, name) 165 | 166 | render(io::IO, var::BeeBool) = print(io, "new_bool($var)\n") 167 | 168 | "The negate of a boolean variable in BEE syntax" 169 | struct BeeNegateBool <:BeeBoolean 170 | boolvar::BeeBool 171 | end 172 | 173 | show(io::IO, v::BeeNegateBool) = print(io, "-", v.boolvar) 174 | 175 | 176 | (-)(var::BeeBool) = BeeNegateBool(var) 177 | (-)(var::BeeNegateBool) = var.boolvar 178 | 179 | # ------------------------------------------------------------- 180 | # BEE literals 181 | # ------------------------------------------------------------- 182 | 183 | "An integer value like `1`" 184 | struct BeeIntLiteral <: BeeInteger 185 | val::Int 186 | end 187 | show(io::IO, v::BeeIntLiteral) = print(io, v.val) 188 | convert(::Type{BeeInteger}, v::Int) = BeeIntLiteral(v) 189 | convert(::Type{BeeSymbol}, v::Int) = BeeIntLiteral(v) 190 | 191 | "A boolean value, i.e., `true` or `false`" 192 | struct BeeBoolLiteral <: BeeBoolean 193 | val::Bool 194 | end 195 | show(io::IO, v::BeeBoolLiteral) = print(io, v.val) 196 | convert(::Type{BeeBoolean}, v::Bool) = BeeBoolLiteral(v) 197 | convert(::Type{BeeSymbol}, v::Bool) = BeeBoolLiteral(v) 198 | 199 | """ 200 | render(obj::BeeObject) 201 | 202 | Render `obj` to BEE syntax and print it to `stdout`. 203 | """ 204 | render(obj::BeeObject) = render(Base.stdout, obj) 205 | 206 | render(arr::Array{T, 1}) where T <: Any = render(Base.stdout, arr) 207 | 208 | render(io::IO, arr::Array{T, 1}) where T <: BeeObject = show(io, arr) 209 | 210 | show(io::IO, v::BeeSymbol) = print(io, v.name) 211 | 212 | function show(io::IO, arr::Array{T, 1}) where T <: BeeObject 213 | print(io, "[", join(arr, ", "), "]") 214 | end 215 | 216 | # ------------------------------------------------------------- 217 | # BEE expressions 218 | # ------------------------------------------------------------- 219 | 220 | """ 221 | An expression can be made part of a `BeeObject`, but they themselves cannot be rendered. 222 | """ 223 | abstract type BeeExpression end 224 | 225 | function show(io::IO, 226 | tuple::NTuple{N, T} where {N, T <: BeeSymbol}) 227 | print(io, join(tuple, ", ")) 228 | end 229 | 230 | # ------------------------------------------------------------- 231 | # BEE Constrains 232 | # ------------------------------------------------------------- 233 | 234 | struct BeeConstraint <: BeeObject 235 | name::String 236 | # Don't check the type of the array 237 | varlist::Array{T, 1} where T 238 | end 239 | 240 | """ 241 | render(io, cons) 242 | 243 | Render `cons` to BEE syntax. For constraints, there's is no difference between how they are rendered 244 | and printed. 245 | """ 246 | render(io::IO, constraint::BeeConstraint) = print(io, constraint, "\n") 247 | 248 | function show(io::IO, constraint::BeeConstraint) 249 | varstr = join(constraint.varlist, ", ") 250 | print(io, constraint.name, "(", varstr, ")") 251 | end 252 | 253 | # ------------------------------------------------------------- 254 | # BEE model 255 | # ------------------------------------------------------------- 256 | struct BeeModel <: BeeObject 257 | name::String 258 | intdict::Dict{String, BeeInt} 259 | booldict::Dict{String, BeeBool} 260 | conslist::Array{BeeConstraint, 1} 261 | end 262 | BeeModel(name::String) = BeeModel(name, Dict{String, BeeInt}(), Dict{String, BeeBool}(), Array{BeeConstraint,1}()) 263 | 264 | show(io::IO, m::BeeModel) = print(io, "BEE model [$(m.name)]") 265 | show(io::IO, ::MIME"text/plain", m::BeeModel) = print(io, 266 | """BEE model [$(m.name)]: 267 | * Integer variables: $(collect(values(m.intdict))) 268 | * Boolean variables: $(collect(values(m.booldict))) 269 | * Constraint: $(m.conslist)""") 270 | 271 | "Check if the model has a variable called `name`" 272 | hasvar(model::BeeModel, name::String) = haskey(model.intdict, name) || haskey(model.booldict, name) 273 | 274 | "Check if the model has a bollean variable called `name` in `model`" 275 | hasbool(model::BeeModel, name) = haskey(model.booldict, name) 276 | 277 | "Check if the model has a bollean variable called `name` in `GBL_MODEL`" 278 | hasbool(name) = hasbool(GBL_MODEL, name) 279 | 280 | "Check if the model has a integer variable called `name` in `model`" 281 | hasint(model::BeeModel, name) = haskey(model.intdict, name) 282 | 283 | "Check if the model has a integer variable called `name` in `GBL_MODEL`" 284 | hasint(name) = hasint(GBL_MODEL, name) 285 | 286 | "Retrive an existing integer variable called `name` in `GBL_MODEL`" 287 | getint(model, name) = model.intdict[name] 288 | 289 | "Retrive an existing integer variable called `name` in `GBL_MODEL`" 290 | getint(name) = getint(GBL_MODEL, name) 291 | 292 | "Either create or retrive an existing boolean variable called `name` in `model`" 293 | function fetchbool(model::BeeModel, name) 294 | if hasbool(model, name) 295 | model.booldict[name] 296 | else 297 | beebool(model, name) 298 | end 299 | end 300 | 301 | "Either create or retrive an existing boolean variable called `name` in `GBL_MODEL`" 302 | fetchbool(name) = fetchbool(GBL_MODEL, name) 303 | 304 | "Retrive an existing boolean variable called `name` in `GBL_MODEL`" 305 | getbool(model, name) = model.booldict[name] 306 | 307 | "Retrive an existing boolean variable called `name` in `GBL_MODEL`" 308 | getbool(name) = getbool(GBL_MODEL, name) 309 | 310 | "Return the number of boolean variables in `GBL_MODEL`" 311 | boolnum() = length(GBL_MODEL.booldict) 312 | 313 | "Return the number of integer variables in `GBL_MODEL`" 314 | intnum() = length(GBL_MODEL.intdict) 315 | 316 | """ 317 | constrain(model, cons) 318 | 319 | Add the `cons` to `model`. Note that unlike a variable, a constraint is not automatically added to 320 | any model when it is created. 321 | """ 322 | function constrain(model::BeeModel, cons::BeeConstraint) 323 | push!(model.conslist, cons) 324 | cons 325 | end 326 | constrain(cons) = constrain(GBL_MODEL, cons) 327 | 328 | macro constrain(cons) 329 | :(constrain($(esc(cons)))) 330 | end 331 | 332 | "Render the global model `GBL_MODEL` to BEE syntax and print it to `stdout`." 333 | render() = render(Base.stdout, GBL_MODEL) 334 | 335 | "Render the global model `GBL_MODEL` to BEE syntax and print it to `io`." 336 | render(io::IO) = render(io, GBL_MODEL) 337 | 338 | function render(io::IO, model::BeeModel) 339 | for intv in values(model.intdict) 340 | render(io, intv) 341 | end 342 | for boolv in values(model.booldict) 343 | render(io, boolv) 344 | end 345 | for cons in model.conslist 346 | render(io, cons) 347 | end 348 | print(io, "solve satisfy\n") 349 | end 350 | 351 | "Delete all variables and constraints from the default model." 352 | function reset() 353 | global GBL_MODEL = BeeModel("defaul model") 354 | end 355 | 356 | # ------------------------------------------------------------- 357 | # Call BumbleBEE directly from Julia 358 | # ------------------------------------------------------------- 359 | struct BeeSolution 360 | sat::Bool 361 | intdict::Dict{String, Int} 362 | booldict::Dict{String, Bool} 363 | end 364 | 365 | "Call `BumbleBEE` to solve the `model` and print the output into `io`" 366 | function solve(model::BeeModel, io::IO, deletefile=true) 367 | # Find where is BumbleBEE 368 | beepath = Sys.which("BumbleBEE") 369 | beedir = dirname(beepath) 370 | 371 | # Render solution to the file 372 | tempf = tempname() * ".bee" 373 | open(tempf, "w") do io 374 | render(io) 375 | end 376 | 377 | tempout = tempname() * ".sol" 378 | 379 | # Solve with BumbleBEE 380 | curdir = pwd() 381 | cd(beedir) 382 | println(tempout) 383 | run(pipeline(`./BumbleBEE $tempf`, stdout=tempout)) 384 | output = readlines(tempout) 385 | cd(curdir) 386 | 387 | if deletefile 388 | rm(tempf) 389 | rm(tempout) 390 | end 391 | 392 | # filter comments 393 | rc = r"^%" 394 | ri = r"^\s*(\w+)\s*=\s*(-?+\d+)" 395 | rb = r"^\s*(\w+)\s*=\s*(true|false)" 396 | runsat = r"=====UNSATISFIABLE=====" 397 | 398 | sat = true 399 | 400 | intdict = Dict{String, Int}() 401 | booldict = Dict{String, Bool}() 402 | 403 | for line in output 404 | println(io, line) 405 | if match(runsat, line) !== nothing 406 | sat = false 407 | end 408 | if match(rc, line) !== nothing 409 | continue 410 | elseif (m = match(ri, line)) !== nothing 411 | name, val = m.captures 412 | intdict[name] = parse(Int, val) 413 | elseif (m = match(rb, line)) !== nothing 414 | name, val = m.captures 415 | booldict[name] = parse(Bool, val) 416 | end 417 | end 418 | 419 | BeeSolution(sat, intdict, booldict) 420 | end 421 | 422 | " Solve the default model and print the solution to `stdout`." 423 | solve() = solve(GBL_MODEL, Base.stdout) 424 | 425 | solve(deletefile::Bool) = solve(GBL_MODEL, Base.stdout, deletefile) 426 | 427 | show(io::IO, ::MIME"text/plain", sol::BeeSolution) = print(io, 428 | """ 429 | BEE solution: 430 | * Satisfiable: $(sol.sat) 431 | * Integer variables: $(sol.intdict) 432 | * Boolean variables: $(sol.booldict)""") 433 | 434 | # ------------------------------------------------------------- 435 | # BEE operator for both integer and boolean 436 | # ------------------------------------------------------------- 437 | 438 | # Create BEE summation expression, which applies to list of symbols 439 | struct BeeSum{T <: BeeSymbol} <: BeeExpression 440 | varlist::Array{BeeSymbol, 1} 441 | end 442 | 443 | sum(varlist::Array{T, 1}) where T <: BeeBoolean = BeeSum{T}(varlist) 444 | sum(varlist::Array{T, 1}) where T <: BeeInteger = BeeSum{T}(varlist) 445 | 446 | # Create BEE operator on summation. 447 | for (VT, VF) in [(:BeeBoolean, :bool), (:BeeInteger, :int)], 448 | (OP, EF) in [(:(<=), :leq), (:(>=), :geq), (:(==), :eq), (:(<), :lt), (:(>), :gt), (:(!=), :neq)] 449 | @eval BeeEncoder begin 450 | # Some of these are not symmetirc. Let's don't switch order 451 | # $OP(lhs::ZZ, rhs::BeeSum{T} where T <: $VT) = $OP(rhs, lhs) 452 | $OP(lhs::BeeSum{T} where T <: $VT, rhs::ZZ) = $(Symbol(VF, :_array_sum_, EF))(lhs.varlist, rhs) 453 | end 454 | end 455 | 456 | # ------------------------------------------------------------- 457 | # BEE operator for integers 458 | # ------------------------------------------------------------- 459 | 460 | # Boolean operator on two integers 461 | intBOP = [(:BeeLeq, :leq, :(<=)), (:BeeGeq, :geq, :(>=)), (:BeeEq, :eq, :(==)), 462 | (:BeeLt, :lt, :(<)), (:BeeGt, :gt, :(>)), (:BeeNeq, :neq, :(!=))] 463 | # Arithmetic operator on two integers 464 | intAOP = [(:BeePlus, :plus, :+), (:BeeTimes, :times, :*), (:BeeMax, :max, :max), 465 | (:BeeMin, :min, :min), (:BeeDiv, :div, :/), (:BeeMod, :mod, :mod)] 466 | 467 | # Define function for integer $OP integer. Avoid matching `Int` $OP `Int` 468 | for (ET, EF, OP) in [intBOP; intAOP] 469 | @eval BeeEncoder begin 470 | $OP(lhs::Int, rhs::BeeInteger) = $ET(BeeIntLiteral(lhs), rhs) 471 | $OP(lhs::BeeInteger, rhs::Int) = $ET(lhs, BeeIntLiteral(rhs)) 472 | $OP(lhs::BeeInteger, rhs::BeeInteger) = $ET(lhs, rhs) 473 | end 474 | end 475 | 476 | # Create BEE boolean expression for two integers, which applies to two `BeeInteger`. 477 | for (ET, EF, OP) in intBOP 478 | @eval BeeEncoder begin 479 | struct $ET <: BeeExpression 480 | lhs::BeeInteger 481 | rhs::BeeInteger 482 | end 483 | 484 | # `lhs` is true `iff` rhs is true 485 | (==)(lhs::BeeBoolean, rhs::$ET) = rhs == lhs 486 | function (==)(lhs::$ET, rhs::BeeBoolean) 487 | $(Symbol(:int_, EF, :_reif))(lhs.lhs, lhs.rhs, rhs) 488 | end 489 | 490 | # `lhs` is true `iff` rhs is true 491 | (==)(lhs::Bool, rhs::$ET) = rhs == lhs 492 | function (==)(lhs::$ET, rhs::Bool) 493 | if rhs 494 | $(Symbol(:int_, EF))(lhs.lhs, lhs.rhs) 495 | else 496 | $(Symbol(:int_, EF, :_reif))(lhs.lhs, lhs.rhs, rhs) 497 | end 498 | end 499 | end 500 | end 501 | 502 | # Create BEE arithmetic expression for two integers, which applies to two `BeeInteger`. 503 | for (ET, EF, OP) in intAOP 504 | @eval BeeEncoder begin 505 | struct $ET <: BeeExpression 506 | lhs::BeeInteger 507 | rhs::BeeInteger 508 | end 509 | 510 | # `lhs` == `rhs` is true 511 | (==)(lhs::ZZ, rhs::$ET) = rhs == lhs 512 | function (==)(lhs::$ET, rhs::ZZ) 513 | $(Symbol(:int_, EF))(lhs.lhs, lhs.rhs, rhs) 514 | end 515 | end 516 | end 517 | 518 | # Create BEE allDiff operations on one integer array 519 | intarrayOP = [(:BeeArrayAllDiff, :allDiff, :alldiff, :_reif)] 520 | for (ET, EF, OP, TAIL) in intarrayOP 521 | @eval BeeEncoder begin 522 | struct $ET <: BeeExpression 523 | varlist::Array{BeeInteger, 1} 524 | end 525 | 526 | # No need to check type here 527 | $OP(varlist::Array{T, 1} where T <: ZZ) = $ET(varlist) 528 | 529 | (==)(lhs::BeeInteger, rhs::$ET) = rhs == lhs 530 | function (==)(lhs::$ET, rhs::BeeInteger) 531 | $(Symbol(:int_array_, EF, TAIL))(lhs.varlist, rhs) 532 | end 533 | 534 | (==)(lhs::Bool, rhs::$ET) = rhs == lhs 535 | function (==)(lhs::$ET, rhs::Bool) 536 | if rhs 537 | $(Symbol(:int_array_, EF))(lhs.varlist) 538 | else 539 | $(Symbol(:int_array_, EF, TAIL))(lhs.varlist, rhs) 540 | end 541 | end 542 | end 543 | end 544 | 545 | # Create BEE operations on one integer array 546 | intarrayOP = [(:BeeArrayPlus, :plus, :plus), 547 | (:BeeArrayTimes, :times, :times), 548 | (:BeeArrayMax, :max, :max), 549 | (:BeeArrayMin, :min, :min)] 550 | for (ET, EF, OP) in intarrayOP 551 | @eval BeeEncoder begin 552 | struct $ET <: BeeExpression 553 | varlist::Array{BeeInteger, 1} 554 | end 555 | 556 | # No need to check type here 557 | $OP(varlist::Array{T, 1} where T <: ZZ) = $ET(varlist) 558 | 559 | (==)(lhs::BeeInteger, rhs::$ET) = rhs == lhs 560 | function (==)(lhs::$ET, rhs::BeeInteger) 561 | $(Symbol(:int_array_, EF))(lhs.varlist, rhs) 562 | end 563 | end 564 | end 565 | 566 | # ------------------------------------------------------------- 567 | # BEE operator for boolean 568 | # ------------------------------------------------------------- 569 | 570 | # hard code this bit since it's just one function 571 | (==)(lhs::Bool, rhs::BeeBoolean) = bool_eq(lhs, rhs) 572 | (==)(lhs::BeeBoolean, rhs::Bool) = bool_eq(lhs, rhs) 573 | (==)(lhs::BeeBoolean, rhs::BeeBoolean) = bool_eq(lhs, rhs) 574 | 575 | # Logic Expressions on two boolean. 576 | boolOP = [(:BeeAnd, :and, :and), (:BeeOr, :or, :or), (:BeeXor, :xor, :xor), (:BeeIff, :iff, :iff)] 577 | for (ET, EF, OP) in boolOP 578 | @eval BeeEncoder begin 579 | struct $ET <: BeeExpression 580 | lhs::BeeSymbol 581 | rhs::BeeSymbol 582 | end 583 | 584 | # No need to check type here 585 | $OP(lhs, rhs) = $ET(lhs, rhs) 586 | 587 | (==)(lhs::BeeBoolean, rhs::$ET) = rhs == lhs 588 | function (==)(lhs::$ET, rhs::BeeBoolean) 589 | $(Symbol(:bool_, EF, :_reif))(lhs.lhs, lhs.rhs, rhs) 590 | end 591 | 592 | (==)(lhs::Bool, rhs::$ET) = rhs == lhs 593 | function (==)(lhs::$ET, rhs::Bool) 594 | if rhs 595 | $(Symbol(:bool_, EF))(lhs.lhs, lhs.rhs) 596 | else 597 | $(Symbol(:bool_, EF, :_reif))(lhs.lhs, lhs.rhs, rhs) 598 | end 599 | end 600 | end 601 | end 602 | 603 | # Create BEE operators on boolean arrays. Note that 604 | boolarrayOP = [(:BeeArrayAnd, :and, :and), (:BeeArrayOr, :or, :or), (:BeeArrayXor, :xor, :xor), (:BeeArrayIff, :iff, :iff)] 605 | for (ET, EF, OP) in boolarrayOP 606 | @eval BeeEncoder begin 607 | struct $ET <: BeeExpression 608 | varlist::Array{BeeBoolean, 1} 609 | end 610 | 611 | # No need to check type here 612 | $OP(varlist) = $ET(varlist) 613 | 614 | (==)(lhs::BeeBoolean, rhs::$ET) = rhs == lhs 615 | function (==)(lhs::$ET, rhs::BeeBoolean) 616 | $(Symbol(:bool_array_, EF, :_reif))(lhs.varlist, rhs) 617 | end 618 | 619 | (==)(lhs::Bool, rhs::$ET) = rhs == lhs 620 | function (==)(lhs::$ET, rhs::Bool) 621 | if rhs 622 | $(Symbol(:bool_array_, EF))(lhs.varlist) 623 | else 624 | $(Symbol(:bool_array_, EF, :_reif))(lhs.varlist, rhs) 625 | end 626 | end 627 | end 628 | end 629 | 630 | # Create BEE operators on two boolean arrays 631 | bool2arrayOP = [(:BeeArrayEq, :eq, :(==)), (:BeeArrayNeq, :neq, :(!=)), 632 | (:BeeLex, :lex, :(<=)), (:BeelexLt, :lexLt, :(<))] 633 | for (ET, EF, OP) in bool2arrayOP 634 | @eval BeeEncoder begin 635 | struct $ET <: BeeExpression 636 | lhs::Array{BeeBoolean, 1} 637 | rhs::Array{BeeBoolean, 1} 638 | end 639 | 640 | $OP(lhs::Array{T, 1} where T <: BB, rhs::Array{T, 1} where T <: BB) = $ET(lhs, rhs) 641 | 642 | (==)(lhs::BeeBoolean, rhs::$ET) = rhs == lhs 643 | function (==)(lhs::$ET, rhs::BeeBoolean) 644 | $(Symbol(:bool_arrays_, EF, :_reif))(lhs.lhs, lhs.rhs, rhs) 645 | end 646 | 647 | (==)(lhs::Bool, rhs::$ET) = rhs == lhs 648 | function (==)(lhs::$ET, rhs::Bool) 649 | if rhs 650 | $(Symbol(:bool_arrays_, EF))(lhs.lhs, lhs.rhs) 651 | else 652 | $(Symbol(:bool_arrays_, EF, :_reif))(lhs.lhs, lhs.rhs, rhs) 653 | end 654 | end 655 | end 656 | end 657 | 658 | 659 | # ------------------------------------------------------------- 660 | # BEE functions 661 | # ------------------------------------------------------------- 662 | 663 | # Adding constraint function. Do not do any check. Let BEE to report errors. 664 | for F in (:int_order2bool_array, 665 | :bool2int, 666 | :bool_eq, 667 | :bool_array_eq_reif, 668 | :bool_array_or, :bool_array_and, :bool_array_xor, :bool_array_iff, 669 | :bool_array_or_reif, :bool_array_and_reif, :bool_array_xor_reif, :bool_array_iff_reif, 670 | :bool_or_reif, :bool_and_reif, :bool_xor_reif, :bool_iff_reif, 671 | :bool_ite, 672 | :bool_ite_reif, 673 | :int_leq, :int_geq, :int_eq, :int_lt, :int_gt, :int_neq, 674 | :int_leq_reif, :int_geq_reif, :int_eq_reif, :int_lt_reif, :int_gt_reif, :int_neq_reif, 675 | :int_array_allDiff, 676 | :int_array_allDiff_reif, 677 | :int_array_allDiffCond, 678 | :int_abs, 679 | :int_plus, :int_times, :int_div, :int_mod, :int_max, :int_min, 680 | :int_array_plus, :int_array_times, :int_array_max, :int_array_min, 681 | :bool_array_sum_leq, :bool_array_sum_geq, :bool_array_sum_eq, :bool_array_sum_lt, :bool_array_sum_gt, 682 | :bool_array_pb_leq, :bool_array_pb_geq, :bool_array_pb_eq, :bool_array_pb_lt, :bool_array_pb_gt, 683 | :int_array_sum_leq, :int_array_sum_geq, :int_array_sum_eq, :int_array_sum_lt, :int_array_sum_gt, 684 | :int_array_lin_leq, :int_array_lin_geq, :int_array_lin_eq, :int_array_lin_lt, :int_array_lin_gt, 685 | :int_array_sumCond_leq, :int_array_sumCond_geq, :int_array_sumCond_eq, :int_array_sumCond_lt, :int_array_sumCond_gt, 686 | :bool_array_sum_modK, 687 | :bool_array_sum_divK, 688 | :bool_array_sum_divModK, 689 | :int_array_sum_modK, 690 | :int_array_sum_divK, 691 | :int_array_sum_divModK, 692 | :bool_arrays_eq, :bool_arrays_neq, 693 | :bool_arrays_eq_reif, :bool_arrays_neq_reif, 694 | :bool_arrays_lex, 695 | :bool_arrays_lexLt, 696 | :bool_arrays_lex_reif, 697 | :bool_arrays_lexLt_reif, 698 | :int_arrays_eq, :int_arrays_neq, 699 | :int_arrays_eq_reif, :int_arrays_neq_reif, 700 | :int_arrays_lex, 701 | :int_arrays_lexLt, 702 | :int_arrays_lex_implied, 703 | :int_arrays_lexLt_implied, 704 | :int_arrays_lex_reif, 705 | :int_arrays_lexLt_reif) 706 | SF = String(F) 707 | @eval BeeEncoder $F(var...) = BeeConstraint($SF, [var...]) 708 | end 709 | 710 | # ------------------------------------------------------------- 711 | # Initialize module 712 | # ------------------------------------------------------------- 713 | 714 | reset() 715 | 716 | # ------------------------------------------------------------- 717 | # For testing 718 | # ------------------------------------------------------------- 719 | function capture_render(c) 720 | @capture_out render(c) 721 | end 722 | 723 | function capture_render() 724 | @capture_out render() 725 | end 726 | 727 | end 728 | --------------------------------------------------------------------------------