diff --git a/Trees/SAT_Construction.ipynb b/Trees/SAT_Construction.ipynb new file mode 100644 index 0000000..48045ca --- /dev/null +++ b/Trees/SAT_Construction.ipynb @@ -0,0 +1,489 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyPHpSfq+tkEJ6pqN8jebPBC", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "jv83sxEU2Ig0" + } + }, + { + "cell_type": "markdown", + "source": [ + "# SAT Constructions\n", + "\n", + "The purpose of this Python notebook is to use investigate SAT constructions that impose constraints on sets of variables. We'll build constructions for ensuring all of the variables are the same, that only one of the variables is true, that at leats $K$ of the variables are true, that fewer than $K$ of the variables are true and that exactly $K$ of the variables are true.\n", + "\n", + "Work through the cells below, running each cell in turn. In various places you will see the words \"TODO\". Follow the instructions at these places and write code to complete the functions.\n", + "\n", + "You can save a local copy of this notebook in your Google account and work through it in Colab (recommended) or you can download the notebook and run it locally using Jupyter notebook or similar. If you are using CoLab, we recommend that turn off AI autocomplete (under cog icon in top-right corner), which will give you the answers and defeat the purpose of the exercise.\n", + "\n", + "A fully working version of this notebook with the complete answers can be found [here](https://colab.research.google.com/github/udlbook/udlbook/blob/main/Trees/SAT_Construction_Answers.ipynb).\n", + "\n", + "Contact me at iclimbtreesmail@gmail.com if you find any mistakes or have any suggestions." + ], + "metadata": { + "id": "IsBSW40O20Hv" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "WZYb15hc19es" + }, + "outputs": [], + "source": [ + "!pip install z3-solver\n", + "from z3 import *\n", + "import numpy as np\n", + "from itertools import combinations" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Same\n", + "\n", + "Let's write a subroutine that returns a Boolean logic formula that constrains $N$ variables to be the same.\n", + "\n", + "$$ \\textrm{Same}[x_{1},x_{2},x_{3},\n", + "\\ldots x_{N}]:= (x_{1}\\land x_{2}\\land x_{3},\\ldots,x_N)\\lor(\\overline{x}_{1}\\land \\overline{x}_{2}\\land \\overline{x}_{3},\\ldots,\\overline{x}_N).$$" + ], + "metadata": { + "id": "aVj9RmuB3ZWJ" + } + }, + { + "cell_type": "markdown", + "source": [ + "First let's define the variables. We'll choose $N=10$" + ], + "metadata": { + "id": "Amv5G3VR3zIJ" + } + }, + { + "cell_type": "code", + "source": [ + "N=10\n", + "x = [ z3.Bool(\"x_{i}\".format(i=i)) for i in range(0,N) ]\n", + "print(x)" + ], + "metadata": { + "id": "J-y3uhTs-LU7" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's write the main routine. We can make use of the fact that the Z3 command 'Or' which combines together a list of literals with Or operations." + ], + "metadata": { + "id": "4pY5yiDo-esv" + } + }, + { + "cell_type": "code", + "source": [ + "# Routine that returns the SAT construction (a Boolean logic formula) for all literals in x being the same.\n", + "def same(x):\n", + " # TODO Complete this routine\n", + " # Replace this line:\n", + " return True" + ], + "metadata": { + "id": "f0esE71E94fm" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Finally, let's test that our routine works. Here's generic routine that evaluates a vector of concrete values against a provided Boolean logic formula\n" + ], + "metadata": { + "id": "EWTuxEvrAfnK" + } + }, + { + "cell_type": "code", + "source": [ + "def check_expression(x, formula, literals):\n", + " # Create the solver\n", + " s = Solver()\n", + " # Add the constraint\n", + " s.add(formula(x))\n", + " # Add the literals\n", + " s.add(And([x[i] if literal else Not(x[i]) for i, literal in enumerate(literals)]))\n", + " # Check if it's SAT (creates the model)\n", + " sat_result = s.check()\n", + " if(sat_result==sat):\n", + " print(\"True\")\n", + " else:\n", + " print(\"False\")" + ], + "metadata": { + "id": "vY5CD6lRArNP" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "check_expression(x, same, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, same, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, same, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, same, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "Q3VMIDqfHEei" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Exactly One\n", + "\n", + "Now let's write a subroutine that returns a Boolean logic formula that is true when only one of the provided variables are the same.\n", + "\n", + "First, we write a formula that ensures at least one of the variables is true:\n", + "\n", + "$$\\textrm{AtLeastOne}[x_1,x_2,x_3]:= x_{1}\\lor x_{2} \\lor x_{3}, \\ldots, x_{N}.$$\n", + "\n", + "Then, we write a formula that ensures that no more than one is true. For each possible pair of literals, we ensure that they are not both true:\n", + "\n", + "$$\\textrm{NoMoreThanOne}[x_{1},x_{2},x_{3}]:= \\lnot (x_{1}\\land x_{2}) \\land \\lnot (x_{1}\\land x_{3}),\\ldots, \\land \\lnot (x_{n-1}\\land x_{N}) $$\n", + "\n", + "Finally, we **AND** together these two formulae:\n", + "\n", + "$$\\textrm{ExactlyOne}[x_1,x_2,x_3,\\ldots, x_N] = \\textrm{AtLeastOne}[x_1,x_2,x_3, \\ldots, x_N]\\land\\textrm{NoMoreThanOne}[x_1,x_2,x_3, \\ldots x_N]$$\n", + "\n", + "You might want to use the function \"combinations\" from itertools (imported above). Example usage:" + ], + "metadata": { + "id": "0IIRG5ZWIZV6" + } + }, + { + "cell_type": "code", + "source": [ + "test = [ z3.Bool(\"x_{i}\".format(i=i)) for i in range(0,4) ]\n", + "for comb in combinations(test, 2):\n", + " print(comb[0], comb[1])" + ], + "metadata": { + "id": "joeZiDT3LV-r" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def at_least_one(x):\n", + " # TODO Complete this routine\n", + " # Replace this line:\n", + " return True\n", + "\n", + "def no_more_than_one(x):\n", + " # TODO Complete this routine\n", + " # Replace this line:\n", + " return True\n", + "\n", + "def exactly_one(x):\n", + " # TODO Complete this routine\n", + " # Replace this line:\n", + " return True" + ], + "metadata": { + "id": "C0HGBOjI99Ex" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Let's test if this works:" + ], + "metadata": { + "id": "2Y_1zYOZIoZI" + } + }, + { + "cell_type": "code", + "source": [ + "check_expression(x, exactly_one, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, exactly_one, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, exactly_one, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, exactly_one, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "31gqasHnM3c3" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# At least K\n", + "\n", + "Finally, we'll build the construction that ensures that there at at least K elements that are true in a vector of length N.\n", + "\n", + "This one is a bit more complicated. We construct an NxK matrix of new literals $r_{i,j}$ Then we add the following constraints.\n", + "\n", + "1. Top left element $r_{0,0}$ is the first data element\n", + "\n", + "$$ r_{00} \\Leftrightarrow x_{0}$$\n", + "\n", + "2. Remaining elements $r_{0,1:}$ must be false\n", + "\n", + "$$ \\overline{r}_{0,j} \\quad\\quad \\forall j\\in\\{1,2,\\ldots K-1\\}$$\n", + "\n", + "3. Remaining elements $r_{i,0}$ in first column are true where x_i is true\n", + "\n", + "$$x_i \\Rightarrow r_{i,0} \\quad\\quad \\forall i\\in\\{1,2,\\ldots N-1\\}$$\n", + "\n", + "4. For rows 1:N-1, if the element above is true, this must be true\n", + "\n", + "$$ r_{i-1,j} \\Rightarrow r_{i,j}\\quad\\quad \\quad\\quad \\forall i\\in\\{1,2,\\ldots N-1\\}, j\\in\\{0,1,\\ldots,K-1\\}$$\n", + "\n", + "5. If x is true for this row and the element above and to the left is true then set this element to true.\n", + "\n", + "$$ (x_i \\land r_{i-1,j-1})\\Rightarrow r_{i,j} \\quad\\quad \\forall i\\in\\{1,2,\\ldots N-1\\}, j\\in\\{1,2,\\ldots,K-1\\} $$\n", + "\n", + "6. If x is false for this row and the element above is false then set to false\n", + "\n", + "$$ (\\overline{x}_{i} \\land \\overline{r}_{i-1,j}) \\Rightarrow \\overline{r}_{i,j} \\quad\\quad \\forall i\\in\\{1,2,\\ldots N-1\\}, j\\in\\{1,2,\\ldots,K-1\\} $$\n", + "\n", + "7. if x is false for this row and the element above and to the left is false then set to false:\n", + "\n", + "$$ (\\overline{x}_i \\land \\overline{r}_{i-1,j-1})\\Rightarrow \\overline{r}_{i,j} \\quad\\quad \\forall i\\in\\{1,2,\\ldots N-1\\}, j\\in\\{1,2,\\ldots,K-1\\} $$\n", + "\n", + "8. The bottom-right element of r must be true\n", + "\n", + "$$ r_{N-1,K-1}$$" + ], + "metadata": { + "id": "MJqWOMBsQrCI" + } + }, + { + "cell_type": "code", + "source": [ + "def at_least_k(x, K):\n", + " # Create nxk table of literals r_{i,j}\n", + " N = len(x) ;\n", + " r = [[ z3.Bool(\"r_{%d,%d}\"%((i,j))) for j in range(0,K) ] for i in range(0,N) ]\n", + " # TODO Complete this routine\n", + " # Replace this line:\n", + " return True ;" + ], + "metadata": { + "id": "aTcc1Ad0M9NX" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#This function defined for convenience so that check_expression works\n", + "def at_least_three(x):\n", + " return at_least_k(x,3)\n", + "\n", + "check_expression(x, at_least_three, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, at_least_three, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, at_least_three, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, at_least_three, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "ESnui2V7YBQC" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Optional: write functions to test if there are:\n", + "\n", + "* Fewer than $K$ elements\n", + "* Exactly $N$ elements\n", + "\n" + ], + "metadata": { + "id": "PwgpncA5g0-C" + } + }, + { + "cell_type": "code", + "source": [ + "def fewer_than_k(x, K):\n", + " # TODO Complete this routine\n", + " # Replace this line:\n", + " return True" + ], + "metadata": { + "id": "RS31wg5Ig0f-" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#This function defined for convenience so that check_expression works\n", + "def fewer_than_three(x):\n", + " return fewer_than_k(x,3)\n", + "\n", + "check_expression(x, fewer_than_three, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, fewer_than_three, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, fewer_than_three, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, fewer_than_three, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "Sy67NXs7hqDS" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def exactly_k(x, K):\n", + " # TODO Complete this routine\n", + " # Replace this line:\n", + " return True" + ], + "metadata": { + "id": "Fzaa1NoaicKT" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#This function defined for convenience so that check_expression works\n", + "def exactly_three(x):\n", + " return exactly_k(x,3)\n", + "\n", + "check_expression(x, exactly_three, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, exactly_three, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, exactly_three, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, exactly_three, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "L_xwfybUkKhw" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now that we've finished, here's a useful tip. Z3 actually contains routines for (AtLeast, FewerThanOrEqualToo, and Exactly). Probably faster than enything you can implement yourself!" + ], + "metadata": { + "id": "H3lipx8akmHp" + } + }, + { + "cell_type": "code", + "source": [ + "def at_least_k_z3(x,K):\n", + " return PbGe([(i,1) for i in x],K)\n", + "\n", + "def at_least_three_z3(x):\n", + " return at_least_k_z3(x,3)\n", + "\n", + "check_expression(x, at_least_three_z3, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, at_least_three_z3, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, at_least_three_z3, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, at_least_three_z3, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "Yho3WtG0luM6" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def fewer_than_k_z3(x,K):\n", + " return PbLe([(i,1) for i in x],K-1)\n", + "\n", + "def fewer_than_three_z3(x):\n", + " return fewer_than_k_z3(x,3)\n", + "\n", + "check_expression(x, fewer_than_three_z3, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, fewer_than_three_z3, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, fewer_than_three_z3, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, fewer_than_three_z3, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "ZjAMpiNFmJPc" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def exactly_k_z3(x,K):\n", + " return PbEq([(i,1) for i in x],K)\n", + "\n", + "def exactly_three_z3(x):\n", + " return exactly_k_z3(x,3)\n", + "\n", + "check_expression(x, exactly_three_z3, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, exactly_three_z3, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, exactly_three_z3, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, exactly_three_z3, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "EgG6jGAskUze" + }, + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file diff --git a/Trees/SAT_Construction2.ipynb b/Trees/SAT_Construction2.ipynb new file mode 100644 index 0000000..316bab3 --- /dev/null +++ b/Trees/SAT_Construction2.ipynb @@ -0,0 +1,271 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyO6l9I4bne2b2Eu/ICYArIt", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "jv83sxEU2Ig0" + } + }, + { + "cell_type": "markdown", + "source": [ + "# SAT Constructions 2\n", + "\n", + "The purpose of this Python notebook is to investigate the SAT construction that tests if a graph is connected.\n", + "\n", + "Work through the cells below, running each cell in turn. In various places you will see the words \"TODO\". Follow the instructions at these places and write code to complete the functions.\n", + "\n", + "You can save a local copy of this notebook in your Google account and work through it in Colab (recommended) or you can download the notebook and run it locally using Jupyter notebook or similar. If you are using CoLab, we recommend that turn off AI autocomplete (under cog icon in top-right corner), which will give you the answers and defeat the purpose of the exercise.\n", + "\n", + "A fully working version of this notebook with the complete answers can be found [here](https://github.com/udlbook/udlbook/blob/main/Trees/SAT_Construction2_Answers.ipynb).\n", + "\n", + "Contact me at iclimbtreesmail@gmail.com if you find any mistakes or have any suggestions." + ], + "metadata": { + "id": "IsBSW40O20Hv" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "WZYb15hc19es" + }, + "outputs": [], + "source": [ + "!pip install z3-solver\n", + "from z3 import *\n", + "import numpy as np\n", + "from itertools import combinations" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Connected components\n", + "\n", + "Finally, let's develop a construction that tells us if the elements of an undirected graph are connected. Consider the two adjacency matrices:\n", + "\n", + "\\begin{equation}\n", + "A_1 = \\begin{bmatrix}1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\\\\n", + " 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 \\\\\n", + " 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 \\\\\n", + " 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 \\\\\n", + " 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 \\\\\n", + " 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 \\\\\n", + " 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 \\\\\n", + " 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 \\\\\n", + " \\end{bmatrix} \\quad\\quad\n", + " A_2 = \\begin{bmatrix}1 & 1 & 0 & 1 & 0 & 0 & 0 & 0 \\\\\n", + " 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 \\\\\n", + " 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 \\\\\n", + " 1 & 0 & 1 & 1 & 1 & 0 & 0 & 0 \\\\\n", + " 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 \\\\\n", + " 0 & 0 & 0 & 0 & 1 & 1 & 0 & 0 \\\\\n", + " 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 \\\\\n", + " 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 \\\\\n", + " \\end{bmatrix}\n", + "\\end{equation}\n", + "\n", + "Each matrix represents the edges in a graph containing eight nodes. Elements $(i,j)$ and $(j,i)$ will be set to one if there is an edge between nodes $i$ and $j$. The diagonal elements are all set to one.\n", + "\n", + "For matrix $A_{1}$ the nodes are all connected; node 1 connects to node 2, which connects to node 3, and so on up to node 8. \n", + "\n", + "For matrix $A_{2}$ however, the nodes are not all connected. Nodes 7 and 8 are connected to each other but not connected any of the other nodes.\n" + ], + "metadata": { + "id": "CZQygtit0D9Q" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Test for connected components" + ], + "metadata": { + "id": "SzoAO6PBnEux" + } + }, + { + "cell_type": "markdown", + "source": [ + "We can test for the connectivity of the graph implied by an $8\\times 8$ matrix $\\mathbf{A}$ by computing new adjacency matrices $\\mathbf{B},\\mathbf{C},\\mathbf{D}$ where:\n", + "\n", + "$$\\begin{aligned}\n", + "B_{ij} &\\Leftrightarrow \\bigvee_k (A_{ik} \\land A_{kj})\\\\\n", + "C_{ij} &\\Leftrightarrow \\bigvee_k (B_{ik} \\land B_{kj})\\\\\n", + "D_{ij} &\\Leftrightarrow \\bigvee_k (C_{ik} \\land C_{kj})\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "and then enforcing the constraint that the first row of $\\mathbf{D}$ contains all true elements:\n", + "\n", + "$$ \\bigvee_k D_{0,k}$$\n", + "\n", + "In general, if the initial matrix $\\mathbf{A}$ is of size $N\\times N$, we will need to compute $\\log_2[N]$ intermediate matrices $\\mathbf{B},\\mathbf{C},\\mathbf{D}$." + ], + "metadata": { + "id": "2brsB9m54aGo" + } + }, + { + "cell_type": "markdown", + "source": [ + "Now let's write a SAT routine to check if an adjacency matrix represents a fully-connected graph" + ], + "metadata": { + "id": "TxctcAsX8rh0" + } + }, + { + "cell_type": "code", + "source": [ + "def is_fully_connected(s, adjacency):\n", + " # Size of the adjacency matrix\n", + " n_components = len(adjacency)\n", + " # We'll construct a N x N x log[N] array of variables\n", + " # The NxN variables in the first layer represent A, the variables in the second layer represent B and so on\n", + " n_layers = math.ceil(math.log(n_components,2))+1\n", + " connected = [[[ z3.Bool(\"conn_{%d,%d,%d}\"%((i,j,n))) for n in range(0, n_layers)] for j in range(0, n_components) ] for i in range(0, n_components) ]\n", + "\n", + " # Constraint 1\n", + " # The value in the top layer of the connected structure is equal to the adjacency matrix\n", + " # TODO -- replace this line\n", + " s.add(connected[0][0][0])\n", + "\n", + " # Constraint 2\n", + " # Value at position [i,j] in layer n is true if there is a k such that position[i,k] and positions [k,j] in layer n-1 are true\n", + " for n in range(1,n_layers):\n", + " for i in range(n_components):\n", + " for j in range(n_components):\n", + " # TODO -- replace this line\n", + " s.add(connected[i][j][n])\n", + "\n", + " # Constraint 3 -- any row of column of the matrix should be full of ones at the end (everything is connected)\n", + " # TODO -- replace this line\n", + " s.add(connected[0][0][n_layers-1])\n", + "\n", + " return s" + ], + "metadata": { + "id": "JpfVC-Rg0LiC" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Finally, let's write a routine that tests the adjacency of the two matrices above using a SAT solver." + ], + "metadata": { + "id": "slbVVZH6-_Jl" + } + }, + { + "cell_type": "code", + "source": [ + "def test_is_fully_connected(A):\n", + " # Set up the SAT solver\n", + " s = Solver()\n", + "\n", + " # Convert the input matrix to z3 Boolean values\n", + " n_components = A.shape[0]\n", + " adjacency= [[ z3.Bool(\"a_{%d,%d}\"%((i,j))) for j in range(0, n_components) ] for i in range(0, n_components) ]\n", + " for i in range(n_components):\n", + " for j in range(n_components):\n", + " if A[i,j]!=0:\n", + " s.add(adjacency[i][j])\n", + " else:\n", + " s.add(Not(adjacency[i][j]))\n", + "\n", + " # Run the routine\n", + " s = is_fully_connected(s, adjacency)\n", + "\n", + " # Check if it's SAT (creates the model)\n", + " sat_result = s.check()\n", + " print(sat_result)\n", + "\n", + " # If it was SAT then print out the layers of the 3D structure\n", + " if sat_result == z3.sat:\n", + " result = s.model()\n", + " c_vals = np.array([[[int(bool(result[z3.Bool(\"conn_{%d,%d,%d}\" % (i, j,n))])) for n in range(0, n_components-1)] for j in range(0,n_components) ] for i in range(0,n_components) ] )\n", + " for n in range(math.ceil(math.log(n_components,2))+1):\n", + " print(\"Layer:\",n)\n", + " print(c_vals[:,:,n])" + ], + "metadata": { + "id": "0veuGhg_--EJ" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "A1 = np.array([[1, 1, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 0, 0, 0, 0, 0],\n", + " [0, 1, 1, 1, 0, 0, 0, 0],\n", + " [0, 0, 1, 1, 1, 0, 0, 0],\n", + " [0, 0, 0, 1, 1, 1, 0, 0],\n", + " [0, 0, 0, 0, 1, 1, 1, 0],\n", + " [0, 0, 0, 0, 0, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 1, 1]])\n", + "test_is_fully_connected(A1)" + ], + "metadata": { + "id": "rk83toMaAkQX" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "A2 = np.array([[1, 1, 0, 1, 0, 0, 0, 0],\n", + " [1, 1, 1, 0, 0, 0, 0, 0],\n", + " [0, 1, 1, 1, 0, 0, 0, 0],\n", + " [1, 0, 1, 1, 1, 0, 0, 0],\n", + " [0, 0, 0, 1, 1, 1, 0, 0],\n", + " [0, 0, 0, 0, 1, 1, 0, 0],\n", + " [0, 0, 0, 0, 0, 0, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 1, 1]])\n", + "test_is_fully_connected(A2)" + ], + "metadata": { + "id": "DRzo_XrCAlrz" + }, + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file diff --git a/Trees/SAT_Construction2_Answers.ipynb b/Trees/SAT_Construction2_Answers.ipynb new file mode 100644 index 0000000..bdbe082 --- /dev/null +++ b/Trees/SAT_Construction2_Answers.ipynb @@ -0,0 +1,261 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyMTKKBKDWLLdEGY6POpEgt/", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "jv83sxEU2Ig0" + } + }, + { + "cell_type": "markdown", + "source": [ + "# SAT Constructions 2\n", + "\n", + "The purpose of this Python notebook is to investigate the SAT construction that tests if a graph is connected.\n", + "\n", + "You can save a local copy of this notebook in your Google account and work through it in Colab (recommended) or you can download the notebook and run it locally using Jupyter notebook or similar.\n", + "\n", + "Contact me at iclimbtreesmail@gmail.com if you find any mistakes or have any suggestions." + ], + "metadata": { + "id": "IsBSW40O20Hv" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "WZYb15hc19es" + }, + "outputs": [], + "source": [ + "!pip install z3-solver\n", + "from z3 import *\n", + "import numpy as np\n", + "from itertools import combinations" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Connected components\n", + "\n", + "Finally, let's develop a construction that tells us if the elements of an undirected graph are connected. Consider the two adjacency matrices:\n", + "\n", + "\\begin{equation}\n", + "A_1 = \\begin{bmatrix}1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\\\\n", + " 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 \\\\\n", + " 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 \\\\\n", + " 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 \\\\\n", + " 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 \\\\\n", + " 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 \\\\\n", + " 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 \\\\\n", + " 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 \\\\\n", + " \\end{bmatrix} \\quad\\quad\n", + " A_2 = \\begin{bmatrix}1 & 1 & 0 & 1 & 0 & 0 & 0 & 0 \\\\\n", + " 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 \\\\\n", + " 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 \\\\\n", + " 1 & 0 & 1 & 1 & 1 & 0 & 0 & 0 \\\\\n", + " 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 \\\\\n", + " 0 & 0 & 0 & 0 & 1 & 1 & 0 & 0 \\\\\n", + " 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 \\\\\n", + " 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 \\\\\n", + " \\end{bmatrix}\n", + "\\end{equation}\n", + "\n", + "Each matrix represents the edges in a graph containing eight nodes. Elements $(i,j)$ and $(j,i)$ will be set to one if there is an edge between nodes $i$ and $j$. The diagonal elements are all set to one.\n", + "\n", + "For matrix $A_{1}$ the nodes are all connected; node 1 connects to node 2, which connects to node 3, and so on up to node 8. \n", + "\n", + "For matrix $A_{2}$ however, the nodes are not all connected. Nodes 7 and 8 are connected to each other but not connected any of the other nodes.\n" + ], + "metadata": { + "id": "CZQygtit0D9Q" + } + }, + { + "cell_type": "markdown", + "source": [ + "We can test for the connectivity of the graph implied by an $8\\times 8$ matrix $\\mathbf{A}$ by computing new adjacency matrices $\\mathbf{B},\\mathbf{C},\\mathbf{D}$ where:\n", + "\n", + "$$\\begin{aligned}\n", + "B_{ij} &\\Leftrightarrow \\bigvee_k (A_{ik} \\land A_{kj})\\\\\n", + "C_{ij} &\\Leftrightarrow \\bigvee_k (B_{ik} \\land B_{kj})\\\\\n", + "D_{ij} &\\Leftrightarrow \\bigvee_k (C_{ik} \\land C_{kj})\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "and then enforcing the constraint that the first row of $\\mathbf{D}$ contains all true elements:\n", + "\n", + "$$ \\bigvee_k D_{0,k}$$\n", + "\n", + "In general, if the initial matrix $\\mathbf{A}$ is of size $N\\times N$, we will need to compute $\\log_2[N]$ intermediate matrices $\\mathbf{B},\\mathbf{C},\\mathbf{D}$." + ], + "metadata": { + "id": "2brsB9m54aGo" + } + }, + { + "cell_type": "markdown", + "source": [ + "Now let's write a SAT routine to check if an adjacency matrix represents a fully-connected graph" + ], + "metadata": { + "id": "TxctcAsX8rh0" + } + }, + { + "cell_type": "code", + "source": [ + "def is_fully_connected(s, adjacency):\n", + " # Size of the adjacency matrix\n", + " n_components = len(adjacency)\n", + " # We'll construct a N x N x log[N] array of variables\n", + " # The NxN variables in the first layer represent A, the variables in the second layer represent B and so on\n", + " n_layers = math.ceil(math.log(n_components,2))+1\n", + " connected = [[[ z3.Bool(\"conn_{%d,%d,%d}\"%((i,j,n))) for n in range(0, n_layers)] for j in range(0, n_components) ] for i in range(0, n_components) ]\n", + "\n", + " # Constraint 1\n", + " # The value in the top layer of the connected structure is equal to the adjacency matrix\n", + " for i in range(n_components):\n", + " for j in range(n_components):\n", + " s.add(connected[i][j][0]==adjacency[i][j])\n", + "\n", + " # Constraint 2\n", + " # Value at position [i,j] in layer n is true if there is a k such that position[i,k] and positions [k,j] in layer n-1 are true\n", + " for n in range(1,n_layers):\n", + " for i in range(n_components):\n", + " for j in range(n_components):\n", + " matrix_entry_ij = False\n", + " for k in range(n_components):\n", + " matrix_entry_ij = Or(matrix_entry_ij, And(connected[i][k][n-1],connected[k][j][n-1]))\n", + " s.add(connected[i][j][n]==matrix_entry_ij)\n", + "\n", + " # Constraint 3 -- any row of column of the matrix should be full of ones at the end (everything is connected)\n", + " for i in range(n_components):\n", + " s.add(connected[i][0][n_layers-1])\n", + "\n", + " return s" + ], + "metadata": { + "id": "JpfVC-Rg0LiC" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Finally, let's write a routine that tests the adjacency of the two matrices above using a SAT solver." + ], + "metadata": { + "id": "slbVVZH6-_Jl" + } + }, + { + "cell_type": "code", + "source": [ + "def test_is_fully_connected(A):\n", + " # Set up the SAT solver\n", + " s = Solver()\n", + "\n", + " # Convert the input matrix to z3 Boolean values\n", + " n_components = A.shape[0]\n", + " adjacency= [[ z3.Bool(\"a_{%d,%d}\"%((i,j))) for j in range(0, n_components) ] for i in range(0, n_components) ]\n", + " for i in range(n_components):\n", + " for j in range(n_components):\n", + " if A[i,j]!=0:\n", + " s.add(adjacency[i][j])\n", + " else:\n", + " s.add(Not(adjacency[i][j]))\n", + "\n", + " # Run the routine\n", + " s = is_fully_connected(s, adjacency)\n", + "\n", + " # Check if it's SAT (creates the model)\n", + " sat_result = s.check()\n", + " print(sat_result)\n", + "\n", + " # If it was SAT then print out the layers of the 3D structure\n", + " if sat_result == z3.sat:\n", + " result = s.model()\n", + " c_vals = np.array([[[int(bool(result[z3.Bool(\"conn_{%d,%d,%d}\" % (i, j,n))])) for n in range(0, n_components-1)] for j in range(0,n_components) ] for i in range(0,n_components) ] )\n", + " for n in range(math.ceil(math.log(n_components,2))+1):\n", + " print(\"Layer:\",n)\n", + " print(c_vals[:,:,n])" + ], + "metadata": { + "id": "0veuGhg_--EJ" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "A1 = np.array([[1, 1, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 0, 0, 0, 0, 0],\n", + " [0, 1, 1, 1, 0, 0, 0, 0],\n", + " [0, 0, 1, 1, 1, 0, 0, 0],\n", + " [0, 0, 0, 1, 1, 1, 0, 0],\n", + " [0, 0, 0, 0, 1, 1, 1, 0],\n", + " [0, 0, 0, 0, 0, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 1, 1]])\n", + "test_is_fully_connected(A1)" + ], + "metadata": { + "id": "rk83toMaAkQX" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "A2 = np.array([[1, 1, 0, 1, 0, 0, 0, 0],\n", + " [1, 1, 1, 0, 0, 0, 0, 0],\n", + " [0, 1, 1, 1, 0, 0, 0, 0],\n", + " [1, 0, 1, 1, 1, 0, 0, 0],\n", + " [0, 0, 0, 1, 1, 1, 0, 0],\n", + " [0, 0, 0, 0, 1, 1, 0, 0],\n", + " [0, 0, 0, 0, 0, 0, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 1, 1]])\n", + "test_is_fully_connected(A2)" + ], + "metadata": { + "id": "DRzo_XrCAlrz" + }, + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file diff --git a/Trees/SAT_Construction_Answers.ipynb b/Trees/SAT_Construction_Answers.ipynb new file mode 100644 index 0000000..99da5d5 --- /dev/null +++ b/Trees/SAT_Construction_Answers.ipynb @@ -0,0 +1,586 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyNalp5KWVPvcXsJJ4jQvQ5U", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "jv83sxEU2Ig0" + } + }, + { + "cell_type": "markdown", + "source": [ + "# SAT Constructions\n", + "\n", + "The purpose of this Python notebook is to investigate SAT constructions that impose constraints on sets of variables. We'll build constructions for ensuring all of the variables are the same, that only one of the variables is true, that at leats $K$ of the variables are true, that fewer than $K$ of the variables are true and that exactly $K$ of the variables are true.\n", + "\n", + "You can save a local copy of this notebook in your Google account and work through it in Colab (recommended) or you can download the notebook and run it locally using Jupyter notebook or similar.\n", + "\n", + "Contact me at iclimbtreesmail@gmail.com if you find any mistakes or have any suggestions." + ], + "metadata": { + "id": "IsBSW40O20Hv" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "WZYb15hc19es", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "d95ed66f-a8f7-422a-c6f4-1401d6bd06a5" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Collecting z3-solver\n", + " Downloading z3_solver-4.14.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (602 bytes)\n", + "Downloading z3_solver-4.14.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (29.5 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m29.5/29.5 MB\u001b[0m \u001b[31m20.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hInstalling collected packages: z3-solver\n", + "Successfully installed z3-solver-4.14.1.0\n" + ] + } + ], + "source": [ + "!pip install z3-solver\n", + "from z3 import *\n", + "import numpy as np\n", + "from itertools import combinations" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Same\n", + "\n", + "Let's write a subroutine that returns a Boolean logic formula that constrains $N$ variables to be the same.\n", + "\n", + "$$ \\textrm{Same}[x_{1},x_{2},x_{3},\n", + "\\ldots x_{N}]:= (x_{1}\\land x_{2}\\land x_{3},\\ldots,x_N)\\lor(\\overline{x}_{1}\\land \\overline{x}_{2}\\land \\overline{x}_{3},\\ldots,\\overline{x}_N).$$" + ], + "metadata": { + "id": "aVj9RmuB3ZWJ" + } + }, + { + "cell_type": "markdown", + "source": [ + "First let's define the variables. We'll choose $N=10$" + ], + "metadata": { + "id": "Amv5G3VR3zIJ" + } + }, + { + "cell_type": "code", + "source": [ + "N=10\n", + "x = [ z3.Bool(\"x_{i}\".format(i=i)) for i in range(0,N) ]\n", + "print(x)" + ], + "metadata": { + "id": "J-y3uhTs-LU7" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's write the main routine. We can make use of the fact that the Z3 command 'Or' which combines together a list of literals with Or operations." + ], + "metadata": { + "id": "4pY5yiDo-esv" + } + }, + { + "cell_type": "code", + "source": [ + "# Routine that returns the SAT construction (a Boolean logic formula) for all literals in x being the same.\n", + "def same(x):\n", + " return Or(And(x), And([Not(xi) for xi in x]))" + ], + "metadata": { + "id": "f0esE71E94fm" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Finally, let's test that our routine works. Here's generic routine that evaluates a vector of concrete values against a provided Boolean logic formula\n" + ], + "metadata": { + "id": "EWTuxEvrAfnK" + } + }, + { + "cell_type": "code", + "source": [ + "def check_expression(x, formula, literals):\n", + " # Create the solver\n", + " s = Solver()\n", + " # Add the constraint\n", + " s.add(formula(x))\n", + " # Add the literals\n", + " s.add(And([x[i] if literal else Not(x[i]) for i, literal in enumerate(literals)]))\n", + " # Check if it's SAT (creates the model)\n", + " sat_result = s.check()\n", + " if(sat_result==sat):\n", + " print(\"True\")\n", + " else:\n", + " print(\"False\")" + ], + "metadata": { + "id": "vY5CD6lRArNP" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "check_expression(x, same, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, same, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, same, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, same, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "Q3VMIDqfHEei" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Exactly One\n", + "\n", + "Now let's write a subroutine that returns a Boolean logic formula that is true when only one of the provided variables are the same.\n", + "\n", + "First, we write a formula that ensures at least one of the variables is true:\n", + "\n", + "$$\\textrm{AtLeastOne}[x_1,x_2,x_3]:= x_{1}\\lor x_{2} \\lor x_{3}, \\ldots, x_{N}.$$\n", + "\n", + "Then, we write a formula that ensures that no more than one is true. For each possible pair of literals, we ensure that they are not both true:\n", + "\n", + "$$\\textrm{NoMoreThanOne}[x_{1},x_{2},x_{3}]:= \\lnot (x_{1}\\land x_{2}) \\land \\lnot (x_{1}\\land x_{3}),\\ldots, \\land \\lnot (x_{n-1}\\land x_{N}) $$\n", + "\n", + "Finally, we **AND** together these two formulae:\n", + "\n", + "$$\\textrm{ExactlyOne}[x_1,x_2,x_3,\\ldots, x_N] = \\textrm{AtLeastOne}[x_1,x_2,x_3, \\ldots, x_N]\\land\\textrm{NoMoreThanOne}[x_1,x_2,x_3, \\ldots x_N]$$\n", + "\n", + "You might want to use the function \"combinations\" from itertools (imported above). Example usage:" + ], + "metadata": { + "id": "0IIRG5ZWIZV6" + } + }, + { + "cell_type": "code", + "source": [ + "test = [ z3.Bool(\"x_{i}\".format(i=i)) for i in range(0,4) ]\n", + "for comb in combinations(test, 2):\n", + " print(comb[0], comb[1])" + ], + "metadata": { + "id": "joeZiDT3LV-r" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def at_least_one(x):\n", + " return Or(x)\n", + "\n", + "def no_more_than_one(x):\n", + " clauses = True ;\n", + " for comb in combinations(x, 2):\n", + " clauses = And([clauses, Or([Not(comb[0]), Not(comb[1])]) ])\n", + " return clauses\n", + "\n", + "def exactly_one(x):\n", + " return And(at_least_one(x), no_more_than_one(x))" + ], + "metadata": { + "id": "C0HGBOjI99Ex" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Let's test if this works:" + ], + "metadata": { + "id": "2Y_1zYOZIoZI" + } + }, + { + "cell_type": "code", + "source": [ + "check_expression(x, exactly_one, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, exactly_one, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, exactly_one, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, exactly_one, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "31gqasHnM3c3" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# At least K\n", + "\n", + "Finally, we'll build the construction that ensures that there at at least K elements that are true in a vector of length N.\n", + "\n", + "This one is a bit more complicated. We construct an NxK matrix of new literals $r_{i,j}$ Then we add the following constraints.\n", + "\n", + "1. Top left element $r_{0,0}$ is the first data element\n", + "\n", + "$$ r_{00} \\Leftrightarrow x_{1}$$\n", + "\n", + "2. Remaining elements $r_{0,1:}$ must be false\n", + "\n", + "$$ \\overline{r}_{0,j} \\quad\\quad \\forall j\\in\\{1,2,\\ldots K-1\\}$$\n", + "\n", + "3. Remaining elements $r_{i,0}$ in first column are true where x_i is true\n", + "\n", + "$$x_i \\Rightarrow r_{i,0} \\quad\\quad \\forall i\\in\\{1,2,\\ldots N-1\\}$$\n", + "\n", + "4. For rows 1:N-1, if the element above is true, this must be true\n", + "\n", + "$$ r_{i-1,j} \\Rightarrow r_{i,j}\\quad\\quad \\quad\\quad \\forall i\\in\\{1,2,\\ldots N-1\\}, j\\in\\{0,1,\\ldots,K-1\\}$$\n", + "\n", + "5. If x is true for this row and the element above and to the left is true then set this element to true.\n", + "\n", + "$$ (x_i \\land r_{i-1,j-1})\\Rightarrow r_{i,j} \\quad\\quad \\forall i\\in\\{1,2,\\ldots N-1\\}, j\\in\\{1,2,\\ldots,K-1\\} $$\n", + "\n", + "6. If x is false for this row and the element above is false then set to false\n", + "\n", + "$$ (\\overline{x}_{i} \\land \\overline{r}_{i-1,j}) \\Rightarrow \\overline{r}_{i,j} \\quad\\quad \\forall i\\in\\{1,2,\\ldots N-1\\}, j\\in\\{1,2,\\ldots,K-1\\} $$\n", + "\n", + "7. if x is false for this row and the element above and to the left is false then set to false:\n", + "\n", + "$$ (\\overline{x}_i \\land \\overline{r}_{i-1,j-1})\\Rightarrow \\overline{r}_{i,j} \\quad\\quad \\forall i\\in\\{1,2,\\ldots N-1\\}, j\\in\\{1,2,\\ldots,K-1\\} $$\n", + "\n", + "8. The bottom-right element of r must be true\n", + "\n", + "$$ r_{N-1,K-1}$$" + ], + "metadata": { + "id": "MJqWOMBsQrCI" + } + }, + { + "cell_type": "code", + "source": [ + "def at_least_k(x, K):\n", + " # Create nxk table of literals r_{i,j}\n", + " N = len(x) ;\n", + " r = [[ z3.Bool(\"r_{%d,%d}\"%((i,j))) for j in range(0,K) ] for i in range(0,N) ]\n", + "\n", + " #1. Top left element $r_{0,0}$ is the first data element\n", + "\n", + " clauses = (r[0][0] == x[0])\n", + "\n", + " #2. Remaining elements $r_{0,1:}$ must be false\n", + "\n", + " clauses = And(clauses, And([ Not(r[0][j]) for j in range(1,K) ]))\n", + "\n", + " #3. Remaining elements $r_{i,0}$ in first column are true where x_i is true\n", + " clauses = And(clauses, And([Implies(x[i],r[i][0]) for i in range(1,N)]))\n", + "\n", + " #4. For rows 1:n-1, if the element above is true, this must be true\n", + "\n", + " for i in range(1,N):\n", + " for j in range(0,K):\n", + " clauses = And(clauses, Implies(r[i-1][j], r[i][j]))\n", + "\n", + " #5. If x is true for this row and the element above and to the left is true then set this element to true.\n", + " #6. If x is false for this row and the element above is false then set to false\n", + " #7. if x is false for this row and the element above and to the left is false then set to false:\n", + " for i in range(1,N):\n", + " for j in range(1,K):\n", + " clauses = And(clauses, Implies(And(x[i], r[i-1][j-1]), r[i][j]))\n", + " clauses = And(clauses, Implies(And(Not(x[i]), Not(r[i-1][j])), Not(r[i][j])))\n", + " clauses = And(clauses, Implies(And(Not(x[i]), Not(r[i-1][j-1])), Not(r[i][j])))\n", + "\n", + " #8. The bottom-right element of r must be true\n", + " clauses = And(clauses, r[N-1][K-1])\n", + "\n", + " return clauses" + ], + "metadata": { + "id": "aTcc1Ad0M9NX" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#This function defined for convenience so that check_expression works\n", + "def at_least_three(x):\n", + " return at_least_k(x,3)\n", + "\n", + "check_expression(x, at_least_three, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, at_least_three, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, at_least_three, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, at_least_three, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "ESnui2V7YBQC" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Optional: write functions to test if there are:\n", + "\n", + "* Fewer than $K$ elements\n", + "* Exactly $N$ elements\n", + "\n" + ], + "metadata": { + "id": "PwgpncA5g0-C" + } + }, + { + "cell_type": "code", + "source": [ + "def fewer_than_k(x, K):\n", + " # Create nxk table of literals r_{i,j}\n", + " N = len(x) ;\n", + " r = [[ z3.Bool(\"r_{%d,%d}\"%((i,j))) for j in range(0,K) ] for i in range(0,N) ]\n", + "\n", + " #1. Top left element $r_{0,0}$ is the first data element\n", + "\n", + " clauses = (r[0][0] == x[0])\n", + "\n", + " #2. Remaining elements $r_{0,1:}$ must be false\n", + "\n", + " clauses = And(clauses, And([ Not(r[0][j]) for j in range(1,K) ]))\n", + "\n", + " #3. Remaining elements $r_{i,0}$ in first column are true where x_i is true\n", + " clauses = And(clauses, And([Implies(x[i],r[i][0]) for i in range(1,N)]))\n", + "\n", + " #4. For rows 1:n-1, if the element above is true, this must be true\n", + " for i in range(1,N):\n", + " for j in range(0,K):\n", + " clauses = And(clauses, Implies(r[i-1][j], r[i][j]))\n", + "\n", + " #5. If x is true for this row and the element above and to the left is true then set this element to true.\n", + " #6. If x is false for this row and the element above is false then set to false\n", + " #7. if x is false for this row and the element above and to the left is false then set to false:\n", + " for i in range(1,N):\n", + " for j in range(1,K):\n", + " clauses = And(clauses, Implies(And(x[i], r[i-1][j-1]), r[i][j]))\n", + " clauses = And(clauses, Implies(And(Not(x[i]), Not(r[i-1][j])), Not(r[i][j])))\n", + " clauses = And(clauses, Implies(And(Not(x[i]), Not(r[i-1][j-1])), Not(r[i][j])))\n", + "\n", + " #8. The bottom-right element of r must be false\n", + " clauses = And(clauses, Not(r[N-1][K-1]))\n", + "\n", + " return clauses" + ], + "metadata": { + "id": "RS31wg5Ig0f-" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#This function defined for convenience so that check_expression works\n", + "def fewer_than_three(x):\n", + " return fewer_than_k(x,3)\n", + "\n", + "check_expression(x, fewer_than_three, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, fewer_than_three, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, fewer_than_three, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, fewer_than_three, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "Sy67NXs7hqDS" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def exactly_k(x, K):\n", + " # Create n x (k+1) table of literals r_{i,j}\n", + " N = len(x) ;\n", + " r = [[ z3.Bool(\"r_{%d,%d}\"%((i,j))) for j in range(0,K+1) ] for i in range(0,N) ]\n", + "\n", + " #1. Top left element $r_{0,0}$ is the first data element\n", + "\n", + " clauses = (r[0][0] == x[0])\n", + "\n", + " #2. Remaining elements $r_{0,1:}$ must be false\n", + "\n", + " clauses = And(clauses, And([ Not(r[0][j]) for j in range(1,K+1) ]))\n", + "\n", + " #3. Remaining elements $r_{i,0}$ in first column are true where x_i is true\n", + " clauses = And(clauses, And([Implies(x[i],r[i][0]) for i in range(1,N)]))\n", + "\n", + " #4. For rows 1:n-1, if the element above is true, this must be true\n", + " for i in range(1,N):\n", + " for j in range(0,K+1):\n", + " clauses = And(clauses, Implies(r[i-1][j], r[i][j]))\n", + "\n", + " #5. If x is true for this row and the element above and to the left is true then set this element to true.\n", + " #6. If x is false for this row and the element above is false then set to false\n", + " #7. if x is false for this row and the element above and to the left is false then set to false:\n", + " for i in range(1,N):\n", + " for j in range(1,K+1):\n", + " clauses = And(clauses, Implies(And(x[i], r[i-1][j-1]), r[i][j]))\n", + " clauses = And(clauses, Implies(And(Not(x[i]), Not(r[i-1][j])), Not(r[i][j])))\n", + " clauses = And(clauses, Implies(And(Not(x[i]), Not(r[i-1][j-1])), Not(r[i][j])))\n", + "\n", + " #8. The bottom-right element of r must be\n", + " clauses = And(clauses, r[N-1][K-1])\n", + " clauses = And(clauses, Not(r[N-1][K]))\n", + "\n", + " return clauses" + ], + "metadata": { + "id": "Fzaa1NoaicKT" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#This function defined for convenience so that check_expression works\n", + "def exactly_three(x):\n", + " return exactly_k(x,3)\n", + "\n", + "check_expression(x, exactly_three, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, exactly_three, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, exactly_three, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, exactly_three, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "L_xwfybUkKhw" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now that we've finished, I'll let you into a secret. Z3 actually contains routines for (AtLeast, FewerThanOrEqualToo, and Exactly)" + ], + "metadata": { + "id": "H3lipx8akmHp" + } + }, + { + "cell_type": "code", + "source": [ + "def at_least_k_z3(x,K):\n", + " return PbGe([(i,1) for i in x],K)\n", + "\n", + "def at_least_three_z3(x):\n", + " return at_least_k_z3(x,3)\n", + "\n", + "check_expression(x, at_least_three_z3, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, at_least_three_z3, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, at_least_three_z3, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, at_least_three_z3, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "Yho3WtG0luM6" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def fewer_than_k_z3(x,K):\n", + " return PbLe([(i,1) for i in x],K-1)\n", + "\n", + "def fewer_than_three_z3(x):\n", + " return fewer_than_k_z3(x,3)\n", + "\n", + "check_expression(x, fewer_than_three_z3, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, fewer_than_three_z3, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, fewer_than_three_z3, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, fewer_than_three_z3, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "ZjAMpiNFmJPc" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def exactly_k_z3(x,K):\n", + " return PbEq([(i,1) for i in x],K)\n", + "\n", + "def exactly_three_z3(x):\n", + " return exactly_k_z3(x,3)\n", + "\n", + "check_expression(x, exactly_three_z3, [False,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, exactly_three_z3, [True,False,False,False,False,False,False,False,False,False])\n", + "check_expression(x, exactly_three_z3, [False,False,False,True,False,False,True,False,True,False])\n", + "check_expression(x, exactly_three_z3, [True,True,True,True,True,True,True,True,True,True])" + ], + "metadata": { + "id": "EgG6jGAskUze" + }, + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file diff --git a/Trees/SAT_Crossword.ipynb b/Trees/SAT_Crossword.ipynb new file mode 100644 index 0000000..823bbd3 --- /dev/null +++ b/Trees/SAT_Crossword.ipynb @@ -0,0 +1,1061 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyMB6nkfgiGNF9TB3L9/dTxZ", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "QjHXD27ieTS-" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Crosswords with SAT\n", + "\n", + "The purpose of this Python notebook is to use investigate using SAT to find a valid arrangement of known answers in a crossword puzzle.\n", + "\n", + "You should have completed the notebook on SAT constructions before attempting this notebook. Note: this exercise is pretty hard. Expect it to take a while!\n", + "\n", + "Work through the cells below, running each cell in turn. In various places you will see the words \"TODO\". Follow the instructions at these places and write code to complete the functions.\n", + "\n", + "You can save a local copy of this notebook in your Google account and work through it in Colab (recommended) or you can download the notebook and run it locally using Jupyter notebook or similar. If you are using CoLab, we recommend that turn off AI autocomplete (under cog icon in top-right corner), which will give you the answers and defeat the purpose of the exercise.\n", + "\n", + "A fully working version of this notebook with the complete answers can be found [here](https://github.com/udlbook/udlbook/blob/main/Trees/SAT_Crossword_Answers.ipynb).\n", + "\n", + "Contact me at iclimbtreesmail@gmail.com if you find any mistakes or have any suggestions." + ], + "metadata": { + "id": "jtMs90veeZIn" + } + }, + { + "cell_type": "code", + "source": [ + "# Install relevant packages\n", + "!pip install z3-solver\n", + "from z3 import *\n", + "import numpy as np\n", + "import time" + ], + "metadata": { + "id": "mF6ngqCses3n", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "9325fa59-8ad2-4cea-acd1-16843db6b19a" + }, + "execution_count": 1, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Collecting z3-solver\n", + " Downloading z3_solver-4.15.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (602 bytes)\n", + "Downloading z3_solver-4.15.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (29.5 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m29.5/29.5 MB\u001b[0m \u001b[31m27.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hInstalling collected packages: z3-solver\n", + "Successfully installed z3-solver-4.15.0.0\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "First let's write some code to visualize a crossword problem. We'll represent the crossword as a ndarray of integers where each integer represents a letter index and zero represents a blank spot. " + ], + "metadata": { + "id": "3A5_7mByYrur" + } + }, + { + "cell_type": "code", + "source": [ + "puzzle = ['ALCOVE NSEC MIC',\n", + " 'LEANED ALTO ADO',\n", + " 'LAVALAMPOON CON',\n", + " 'ASSN EKG GABLE',\n", + " ' DENTI MEMO ',\n", + " ' AEOLIANHARPOON',\n", + " 'MOANER SAX SKUA',\n", + " 'ERS MVP TWI PTS',\n", + " 'OTTO AUS ESPRIT',\n", + " 'WAILINGWALLOON ',\n", + " ' NARA IDLES ',\n", + " 'REDYE UMA ECHO',\n", + " 'ARI FILMBUFFOON',\n", + " 'JOE UTNE SLOPPY',\n", + " 'ASS LEAR CORTEX']\n", + "\n", + "# Convert to a list of lists\n", + "for i in range(len(puzzle)):\n", + " puzzle[i] = [char for char in puzzle[i]]\n", + "\n", + "# Represent the puzzle as integers in a grid\n", + "puzzle_as_integers = np.zeros((len(puzzle), len(puzzle[0])), dtype=int)\n", + "for i in range(len(puzzle)):\n", + " for j in range(len(puzzle[i])):\n", + " if puzzle[i][j] == ' ':\n", + " puzzle_as_integers[i][j] = 0\n", + " else:\n", + " puzzle_as_integers[i][j] = ord(puzzle[i][j]) - ord('A') + 1\n", + "\n", + "print(puzzle)\n", + "print(puzzle_as_integers)" + ], + "metadata": { + "id": "cvGNbKkf-Qix", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "6fb16e40-886c-4633-c768-11f5a6178aa8" + }, + "execution_count": 2, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[['A', 'L', 'C', 'O', 'V', 'E', ' ', 'N', 'S', 'E', 'C', ' ', 'M', 'I', 'C'], ['L', 'E', 'A', 'N', 'E', 'D', ' ', 'A', 'L', 'T', 'O', ' ', 'A', 'D', 'O'], ['L', 'A', 'V', 'A', 'L', 'A', 'M', 'P', 'O', 'O', 'N', ' ', 'C', 'O', 'N'], ['A', 'S', 'S', 'N', ' ', ' ', 'E', 'K', 'G', ' ', 'G', 'A', 'B', 'L', 'E'], [' ', ' ', ' ', 'D', 'E', 'N', 'T', 'I', ' ', 'M', 'E', 'M', 'O', ' ', ' '], [' ', 'A', 'E', 'O', 'L', 'I', 'A', 'N', 'H', 'A', 'R', 'P', 'O', 'O', 'N'], ['M', 'O', 'A', 'N', 'E', 'R', ' ', 'S', 'A', 'X', ' ', 'S', 'K', 'U', 'A'], ['E', 'R', 'S', ' ', 'M', 'V', 'P', ' ', 'T', 'W', 'I', ' ', 'P', 'T', 'S'], ['O', 'T', 'T', 'O', ' ', 'A', 'U', 'S', ' ', 'E', 'S', 'P', 'R', 'I', 'T'], ['W', 'A', 'I', 'L', 'I', 'N', 'G', 'W', 'A', 'L', 'L', 'O', 'O', 'N', ' '], [' ', ' ', 'N', 'A', 'R', 'A', ' ', 'I', 'D', 'L', 'E', 'S', ' ', ' ', ' '], ['R', 'E', 'D', 'Y', 'E', ' ', 'U', 'M', 'A', ' ', ' ', 'E', 'C', 'H', 'O'], ['A', 'R', 'I', ' ', 'F', 'I', 'L', 'M', 'B', 'U', 'F', 'F', 'O', 'O', 'N'], ['J', 'O', 'E', ' ', 'U', 'T', 'N', 'E', ' ', 'S', 'L', 'O', 'P', 'P', 'Y'], ['A', 'S', 'S', ' ', 'L', 'E', 'A', 'R', ' ', 'C', 'O', 'R', 'T', 'E', 'X']]\n", + "[[ 1 12 3 15 22 5 0 14 19 5 3 0 13 9 3]\n", + " [12 5 1 14 5 4 0 1 12 20 15 0 1 4 15]\n", + " [12 1 22 1 12 1 13 16 15 15 14 0 3 15 14]\n", + " [ 1 19 19 14 0 0 5 11 7 0 7 1 2 12 5]\n", + " [ 0 0 0 4 5 14 20 9 0 13 5 13 15 0 0]\n", + " [ 0 1 5 15 12 9 1 14 8 1 18 16 15 15 14]\n", + " [13 15 1 14 5 18 0 19 1 24 0 19 11 21 1]\n", + " [ 5 18 19 0 13 22 16 0 20 23 9 0 16 20 19]\n", + " [15 20 20 15 0 1 21 19 0 5 19 16 18 9 20]\n", + " [23 1 9 12 9 14 7 23 1 12 12 15 15 14 0]\n", + " [ 0 0 14 1 18 1 0 9 4 12 5 19 0 0 0]\n", + " [18 5 4 25 5 0 21 13 1 0 0 5 3 8 15]\n", + " [ 1 18 9 0 6 9 12 13 2 21 6 6 15 15 14]\n", + " [10 15 5 0 21 20 14 5 0 19 12 15 16 16 25]\n", + " [ 1 19 19 0 12 5 1 18 0 3 15 18 20 5 24]]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Let's write a routine that draws this out nicely" + ], + "metadata": { + "id": "v7UbEiIjYdxj" + } + }, + { + "cell_type": "code", + "source": [ + "def draw_crossword(puzzle_as_integers):\n", + "\n", + " # Find number of rows and columns\n", + " n_rows = puzzle_as_integers.shape[0]\n", + " n_cols = puzzle_as_integers.shape[1]\n", + "\n", + " # Draw the top row\n", + " print(\"╔\", end=\"\")\n", + " for i in range(n_cols-1):\n", + " print(\"═╤\", end=\"\")\n", + " print(\"═╗\")\n", + "\n", + " for c_row in range(n_rows):\n", + " print(\"║\", end=\"\")\n", + " for c_col in range(n_cols):\n", + " if puzzle_as_integers[c_row][c_col] == 0:\n", + " print(u\"\\u2588\", end=\"\") # Use block character for blank spaces\n", + " else:\n", + " print(chr(puzzle_as_integers[c_row][c_col] + ord('A') - 1), end=\"\")\n", + " if(c_col < n_cols-1):\n", + " print(\"│\", end=\"\")\n", + " print(\"║\")\n", + "\n", + "\n", + " # Draw the bottom row\n", + " print(\"╚\", end=\"\")\n", + " for i in range(n_cols-1):\n", + " print(\"═╧\", end=\"\")\n", + " print(\"═╝\")\n", + "\n", + "draw_crossword(puzzle_as_integers)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "gdJakiT6TrIU", + "outputId": "aa8f182f-ad60-47a6-e204-8d88c55e0501" + }, + "execution_count": 3, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║A│L│C│O│V│E│█│N│S│E│C│█│M│I│C║\n", + "║L│E│A│N│E│D│█│A│L│T│O│█│A│D│O║\n", + "║L│A│V│A│L│A│M│P│O│O│N│█│C│O│N║\n", + "║A│S│S│N│█│█│E│K│G│█│G│A│B│L│E║\n", + "║█│█│█│D│E│N│T│I│█│M│E│M│O│█│█║\n", + "║█│A│E│O│L│I│A│N│H│A│R│P│O│O│N║\n", + "║M│O│A│N│E│R│█│S│A│X│█│S│K│U│A║\n", + "║E│R│S│█│M│V│P│█│T│W│I│█│P│T│S║\n", + "║O│T│T│O│█│A│U│S│█│E│S│P│R│I│T║\n", + "║W│A│I│L│I│N│G│W│A│L│L│O│O│N│█║\n", + "║█│█│N│A│R│A│█│I│D│L│E│S│█│█│█║\n", + "║R│E│D│Y│E│█│U│M│A│█│█│E│C│H│O║\n", + "║A│R│I│█│F│I│L│M│B│U│F│F│O│O│N║\n", + "║J│O│E│█│U│T│N│E│█│S│L│O│P│P│Y║\n", + "║A│S│S│█│L│E│A│R│█│C│O│R│T│E│X║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╧═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "The goal of this notebook will be to take a set of words and create a crossword layout like this in an $n \\times n$ grid. We'll start with just a small set of clues. " + ], + "metadata": { + "id": "yDJLSLNtaSOL" + } + }, + { + "cell_type": "code", + "source": [ + "words = ['JANE','AUSTEN','PRIDE','NOVEL','DARCY','SENSE','EMMA','ESTATE','BENNET','BATH']" + ], + "metadata": { + "id": "NkNMg4GuYt-h" + }, + "execution_count": 4, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "This routine takes the words, the grid size and various sets of constraints (which we'll develop one at a time). It then runs the solver and displays the crossword." + ], + "metadata": { + "id": "NdMkEZVl_Dci" + } + }, + { + "cell_type": "code", + "source": [ + "def solve_crossword (words, grid_size, add_constraint_set1, add_constraint_set2=None, add_constraint_set3=None, add_constraint_set4=None ):\n", + "\n", + " # Fail if longest string length is not large enough to fit in grid\n", + " longest_string_length = max(len(word) for word in words)\n", + " if (longest_string_length > grid_size):\n", + " print(\"Grid too small, no solution\")\n", + " return ;\n", + "\n", + " start_time = time.time()\n", + " # Set up the SAT solver\n", + " s = Solver()\n", + "\n", + " # This is a dictionary indexed by the word itself that contains the possible start\n", + " # positions of that word, and whether the word is horizontal or vertical\n", + " # The number of possible positions depend on the grid size as the word cannot exceed\n", + " # grid.\n", + " placement_vars = {word: [[[z3.Bool(f'{word}_{orientation}_{y},{x}')\n", + " for x in range(grid_size-len(word)+1 if orientation=='h' else grid_size )]\n", + " for y in range(grid_size-len(word)+1 if orientation=='v' else grid_size )]\n", + " for orientation in ['h', 'v']]\n", + " for word in words}\n", + "\n", + " # We will also define variables that indicate which letter is at which position\n", + " # There are 27 possible characters (26 letters and a blank)\n", + " # The variable x_i,j,k says that letter k is at position (i,j) in the grid\n", + " letter_posns = [[[ z3.Bool(\"x_{%d,%d,%d}\"%((i,j,k))) for k in range(0,27)] for j in range(0,grid_size) ] for i in range(0,grid_size) ]\n", + "\n", + " # Add the first set of constraints\n", + " s = add_constraint_set1(s, placement_vars, letter_posns, words, grid_size)\n", + " # Add the second set of constraints if present\n", + " if add_constraint_set2 is not None:\n", + " s = add_constraint_set2(s, placement_vars, letter_posns, words, grid_size)\n", + " # Add the third set of constraints if present\n", + " if add_constraint_set3 is not None:\n", + " s = add_constraint_set3(s, placement_vars, letter_posns, words, grid_size)\n", + " # Add the fourth set of constraints if present\n", + " if add_constraint_set4 is not None:\n", + " s = add_constraint_set4(s, placement_vars, letter_posns, words, grid_size)\n", + "\n", + " # Check if it's SAT (creates the model)\n", + " sat_result = s.check()\n", + " print(f\"Executed in {time.time()-start_time:.4f} seconds\")\n", + " print(sat_result)\n", + "\n", + " # If it is then draw crossword, otherwise return\n", + " if sat_result == z3.sat:\n", + " result = s.model()\n", + " # Retrieve the letter position variables in the solution as [0,1] values\n", + " x_vals = np.array([[[int(bool(result[z3.Bool(\"x_{%d,%d,%d}\" % (i, j, k))])) for k in range(0,27)] for j in range(0,grid_size) ] for i in range(0,grid_size) ] )\n", + "\n", + " # Find the position of the true value -- this is now a 2D grid with a 0 where there is a space and a value 1-26 representing a letter\n", + " solution = np.argmax(x_vals, axis=2)\n", + " # Draw the solution\n", + " draw_crossword(solution)\n", + " else:\n", + " print(\"No solution\")" + ], + "metadata": { + "id": "Ijuo4HdSefPk" + }, + "execution_count": 5, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Here's a couple of helpful routines that we can make use of" + ], + "metadata": { + "id": "BBD_gK-q_AaT" + } + }, + { + "cell_type": "code", + "source": [ + "# Takes a list of z3.Bool variables and returns constraints\n", + "# ensuring that there is exactly one true\n", + "def exactly_one(x):\n", + " return PbEq([(i,1) for i in x],1)\n", + "\n", + "# Converts a word in capital letters to its indices so 'ABD' becomes [1,2,4]\n", + "def letter_to_index(word):\n", + " return [ord(char) - ord('A') + 1 for char in word]" + ], + "metadata": { + "id": "O5p-8Ul6cvsk" + }, + "execution_count": 6, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Let's work on the first set of constraints. \n", + "\n", + "\n", + "1. Each word can only appear at one valid position\n", + "2. Each position in the grid can have only a single letter present\n", + "3. The letters implied by the word positions must agree where the words overlap\n", + "\n" + ], + "metadata": { + "id": "VT-QNf-FHClF" + } + }, + { + "cell_type": "code", + "source": [ + "def add_constraint_set1(s, placement_vars, letter_posns, words, grid_size):\n", + " # Constraint 1: Each word can only be placed in exactly one position\n", + " for word in words:\n", + " # TODO implement this constraint\n", + " # Replace these lines\n", + " print(placement_vars[word]) # Will help you understand what to do!\n", + " s.add(placement_vars[word][0][0][0])\n", + "\n", + " # Constraint 2: Each grid position can only have one letter present\n", + " for i in range(0,grid_size):\n", + " for j in range(0,grid_size):\n", + " #TODO implement this constraint\n", + " # Replace this line\n", + " s.add(letter_posns[0][0][0])\n", + "\n", + " # Constraint 3: If a word is in a given position and orientation, the letters at the\n", + " # appropriate grid positions must correspond (uses the routine letter_to_index() defined above)\n", + " for word in words:\n", + " for i in range(0,grid_size):\n", + " # We'll do the horizontal words for you. Read this code closely.\n", + " for j in range(0,grid_size-len(word)+1):\n", + " for letter_index in range(0,len(word)):\n", + " s.add(Implies(placement_vars[word][0][i][j], letter_posns[i][j+letter_index][letter_to_index(word)[letter_index]]))\n", + " # TODO define an equivalent constraint for the vertical positions\n", + " # Replace this line\n", + " s.add(letter_posns[0][0][0])\n", + "\n", + " return s" + ], + "metadata": { + "id": "NdjsISBCFr6K" + }, + "execution_count": 19, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Let's test this routine so far\n", + "solve_crossword(words, 10, add_constraint_set1)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "ksTQlgvnHiqK", + "outputId": "498bce1e-26fc-47ed-b067-31b01ac4e65c" + }, + "execution_count": 20, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[[[JANE_h_0,0, JANE_h_0,1, JANE_h_0,2, JANE_h_0,3, JANE_h_0,4, JANE_h_0,5, JANE_h_0,6], [JANE_h_1,0, JANE_h_1,1, JANE_h_1,2, JANE_h_1,3, JANE_h_1,4, JANE_h_1,5, JANE_h_1,6], [JANE_h_2,0, JANE_h_2,1, JANE_h_2,2, JANE_h_2,3, JANE_h_2,4, JANE_h_2,5, JANE_h_2,6], [JANE_h_3,0, JANE_h_3,1, JANE_h_3,2, JANE_h_3,3, JANE_h_3,4, JANE_h_3,5, JANE_h_3,6], [JANE_h_4,0, JANE_h_4,1, JANE_h_4,2, JANE_h_4,3, JANE_h_4,4, JANE_h_4,5, JANE_h_4,6], [JANE_h_5,0, JANE_h_5,1, JANE_h_5,2, JANE_h_5,3, JANE_h_5,4, JANE_h_5,5, JANE_h_5,6], [JANE_h_6,0, JANE_h_6,1, JANE_h_6,2, JANE_h_6,3, JANE_h_6,4, JANE_h_6,5, JANE_h_6,6], [JANE_h_7,0, JANE_h_7,1, JANE_h_7,2, JANE_h_7,3, JANE_h_7,4, JANE_h_7,5, JANE_h_7,6], [JANE_h_8,0, JANE_h_8,1, JANE_h_8,2, JANE_h_8,3, JANE_h_8,4, JANE_h_8,5, JANE_h_8,6], [JANE_h_9,0, JANE_h_9,1, JANE_h_9,2, JANE_h_9,3, JANE_h_9,4, JANE_h_9,5, JANE_h_9,6]], [[JANE_v_0,0, JANE_v_0,1, JANE_v_0,2, JANE_v_0,3, JANE_v_0,4, JANE_v_0,5, JANE_v_0,6, JANE_v_0,7, JANE_v_0,8, JANE_v_0,9], [JANE_v_1,0, JANE_v_1,1, JANE_v_1,2, JANE_v_1,3, JANE_v_1,4, JANE_v_1,5, JANE_v_1,6, JANE_v_1,7, JANE_v_1,8, JANE_v_1,9], [JANE_v_2,0, JANE_v_2,1, JANE_v_2,2, JANE_v_2,3, JANE_v_2,4, JANE_v_2,5, JANE_v_2,6, JANE_v_2,7, JANE_v_2,8, JANE_v_2,9], [JANE_v_3,0, JANE_v_3,1, JANE_v_3,2, JANE_v_3,3, JANE_v_3,4, JANE_v_3,5, JANE_v_3,6, JANE_v_3,7, JANE_v_3,8, JANE_v_3,9], [JANE_v_4,0, JANE_v_4,1, JANE_v_4,2, JANE_v_4,3, JANE_v_4,4, JANE_v_4,5, JANE_v_4,6, JANE_v_4,7, JANE_v_4,8, JANE_v_4,9], [JANE_v_5,0, JANE_v_5,1, JANE_v_5,2, JANE_v_5,3, JANE_v_5,4, JANE_v_5,5, JANE_v_5,6, JANE_v_5,7, JANE_v_5,8, JANE_v_5,9], [JANE_v_6,0, JANE_v_6,1, JANE_v_6,2, JANE_v_6,3, JANE_v_6,4, JANE_v_6,5, JANE_v_6,6, JANE_v_6,7, JANE_v_6,8, JANE_v_6,9]]]\n", + "[[[AUSTEN_h_0,0, AUSTEN_h_0,1, AUSTEN_h_0,2, AUSTEN_h_0,3, AUSTEN_h_0,4], [AUSTEN_h_1,0, AUSTEN_h_1,1, AUSTEN_h_1,2, AUSTEN_h_1,3, AUSTEN_h_1,4], [AUSTEN_h_2,0, AUSTEN_h_2,1, AUSTEN_h_2,2, AUSTEN_h_2,3, AUSTEN_h_2,4], [AUSTEN_h_3,0, AUSTEN_h_3,1, AUSTEN_h_3,2, AUSTEN_h_3,3, AUSTEN_h_3,4], [AUSTEN_h_4,0, AUSTEN_h_4,1, AUSTEN_h_4,2, AUSTEN_h_4,3, AUSTEN_h_4,4], [AUSTEN_h_5,0, AUSTEN_h_5,1, AUSTEN_h_5,2, AUSTEN_h_5,3, AUSTEN_h_5,4], [AUSTEN_h_6,0, AUSTEN_h_6,1, AUSTEN_h_6,2, AUSTEN_h_6,3, AUSTEN_h_6,4], [AUSTEN_h_7,0, AUSTEN_h_7,1, AUSTEN_h_7,2, AUSTEN_h_7,3, AUSTEN_h_7,4], [AUSTEN_h_8,0, AUSTEN_h_8,1, AUSTEN_h_8,2, AUSTEN_h_8,3, AUSTEN_h_8,4], [AUSTEN_h_9,0, AUSTEN_h_9,1, AUSTEN_h_9,2, AUSTEN_h_9,3, AUSTEN_h_9,4]], [[AUSTEN_v_0,0, AUSTEN_v_0,1, AUSTEN_v_0,2, AUSTEN_v_0,3, AUSTEN_v_0,4, AUSTEN_v_0,5, AUSTEN_v_0,6, AUSTEN_v_0,7, AUSTEN_v_0,8, AUSTEN_v_0,9], [AUSTEN_v_1,0, AUSTEN_v_1,1, AUSTEN_v_1,2, AUSTEN_v_1,3, AUSTEN_v_1,4, AUSTEN_v_1,5, AUSTEN_v_1,6, AUSTEN_v_1,7, AUSTEN_v_1,8, AUSTEN_v_1,9], [AUSTEN_v_2,0, AUSTEN_v_2,1, AUSTEN_v_2,2, AUSTEN_v_2,3, AUSTEN_v_2,4, AUSTEN_v_2,5, AUSTEN_v_2,6, AUSTEN_v_2,7, AUSTEN_v_2,8, AUSTEN_v_2,9], [AUSTEN_v_3,0, AUSTEN_v_3,1, AUSTEN_v_3,2, AUSTEN_v_3,3, AUSTEN_v_3,4, AUSTEN_v_3,5, AUSTEN_v_3,6, AUSTEN_v_3,7, AUSTEN_v_3,8, AUSTEN_v_3,9], [AUSTEN_v_4,0, AUSTEN_v_4,1, AUSTEN_v_4,2, AUSTEN_v_4,3, AUSTEN_v_4,4, AUSTEN_v_4,5, AUSTEN_v_4,6, AUSTEN_v_4,7, AUSTEN_v_4,8, AUSTEN_v_4,9]]]\n", + "[[[PRIDE_h_0,0, PRIDE_h_0,1, PRIDE_h_0,2, PRIDE_h_0,3, PRIDE_h_0,4, PRIDE_h_0,5], [PRIDE_h_1,0, PRIDE_h_1,1, PRIDE_h_1,2, PRIDE_h_1,3, PRIDE_h_1,4, PRIDE_h_1,5], [PRIDE_h_2,0, PRIDE_h_2,1, PRIDE_h_2,2, PRIDE_h_2,3, PRIDE_h_2,4, PRIDE_h_2,5], [PRIDE_h_3,0, PRIDE_h_3,1, PRIDE_h_3,2, PRIDE_h_3,3, PRIDE_h_3,4, PRIDE_h_3,5], [PRIDE_h_4,0, PRIDE_h_4,1, PRIDE_h_4,2, PRIDE_h_4,3, PRIDE_h_4,4, PRIDE_h_4,5], [PRIDE_h_5,0, PRIDE_h_5,1, PRIDE_h_5,2, PRIDE_h_5,3, PRIDE_h_5,4, PRIDE_h_5,5], [PRIDE_h_6,0, PRIDE_h_6,1, PRIDE_h_6,2, PRIDE_h_6,3, PRIDE_h_6,4, PRIDE_h_6,5], [PRIDE_h_7,0, PRIDE_h_7,1, PRIDE_h_7,2, PRIDE_h_7,3, PRIDE_h_7,4, PRIDE_h_7,5], [PRIDE_h_8,0, PRIDE_h_8,1, PRIDE_h_8,2, PRIDE_h_8,3, PRIDE_h_8,4, PRIDE_h_8,5], [PRIDE_h_9,0, PRIDE_h_9,1, PRIDE_h_9,2, PRIDE_h_9,3, PRIDE_h_9,4, PRIDE_h_9,5]], [[PRIDE_v_0,0, PRIDE_v_0,1, PRIDE_v_0,2, PRIDE_v_0,3, PRIDE_v_0,4, PRIDE_v_0,5, PRIDE_v_0,6, PRIDE_v_0,7, PRIDE_v_0,8, PRIDE_v_0,9], [PRIDE_v_1,0, PRIDE_v_1,1, PRIDE_v_1,2, PRIDE_v_1,3, PRIDE_v_1,4, PRIDE_v_1,5, PRIDE_v_1,6, PRIDE_v_1,7, PRIDE_v_1,8, PRIDE_v_1,9], [PRIDE_v_2,0, PRIDE_v_2,1, PRIDE_v_2,2, PRIDE_v_2,3, PRIDE_v_2,4, PRIDE_v_2,5, PRIDE_v_2,6, PRIDE_v_2,7, PRIDE_v_2,8, PRIDE_v_2,9], [PRIDE_v_3,0, PRIDE_v_3,1, PRIDE_v_3,2, PRIDE_v_3,3, PRIDE_v_3,4, PRIDE_v_3,5, PRIDE_v_3,6, PRIDE_v_3,7, PRIDE_v_3,8, PRIDE_v_3,9], [PRIDE_v_4,0, PRIDE_v_4,1, PRIDE_v_4,2, PRIDE_v_4,3, PRIDE_v_4,4, PRIDE_v_4,5, PRIDE_v_4,6, PRIDE_v_4,7, PRIDE_v_4,8, PRIDE_v_4,9], [PRIDE_v_5,0, PRIDE_v_5,1, PRIDE_v_5,2, PRIDE_v_5,3, PRIDE_v_5,4, PRIDE_v_5,5, PRIDE_v_5,6, PRIDE_v_5,7, PRIDE_v_5,8, PRIDE_v_5,9]]]\n", + "[[[NOVEL_h_0,0, NOVEL_h_0,1, NOVEL_h_0,2, NOVEL_h_0,3, NOVEL_h_0,4, NOVEL_h_0,5], [NOVEL_h_1,0, NOVEL_h_1,1, NOVEL_h_1,2, NOVEL_h_1,3, NOVEL_h_1,4, NOVEL_h_1,5], [NOVEL_h_2,0, NOVEL_h_2,1, NOVEL_h_2,2, NOVEL_h_2,3, NOVEL_h_2,4, NOVEL_h_2,5], [NOVEL_h_3,0, NOVEL_h_3,1, NOVEL_h_3,2, NOVEL_h_3,3, NOVEL_h_3,4, NOVEL_h_3,5], [NOVEL_h_4,0, NOVEL_h_4,1, NOVEL_h_4,2, NOVEL_h_4,3, NOVEL_h_4,4, NOVEL_h_4,5], [NOVEL_h_5,0, NOVEL_h_5,1, NOVEL_h_5,2, NOVEL_h_5,3, NOVEL_h_5,4, NOVEL_h_5,5], [NOVEL_h_6,0, NOVEL_h_6,1, NOVEL_h_6,2, NOVEL_h_6,3, NOVEL_h_6,4, NOVEL_h_6,5], [NOVEL_h_7,0, NOVEL_h_7,1, NOVEL_h_7,2, NOVEL_h_7,3, NOVEL_h_7,4, NOVEL_h_7,5], [NOVEL_h_8,0, NOVEL_h_8,1, NOVEL_h_8,2, NOVEL_h_8,3, NOVEL_h_8,4, NOVEL_h_8,5], [NOVEL_h_9,0, NOVEL_h_9,1, NOVEL_h_9,2, NOVEL_h_9,3, NOVEL_h_9,4, NOVEL_h_9,5]], [[NOVEL_v_0,0, NOVEL_v_0,1, NOVEL_v_0,2, NOVEL_v_0,3, NOVEL_v_0,4, NOVEL_v_0,5, NOVEL_v_0,6, NOVEL_v_0,7, NOVEL_v_0,8, NOVEL_v_0,9], [NOVEL_v_1,0, NOVEL_v_1,1, NOVEL_v_1,2, NOVEL_v_1,3, NOVEL_v_1,4, NOVEL_v_1,5, NOVEL_v_1,6, NOVEL_v_1,7, NOVEL_v_1,8, NOVEL_v_1,9], [NOVEL_v_2,0, NOVEL_v_2,1, NOVEL_v_2,2, NOVEL_v_2,3, NOVEL_v_2,4, NOVEL_v_2,5, NOVEL_v_2,6, NOVEL_v_2,7, NOVEL_v_2,8, NOVEL_v_2,9], [NOVEL_v_3,0, NOVEL_v_3,1, NOVEL_v_3,2, NOVEL_v_3,3, NOVEL_v_3,4, NOVEL_v_3,5, NOVEL_v_3,6, NOVEL_v_3,7, NOVEL_v_3,8, NOVEL_v_3,9], [NOVEL_v_4,0, NOVEL_v_4,1, NOVEL_v_4,2, NOVEL_v_4,3, NOVEL_v_4,4, NOVEL_v_4,5, NOVEL_v_4,6, NOVEL_v_4,7, NOVEL_v_4,8, NOVEL_v_4,9], [NOVEL_v_5,0, NOVEL_v_5,1, NOVEL_v_5,2, NOVEL_v_5,3, NOVEL_v_5,4, NOVEL_v_5,5, NOVEL_v_5,6, NOVEL_v_5,7, NOVEL_v_5,8, NOVEL_v_5,9]]]\n", + "[[[DARCY_h_0,0, DARCY_h_0,1, DARCY_h_0,2, DARCY_h_0,3, DARCY_h_0,4, DARCY_h_0,5], [DARCY_h_1,0, DARCY_h_1,1, DARCY_h_1,2, DARCY_h_1,3, DARCY_h_1,4, DARCY_h_1,5], [DARCY_h_2,0, DARCY_h_2,1, DARCY_h_2,2, DARCY_h_2,3, DARCY_h_2,4, DARCY_h_2,5], [DARCY_h_3,0, DARCY_h_3,1, DARCY_h_3,2, DARCY_h_3,3, DARCY_h_3,4, DARCY_h_3,5], [DARCY_h_4,0, DARCY_h_4,1, DARCY_h_4,2, DARCY_h_4,3, DARCY_h_4,4, DARCY_h_4,5], [DARCY_h_5,0, DARCY_h_5,1, DARCY_h_5,2, DARCY_h_5,3, DARCY_h_5,4, DARCY_h_5,5], [DARCY_h_6,0, DARCY_h_6,1, DARCY_h_6,2, DARCY_h_6,3, DARCY_h_6,4, DARCY_h_6,5], [DARCY_h_7,0, DARCY_h_7,1, DARCY_h_7,2, DARCY_h_7,3, DARCY_h_7,4, DARCY_h_7,5], [DARCY_h_8,0, DARCY_h_8,1, DARCY_h_8,2, DARCY_h_8,3, DARCY_h_8,4, DARCY_h_8,5], [DARCY_h_9,0, DARCY_h_9,1, DARCY_h_9,2, DARCY_h_9,3, DARCY_h_9,4, DARCY_h_9,5]], [[DARCY_v_0,0, DARCY_v_0,1, DARCY_v_0,2, DARCY_v_0,3, DARCY_v_0,4, DARCY_v_0,5, DARCY_v_0,6, DARCY_v_0,7, DARCY_v_0,8, DARCY_v_0,9], [DARCY_v_1,0, DARCY_v_1,1, DARCY_v_1,2, DARCY_v_1,3, DARCY_v_1,4, DARCY_v_1,5, DARCY_v_1,6, DARCY_v_1,7, DARCY_v_1,8, DARCY_v_1,9], [DARCY_v_2,0, DARCY_v_2,1, DARCY_v_2,2, DARCY_v_2,3, DARCY_v_2,4, DARCY_v_2,5, DARCY_v_2,6, DARCY_v_2,7, DARCY_v_2,8, DARCY_v_2,9], [DARCY_v_3,0, DARCY_v_3,1, DARCY_v_3,2, DARCY_v_3,3, DARCY_v_3,4, DARCY_v_3,5, DARCY_v_3,6, DARCY_v_3,7, DARCY_v_3,8, DARCY_v_3,9], [DARCY_v_4,0, DARCY_v_4,1, DARCY_v_4,2, DARCY_v_4,3, DARCY_v_4,4, DARCY_v_4,5, DARCY_v_4,6, DARCY_v_4,7, DARCY_v_4,8, DARCY_v_4,9], [DARCY_v_5,0, DARCY_v_5,1, DARCY_v_5,2, DARCY_v_5,3, DARCY_v_5,4, DARCY_v_5,5, DARCY_v_5,6, DARCY_v_5,7, DARCY_v_5,8, DARCY_v_5,9]]]\n", + "[[[SENSE_h_0,0, SENSE_h_0,1, SENSE_h_0,2, SENSE_h_0,3, SENSE_h_0,4, SENSE_h_0,5], [SENSE_h_1,0, SENSE_h_1,1, SENSE_h_1,2, SENSE_h_1,3, SENSE_h_1,4, SENSE_h_1,5], [SENSE_h_2,0, SENSE_h_2,1, SENSE_h_2,2, SENSE_h_2,3, SENSE_h_2,4, SENSE_h_2,5], [SENSE_h_3,0, SENSE_h_3,1, SENSE_h_3,2, SENSE_h_3,3, SENSE_h_3,4, SENSE_h_3,5], [SENSE_h_4,0, SENSE_h_4,1, SENSE_h_4,2, SENSE_h_4,3, SENSE_h_4,4, SENSE_h_4,5], [SENSE_h_5,0, SENSE_h_5,1, SENSE_h_5,2, SENSE_h_5,3, SENSE_h_5,4, SENSE_h_5,5], [SENSE_h_6,0, SENSE_h_6,1, SENSE_h_6,2, SENSE_h_6,3, SENSE_h_6,4, SENSE_h_6,5], [SENSE_h_7,0, SENSE_h_7,1, SENSE_h_7,2, SENSE_h_7,3, SENSE_h_7,4, SENSE_h_7,5], [SENSE_h_8,0, SENSE_h_8,1, SENSE_h_8,2, SENSE_h_8,3, SENSE_h_8,4, SENSE_h_8,5], [SENSE_h_9,0, SENSE_h_9,1, SENSE_h_9,2, SENSE_h_9,3, SENSE_h_9,4, SENSE_h_9,5]], [[SENSE_v_0,0, SENSE_v_0,1, SENSE_v_0,2, SENSE_v_0,3, SENSE_v_0,4, SENSE_v_0,5, SENSE_v_0,6, SENSE_v_0,7, SENSE_v_0,8, SENSE_v_0,9], [SENSE_v_1,0, SENSE_v_1,1, SENSE_v_1,2, SENSE_v_1,3, SENSE_v_1,4, SENSE_v_1,5, SENSE_v_1,6, SENSE_v_1,7, SENSE_v_1,8, SENSE_v_1,9], [SENSE_v_2,0, SENSE_v_2,1, SENSE_v_2,2, SENSE_v_2,3, SENSE_v_2,4, SENSE_v_2,5, SENSE_v_2,6, SENSE_v_2,7, SENSE_v_2,8, SENSE_v_2,9], [SENSE_v_3,0, SENSE_v_3,1, SENSE_v_3,2, SENSE_v_3,3, SENSE_v_3,4, SENSE_v_3,5, SENSE_v_3,6, SENSE_v_3,7, SENSE_v_3,8, SENSE_v_3,9], [SENSE_v_4,0, SENSE_v_4,1, SENSE_v_4,2, SENSE_v_4,3, SENSE_v_4,4, SENSE_v_4,5, SENSE_v_4,6, SENSE_v_4,7, SENSE_v_4,8, SENSE_v_4,9], [SENSE_v_5,0, SENSE_v_5,1, SENSE_v_5,2, SENSE_v_5,3, SENSE_v_5,4, SENSE_v_5,5, SENSE_v_5,6, SENSE_v_5,7, SENSE_v_5,8, SENSE_v_5,9]]]\n", + "[[[EMMA_h_0,0, EMMA_h_0,1, EMMA_h_0,2, EMMA_h_0,3, EMMA_h_0,4, EMMA_h_0,5, EMMA_h_0,6], [EMMA_h_1,0, EMMA_h_1,1, EMMA_h_1,2, EMMA_h_1,3, EMMA_h_1,4, EMMA_h_1,5, EMMA_h_1,6], [EMMA_h_2,0, EMMA_h_2,1, EMMA_h_2,2, EMMA_h_2,3, EMMA_h_2,4, EMMA_h_2,5, EMMA_h_2,6], [EMMA_h_3,0, EMMA_h_3,1, EMMA_h_3,2, EMMA_h_3,3, EMMA_h_3,4, EMMA_h_3,5, EMMA_h_3,6], [EMMA_h_4,0, EMMA_h_4,1, EMMA_h_4,2, EMMA_h_4,3, EMMA_h_4,4, EMMA_h_4,5, EMMA_h_4,6], [EMMA_h_5,0, EMMA_h_5,1, EMMA_h_5,2, EMMA_h_5,3, EMMA_h_5,4, EMMA_h_5,5, EMMA_h_5,6], [EMMA_h_6,0, EMMA_h_6,1, EMMA_h_6,2, EMMA_h_6,3, EMMA_h_6,4, EMMA_h_6,5, EMMA_h_6,6], [EMMA_h_7,0, EMMA_h_7,1, EMMA_h_7,2, EMMA_h_7,3, EMMA_h_7,4, EMMA_h_7,5, EMMA_h_7,6], [EMMA_h_8,0, EMMA_h_8,1, EMMA_h_8,2, EMMA_h_8,3, EMMA_h_8,4, EMMA_h_8,5, EMMA_h_8,6], [EMMA_h_9,0, EMMA_h_9,1, EMMA_h_9,2, EMMA_h_9,3, EMMA_h_9,4, EMMA_h_9,5, EMMA_h_9,6]], [[EMMA_v_0,0, EMMA_v_0,1, EMMA_v_0,2, EMMA_v_0,3, EMMA_v_0,4, EMMA_v_0,5, EMMA_v_0,6, EMMA_v_0,7, EMMA_v_0,8, EMMA_v_0,9], [EMMA_v_1,0, EMMA_v_1,1, EMMA_v_1,2, EMMA_v_1,3, EMMA_v_1,4, EMMA_v_1,5, EMMA_v_1,6, EMMA_v_1,7, EMMA_v_1,8, EMMA_v_1,9], [EMMA_v_2,0, EMMA_v_2,1, EMMA_v_2,2, EMMA_v_2,3, EMMA_v_2,4, EMMA_v_2,5, EMMA_v_2,6, EMMA_v_2,7, EMMA_v_2,8, EMMA_v_2,9], [EMMA_v_3,0, EMMA_v_3,1, EMMA_v_3,2, EMMA_v_3,3, EMMA_v_3,4, EMMA_v_3,5, EMMA_v_3,6, EMMA_v_3,7, EMMA_v_3,8, EMMA_v_3,9], [EMMA_v_4,0, EMMA_v_4,1, EMMA_v_4,2, EMMA_v_4,3, EMMA_v_4,4, EMMA_v_4,5, EMMA_v_4,6, EMMA_v_4,7, EMMA_v_4,8, EMMA_v_4,9], [EMMA_v_5,0, EMMA_v_5,1, EMMA_v_5,2, EMMA_v_5,3, EMMA_v_5,4, EMMA_v_5,5, EMMA_v_5,6, EMMA_v_5,7, EMMA_v_5,8, EMMA_v_5,9], [EMMA_v_6,0, EMMA_v_6,1, EMMA_v_6,2, EMMA_v_6,3, EMMA_v_6,4, EMMA_v_6,5, EMMA_v_6,6, EMMA_v_6,7, EMMA_v_6,8, EMMA_v_6,9]]]\n", + "[[[ESTATE_h_0,0, ESTATE_h_0,1, ESTATE_h_0,2, ESTATE_h_0,3, ESTATE_h_0,4], [ESTATE_h_1,0, ESTATE_h_1,1, ESTATE_h_1,2, ESTATE_h_1,3, ESTATE_h_1,4], [ESTATE_h_2,0, ESTATE_h_2,1, ESTATE_h_2,2, ESTATE_h_2,3, ESTATE_h_2,4], [ESTATE_h_3,0, ESTATE_h_3,1, ESTATE_h_3,2, ESTATE_h_3,3, ESTATE_h_3,4], [ESTATE_h_4,0, ESTATE_h_4,1, ESTATE_h_4,2, ESTATE_h_4,3, ESTATE_h_4,4], [ESTATE_h_5,0, ESTATE_h_5,1, ESTATE_h_5,2, ESTATE_h_5,3, ESTATE_h_5,4], [ESTATE_h_6,0, ESTATE_h_6,1, ESTATE_h_6,2, ESTATE_h_6,3, ESTATE_h_6,4], [ESTATE_h_7,0, ESTATE_h_7,1, ESTATE_h_7,2, ESTATE_h_7,3, ESTATE_h_7,4], [ESTATE_h_8,0, ESTATE_h_8,1, ESTATE_h_8,2, ESTATE_h_8,3, ESTATE_h_8,4], [ESTATE_h_9,0, ESTATE_h_9,1, ESTATE_h_9,2, ESTATE_h_9,3, ESTATE_h_9,4]], [[ESTATE_v_0,0, ESTATE_v_0,1, ESTATE_v_0,2, ESTATE_v_0,3, ESTATE_v_0,4, ESTATE_v_0,5, ESTATE_v_0,6, ESTATE_v_0,7, ESTATE_v_0,8, ESTATE_v_0,9], [ESTATE_v_1,0, ESTATE_v_1,1, ESTATE_v_1,2, ESTATE_v_1,3, ESTATE_v_1,4, ESTATE_v_1,5, ESTATE_v_1,6, ESTATE_v_1,7, ESTATE_v_1,8, ESTATE_v_1,9], [ESTATE_v_2,0, ESTATE_v_2,1, ESTATE_v_2,2, ESTATE_v_2,3, ESTATE_v_2,4, ESTATE_v_2,5, ESTATE_v_2,6, ESTATE_v_2,7, ESTATE_v_2,8, ESTATE_v_2,9], [ESTATE_v_3,0, ESTATE_v_3,1, ESTATE_v_3,2, ESTATE_v_3,3, ESTATE_v_3,4, ESTATE_v_3,5, ESTATE_v_3,6, ESTATE_v_3,7, ESTATE_v_3,8, ESTATE_v_3,9], [ESTATE_v_4,0, ESTATE_v_4,1, ESTATE_v_4,2, ESTATE_v_4,3, ESTATE_v_4,4, ESTATE_v_4,5, ESTATE_v_4,6, ESTATE_v_4,7, ESTATE_v_4,8, ESTATE_v_4,9]]]\n", + "[[[BENNET_h_0,0, BENNET_h_0,1, BENNET_h_0,2, BENNET_h_0,3, BENNET_h_0,4], [BENNET_h_1,0, BENNET_h_1,1, BENNET_h_1,2, BENNET_h_1,3, BENNET_h_1,4], [BENNET_h_2,0, BENNET_h_2,1, BENNET_h_2,2, BENNET_h_2,3, BENNET_h_2,4], [BENNET_h_3,0, BENNET_h_3,1, BENNET_h_3,2, BENNET_h_3,3, BENNET_h_3,4], [BENNET_h_4,0, BENNET_h_4,1, BENNET_h_4,2, BENNET_h_4,3, BENNET_h_4,4], [BENNET_h_5,0, BENNET_h_5,1, BENNET_h_5,2, BENNET_h_5,3, BENNET_h_5,4], [BENNET_h_6,0, BENNET_h_6,1, BENNET_h_6,2, BENNET_h_6,3, BENNET_h_6,4], [BENNET_h_7,0, BENNET_h_7,1, BENNET_h_7,2, BENNET_h_7,3, BENNET_h_7,4], [BENNET_h_8,0, BENNET_h_8,1, BENNET_h_8,2, BENNET_h_8,3, BENNET_h_8,4], [BENNET_h_9,0, BENNET_h_9,1, BENNET_h_9,2, BENNET_h_9,3, BENNET_h_9,4]], [[BENNET_v_0,0, BENNET_v_0,1, BENNET_v_0,2, BENNET_v_0,3, BENNET_v_0,4, BENNET_v_0,5, BENNET_v_0,6, BENNET_v_0,7, BENNET_v_0,8, BENNET_v_0,9], [BENNET_v_1,0, BENNET_v_1,1, BENNET_v_1,2, BENNET_v_1,3, BENNET_v_1,4, BENNET_v_1,5, BENNET_v_1,6, BENNET_v_1,7, BENNET_v_1,8, BENNET_v_1,9], [BENNET_v_2,0, BENNET_v_2,1, BENNET_v_2,2, BENNET_v_2,3, BENNET_v_2,4, BENNET_v_2,5, BENNET_v_2,6, BENNET_v_2,7, BENNET_v_2,8, BENNET_v_2,9], [BENNET_v_3,0, BENNET_v_3,1, BENNET_v_3,2, BENNET_v_3,3, BENNET_v_3,4, BENNET_v_3,5, BENNET_v_3,6, BENNET_v_3,7, BENNET_v_3,8, BENNET_v_3,9], [BENNET_v_4,0, BENNET_v_4,1, BENNET_v_4,2, BENNET_v_4,3, BENNET_v_4,4, BENNET_v_4,5, BENNET_v_4,6, BENNET_v_4,7, BENNET_v_4,8, BENNET_v_4,9]]]\n", + "[[[BATH_h_0,0, BATH_h_0,1, BATH_h_0,2, BATH_h_0,3, BATH_h_0,4, BATH_h_0,5, BATH_h_0,6], [BATH_h_1,0, BATH_h_1,1, BATH_h_1,2, BATH_h_1,3, BATH_h_1,4, BATH_h_1,5, BATH_h_1,6], [BATH_h_2,0, BATH_h_2,1, BATH_h_2,2, BATH_h_2,3, BATH_h_2,4, BATH_h_2,5, BATH_h_2,6], [BATH_h_3,0, BATH_h_3,1, BATH_h_3,2, BATH_h_3,3, BATH_h_3,4, BATH_h_3,5, BATH_h_3,6], [BATH_h_4,0, BATH_h_4,1, BATH_h_4,2, BATH_h_4,3, BATH_h_4,4, BATH_h_4,5, BATH_h_4,6], [BATH_h_5,0, BATH_h_5,1, BATH_h_5,2, BATH_h_5,3, BATH_h_5,4, BATH_h_5,5, BATH_h_5,6], [BATH_h_6,0, BATH_h_6,1, BATH_h_6,2, BATH_h_6,3, BATH_h_6,4, BATH_h_6,5, BATH_h_6,6], [BATH_h_7,0, BATH_h_7,1, BATH_h_7,2, BATH_h_7,3, BATH_h_7,4, BATH_h_7,5, BATH_h_7,6], [BATH_h_8,0, BATH_h_8,1, BATH_h_8,2, BATH_h_8,3, BATH_h_8,4, BATH_h_8,5, BATH_h_8,6], [BATH_h_9,0, BATH_h_9,1, BATH_h_9,2, BATH_h_9,3, BATH_h_9,4, BATH_h_9,5, BATH_h_9,6]], [[BATH_v_0,0, BATH_v_0,1, BATH_v_0,2, BATH_v_0,3, BATH_v_0,4, BATH_v_0,5, BATH_v_0,6, BATH_v_0,7, BATH_v_0,8, BATH_v_0,9], [BATH_v_1,0, BATH_v_1,1, BATH_v_1,2, BATH_v_1,3, BATH_v_1,4, BATH_v_1,5, BATH_v_1,6, BATH_v_1,7, BATH_v_1,8, BATH_v_1,9], [BATH_v_2,0, BATH_v_2,1, BATH_v_2,2, BATH_v_2,3, BATH_v_2,4, BATH_v_2,5, BATH_v_2,6, BATH_v_2,7, BATH_v_2,8, BATH_v_2,9], [BATH_v_3,0, BATH_v_3,1, BATH_v_3,2, BATH_v_3,3, BATH_v_3,4, BATH_v_3,5, BATH_v_3,6, BATH_v_3,7, BATH_v_3,8, BATH_v_3,9], [BATH_v_4,0, BATH_v_4,1, BATH_v_4,2, BATH_v_4,3, BATH_v_4,4, BATH_v_4,5, BATH_v_4,6, BATH_v_4,7, BATH_v_4,8, BATH_v_4,9], [BATH_v_5,0, BATH_v_5,1, BATH_v_5,2, BATH_v_5,3, BATH_v_5,4, BATH_v_5,5, BATH_v_5,6, BATH_v_5,7, BATH_v_5,8, BATH_v_5,9], [BATH_v_6,0, BATH_v_6,1, BATH_v_6,2, BATH_v_6,3, BATH_v_6,4, BATH_v_6,5, BATH_v_6,6, BATH_v_6,7, BATH_v_6,8, BATH_v_6,9]]]\n", + "Executed in 0.2938 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║█│A│I│A│E│E│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "If you did this correctly, you should see that the words are all in there, but we don't have blank spaces where we should. We need to add two further constraints to improve matters\n", + "\n", + "1. Horizontal words must have a blank space or an edge to the left and right of their positions. Vertical words must have a blank space or and edge above or below their positions.\n", + "2. Any position that is not part of a word should be blank\n", + "\n" + ], + "metadata": { + "id": "SlzqglXAH1wG" + } + }, + { + "cell_type": "code", + "source": [ + "def add_constraint_set2(s, placement_vars, letter_posns, words, grid_size):\n", + " # Constraint 1: Horizontal words must either start in the first column or have a 0 to their left\n", + " # Horizontal words must either finish in the last column of have a 0 to their right\n", + " # Vertical words must either start in the first row or have a 0 above them\n", + " # Vertical words must either end in the last row of have a 0 below them\n", + " for word in words:\n", + " # Horizontal words -- We'll do this one for you (read this code carefully)\n", + " for i in range(grid_size):\n", + " for j in range(1, grid_size - len(word)+1 ):\n", + " # Check for border or blank square before the word starts\n", + " s.add(Implies(placement_vars[word][0][i][j], letter_posns[i][j-1][0]))\n", + " s.add(Implies(placement_vars[word][0][i][j-1], letter_posns[i][j+len(word)-1][0]))\n", + "\n", + " # Vertical words\n", + " for i in range(1,grid_size - len(word)+1 ):\n", + " for j in range(grid_size):\n", + " # TODO -- write the equivalent constraint for the vertical words\n", + " # Replace this line\n", + " s.add(letter_posns[0][0][0])\n", + "\n", + " # Constraint 2: Any position in the crossword grid that is not part of a word must be a blank space\n", + " # This stops random characters appearing outside the solution\n", + " for i in range(grid_size):\n", + " for j in range(grid_size):\n", + " # Create a list of placement variables that add a letter to the current square\n", + " relevant_placements = []\n", + " for word in words:\n", + " # Horizontal words\n", + " for col in range(grid_size - len(word) + 1):\n", + " if j >= col and j < col + len(word):\n", + " relevant_placements.append(placement_vars[word][0][i][col])\n", + "\n", + " # Vertical words\n", + " for row in range(grid_size - len(word) + 1):\n", + " if i >= row and i < row + len(word):\n", + " relevant_placements.append(placement_vars[word][1][row][j])\n", + "\n", + "\n", + " # If none of the relevant placements are true, the square must be blank\n", + " # TODO implement this constraint\n", + " # Replace this line\n", + " s.add(letter_posns[0][0][0])\n", + "\n", + " return s" + ], + "metadata": { + "id": "7iHXNe_0F7ej" + }, + "execution_count": 21, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Let's test this routine so far\n", + "solve_crossword(words, 10, add_constraint_set1, add_constraint_set2)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "lPEcYEIbItHp", + "outputId": "15cc8da2-8688-4cfc-8cda-32362f603583" + }, + "execution_count": 22, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[[[JANE_h_0,0, JANE_h_0,1, JANE_h_0,2, JANE_h_0,3, JANE_h_0,4, JANE_h_0,5, JANE_h_0,6], [JANE_h_1,0, JANE_h_1,1, JANE_h_1,2, JANE_h_1,3, JANE_h_1,4, JANE_h_1,5, JANE_h_1,6], [JANE_h_2,0, JANE_h_2,1, JANE_h_2,2, JANE_h_2,3, JANE_h_2,4, JANE_h_2,5, JANE_h_2,6], [JANE_h_3,0, JANE_h_3,1, JANE_h_3,2, JANE_h_3,3, JANE_h_3,4, JANE_h_3,5, JANE_h_3,6], [JANE_h_4,0, JANE_h_4,1, JANE_h_4,2, JANE_h_4,3, JANE_h_4,4, JANE_h_4,5, JANE_h_4,6], [JANE_h_5,0, JANE_h_5,1, JANE_h_5,2, JANE_h_5,3, JANE_h_5,4, JANE_h_5,5, JANE_h_5,6], [JANE_h_6,0, JANE_h_6,1, JANE_h_6,2, JANE_h_6,3, JANE_h_6,4, JANE_h_6,5, JANE_h_6,6], [JANE_h_7,0, JANE_h_7,1, JANE_h_7,2, JANE_h_7,3, JANE_h_7,4, JANE_h_7,5, JANE_h_7,6], [JANE_h_8,0, JANE_h_8,1, JANE_h_8,2, JANE_h_8,3, JANE_h_8,4, JANE_h_8,5, JANE_h_8,6], [JANE_h_9,0, JANE_h_9,1, JANE_h_9,2, JANE_h_9,3, JANE_h_9,4, JANE_h_9,5, JANE_h_9,6]], [[JANE_v_0,0, JANE_v_0,1, JANE_v_0,2, JANE_v_0,3, JANE_v_0,4, JANE_v_0,5, JANE_v_0,6, JANE_v_0,7, JANE_v_0,8, JANE_v_0,9], [JANE_v_1,0, JANE_v_1,1, JANE_v_1,2, JANE_v_1,3, JANE_v_1,4, JANE_v_1,5, JANE_v_1,6, JANE_v_1,7, JANE_v_1,8, JANE_v_1,9], [JANE_v_2,0, JANE_v_2,1, JANE_v_2,2, JANE_v_2,3, JANE_v_2,4, JANE_v_2,5, JANE_v_2,6, JANE_v_2,7, JANE_v_2,8, JANE_v_2,9], [JANE_v_3,0, JANE_v_3,1, JANE_v_3,2, JANE_v_3,3, JANE_v_3,4, JANE_v_3,5, JANE_v_3,6, JANE_v_3,7, JANE_v_3,8, JANE_v_3,9], [JANE_v_4,0, JANE_v_4,1, JANE_v_4,2, JANE_v_4,3, JANE_v_4,4, JANE_v_4,5, JANE_v_4,6, JANE_v_4,7, JANE_v_4,8, JANE_v_4,9], [JANE_v_5,0, JANE_v_5,1, JANE_v_5,2, JANE_v_5,3, JANE_v_5,4, JANE_v_5,5, JANE_v_5,6, JANE_v_5,7, JANE_v_5,8, JANE_v_5,9], [JANE_v_6,0, JANE_v_6,1, JANE_v_6,2, JANE_v_6,3, JANE_v_6,4, JANE_v_6,5, JANE_v_6,6, JANE_v_6,7, JANE_v_6,8, JANE_v_6,9]]]\n", + "[[[AUSTEN_h_0,0, AUSTEN_h_0,1, AUSTEN_h_0,2, AUSTEN_h_0,3, AUSTEN_h_0,4], [AUSTEN_h_1,0, AUSTEN_h_1,1, AUSTEN_h_1,2, AUSTEN_h_1,3, AUSTEN_h_1,4], [AUSTEN_h_2,0, AUSTEN_h_2,1, AUSTEN_h_2,2, AUSTEN_h_2,3, AUSTEN_h_2,4], [AUSTEN_h_3,0, AUSTEN_h_3,1, AUSTEN_h_3,2, AUSTEN_h_3,3, AUSTEN_h_3,4], [AUSTEN_h_4,0, AUSTEN_h_4,1, AUSTEN_h_4,2, AUSTEN_h_4,3, AUSTEN_h_4,4], [AUSTEN_h_5,0, AUSTEN_h_5,1, AUSTEN_h_5,2, AUSTEN_h_5,3, AUSTEN_h_5,4], [AUSTEN_h_6,0, AUSTEN_h_6,1, AUSTEN_h_6,2, AUSTEN_h_6,3, AUSTEN_h_6,4], [AUSTEN_h_7,0, AUSTEN_h_7,1, AUSTEN_h_7,2, AUSTEN_h_7,3, AUSTEN_h_7,4], [AUSTEN_h_8,0, AUSTEN_h_8,1, AUSTEN_h_8,2, AUSTEN_h_8,3, AUSTEN_h_8,4], [AUSTEN_h_9,0, AUSTEN_h_9,1, AUSTEN_h_9,2, AUSTEN_h_9,3, AUSTEN_h_9,4]], [[AUSTEN_v_0,0, AUSTEN_v_0,1, AUSTEN_v_0,2, AUSTEN_v_0,3, AUSTEN_v_0,4, AUSTEN_v_0,5, AUSTEN_v_0,6, AUSTEN_v_0,7, AUSTEN_v_0,8, AUSTEN_v_0,9], [AUSTEN_v_1,0, AUSTEN_v_1,1, AUSTEN_v_1,2, AUSTEN_v_1,3, AUSTEN_v_1,4, AUSTEN_v_1,5, AUSTEN_v_1,6, AUSTEN_v_1,7, AUSTEN_v_1,8, AUSTEN_v_1,9], [AUSTEN_v_2,0, AUSTEN_v_2,1, AUSTEN_v_2,2, AUSTEN_v_2,3, AUSTEN_v_2,4, AUSTEN_v_2,5, AUSTEN_v_2,6, AUSTEN_v_2,7, AUSTEN_v_2,8, AUSTEN_v_2,9], [AUSTEN_v_3,0, AUSTEN_v_3,1, AUSTEN_v_3,2, AUSTEN_v_3,3, AUSTEN_v_3,4, AUSTEN_v_3,5, AUSTEN_v_3,6, AUSTEN_v_3,7, AUSTEN_v_3,8, AUSTEN_v_3,9], [AUSTEN_v_4,0, AUSTEN_v_4,1, AUSTEN_v_4,2, AUSTEN_v_4,3, AUSTEN_v_4,4, AUSTEN_v_4,5, AUSTEN_v_4,6, AUSTEN_v_4,7, AUSTEN_v_4,8, AUSTEN_v_4,9]]]\n", + "[[[PRIDE_h_0,0, PRIDE_h_0,1, PRIDE_h_0,2, PRIDE_h_0,3, PRIDE_h_0,4, PRIDE_h_0,5], [PRIDE_h_1,0, PRIDE_h_1,1, PRIDE_h_1,2, PRIDE_h_1,3, PRIDE_h_1,4, PRIDE_h_1,5], [PRIDE_h_2,0, PRIDE_h_2,1, PRIDE_h_2,2, PRIDE_h_2,3, PRIDE_h_2,4, PRIDE_h_2,5], [PRIDE_h_3,0, PRIDE_h_3,1, PRIDE_h_3,2, PRIDE_h_3,3, PRIDE_h_3,4, PRIDE_h_3,5], [PRIDE_h_4,0, PRIDE_h_4,1, PRIDE_h_4,2, PRIDE_h_4,3, PRIDE_h_4,4, PRIDE_h_4,5], [PRIDE_h_5,0, PRIDE_h_5,1, PRIDE_h_5,2, PRIDE_h_5,3, PRIDE_h_5,4, PRIDE_h_5,5], [PRIDE_h_6,0, PRIDE_h_6,1, PRIDE_h_6,2, PRIDE_h_6,3, PRIDE_h_6,4, PRIDE_h_6,5], [PRIDE_h_7,0, PRIDE_h_7,1, PRIDE_h_7,2, PRIDE_h_7,3, PRIDE_h_7,4, PRIDE_h_7,5], [PRIDE_h_8,0, PRIDE_h_8,1, PRIDE_h_8,2, PRIDE_h_8,3, PRIDE_h_8,4, PRIDE_h_8,5], [PRIDE_h_9,0, PRIDE_h_9,1, PRIDE_h_9,2, PRIDE_h_9,3, PRIDE_h_9,4, PRIDE_h_9,5]], [[PRIDE_v_0,0, PRIDE_v_0,1, PRIDE_v_0,2, PRIDE_v_0,3, PRIDE_v_0,4, PRIDE_v_0,5, PRIDE_v_0,6, PRIDE_v_0,7, PRIDE_v_0,8, PRIDE_v_0,9], [PRIDE_v_1,0, PRIDE_v_1,1, PRIDE_v_1,2, PRIDE_v_1,3, PRIDE_v_1,4, PRIDE_v_1,5, PRIDE_v_1,6, PRIDE_v_1,7, PRIDE_v_1,8, PRIDE_v_1,9], [PRIDE_v_2,0, PRIDE_v_2,1, PRIDE_v_2,2, PRIDE_v_2,3, PRIDE_v_2,4, PRIDE_v_2,5, PRIDE_v_2,6, PRIDE_v_2,7, PRIDE_v_2,8, PRIDE_v_2,9], [PRIDE_v_3,0, PRIDE_v_3,1, PRIDE_v_3,2, PRIDE_v_3,3, PRIDE_v_3,4, PRIDE_v_3,5, PRIDE_v_3,6, PRIDE_v_3,7, PRIDE_v_3,8, PRIDE_v_3,9], [PRIDE_v_4,0, PRIDE_v_4,1, PRIDE_v_4,2, PRIDE_v_4,3, PRIDE_v_4,4, PRIDE_v_4,5, PRIDE_v_4,6, PRIDE_v_4,7, PRIDE_v_4,8, PRIDE_v_4,9], [PRIDE_v_5,0, PRIDE_v_5,1, PRIDE_v_5,2, PRIDE_v_5,3, PRIDE_v_5,4, PRIDE_v_5,5, PRIDE_v_5,6, PRIDE_v_5,7, PRIDE_v_5,8, PRIDE_v_5,9]]]\n", + "[[[NOVEL_h_0,0, NOVEL_h_0,1, NOVEL_h_0,2, NOVEL_h_0,3, NOVEL_h_0,4, NOVEL_h_0,5], [NOVEL_h_1,0, NOVEL_h_1,1, NOVEL_h_1,2, NOVEL_h_1,3, NOVEL_h_1,4, NOVEL_h_1,5], [NOVEL_h_2,0, NOVEL_h_2,1, NOVEL_h_2,2, NOVEL_h_2,3, NOVEL_h_2,4, NOVEL_h_2,5], [NOVEL_h_3,0, NOVEL_h_3,1, NOVEL_h_3,2, NOVEL_h_3,3, NOVEL_h_3,4, NOVEL_h_3,5], [NOVEL_h_4,0, NOVEL_h_4,1, NOVEL_h_4,2, NOVEL_h_4,3, NOVEL_h_4,4, NOVEL_h_4,5], [NOVEL_h_5,0, NOVEL_h_5,1, NOVEL_h_5,2, NOVEL_h_5,3, NOVEL_h_5,4, NOVEL_h_5,5], [NOVEL_h_6,0, NOVEL_h_6,1, NOVEL_h_6,2, NOVEL_h_6,3, NOVEL_h_6,4, NOVEL_h_6,5], [NOVEL_h_7,0, NOVEL_h_7,1, NOVEL_h_7,2, NOVEL_h_7,3, NOVEL_h_7,4, NOVEL_h_7,5], [NOVEL_h_8,0, NOVEL_h_8,1, NOVEL_h_8,2, NOVEL_h_8,3, NOVEL_h_8,4, NOVEL_h_8,5], [NOVEL_h_9,0, NOVEL_h_9,1, NOVEL_h_9,2, NOVEL_h_9,3, NOVEL_h_9,4, NOVEL_h_9,5]], [[NOVEL_v_0,0, NOVEL_v_0,1, NOVEL_v_0,2, NOVEL_v_0,3, NOVEL_v_0,4, NOVEL_v_0,5, NOVEL_v_0,6, NOVEL_v_0,7, NOVEL_v_0,8, NOVEL_v_0,9], [NOVEL_v_1,0, NOVEL_v_1,1, NOVEL_v_1,2, NOVEL_v_1,3, NOVEL_v_1,4, NOVEL_v_1,5, NOVEL_v_1,6, NOVEL_v_1,7, NOVEL_v_1,8, NOVEL_v_1,9], [NOVEL_v_2,0, NOVEL_v_2,1, NOVEL_v_2,2, NOVEL_v_2,3, NOVEL_v_2,4, NOVEL_v_2,5, NOVEL_v_2,6, NOVEL_v_2,7, NOVEL_v_2,8, NOVEL_v_2,9], [NOVEL_v_3,0, NOVEL_v_3,1, NOVEL_v_3,2, NOVEL_v_3,3, NOVEL_v_3,4, NOVEL_v_3,5, NOVEL_v_3,6, NOVEL_v_3,7, NOVEL_v_3,8, NOVEL_v_3,9], [NOVEL_v_4,0, NOVEL_v_4,1, NOVEL_v_4,2, NOVEL_v_4,3, NOVEL_v_4,4, NOVEL_v_4,5, NOVEL_v_4,6, NOVEL_v_4,7, NOVEL_v_4,8, NOVEL_v_4,9], [NOVEL_v_5,0, NOVEL_v_5,1, NOVEL_v_5,2, NOVEL_v_5,3, NOVEL_v_5,4, NOVEL_v_5,5, NOVEL_v_5,6, NOVEL_v_5,7, NOVEL_v_5,8, NOVEL_v_5,9]]]\n", + "[[[DARCY_h_0,0, DARCY_h_0,1, DARCY_h_0,2, DARCY_h_0,3, DARCY_h_0,4, DARCY_h_0,5], [DARCY_h_1,0, DARCY_h_1,1, DARCY_h_1,2, DARCY_h_1,3, DARCY_h_1,4, DARCY_h_1,5], [DARCY_h_2,0, DARCY_h_2,1, DARCY_h_2,2, DARCY_h_2,3, DARCY_h_2,4, DARCY_h_2,5], [DARCY_h_3,0, DARCY_h_3,1, DARCY_h_3,2, DARCY_h_3,3, DARCY_h_3,4, DARCY_h_3,5], [DARCY_h_4,0, DARCY_h_4,1, DARCY_h_4,2, DARCY_h_4,3, DARCY_h_4,4, DARCY_h_4,5], [DARCY_h_5,0, DARCY_h_5,1, DARCY_h_5,2, DARCY_h_5,3, DARCY_h_5,4, DARCY_h_5,5], [DARCY_h_6,0, DARCY_h_6,1, DARCY_h_6,2, DARCY_h_6,3, DARCY_h_6,4, DARCY_h_6,5], [DARCY_h_7,0, DARCY_h_7,1, DARCY_h_7,2, DARCY_h_7,3, DARCY_h_7,4, DARCY_h_7,5], [DARCY_h_8,0, DARCY_h_8,1, DARCY_h_8,2, DARCY_h_8,3, DARCY_h_8,4, DARCY_h_8,5], [DARCY_h_9,0, DARCY_h_9,1, DARCY_h_9,2, DARCY_h_9,3, DARCY_h_9,4, DARCY_h_9,5]], [[DARCY_v_0,0, DARCY_v_0,1, DARCY_v_0,2, DARCY_v_0,3, DARCY_v_0,4, DARCY_v_0,5, DARCY_v_0,6, DARCY_v_0,7, DARCY_v_0,8, DARCY_v_0,9], [DARCY_v_1,0, DARCY_v_1,1, DARCY_v_1,2, DARCY_v_1,3, DARCY_v_1,4, DARCY_v_1,5, DARCY_v_1,6, DARCY_v_1,7, DARCY_v_1,8, DARCY_v_1,9], [DARCY_v_2,0, DARCY_v_2,1, DARCY_v_2,2, DARCY_v_2,3, DARCY_v_2,4, DARCY_v_2,5, DARCY_v_2,6, DARCY_v_2,7, DARCY_v_2,8, DARCY_v_2,9], [DARCY_v_3,0, DARCY_v_3,1, DARCY_v_3,2, DARCY_v_3,3, DARCY_v_3,4, DARCY_v_3,5, DARCY_v_3,6, DARCY_v_3,7, DARCY_v_3,8, DARCY_v_3,9], [DARCY_v_4,0, DARCY_v_4,1, DARCY_v_4,2, DARCY_v_4,3, DARCY_v_4,4, DARCY_v_4,5, DARCY_v_4,6, DARCY_v_4,7, DARCY_v_4,8, DARCY_v_4,9], [DARCY_v_5,0, DARCY_v_5,1, DARCY_v_5,2, DARCY_v_5,3, DARCY_v_5,4, DARCY_v_5,5, DARCY_v_5,6, DARCY_v_5,7, DARCY_v_5,8, DARCY_v_5,9]]]\n", + "[[[SENSE_h_0,0, SENSE_h_0,1, SENSE_h_0,2, SENSE_h_0,3, SENSE_h_0,4, SENSE_h_0,5], [SENSE_h_1,0, SENSE_h_1,1, SENSE_h_1,2, SENSE_h_1,3, SENSE_h_1,4, SENSE_h_1,5], [SENSE_h_2,0, SENSE_h_2,1, SENSE_h_2,2, SENSE_h_2,3, SENSE_h_2,4, SENSE_h_2,5], [SENSE_h_3,0, SENSE_h_3,1, SENSE_h_3,2, SENSE_h_3,3, SENSE_h_3,4, SENSE_h_3,5], [SENSE_h_4,0, SENSE_h_4,1, SENSE_h_4,2, SENSE_h_4,3, SENSE_h_4,4, SENSE_h_4,5], [SENSE_h_5,0, SENSE_h_5,1, SENSE_h_5,2, SENSE_h_5,3, SENSE_h_5,4, SENSE_h_5,5], [SENSE_h_6,0, SENSE_h_6,1, SENSE_h_6,2, SENSE_h_6,3, SENSE_h_6,4, SENSE_h_6,5], [SENSE_h_7,0, SENSE_h_7,1, SENSE_h_7,2, SENSE_h_7,3, SENSE_h_7,4, SENSE_h_7,5], [SENSE_h_8,0, SENSE_h_8,1, SENSE_h_8,2, SENSE_h_8,3, SENSE_h_8,4, SENSE_h_8,5], [SENSE_h_9,0, SENSE_h_9,1, SENSE_h_9,2, SENSE_h_9,3, SENSE_h_9,4, SENSE_h_9,5]], [[SENSE_v_0,0, SENSE_v_0,1, SENSE_v_0,2, SENSE_v_0,3, SENSE_v_0,4, SENSE_v_0,5, SENSE_v_0,6, SENSE_v_0,7, SENSE_v_0,8, SENSE_v_0,9], [SENSE_v_1,0, SENSE_v_1,1, SENSE_v_1,2, SENSE_v_1,3, SENSE_v_1,4, SENSE_v_1,5, SENSE_v_1,6, SENSE_v_1,7, SENSE_v_1,8, SENSE_v_1,9], [SENSE_v_2,0, SENSE_v_2,1, SENSE_v_2,2, SENSE_v_2,3, SENSE_v_2,4, SENSE_v_2,5, SENSE_v_2,6, SENSE_v_2,7, SENSE_v_2,8, SENSE_v_2,9], [SENSE_v_3,0, SENSE_v_3,1, SENSE_v_3,2, SENSE_v_3,3, SENSE_v_3,4, SENSE_v_3,5, SENSE_v_3,6, SENSE_v_3,7, SENSE_v_3,8, SENSE_v_3,9], [SENSE_v_4,0, SENSE_v_4,1, SENSE_v_4,2, SENSE_v_4,3, SENSE_v_4,4, SENSE_v_4,5, SENSE_v_4,6, SENSE_v_4,7, SENSE_v_4,8, SENSE_v_4,9], [SENSE_v_5,0, SENSE_v_5,1, SENSE_v_5,2, SENSE_v_5,3, SENSE_v_5,4, SENSE_v_5,5, SENSE_v_5,6, SENSE_v_5,7, SENSE_v_5,8, SENSE_v_5,9]]]\n", + "[[[EMMA_h_0,0, EMMA_h_0,1, EMMA_h_0,2, EMMA_h_0,3, EMMA_h_0,4, EMMA_h_0,5, EMMA_h_0,6], [EMMA_h_1,0, EMMA_h_1,1, EMMA_h_1,2, EMMA_h_1,3, EMMA_h_1,4, EMMA_h_1,5, EMMA_h_1,6], [EMMA_h_2,0, EMMA_h_2,1, EMMA_h_2,2, EMMA_h_2,3, EMMA_h_2,4, EMMA_h_2,5, EMMA_h_2,6], [EMMA_h_3,0, EMMA_h_3,1, EMMA_h_3,2, EMMA_h_3,3, EMMA_h_3,4, EMMA_h_3,5, EMMA_h_3,6], [EMMA_h_4,0, EMMA_h_4,1, EMMA_h_4,2, EMMA_h_4,3, EMMA_h_4,4, EMMA_h_4,5, EMMA_h_4,6], [EMMA_h_5,0, EMMA_h_5,1, EMMA_h_5,2, EMMA_h_5,3, EMMA_h_5,4, EMMA_h_5,5, EMMA_h_5,6], [EMMA_h_6,0, EMMA_h_6,1, EMMA_h_6,2, EMMA_h_6,3, EMMA_h_6,4, EMMA_h_6,5, EMMA_h_6,6], [EMMA_h_7,0, EMMA_h_7,1, EMMA_h_7,2, EMMA_h_7,3, EMMA_h_7,4, EMMA_h_7,5, EMMA_h_7,6], [EMMA_h_8,0, EMMA_h_8,1, EMMA_h_8,2, EMMA_h_8,3, EMMA_h_8,4, EMMA_h_8,5, EMMA_h_8,6], [EMMA_h_9,0, EMMA_h_9,1, EMMA_h_9,2, EMMA_h_9,3, EMMA_h_9,4, EMMA_h_9,5, EMMA_h_9,6]], [[EMMA_v_0,0, EMMA_v_0,1, EMMA_v_0,2, EMMA_v_0,3, EMMA_v_0,4, EMMA_v_0,5, EMMA_v_0,6, EMMA_v_0,7, EMMA_v_0,8, EMMA_v_0,9], [EMMA_v_1,0, EMMA_v_1,1, EMMA_v_1,2, EMMA_v_1,3, EMMA_v_1,4, EMMA_v_1,5, EMMA_v_1,6, EMMA_v_1,7, EMMA_v_1,8, EMMA_v_1,9], [EMMA_v_2,0, EMMA_v_2,1, EMMA_v_2,2, EMMA_v_2,3, EMMA_v_2,4, EMMA_v_2,5, EMMA_v_2,6, EMMA_v_2,7, EMMA_v_2,8, EMMA_v_2,9], [EMMA_v_3,0, EMMA_v_3,1, EMMA_v_3,2, EMMA_v_3,3, EMMA_v_3,4, EMMA_v_3,5, EMMA_v_3,6, EMMA_v_3,7, EMMA_v_3,8, EMMA_v_3,9], [EMMA_v_4,0, EMMA_v_4,1, EMMA_v_4,2, EMMA_v_4,3, EMMA_v_4,4, EMMA_v_4,5, EMMA_v_4,6, EMMA_v_4,7, EMMA_v_4,8, EMMA_v_4,9], [EMMA_v_5,0, EMMA_v_5,1, EMMA_v_5,2, EMMA_v_5,3, EMMA_v_5,4, EMMA_v_5,5, EMMA_v_5,6, EMMA_v_5,7, EMMA_v_5,8, EMMA_v_5,9], [EMMA_v_6,0, EMMA_v_6,1, EMMA_v_6,2, EMMA_v_6,3, EMMA_v_6,4, EMMA_v_6,5, EMMA_v_6,6, EMMA_v_6,7, EMMA_v_6,8, EMMA_v_6,9]]]\n", + "[[[ESTATE_h_0,0, ESTATE_h_0,1, ESTATE_h_0,2, ESTATE_h_0,3, ESTATE_h_0,4], [ESTATE_h_1,0, ESTATE_h_1,1, ESTATE_h_1,2, ESTATE_h_1,3, ESTATE_h_1,4], [ESTATE_h_2,0, ESTATE_h_2,1, ESTATE_h_2,2, ESTATE_h_2,3, ESTATE_h_2,4], [ESTATE_h_3,0, ESTATE_h_3,1, ESTATE_h_3,2, ESTATE_h_3,3, ESTATE_h_3,4], [ESTATE_h_4,0, ESTATE_h_4,1, ESTATE_h_4,2, ESTATE_h_4,3, ESTATE_h_4,4], [ESTATE_h_5,0, ESTATE_h_5,1, ESTATE_h_5,2, ESTATE_h_5,3, ESTATE_h_5,4], [ESTATE_h_6,0, ESTATE_h_6,1, ESTATE_h_6,2, ESTATE_h_6,3, ESTATE_h_6,4], [ESTATE_h_7,0, ESTATE_h_7,1, ESTATE_h_7,2, ESTATE_h_7,3, ESTATE_h_7,4], [ESTATE_h_8,0, ESTATE_h_8,1, ESTATE_h_8,2, ESTATE_h_8,3, ESTATE_h_8,4], [ESTATE_h_9,0, ESTATE_h_9,1, ESTATE_h_9,2, ESTATE_h_9,3, ESTATE_h_9,4]], [[ESTATE_v_0,0, ESTATE_v_0,1, ESTATE_v_0,2, ESTATE_v_0,3, ESTATE_v_0,4, ESTATE_v_0,5, ESTATE_v_0,6, ESTATE_v_0,7, ESTATE_v_0,8, ESTATE_v_0,9], [ESTATE_v_1,0, ESTATE_v_1,1, ESTATE_v_1,2, ESTATE_v_1,3, ESTATE_v_1,4, ESTATE_v_1,5, ESTATE_v_1,6, ESTATE_v_1,7, ESTATE_v_1,8, ESTATE_v_1,9], [ESTATE_v_2,0, ESTATE_v_2,1, ESTATE_v_2,2, ESTATE_v_2,3, ESTATE_v_2,4, ESTATE_v_2,5, ESTATE_v_2,6, ESTATE_v_2,7, ESTATE_v_2,8, ESTATE_v_2,9], [ESTATE_v_3,0, ESTATE_v_3,1, ESTATE_v_3,2, ESTATE_v_3,3, ESTATE_v_3,4, ESTATE_v_3,5, ESTATE_v_3,6, ESTATE_v_3,7, ESTATE_v_3,8, ESTATE_v_3,9], [ESTATE_v_4,0, ESTATE_v_4,1, ESTATE_v_4,2, ESTATE_v_4,3, ESTATE_v_4,4, ESTATE_v_4,5, ESTATE_v_4,6, ESTATE_v_4,7, ESTATE_v_4,8, ESTATE_v_4,9]]]\n", + "[[[BENNET_h_0,0, BENNET_h_0,1, BENNET_h_0,2, BENNET_h_0,3, BENNET_h_0,4], [BENNET_h_1,0, BENNET_h_1,1, BENNET_h_1,2, BENNET_h_1,3, BENNET_h_1,4], [BENNET_h_2,0, BENNET_h_2,1, BENNET_h_2,2, BENNET_h_2,3, BENNET_h_2,4], [BENNET_h_3,0, BENNET_h_3,1, BENNET_h_3,2, BENNET_h_3,3, BENNET_h_3,4], [BENNET_h_4,0, BENNET_h_4,1, BENNET_h_4,2, BENNET_h_4,3, BENNET_h_4,4], [BENNET_h_5,0, BENNET_h_5,1, BENNET_h_5,2, BENNET_h_5,3, BENNET_h_5,4], [BENNET_h_6,0, BENNET_h_6,1, BENNET_h_6,2, BENNET_h_6,3, BENNET_h_6,4], [BENNET_h_7,0, BENNET_h_7,1, BENNET_h_7,2, BENNET_h_7,3, BENNET_h_7,4], [BENNET_h_8,0, BENNET_h_8,1, BENNET_h_8,2, BENNET_h_8,3, BENNET_h_8,4], [BENNET_h_9,0, BENNET_h_9,1, BENNET_h_9,2, BENNET_h_9,3, BENNET_h_9,4]], [[BENNET_v_0,0, BENNET_v_0,1, BENNET_v_0,2, BENNET_v_0,3, BENNET_v_0,4, BENNET_v_0,5, BENNET_v_0,6, BENNET_v_0,7, BENNET_v_0,8, BENNET_v_0,9], [BENNET_v_1,0, BENNET_v_1,1, BENNET_v_1,2, BENNET_v_1,3, BENNET_v_1,4, BENNET_v_1,5, BENNET_v_1,6, BENNET_v_1,7, BENNET_v_1,8, BENNET_v_1,9], [BENNET_v_2,0, BENNET_v_2,1, BENNET_v_2,2, BENNET_v_2,3, BENNET_v_2,4, BENNET_v_2,5, BENNET_v_2,6, BENNET_v_2,7, BENNET_v_2,8, BENNET_v_2,9], [BENNET_v_3,0, BENNET_v_3,1, BENNET_v_3,2, BENNET_v_3,3, BENNET_v_3,4, BENNET_v_3,5, BENNET_v_3,6, BENNET_v_3,7, BENNET_v_3,8, BENNET_v_3,9], [BENNET_v_4,0, BENNET_v_4,1, BENNET_v_4,2, BENNET_v_4,3, BENNET_v_4,4, BENNET_v_4,5, BENNET_v_4,6, BENNET_v_4,7, BENNET_v_4,8, BENNET_v_4,9]]]\n", + "[[[BATH_h_0,0, BATH_h_0,1, BATH_h_0,2, BATH_h_0,3, BATH_h_0,4, BATH_h_0,5, BATH_h_0,6], [BATH_h_1,0, BATH_h_1,1, BATH_h_1,2, BATH_h_1,3, BATH_h_1,4, BATH_h_1,5, BATH_h_1,6], [BATH_h_2,0, BATH_h_2,1, BATH_h_2,2, BATH_h_2,3, BATH_h_2,4, BATH_h_2,5, BATH_h_2,6], [BATH_h_3,0, BATH_h_3,1, BATH_h_3,2, BATH_h_3,3, BATH_h_3,4, BATH_h_3,5, BATH_h_3,6], [BATH_h_4,0, BATH_h_4,1, BATH_h_4,2, BATH_h_4,3, BATH_h_4,4, BATH_h_4,5, BATH_h_4,6], [BATH_h_5,0, BATH_h_5,1, BATH_h_5,2, BATH_h_5,3, BATH_h_5,4, BATH_h_5,5, BATH_h_5,6], [BATH_h_6,0, BATH_h_6,1, BATH_h_6,2, BATH_h_6,3, BATH_h_6,4, BATH_h_6,5, BATH_h_6,6], [BATH_h_7,0, BATH_h_7,1, BATH_h_7,2, BATH_h_7,3, BATH_h_7,4, BATH_h_7,5, BATH_h_7,6], [BATH_h_8,0, BATH_h_8,1, BATH_h_8,2, BATH_h_8,3, BATH_h_8,4, BATH_h_8,5, BATH_h_8,6], [BATH_h_9,0, BATH_h_9,1, BATH_h_9,2, BATH_h_9,3, BATH_h_9,4, BATH_h_9,5, BATH_h_9,6]], [[BATH_v_0,0, BATH_v_0,1, BATH_v_0,2, BATH_v_0,3, BATH_v_0,4, BATH_v_0,5, BATH_v_0,6, BATH_v_0,7, BATH_v_0,8, BATH_v_0,9], [BATH_v_1,0, BATH_v_1,1, BATH_v_1,2, BATH_v_1,3, BATH_v_1,4, BATH_v_1,5, BATH_v_1,6, BATH_v_1,7, BATH_v_1,8, BATH_v_1,9], [BATH_v_2,0, BATH_v_2,1, BATH_v_2,2, BATH_v_2,3, BATH_v_2,4, BATH_v_2,5, BATH_v_2,6, BATH_v_2,7, BATH_v_2,8, BATH_v_2,9], [BATH_v_3,0, BATH_v_3,1, BATH_v_3,2, BATH_v_3,3, BATH_v_3,4, BATH_v_3,5, BATH_v_3,6, BATH_v_3,7, BATH_v_3,8, BATH_v_3,9], [BATH_v_4,0, BATH_v_4,1, BATH_v_4,2, BATH_v_4,3, BATH_v_4,4, BATH_v_4,5, BATH_v_4,6, BATH_v_4,7, BATH_v_4,8, BATH_v_4,9], [BATH_v_5,0, BATH_v_5,1, BATH_v_5,2, BATH_v_5,3, BATH_v_5,4, BATH_v_5,5, BATH_v_5,6, BATH_v_5,7, BATH_v_5,8, BATH_v_5,9], [BATH_v_6,0, BATH_v_6,1, BATH_v_6,2, BATH_v_6,3, BATH_v_6,4, BATH_v_6,5, BATH_v_6,6, BATH_v_6,7, BATH_v_6,8, BATH_v_6,9]]]\n", + "Executed in 0.3757 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║█│A│I│A│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "If you did this correctly, it should be an improvement, but it's not perfect; every letter is now part of either a horizontal or a vertical word. However, when there are several vertical words adjacent to each other and we read horizontally across these words, we get nonsense. Similarly, when there are several horizontal words adjacent to each other an we read vertically through these words, we get nonsense. We can fix this by adding another constraint:\n", + "\n", + "* If a letter is in a horizontal word, it is either inside a vertical word as well *OR* it has a blank square (or the edge of the grid) above and below it.\n", + "* If a letter is in a vertical word, it is either inside a horizontal word as well *OR* it has a blank square (or the edge of the grid) to the left and the right of it.\n", + "\n", + "This one is pretty tricky to get right, so just read the code and try to understand how it works." + ], + "metadata": { + "id": "2X4guZwZI_JW" + } + }, + { + "cell_type": "code", + "source": [ + "def add_constraint_set3(s, placement_vars, letter_posns, words, grid_size):\n", + " # Constraint 1: If a letter is in a horizontal word, it either\n", + " # -- is inside a vertical word as well\n", + " # -- has a blank (or edge) above and below it\n", + " # If a letter in a vertical word exists, it is either\n", + " # -- is inside a horizontal word too\n", + " # -- has a blank (or edge) to the left and to the right of it.\n", + " for i in range(0,grid_size):\n", + " for j in range(0,grid_size):\n", + " relevant_placements_horz = []\n", + " relevant_placements_vert = []\n", + " for word in words:\n", + " for j2 in range (max(0,j-len(word)+1), min(j+1,grid_size-len(word)+1)):\n", + " relevant_placements_horz.append(placement_vars[word][0][i][j2])\n", + " for i2 in range(max(0,i-len(word)+1), min(i+1,grid_size-len(word)+1)):\n", + " relevant_placements_vert.append(placement_vars[word][1][i2][j])\n", + " in_horizontal_word = Or(relevant_placements_horz)\n", + " in_vertical_word = Or(relevant_placements_vert)\n", + "\n", + " if(i == 0):\n", + " above_and_below_are_blank = letter_posns[i+1][j][0]\n", + " else:\n", + " if(i == grid_size-1):\n", + " above_and_below_are_blank = letter_posns[i-1][j][0]\n", + " else:\n", + " above_and_below_are_blank = And(letter_posns[i-1][j][0],letter_posns[i+1][j][0])\n", + "\n", + " if(j == 0):\n", + " left_and_right_are_blank = letter_posns[i][j+1][0]\n", + " else:\n", + " if(j == grid_size-1):\n", + " left_and_right_are_blank = letter_posns[i][j-1][0]\n", + " else:\n", + " left_and_right_are_blank = And(letter_posns[i][j-1][0],letter_posns[i][j+1][0])\n", + " s.add(Implies(in_horizontal_word, Or(in_vertical_word, above_and_below_are_blank)))\n", + " s.add(Implies(in_vertical_word, Or(in_horizontal_word, left_and_right_are_blank)))\n", + "\n", + " return s" + ], + "metadata": { + "id": "4LSgimAjGQdT" + }, + "execution_count": 23, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Let's see how this improves things\n", + "solve_crossword(words, 10, add_constraint_set1, add_constraint_set2, add_constraint_set3)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "8HHqCWMzKCTL", + "outputId": "401fbfd2-194b-4258-a890-7d99fc601937" + }, + "execution_count": 24, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[[[JANE_h_0,0, JANE_h_0,1, JANE_h_0,2, JANE_h_0,3, JANE_h_0,4, JANE_h_0,5, JANE_h_0,6], [JANE_h_1,0, JANE_h_1,1, JANE_h_1,2, JANE_h_1,3, JANE_h_1,4, JANE_h_1,5, JANE_h_1,6], [JANE_h_2,0, JANE_h_2,1, JANE_h_2,2, JANE_h_2,3, JANE_h_2,4, JANE_h_2,5, JANE_h_2,6], [JANE_h_3,0, JANE_h_3,1, JANE_h_3,2, JANE_h_3,3, JANE_h_3,4, JANE_h_3,5, JANE_h_3,6], [JANE_h_4,0, JANE_h_4,1, JANE_h_4,2, JANE_h_4,3, JANE_h_4,4, JANE_h_4,5, JANE_h_4,6], [JANE_h_5,0, JANE_h_5,1, JANE_h_5,2, JANE_h_5,3, JANE_h_5,4, JANE_h_5,5, JANE_h_5,6], [JANE_h_6,0, JANE_h_6,1, JANE_h_6,2, JANE_h_6,3, JANE_h_6,4, JANE_h_6,5, JANE_h_6,6], [JANE_h_7,0, JANE_h_7,1, JANE_h_7,2, JANE_h_7,3, JANE_h_7,4, JANE_h_7,5, JANE_h_7,6], [JANE_h_8,0, JANE_h_8,1, JANE_h_8,2, JANE_h_8,3, JANE_h_8,4, JANE_h_8,5, JANE_h_8,6], [JANE_h_9,0, JANE_h_9,1, JANE_h_9,2, JANE_h_9,3, JANE_h_9,4, JANE_h_9,5, JANE_h_9,6]], [[JANE_v_0,0, JANE_v_0,1, JANE_v_0,2, JANE_v_0,3, JANE_v_0,4, JANE_v_0,5, JANE_v_0,6, JANE_v_0,7, JANE_v_0,8, JANE_v_0,9], [JANE_v_1,0, JANE_v_1,1, JANE_v_1,2, JANE_v_1,3, JANE_v_1,4, JANE_v_1,5, JANE_v_1,6, JANE_v_1,7, JANE_v_1,8, JANE_v_1,9], [JANE_v_2,0, JANE_v_2,1, JANE_v_2,2, JANE_v_2,3, JANE_v_2,4, JANE_v_2,5, JANE_v_2,6, JANE_v_2,7, JANE_v_2,8, JANE_v_2,9], [JANE_v_3,0, JANE_v_3,1, JANE_v_3,2, JANE_v_3,3, JANE_v_3,4, JANE_v_3,5, JANE_v_3,6, JANE_v_3,7, JANE_v_3,8, JANE_v_3,9], [JANE_v_4,0, JANE_v_4,1, JANE_v_4,2, JANE_v_4,3, JANE_v_4,4, JANE_v_4,5, JANE_v_4,6, JANE_v_4,7, JANE_v_4,8, JANE_v_4,9], [JANE_v_5,0, JANE_v_5,1, JANE_v_5,2, JANE_v_5,3, JANE_v_5,4, JANE_v_5,5, JANE_v_5,6, JANE_v_5,7, JANE_v_5,8, JANE_v_5,9], [JANE_v_6,0, JANE_v_6,1, JANE_v_6,2, JANE_v_6,3, JANE_v_6,4, JANE_v_6,5, JANE_v_6,6, JANE_v_6,7, JANE_v_6,8, JANE_v_6,9]]]\n", + "[[[AUSTEN_h_0,0, AUSTEN_h_0,1, AUSTEN_h_0,2, AUSTEN_h_0,3, AUSTEN_h_0,4], [AUSTEN_h_1,0, AUSTEN_h_1,1, AUSTEN_h_1,2, AUSTEN_h_1,3, AUSTEN_h_1,4], [AUSTEN_h_2,0, AUSTEN_h_2,1, AUSTEN_h_2,2, AUSTEN_h_2,3, AUSTEN_h_2,4], [AUSTEN_h_3,0, AUSTEN_h_3,1, AUSTEN_h_3,2, AUSTEN_h_3,3, AUSTEN_h_3,4], [AUSTEN_h_4,0, AUSTEN_h_4,1, AUSTEN_h_4,2, AUSTEN_h_4,3, AUSTEN_h_4,4], [AUSTEN_h_5,0, AUSTEN_h_5,1, AUSTEN_h_5,2, AUSTEN_h_5,3, AUSTEN_h_5,4], [AUSTEN_h_6,0, AUSTEN_h_6,1, AUSTEN_h_6,2, AUSTEN_h_6,3, AUSTEN_h_6,4], [AUSTEN_h_7,0, AUSTEN_h_7,1, AUSTEN_h_7,2, AUSTEN_h_7,3, AUSTEN_h_7,4], [AUSTEN_h_8,0, AUSTEN_h_8,1, AUSTEN_h_8,2, AUSTEN_h_8,3, AUSTEN_h_8,4], [AUSTEN_h_9,0, AUSTEN_h_9,1, AUSTEN_h_9,2, AUSTEN_h_9,3, AUSTEN_h_9,4]], [[AUSTEN_v_0,0, AUSTEN_v_0,1, AUSTEN_v_0,2, AUSTEN_v_0,3, AUSTEN_v_0,4, AUSTEN_v_0,5, AUSTEN_v_0,6, AUSTEN_v_0,7, AUSTEN_v_0,8, AUSTEN_v_0,9], [AUSTEN_v_1,0, AUSTEN_v_1,1, AUSTEN_v_1,2, AUSTEN_v_1,3, AUSTEN_v_1,4, AUSTEN_v_1,5, AUSTEN_v_1,6, AUSTEN_v_1,7, AUSTEN_v_1,8, AUSTEN_v_1,9], [AUSTEN_v_2,0, AUSTEN_v_2,1, AUSTEN_v_2,2, AUSTEN_v_2,3, AUSTEN_v_2,4, AUSTEN_v_2,5, AUSTEN_v_2,6, AUSTEN_v_2,7, AUSTEN_v_2,8, AUSTEN_v_2,9], [AUSTEN_v_3,0, AUSTEN_v_3,1, AUSTEN_v_3,2, AUSTEN_v_3,3, AUSTEN_v_3,4, AUSTEN_v_3,5, AUSTEN_v_3,6, AUSTEN_v_3,7, AUSTEN_v_3,8, AUSTEN_v_3,9], [AUSTEN_v_4,0, AUSTEN_v_4,1, AUSTEN_v_4,2, AUSTEN_v_4,3, AUSTEN_v_4,4, AUSTEN_v_4,5, AUSTEN_v_4,6, AUSTEN_v_4,7, AUSTEN_v_4,8, AUSTEN_v_4,9]]]\n", + "[[[PRIDE_h_0,0, PRIDE_h_0,1, PRIDE_h_0,2, PRIDE_h_0,3, PRIDE_h_0,4, PRIDE_h_0,5], [PRIDE_h_1,0, PRIDE_h_1,1, PRIDE_h_1,2, PRIDE_h_1,3, PRIDE_h_1,4, PRIDE_h_1,5], [PRIDE_h_2,0, PRIDE_h_2,1, PRIDE_h_2,2, PRIDE_h_2,3, PRIDE_h_2,4, PRIDE_h_2,5], [PRIDE_h_3,0, PRIDE_h_3,1, PRIDE_h_3,2, PRIDE_h_3,3, PRIDE_h_3,4, PRIDE_h_3,5], [PRIDE_h_4,0, PRIDE_h_4,1, PRIDE_h_4,2, PRIDE_h_4,3, PRIDE_h_4,4, PRIDE_h_4,5], [PRIDE_h_5,0, PRIDE_h_5,1, PRIDE_h_5,2, PRIDE_h_5,3, PRIDE_h_5,4, PRIDE_h_5,5], [PRIDE_h_6,0, PRIDE_h_6,1, PRIDE_h_6,2, PRIDE_h_6,3, PRIDE_h_6,4, PRIDE_h_6,5], [PRIDE_h_7,0, PRIDE_h_7,1, PRIDE_h_7,2, PRIDE_h_7,3, PRIDE_h_7,4, PRIDE_h_7,5], [PRIDE_h_8,0, PRIDE_h_8,1, PRIDE_h_8,2, PRIDE_h_8,3, PRIDE_h_8,4, PRIDE_h_8,5], [PRIDE_h_9,0, PRIDE_h_9,1, PRIDE_h_9,2, PRIDE_h_9,3, PRIDE_h_9,4, PRIDE_h_9,5]], [[PRIDE_v_0,0, PRIDE_v_0,1, PRIDE_v_0,2, PRIDE_v_0,3, PRIDE_v_0,4, PRIDE_v_0,5, PRIDE_v_0,6, PRIDE_v_0,7, PRIDE_v_0,8, PRIDE_v_0,9], [PRIDE_v_1,0, PRIDE_v_1,1, PRIDE_v_1,2, PRIDE_v_1,3, PRIDE_v_1,4, PRIDE_v_1,5, PRIDE_v_1,6, PRIDE_v_1,7, PRIDE_v_1,8, PRIDE_v_1,9], [PRIDE_v_2,0, PRIDE_v_2,1, PRIDE_v_2,2, PRIDE_v_2,3, PRIDE_v_2,4, PRIDE_v_2,5, PRIDE_v_2,6, PRIDE_v_2,7, PRIDE_v_2,8, PRIDE_v_2,9], [PRIDE_v_3,0, PRIDE_v_3,1, PRIDE_v_3,2, PRIDE_v_3,3, PRIDE_v_3,4, PRIDE_v_3,5, PRIDE_v_3,6, PRIDE_v_3,7, PRIDE_v_3,8, PRIDE_v_3,9], [PRIDE_v_4,0, PRIDE_v_4,1, PRIDE_v_4,2, PRIDE_v_4,3, PRIDE_v_4,4, PRIDE_v_4,5, PRIDE_v_4,6, PRIDE_v_4,7, PRIDE_v_4,8, PRIDE_v_4,9], [PRIDE_v_5,0, PRIDE_v_5,1, PRIDE_v_5,2, PRIDE_v_5,3, PRIDE_v_5,4, PRIDE_v_5,5, PRIDE_v_5,6, PRIDE_v_5,7, PRIDE_v_5,8, PRIDE_v_5,9]]]\n", + "[[[NOVEL_h_0,0, NOVEL_h_0,1, NOVEL_h_0,2, NOVEL_h_0,3, NOVEL_h_0,4, NOVEL_h_0,5], [NOVEL_h_1,0, NOVEL_h_1,1, NOVEL_h_1,2, NOVEL_h_1,3, NOVEL_h_1,4, NOVEL_h_1,5], [NOVEL_h_2,0, NOVEL_h_2,1, NOVEL_h_2,2, NOVEL_h_2,3, NOVEL_h_2,4, NOVEL_h_2,5], [NOVEL_h_3,0, NOVEL_h_3,1, NOVEL_h_3,2, NOVEL_h_3,3, NOVEL_h_3,4, NOVEL_h_3,5], [NOVEL_h_4,0, NOVEL_h_4,1, NOVEL_h_4,2, NOVEL_h_4,3, NOVEL_h_4,4, NOVEL_h_4,5], [NOVEL_h_5,0, NOVEL_h_5,1, NOVEL_h_5,2, NOVEL_h_5,3, NOVEL_h_5,4, NOVEL_h_5,5], [NOVEL_h_6,0, NOVEL_h_6,1, NOVEL_h_6,2, NOVEL_h_6,3, NOVEL_h_6,4, NOVEL_h_6,5], [NOVEL_h_7,0, NOVEL_h_7,1, NOVEL_h_7,2, NOVEL_h_7,3, NOVEL_h_7,4, NOVEL_h_7,5], [NOVEL_h_8,0, NOVEL_h_8,1, NOVEL_h_8,2, NOVEL_h_8,3, NOVEL_h_8,4, NOVEL_h_8,5], [NOVEL_h_9,0, NOVEL_h_9,1, NOVEL_h_9,2, NOVEL_h_9,3, NOVEL_h_9,4, NOVEL_h_9,5]], [[NOVEL_v_0,0, NOVEL_v_0,1, NOVEL_v_0,2, NOVEL_v_0,3, NOVEL_v_0,4, NOVEL_v_0,5, NOVEL_v_0,6, NOVEL_v_0,7, NOVEL_v_0,8, NOVEL_v_0,9], [NOVEL_v_1,0, NOVEL_v_1,1, NOVEL_v_1,2, NOVEL_v_1,3, NOVEL_v_1,4, NOVEL_v_1,5, NOVEL_v_1,6, NOVEL_v_1,7, NOVEL_v_1,8, NOVEL_v_1,9], [NOVEL_v_2,0, NOVEL_v_2,1, NOVEL_v_2,2, NOVEL_v_2,3, NOVEL_v_2,4, NOVEL_v_2,5, NOVEL_v_2,6, NOVEL_v_2,7, NOVEL_v_2,8, NOVEL_v_2,9], [NOVEL_v_3,0, NOVEL_v_3,1, NOVEL_v_3,2, NOVEL_v_3,3, NOVEL_v_3,4, NOVEL_v_3,5, NOVEL_v_3,6, NOVEL_v_3,7, NOVEL_v_3,8, NOVEL_v_3,9], [NOVEL_v_4,0, NOVEL_v_4,1, NOVEL_v_4,2, NOVEL_v_4,3, NOVEL_v_4,4, NOVEL_v_4,5, NOVEL_v_4,6, NOVEL_v_4,7, NOVEL_v_4,8, NOVEL_v_4,9], [NOVEL_v_5,0, NOVEL_v_5,1, NOVEL_v_5,2, NOVEL_v_5,3, NOVEL_v_5,4, NOVEL_v_5,5, NOVEL_v_5,6, NOVEL_v_5,7, NOVEL_v_5,8, NOVEL_v_5,9]]]\n", + "[[[DARCY_h_0,0, DARCY_h_0,1, DARCY_h_0,2, DARCY_h_0,3, DARCY_h_0,4, DARCY_h_0,5], [DARCY_h_1,0, DARCY_h_1,1, DARCY_h_1,2, DARCY_h_1,3, DARCY_h_1,4, DARCY_h_1,5], [DARCY_h_2,0, DARCY_h_2,1, DARCY_h_2,2, DARCY_h_2,3, DARCY_h_2,4, DARCY_h_2,5], [DARCY_h_3,0, DARCY_h_3,1, DARCY_h_3,2, DARCY_h_3,3, DARCY_h_3,4, DARCY_h_3,5], [DARCY_h_4,0, DARCY_h_4,1, DARCY_h_4,2, DARCY_h_4,3, DARCY_h_4,4, DARCY_h_4,5], [DARCY_h_5,0, DARCY_h_5,1, DARCY_h_5,2, DARCY_h_5,3, DARCY_h_5,4, DARCY_h_5,5], [DARCY_h_6,0, DARCY_h_6,1, DARCY_h_6,2, DARCY_h_6,3, DARCY_h_6,4, DARCY_h_6,5], [DARCY_h_7,0, DARCY_h_7,1, DARCY_h_7,2, DARCY_h_7,3, DARCY_h_7,4, DARCY_h_7,5], [DARCY_h_8,0, DARCY_h_8,1, DARCY_h_8,2, DARCY_h_8,3, DARCY_h_8,4, DARCY_h_8,5], [DARCY_h_9,0, DARCY_h_9,1, DARCY_h_9,2, DARCY_h_9,3, DARCY_h_9,4, DARCY_h_9,5]], [[DARCY_v_0,0, DARCY_v_0,1, DARCY_v_0,2, DARCY_v_0,3, DARCY_v_0,4, DARCY_v_0,5, DARCY_v_0,6, DARCY_v_0,7, DARCY_v_0,8, DARCY_v_0,9], [DARCY_v_1,0, DARCY_v_1,1, DARCY_v_1,2, DARCY_v_1,3, DARCY_v_1,4, DARCY_v_1,5, DARCY_v_1,6, DARCY_v_1,7, DARCY_v_1,8, DARCY_v_1,9], [DARCY_v_2,0, DARCY_v_2,1, DARCY_v_2,2, DARCY_v_2,3, DARCY_v_2,4, DARCY_v_2,5, DARCY_v_2,6, DARCY_v_2,7, DARCY_v_2,8, DARCY_v_2,9], [DARCY_v_3,0, DARCY_v_3,1, DARCY_v_3,2, DARCY_v_3,3, DARCY_v_3,4, DARCY_v_3,5, DARCY_v_3,6, DARCY_v_3,7, DARCY_v_3,8, DARCY_v_3,9], [DARCY_v_4,0, DARCY_v_4,1, DARCY_v_4,2, DARCY_v_4,3, DARCY_v_4,4, DARCY_v_4,5, DARCY_v_4,6, DARCY_v_4,7, DARCY_v_4,8, DARCY_v_4,9], [DARCY_v_5,0, DARCY_v_5,1, DARCY_v_5,2, DARCY_v_5,3, DARCY_v_5,4, DARCY_v_5,5, DARCY_v_5,6, DARCY_v_5,7, DARCY_v_5,8, DARCY_v_5,9]]]\n", + "[[[SENSE_h_0,0, SENSE_h_0,1, SENSE_h_0,2, SENSE_h_0,3, SENSE_h_0,4, SENSE_h_0,5], [SENSE_h_1,0, SENSE_h_1,1, SENSE_h_1,2, SENSE_h_1,3, SENSE_h_1,4, SENSE_h_1,5], [SENSE_h_2,0, SENSE_h_2,1, SENSE_h_2,2, SENSE_h_2,3, SENSE_h_2,4, SENSE_h_2,5], [SENSE_h_3,0, SENSE_h_3,1, SENSE_h_3,2, SENSE_h_3,3, SENSE_h_3,4, SENSE_h_3,5], [SENSE_h_4,0, SENSE_h_4,1, SENSE_h_4,2, SENSE_h_4,3, SENSE_h_4,4, SENSE_h_4,5], [SENSE_h_5,0, SENSE_h_5,1, SENSE_h_5,2, SENSE_h_5,3, SENSE_h_5,4, SENSE_h_5,5], [SENSE_h_6,0, SENSE_h_6,1, SENSE_h_6,2, SENSE_h_6,3, SENSE_h_6,4, SENSE_h_6,5], [SENSE_h_7,0, SENSE_h_7,1, SENSE_h_7,2, SENSE_h_7,3, SENSE_h_7,4, SENSE_h_7,5], [SENSE_h_8,0, SENSE_h_8,1, SENSE_h_8,2, SENSE_h_8,3, SENSE_h_8,4, SENSE_h_8,5], [SENSE_h_9,0, SENSE_h_9,1, SENSE_h_9,2, SENSE_h_9,3, SENSE_h_9,4, SENSE_h_9,5]], [[SENSE_v_0,0, SENSE_v_0,1, SENSE_v_0,2, SENSE_v_0,3, SENSE_v_0,4, SENSE_v_0,5, SENSE_v_0,6, SENSE_v_0,7, SENSE_v_0,8, SENSE_v_0,9], [SENSE_v_1,0, SENSE_v_1,1, SENSE_v_1,2, SENSE_v_1,3, SENSE_v_1,4, SENSE_v_1,5, SENSE_v_1,6, SENSE_v_1,7, SENSE_v_1,8, SENSE_v_1,9], [SENSE_v_2,0, SENSE_v_2,1, SENSE_v_2,2, SENSE_v_2,3, SENSE_v_2,4, SENSE_v_2,5, SENSE_v_2,6, SENSE_v_2,7, SENSE_v_2,8, SENSE_v_2,9], [SENSE_v_3,0, SENSE_v_3,1, SENSE_v_3,2, SENSE_v_3,3, SENSE_v_3,4, SENSE_v_3,5, SENSE_v_3,6, SENSE_v_3,7, SENSE_v_3,8, SENSE_v_3,9], [SENSE_v_4,0, SENSE_v_4,1, SENSE_v_4,2, SENSE_v_4,3, SENSE_v_4,4, SENSE_v_4,5, SENSE_v_4,6, SENSE_v_4,7, SENSE_v_4,8, SENSE_v_4,9], [SENSE_v_5,0, SENSE_v_5,1, SENSE_v_5,2, SENSE_v_5,3, SENSE_v_5,4, SENSE_v_5,5, SENSE_v_5,6, SENSE_v_5,7, SENSE_v_5,8, SENSE_v_5,9]]]\n", + "[[[EMMA_h_0,0, EMMA_h_0,1, EMMA_h_0,2, EMMA_h_0,3, EMMA_h_0,4, EMMA_h_0,5, EMMA_h_0,6], [EMMA_h_1,0, EMMA_h_1,1, EMMA_h_1,2, EMMA_h_1,3, EMMA_h_1,4, EMMA_h_1,5, EMMA_h_1,6], [EMMA_h_2,0, EMMA_h_2,1, EMMA_h_2,2, EMMA_h_2,3, EMMA_h_2,4, EMMA_h_2,5, EMMA_h_2,6], [EMMA_h_3,0, EMMA_h_3,1, EMMA_h_3,2, EMMA_h_3,3, EMMA_h_3,4, EMMA_h_3,5, EMMA_h_3,6], [EMMA_h_4,0, EMMA_h_4,1, EMMA_h_4,2, EMMA_h_4,3, EMMA_h_4,4, EMMA_h_4,5, EMMA_h_4,6], [EMMA_h_5,0, EMMA_h_5,1, EMMA_h_5,2, EMMA_h_5,3, EMMA_h_5,4, EMMA_h_5,5, EMMA_h_5,6], [EMMA_h_6,0, EMMA_h_6,1, EMMA_h_6,2, EMMA_h_6,3, EMMA_h_6,4, EMMA_h_6,5, EMMA_h_6,6], [EMMA_h_7,0, EMMA_h_7,1, EMMA_h_7,2, EMMA_h_7,3, EMMA_h_7,4, EMMA_h_7,5, EMMA_h_7,6], [EMMA_h_8,0, EMMA_h_8,1, EMMA_h_8,2, EMMA_h_8,3, EMMA_h_8,4, EMMA_h_8,5, EMMA_h_8,6], [EMMA_h_9,0, EMMA_h_9,1, EMMA_h_9,2, EMMA_h_9,3, EMMA_h_9,4, EMMA_h_9,5, EMMA_h_9,6]], [[EMMA_v_0,0, EMMA_v_0,1, EMMA_v_0,2, EMMA_v_0,3, EMMA_v_0,4, EMMA_v_0,5, EMMA_v_0,6, EMMA_v_0,7, EMMA_v_0,8, EMMA_v_0,9], [EMMA_v_1,0, EMMA_v_1,1, EMMA_v_1,2, EMMA_v_1,3, EMMA_v_1,4, EMMA_v_1,5, EMMA_v_1,6, EMMA_v_1,7, EMMA_v_1,8, EMMA_v_1,9], [EMMA_v_2,0, EMMA_v_2,1, EMMA_v_2,2, EMMA_v_2,3, EMMA_v_2,4, EMMA_v_2,5, EMMA_v_2,6, EMMA_v_2,7, EMMA_v_2,8, EMMA_v_2,9], [EMMA_v_3,0, EMMA_v_3,1, EMMA_v_3,2, EMMA_v_3,3, EMMA_v_3,4, EMMA_v_3,5, EMMA_v_3,6, EMMA_v_3,7, EMMA_v_3,8, EMMA_v_3,9], [EMMA_v_4,0, EMMA_v_4,1, EMMA_v_4,2, EMMA_v_4,3, EMMA_v_4,4, EMMA_v_4,5, EMMA_v_4,6, EMMA_v_4,7, EMMA_v_4,8, EMMA_v_4,9], [EMMA_v_5,0, EMMA_v_5,1, EMMA_v_5,2, EMMA_v_5,3, EMMA_v_5,4, EMMA_v_5,5, EMMA_v_5,6, EMMA_v_5,7, EMMA_v_5,8, EMMA_v_5,9], [EMMA_v_6,0, EMMA_v_6,1, EMMA_v_6,2, EMMA_v_6,3, EMMA_v_6,4, EMMA_v_6,5, EMMA_v_6,6, EMMA_v_6,7, EMMA_v_6,8, EMMA_v_6,9]]]\n", + "[[[ESTATE_h_0,0, ESTATE_h_0,1, ESTATE_h_0,2, ESTATE_h_0,3, ESTATE_h_0,4], [ESTATE_h_1,0, ESTATE_h_1,1, ESTATE_h_1,2, ESTATE_h_1,3, ESTATE_h_1,4], [ESTATE_h_2,0, ESTATE_h_2,1, ESTATE_h_2,2, ESTATE_h_2,3, ESTATE_h_2,4], [ESTATE_h_3,0, ESTATE_h_3,1, ESTATE_h_3,2, ESTATE_h_3,3, ESTATE_h_3,4], [ESTATE_h_4,0, ESTATE_h_4,1, ESTATE_h_4,2, ESTATE_h_4,3, ESTATE_h_4,4], [ESTATE_h_5,0, ESTATE_h_5,1, ESTATE_h_5,2, ESTATE_h_5,3, ESTATE_h_5,4], [ESTATE_h_6,0, ESTATE_h_6,1, ESTATE_h_6,2, ESTATE_h_6,3, ESTATE_h_6,4], [ESTATE_h_7,0, ESTATE_h_7,1, ESTATE_h_7,2, ESTATE_h_7,3, ESTATE_h_7,4], [ESTATE_h_8,0, ESTATE_h_8,1, ESTATE_h_8,2, ESTATE_h_8,3, ESTATE_h_8,4], [ESTATE_h_9,0, ESTATE_h_9,1, ESTATE_h_9,2, ESTATE_h_9,3, ESTATE_h_9,4]], [[ESTATE_v_0,0, ESTATE_v_0,1, ESTATE_v_0,2, ESTATE_v_0,3, ESTATE_v_0,4, ESTATE_v_0,5, ESTATE_v_0,6, ESTATE_v_0,7, ESTATE_v_0,8, ESTATE_v_0,9], [ESTATE_v_1,0, ESTATE_v_1,1, ESTATE_v_1,2, ESTATE_v_1,3, ESTATE_v_1,4, ESTATE_v_1,5, ESTATE_v_1,6, ESTATE_v_1,7, ESTATE_v_1,8, ESTATE_v_1,9], [ESTATE_v_2,0, ESTATE_v_2,1, ESTATE_v_2,2, ESTATE_v_2,3, ESTATE_v_2,4, ESTATE_v_2,5, ESTATE_v_2,6, ESTATE_v_2,7, ESTATE_v_2,8, ESTATE_v_2,9], [ESTATE_v_3,0, ESTATE_v_3,1, ESTATE_v_3,2, ESTATE_v_3,3, ESTATE_v_3,4, ESTATE_v_3,5, ESTATE_v_3,6, ESTATE_v_3,7, ESTATE_v_3,8, ESTATE_v_3,9], [ESTATE_v_4,0, ESTATE_v_4,1, ESTATE_v_4,2, ESTATE_v_4,3, ESTATE_v_4,4, ESTATE_v_4,5, ESTATE_v_4,6, ESTATE_v_4,7, ESTATE_v_4,8, ESTATE_v_4,9]]]\n", + "[[[BENNET_h_0,0, BENNET_h_0,1, BENNET_h_0,2, BENNET_h_0,3, BENNET_h_0,4], [BENNET_h_1,0, BENNET_h_1,1, BENNET_h_1,2, BENNET_h_1,3, BENNET_h_1,4], [BENNET_h_2,0, BENNET_h_2,1, BENNET_h_2,2, BENNET_h_2,3, BENNET_h_2,4], [BENNET_h_3,0, BENNET_h_3,1, BENNET_h_3,2, BENNET_h_3,3, BENNET_h_3,4], [BENNET_h_4,0, BENNET_h_4,1, BENNET_h_4,2, BENNET_h_4,3, BENNET_h_4,4], [BENNET_h_5,0, BENNET_h_5,1, BENNET_h_5,2, BENNET_h_5,3, BENNET_h_5,4], [BENNET_h_6,0, BENNET_h_6,1, BENNET_h_6,2, BENNET_h_6,3, BENNET_h_6,4], [BENNET_h_7,0, BENNET_h_7,1, BENNET_h_7,2, BENNET_h_7,3, BENNET_h_7,4], [BENNET_h_8,0, BENNET_h_8,1, BENNET_h_8,2, BENNET_h_8,3, BENNET_h_8,4], [BENNET_h_9,0, BENNET_h_9,1, BENNET_h_9,2, BENNET_h_9,3, BENNET_h_9,4]], [[BENNET_v_0,0, BENNET_v_0,1, BENNET_v_0,2, BENNET_v_0,3, BENNET_v_0,4, BENNET_v_0,5, BENNET_v_0,6, BENNET_v_0,7, BENNET_v_0,8, BENNET_v_0,9], [BENNET_v_1,0, BENNET_v_1,1, BENNET_v_1,2, BENNET_v_1,3, BENNET_v_1,4, BENNET_v_1,5, BENNET_v_1,6, BENNET_v_1,7, BENNET_v_1,8, BENNET_v_1,9], [BENNET_v_2,0, BENNET_v_2,1, BENNET_v_2,2, BENNET_v_2,3, BENNET_v_2,4, BENNET_v_2,5, BENNET_v_2,6, BENNET_v_2,7, BENNET_v_2,8, BENNET_v_2,9], [BENNET_v_3,0, BENNET_v_3,1, BENNET_v_3,2, BENNET_v_3,3, BENNET_v_3,4, BENNET_v_3,5, BENNET_v_3,6, BENNET_v_3,7, BENNET_v_3,8, BENNET_v_3,9], [BENNET_v_4,0, BENNET_v_4,1, BENNET_v_4,2, BENNET_v_4,3, BENNET_v_4,4, BENNET_v_4,5, BENNET_v_4,6, BENNET_v_4,7, BENNET_v_4,8, BENNET_v_4,9]]]\n", + "[[[BATH_h_0,0, BATH_h_0,1, BATH_h_0,2, BATH_h_0,3, BATH_h_0,4, BATH_h_0,5, BATH_h_0,6], [BATH_h_1,0, BATH_h_1,1, BATH_h_1,2, BATH_h_1,3, BATH_h_1,4, BATH_h_1,5, BATH_h_1,6], [BATH_h_2,0, BATH_h_2,1, BATH_h_2,2, BATH_h_2,3, BATH_h_2,4, BATH_h_2,5, BATH_h_2,6], [BATH_h_3,0, BATH_h_3,1, BATH_h_3,2, BATH_h_3,3, BATH_h_3,4, BATH_h_3,5, BATH_h_3,6], [BATH_h_4,0, BATH_h_4,1, BATH_h_4,2, BATH_h_4,3, BATH_h_4,4, BATH_h_4,5, BATH_h_4,6], [BATH_h_5,0, BATH_h_5,1, BATH_h_5,2, BATH_h_5,3, BATH_h_5,4, BATH_h_5,5, BATH_h_5,6], [BATH_h_6,0, BATH_h_6,1, BATH_h_6,2, BATH_h_6,3, BATH_h_6,4, BATH_h_6,5, BATH_h_6,6], [BATH_h_7,0, BATH_h_7,1, BATH_h_7,2, BATH_h_7,3, BATH_h_7,4, BATH_h_7,5, BATH_h_7,6], [BATH_h_8,0, BATH_h_8,1, BATH_h_8,2, BATH_h_8,3, BATH_h_8,4, BATH_h_8,5, BATH_h_8,6], [BATH_h_9,0, BATH_h_9,1, BATH_h_9,2, BATH_h_9,3, BATH_h_9,4, BATH_h_9,5, BATH_h_9,6]], [[BATH_v_0,0, BATH_v_0,1, BATH_v_0,2, BATH_v_0,3, BATH_v_0,4, BATH_v_0,5, BATH_v_0,6, BATH_v_0,7, BATH_v_0,8, BATH_v_0,9], [BATH_v_1,0, BATH_v_1,1, BATH_v_1,2, BATH_v_1,3, BATH_v_1,4, BATH_v_1,5, BATH_v_1,6, BATH_v_1,7, BATH_v_1,8, BATH_v_1,9], [BATH_v_2,0, BATH_v_2,1, BATH_v_2,2, BATH_v_2,3, BATH_v_2,4, BATH_v_2,5, BATH_v_2,6, BATH_v_2,7, BATH_v_2,8, BATH_v_2,9], [BATH_v_3,0, BATH_v_3,1, BATH_v_3,2, BATH_v_3,3, BATH_v_3,4, BATH_v_3,5, BATH_v_3,6, BATH_v_3,7, BATH_v_3,8, BATH_v_3,9], [BATH_v_4,0, BATH_v_4,1, BATH_v_4,2, BATH_v_4,3, BATH_v_4,4, BATH_v_4,5, BATH_v_4,6, BATH_v_4,7, BATH_v_4,8, BATH_v_4,9], [BATH_v_5,0, BATH_v_5,1, BATH_v_5,2, BATH_v_5,3, BATH_v_5,4, BATH_v_5,5, BATH_v_5,6, BATH_v_5,7, BATH_v_5,8, BATH_v_5,9], [BATH_v_6,0, BATH_v_6,1, BATH_v_6,2, BATH_v_6,3, BATH_v_6,4, BATH_v_6,5, BATH_v_6,6, BATH_v_6,7, BATH_v_6,8, BATH_v_6,9]]]\n", + "Executed in 0.5563 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "If you've done this correctly, it should be working better, but we now have the problem that words do not all connect to each other. Hence, we have to add a final constraint that all of the letters are connected.\n", + "\n", + "First we form an $N\\times N$ adjacency matrix which the value is true if word $i$ intersects with word $j$. Then we use the 'is_fully_connected' construction that we developed in the notebook on SAT constructions." + ], + "metadata": { + "id": "R69urJyN9wlc" + } + }, + { + "cell_type": "code", + "source": [ + "def is_fully_connected(s, adjacency):\n", + " # Size of the adjacency matrix\n", + " n_components = len(adjacency)\n", + " # We'll construct a N x N x log[N] array of variables\n", + " # The NxN variables in the first layer represent A, the variables in the second layer represent B and so on\n", + " n_layers = math.ceil(math.log(n_components,2))+1\n", + " connected = [[[ z3.Bool(\"conn_{%d,%d,%d}\"%((i,j,n))) for n in range(0, n_layers)] for j in range(0, n_components) ] for i in range(0, n_components) ]\n", + "\n", + " # Constraint 1\n", + " # The value in the top layer of the connected structure is equal to the adjacency matrix\n", + " for i in range(n_components):\n", + " for j in range(n_components):\n", + " s.add(connected[i][j][0]==adjacency[i][j])\n", + "\n", + " # Constraint 2\n", + " # Value at position [i,j] in layer n is value at position [i,j] of the binary matrix product of layer n-1 with itself\n", + " for n in range(1,n_layers):\n", + " for i in range(n_components):\n", + " for j in range(n_components):\n", + " matrix_entry_ij = False\n", + " for k in range(n_components):\n", + " matrix_entry_ij = Or(matrix_entry_ij, And(connected[i][k][n-1],connected[k][j][n-1]))\n", + " s.add(connected[i][j][n]==matrix_entry_ij)\n", + "\n", + " # Constraint 3 -- any row of column of the matrix should be full of ones at the end (everything is connected)\n", + " for i in range(n_components):\n", + " s.add(connected[i][0][n_layers-1])\n", + "\n", + " return s" + ], + "metadata": { + "id": "2uPPXENwLugr" + }, + "execution_count": 28, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Helper routine that returns true if the current word is at position (i,j) in the grid\n", + "def word_at_position_ij(i,j, placement_vars, word, grid_size):\n", + "\n", + " relevant_placements = [] ;\n", + " # Deal with horizontal words first\n", + " for horz_pos in range(np.max([0, j-len(word)+1]), np.min([j+1, grid_size-len(word)+1])):\n", + " # First the horizontal words\n", + " relevant_placements.append(placement_vars[word][0][i][horz_pos])\n", + " # Then the vertical words\n", + " for vert_pos in range(np.max([0, i-len(word)+1]), np.min([i+1, grid_size-len(word)+1])):\n", + " relevant_placements.append(placement_vars[word][1][vert_pos][j])\n", + "\n", + " return Or(relevant_placements) ;" + ], + "metadata": { + "id": "FhJPmmAOV3AS" + }, + "execution_count": 29, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def add_constraint_set4(s, placement_vars, letter_posns, words, grid_size):\n", + " # First lets create a new variable that represents the adjacency matrix of the words\n", + " adjacency = [[ z3.Bool(\"adj_{%d,%d}\"%((i,j))) for j in range(0, len(words)) ] for i in range(0, len(words)) ]\n", + "\n", + " # Run through each word\n", + " for c_w1 in range(len(words)):\n", + " for c_w2 in range(c_w1, len(words)):\n", + " # If word indices are the same (i.e., c_w1=c_w2) then adjacency at c_w1,c_w2 is true\n", + " # TODO -- replace this line\n", + " s.add(adjacency[0][0])\n", + "\n", + " word1 = words[c_w1]\n", + " word2 = words[c_w2]\n", + " # TODO determine if word1 and word2 intersect. You can use the routine \"word_at_position_ij\" above\n", + " # Replace this line\n", + " words_intersect = True\n", + "\n", + " # Set value and symmetric value of adjacency matrix\n", + " s.add(adjacency[c_w1][c_w2] == words_intersect)\n", + " s.add(adjacency[c_w2][c_w1] == adjacency[c_w1][c_w2])\n", + "\n", + " # Add the constraint that the adjacency matrix must be fully connected\n", + " s = is_fully_connected(s, adjacency)\n", + " return s" + ], + "metadata": { + "id": "Xmzv8g_-RIid" + }, + "execution_count": 32, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Let's see how this improves things (took 32 seconds to run for me, but might be longer)\n", + "solve_crossword(words, 11, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "M7pPhYTfaLEd", + "outputId": "dbe3c395-a935-45a3-8c2d-d615e6b4ed39" + }, + "execution_count": 33, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[[[JANE_h_0,0, JANE_h_0,1, JANE_h_0,2, JANE_h_0,3, JANE_h_0,4, JANE_h_0,5, JANE_h_0,6, JANE_h_0,7], [JANE_h_1,0, JANE_h_1,1, JANE_h_1,2, JANE_h_1,3, JANE_h_1,4, JANE_h_1,5, JANE_h_1,6, JANE_h_1,7], [JANE_h_2,0, JANE_h_2,1, JANE_h_2,2, JANE_h_2,3, JANE_h_2,4, JANE_h_2,5, JANE_h_2,6, JANE_h_2,7], [JANE_h_3,0, JANE_h_3,1, JANE_h_3,2, JANE_h_3,3, JANE_h_3,4, JANE_h_3,5, JANE_h_3,6, JANE_h_3,7], [JANE_h_4,0, JANE_h_4,1, JANE_h_4,2, JANE_h_4,3, JANE_h_4,4, JANE_h_4,5, JANE_h_4,6, JANE_h_4,7], [JANE_h_5,0, JANE_h_5,1, JANE_h_5,2, JANE_h_5,3, JANE_h_5,4, JANE_h_5,5, JANE_h_5,6, JANE_h_5,7], [JANE_h_6,0, JANE_h_6,1, JANE_h_6,2, JANE_h_6,3, JANE_h_6,4, JANE_h_6,5, JANE_h_6,6, JANE_h_6,7], [JANE_h_7,0, JANE_h_7,1, JANE_h_7,2, JANE_h_7,3, JANE_h_7,4, JANE_h_7,5, JANE_h_7,6, JANE_h_7,7], [JANE_h_8,0, JANE_h_8,1, JANE_h_8,2, JANE_h_8,3, JANE_h_8,4, JANE_h_8,5, JANE_h_8,6, JANE_h_8,7], [JANE_h_9,0, JANE_h_9,1, JANE_h_9,2, JANE_h_9,3, JANE_h_9,4, JANE_h_9,5, JANE_h_9,6, JANE_h_9,7], [JANE_h_10,0, JANE_h_10,1, JANE_h_10,2, JANE_h_10,3, JANE_h_10,4, JANE_h_10,5, JANE_h_10,6, JANE_h_10,7]], [[JANE_v_0,0, JANE_v_0,1, JANE_v_0,2, JANE_v_0,3, JANE_v_0,4, JANE_v_0,5, JANE_v_0,6, JANE_v_0,7, JANE_v_0,8, JANE_v_0,9, JANE_v_0,10], [JANE_v_1,0, JANE_v_1,1, JANE_v_1,2, JANE_v_1,3, JANE_v_1,4, JANE_v_1,5, JANE_v_1,6, JANE_v_1,7, JANE_v_1,8, JANE_v_1,9, JANE_v_1,10], [JANE_v_2,0, JANE_v_2,1, JANE_v_2,2, JANE_v_2,3, JANE_v_2,4, JANE_v_2,5, JANE_v_2,6, JANE_v_2,7, JANE_v_2,8, JANE_v_2,9, JANE_v_2,10], [JANE_v_3,0, JANE_v_3,1, JANE_v_3,2, JANE_v_3,3, JANE_v_3,4, JANE_v_3,5, JANE_v_3,6, JANE_v_3,7, JANE_v_3,8, JANE_v_3,9, JANE_v_3,10], [JANE_v_4,0, JANE_v_4,1, JANE_v_4,2, JANE_v_4,3, JANE_v_4,4, JANE_v_4,5, JANE_v_4,6, JANE_v_4,7, JANE_v_4,8, JANE_v_4,9, JANE_v_4,10], [JANE_v_5,0, JANE_v_5,1, JANE_v_5,2, JANE_v_5,3, JANE_v_5,4, JANE_v_5,5, JANE_v_5,6, JANE_v_5,7, JANE_v_5,8, JANE_v_5,9, JANE_v_5,10], [JANE_v_6,0, JANE_v_6,1, JANE_v_6,2, JANE_v_6,3, JANE_v_6,4, JANE_v_6,5, JANE_v_6,6, JANE_v_6,7, JANE_v_6,8, JANE_v_6,9, JANE_v_6,10], [JANE_v_7,0, JANE_v_7,1, JANE_v_7,2, JANE_v_7,3, JANE_v_7,4, JANE_v_7,5, JANE_v_7,6, JANE_v_7,7, JANE_v_7,8, JANE_v_7,9, JANE_v_7,10]]]\n", + "[[[AUSTEN_h_0,0, AUSTEN_h_0,1, AUSTEN_h_0,2, AUSTEN_h_0,3, AUSTEN_h_0,4, AUSTEN_h_0,5], [AUSTEN_h_1,0, AUSTEN_h_1,1, AUSTEN_h_1,2, AUSTEN_h_1,3, AUSTEN_h_1,4, AUSTEN_h_1,5], [AUSTEN_h_2,0, AUSTEN_h_2,1, AUSTEN_h_2,2, AUSTEN_h_2,3, AUSTEN_h_2,4, AUSTEN_h_2,5], [AUSTEN_h_3,0, AUSTEN_h_3,1, AUSTEN_h_3,2, AUSTEN_h_3,3, AUSTEN_h_3,4, AUSTEN_h_3,5], [AUSTEN_h_4,0, AUSTEN_h_4,1, AUSTEN_h_4,2, AUSTEN_h_4,3, AUSTEN_h_4,4, AUSTEN_h_4,5], [AUSTEN_h_5,0, AUSTEN_h_5,1, AUSTEN_h_5,2, AUSTEN_h_5,3, AUSTEN_h_5,4, AUSTEN_h_5,5], [AUSTEN_h_6,0, AUSTEN_h_6,1, AUSTEN_h_6,2, AUSTEN_h_6,3, AUSTEN_h_6,4, AUSTEN_h_6,5], [AUSTEN_h_7,0, AUSTEN_h_7,1, AUSTEN_h_7,2, AUSTEN_h_7,3, AUSTEN_h_7,4, AUSTEN_h_7,5], [AUSTEN_h_8,0, AUSTEN_h_8,1, AUSTEN_h_8,2, AUSTEN_h_8,3, AUSTEN_h_8,4, AUSTEN_h_8,5], [AUSTEN_h_9,0, AUSTEN_h_9,1, AUSTEN_h_9,2, AUSTEN_h_9,3, AUSTEN_h_9,4, AUSTEN_h_9,5], [AUSTEN_h_10,0, AUSTEN_h_10,1, AUSTEN_h_10,2, AUSTEN_h_10,3, AUSTEN_h_10,4, AUSTEN_h_10,5]], [[AUSTEN_v_0,0, AUSTEN_v_0,1, AUSTEN_v_0,2, AUSTEN_v_0,3, AUSTEN_v_0,4, AUSTEN_v_0,5, AUSTEN_v_0,6, AUSTEN_v_0,7, AUSTEN_v_0,8, AUSTEN_v_0,9, AUSTEN_v_0,10], [AUSTEN_v_1,0, AUSTEN_v_1,1, AUSTEN_v_1,2, AUSTEN_v_1,3, AUSTEN_v_1,4, AUSTEN_v_1,5, AUSTEN_v_1,6, AUSTEN_v_1,7, AUSTEN_v_1,8, AUSTEN_v_1,9, AUSTEN_v_1,10], [AUSTEN_v_2,0, AUSTEN_v_2,1, AUSTEN_v_2,2, AUSTEN_v_2,3, AUSTEN_v_2,4, AUSTEN_v_2,5, AUSTEN_v_2,6, AUSTEN_v_2,7, AUSTEN_v_2,8, AUSTEN_v_2,9, AUSTEN_v_2,10], [AUSTEN_v_3,0, AUSTEN_v_3,1, AUSTEN_v_3,2, AUSTEN_v_3,3, AUSTEN_v_3,4, AUSTEN_v_3,5, AUSTEN_v_3,6, AUSTEN_v_3,7, AUSTEN_v_3,8, AUSTEN_v_3,9, AUSTEN_v_3,10], [AUSTEN_v_4,0, AUSTEN_v_4,1, AUSTEN_v_4,2, AUSTEN_v_4,3, AUSTEN_v_4,4, AUSTEN_v_4,5, AUSTEN_v_4,6, AUSTEN_v_4,7, AUSTEN_v_4,8, AUSTEN_v_4,9, AUSTEN_v_4,10], [AUSTEN_v_5,0, AUSTEN_v_5,1, AUSTEN_v_5,2, AUSTEN_v_5,3, AUSTEN_v_5,4, AUSTEN_v_5,5, AUSTEN_v_5,6, AUSTEN_v_5,7, AUSTEN_v_5,8, AUSTEN_v_5,9, AUSTEN_v_5,10]]]\n", + "[[[PRIDE_h_0,0, PRIDE_h_0,1, PRIDE_h_0,2, PRIDE_h_0,3, PRIDE_h_0,4, PRIDE_h_0,5, PRIDE_h_0,6], [PRIDE_h_1,0, PRIDE_h_1,1, PRIDE_h_1,2, PRIDE_h_1,3, PRIDE_h_1,4, PRIDE_h_1,5, PRIDE_h_1,6], [PRIDE_h_2,0, PRIDE_h_2,1, PRIDE_h_2,2, PRIDE_h_2,3, PRIDE_h_2,4, PRIDE_h_2,5, PRIDE_h_2,6], [PRIDE_h_3,0, PRIDE_h_3,1, PRIDE_h_3,2, PRIDE_h_3,3, PRIDE_h_3,4, PRIDE_h_3,5, PRIDE_h_3,6], [PRIDE_h_4,0, PRIDE_h_4,1, PRIDE_h_4,2, PRIDE_h_4,3, PRIDE_h_4,4, PRIDE_h_4,5, PRIDE_h_4,6], [PRIDE_h_5,0, PRIDE_h_5,1, PRIDE_h_5,2, PRIDE_h_5,3, PRIDE_h_5,4, PRIDE_h_5,5, PRIDE_h_5,6], [PRIDE_h_6,0, PRIDE_h_6,1, PRIDE_h_6,2, PRIDE_h_6,3, PRIDE_h_6,4, PRIDE_h_6,5, PRIDE_h_6,6], [PRIDE_h_7,0, PRIDE_h_7,1, PRIDE_h_7,2, PRIDE_h_7,3, PRIDE_h_7,4, PRIDE_h_7,5, PRIDE_h_7,6], [PRIDE_h_8,0, PRIDE_h_8,1, PRIDE_h_8,2, PRIDE_h_8,3, PRIDE_h_8,4, PRIDE_h_8,5, PRIDE_h_8,6], [PRIDE_h_9,0, PRIDE_h_9,1, PRIDE_h_9,2, PRIDE_h_9,3, PRIDE_h_9,4, PRIDE_h_9,5, PRIDE_h_9,6], [PRIDE_h_10,0, PRIDE_h_10,1, PRIDE_h_10,2, PRIDE_h_10,3, PRIDE_h_10,4, PRIDE_h_10,5, PRIDE_h_10,6]], [[PRIDE_v_0,0, PRIDE_v_0,1, PRIDE_v_0,2, PRIDE_v_0,3, PRIDE_v_0,4, PRIDE_v_0,5, PRIDE_v_0,6, PRIDE_v_0,7, PRIDE_v_0,8, PRIDE_v_0,9, PRIDE_v_0,10], [PRIDE_v_1,0, PRIDE_v_1,1, PRIDE_v_1,2, PRIDE_v_1,3, PRIDE_v_1,4, PRIDE_v_1,5, PRIDE_v_1,6, PRIDE_v_1,7, PRIDE_v_1,8, PRIDE_v_1,9, PRIDE_v_1,10], [PRIDE_v_2,0, PRIDE_v_2,1, PRIDE_v_2,2, PRIDE_v_2,3, PRIDE_v_2,4, PRIDE_v_2,5, PRIDE_v_2,6, PRIDE_v_2,7, PRIDE_v_2,8, PRIDE_v_2,9, PRIDE_v_2,10], [PRIDE_v_3,0, PRIDE_v_3,1, PRIDE_v_3,2, PRIDE_v_3,3, PRIDE_v_3,4, PRIDE_v_3,5, PRIDE_v_3,6, PRIDE_v_3,7, PRIDE_v_3,8, PRIDE_v_3,9, PRIDE_v_3,10], [PRIDE_v_4,0, PRIDE_v_4,1, PRIDE_v_4,2, PRIDE_v_4,3, PRIDE_v_4,4, PRIDE_v_4,5, PRIDE_v_4,6, PRIDE_v_4,7, PRIDE_v_4,8, PRIDE_v_4,9, PRIDE_v_4,10], [PRIDE_v_5,0, PRIDE_v_5,1, PRIDE_v_5,2, PRIDE_v_5,3, PRIDE_v_5,4, PRIDE_v_5,5, PRIDE_v_5,6, PRIDE_v_5,7, PRIDE_v_5,8, PRIDE_v_5,9, PRIDE_v_5,10], [PRIDE_v_6,0, PRIDE_v_6,1, PRIDE_v_6,2, PRIDE_v_6,3, PRIDE_v_6,4, PRIDE_v_6,5, PRIDE_v_6,6, PRIDE_v_6,7, PRIDE_v_6,8, PRIDE_v_6,9, PRIDE_v_6,10]]]\n", + "[[[NOVEL_h_0,0, NOVEL_h_0,1, NOVEL_h_0,2, NOVEL_h_0,3, NOVEL_h_0,4, NOVEL_h_0,5, NOVEL_h_0,6], [NOVEL_h_1,0, NOVEL_h_1,1, NOVEL_h_1,2, NOVEL_h_1,3, NOVEL_h_1,4, NOVEL_h_1,5, NOVEL_h_1,6], [NOVEL_h_2,0, NOVEL_h_2,1, NOVEL_h_2,2, NOVEL_h_2,3, NOVEL_h_2,4, NOVEL_h_2,5, NOVEL_h_2,6], [NOVEL_h_3,0, NOVEL_h_3,1, NOVEL_h_3,2, NOVEL_h_3,3, NOVEL_h_3,4, NOVEL_h_3,5, NOVEL_h_3,6], [NOVEL_h_4,0, NOVEL_h_4,1, NOVEL_h_4,2, NOVEL_h_4,3, NOVEL_h_4,4, NOVEL_h_4,5, NOVEL_h_4,6], [NOVEL_h_5,0, NOVEL_h_5,1, NOVEL_h_5,2, NOVEL_h_5,3, NOVEL_h_5,4, NOVEL_h_5,5, NOVEL_h_5,6], [NOVEL_h_6,0, NOVEL_h_6,1, NOVEL_h_6,2, NOVEL_h_6,3, NOVEL_h_6,4, NOVEL_h_6,5, NOVEL_h_6,6], [NOVEL_h_7,0, NOVEL_h_7,1, NOVEL_h_7,2, NOVEL_h_7,3, NOVEL_h_7,4, NOVEL_h_7,5, NOVEL_h_7,6], [NOVEL_h_8,0, NOVEL_h_8,1, NOVEL_h_8,2, NOVEL_h_8,3, NOVEL_h_8,4, NOVEL_h_8,5, NOVEL_h_8,6], [NOVEL_h_9,0, NOVEL_h_9,1, NOVEL_h_9,2, NOVEL_h_9,3, NOVEL_h_9,4, NOVEL_h_9,5, NOVEL_h_9,6], [NOVEL_h_10,0, NOVEL_h_10,1, NOVEL_h_10,2, NOVEL_h_10,3, NOVEL_h_10,4, NOVEL_h_10,5, NOVEL_h_10,6]], [[NOVEL_v_0,0, NOVEL_v_0,1, NOVEL_v_0,2, NOVEL_v_0,3, NOVEL_v_0,4, NOVEL_v_0,5, NOVEL_v_0,6, NOVEL_v_0,7, NOVEL_v_0,8, NOVEL_v_0,9, NOVEL_v_0,10], [NOVEL_v_1,0, NOVEL_v_1,1, NOVEL_v_1,2, NOVEL_v_1,3, NOVEL_v_1,4, NOVEL_v_1,5, NOVEL_v_1,6, NOVEL_v_1,7, NOVEL_v_1,8, NOVEL_v_1,9, NOVEL_v_1,10], [NOVEL_v_2,0, NOVEL_v_2,1, NOVEL_v_2,2, NOVEL_v_2,3, NOVEL_v_2,4, NOVEL_v_2,5, NOVEL_v_2,6, NOVEL_v_2,7, NOVEL_v_2,8, NOVEL_v_2,9, NOVEL_v_2,10], [NOVEL_v_3,0, NOVEL_v_3,1, NOVEL_v_3,2, NOVEL_v_3,3, NOVEL_v_3,4, NOVEL_v_3,5, NOVEL_v_3,6, NOVEL_v_3,7, NOVEL_v_3,8, NOVEL_v_3,9, NOVEL_v_3,10], [NOVEL_v_4,0, NOVEL_v_4,1, NOVEL_v_4,2, NOVEL_v_4,3, NOVEL_v_4,4, NOVEL_v_4,5, NOVEL_v_4,6, NOVEL_v_4,7, NOVEL_v_4,8, NOVEL_v_4,9, NOVEL_v_4,10], [NOVEL_v_5,0, NOVEL_v_5,1, NOVEL_v_5,2, NOVEL_v_5,3, NOVEL_v_5,4, NOVEL_v_5,5, NOVEL_v_5,6, NOVEL_v_5,7, NOVEL_v_5,8, NOVEL_v_5,9, NOVEL_v_5,10], [NOVEL_v_6,0, NOVEL_v_6,1, NOVEL_v_6,2, NOVEL_v_6,3, NOVEL_v_6,4, NOVEL_v_6,5, NOVEL_v_6,6, NOVEL_v_6,7, NOVEL_v_6,8, NOVEL_v_6,9, NOVEL_v_6,10]]]\n", + "[[[DARCY_h_0,0, DARCY_h_0,1, DARCY_h_0,2, DARCY_h_0,3, DARCY_h_0,4, DARCY_h_0,5, DARCY_h_0,6], [DARCY_h_1,0, DARCY_h_1,1, DARCY_h_1,2, DARCY_h_1,3, DARCY_h_1,4, DARCY_h_1,5, DARCY_h_1,6], [DARCY_h_2,0, DARCY_h_2,1, DARCY_h_2,2, DARCY_h_2,3, DARCY_h_2,4, DARCY_h_2,5, DARCY_h_2,6], [DARCY_h_3,0, DARCY_h_3,1, DARCY_h_3,2, DARCY_h_3,3, DARCY_h_3,4, DARCY_h_3,5, DARCY_h_3,6], [DARCY_h_4,0, DARCY_h_4,1, DARCY_h_4,2, DARCY_h_4,3, DARCY_h_4,4, DARCY_h_4,5, DARCY_h_4,6], [DARCY_h_5,0, DARCY_h_5,1, DARCY_h_5,2, DARCY_h_5,3, DARCY_h_5,4, DARCY_h_5,5, DARCY_h_5,6], [DARCY_h_6,0, DARCY_h_6,1, DARCY_h_6,2, DARCY_h_6,3, DARCY_h_6,4, DARCY_h_6,5, DARCY_h_6,6], [DARCY_h_7,0, DARCY_h_7,1, DARCY_h_7,2, DARCY_h_7,3, DARCY_h_7,4, DARCY_h_7,5, DARCY_h_7,6], [DARCY_h_8,0, DARCY_h_8,1, DARCY_h_8,2, DARCY_h_8,3, DARCY_h_8,4, DARCY_h_8,5, DARCY_h_8,6], [DARCY_h_9,0, DARCY_h_9,1, DARCY_h_9,2, DARCY_h_9,3, DARCY_h_9,4, DARCY_h_9,5, DARCY_h_9,6], [DARCY_h_10,0, DARCY_h_10,1, DARCY_h_10,2, DARCY_h_10,3, DARCY_h_10,4, DARCY_h_10,5, DARCY_h_10,6]], [[DARCY_v_0,0, DARCY_v_0,1, DARCY_v_0,2, DARCY_v_0,3, DARCY_v_0,4, DARCY_v_0,5, DARCY_v_0,6, DARCY_v_0,7, DARCY_v_0,8, DARCY_v_0,9, DARCY_v_0,10], [DARCY_v_1,0, DARCY_v_1,1, DARCY_v_1,2, DARCY_v_1,3, DARCY_v_1,4, DARCY_v_1,5, DARCY_v_1,6, DARCY_v_1,7, DARCY_v_1,8, DARCY_v_1,9, DARCY_v_1,10], [DARCY_v_2,0, DARCY_v_2,1, DARCY_v_2,2, DARCY_v_2,3, DARCY_v_2,4, DARCY_v_2,5, DARCY_v_2,6, DARCY_v_2,7, DARCY_v_2,8, DARCY_v_2,9, DARCY_v_2,10], [DARCY_v_3,0, DARCY_v_3,1, DARCY_v_3,2, DARCY_v_3,3, DARCY_v_3,4, DARCY_v_3,5, DARCY_v_3,6, DARCY_v_3,7, DARCY_v_3,8, DARCY_v_3,9, DARCY_v_3,10], [DARCY_v_4,0, DARCY_v_4,1, DARCY_v_4,2, DARCY_v_4,3, DARCY_v_4,4, DARCY_v_4,5, DARCY_v_4,6, DARCY_v_4,7, DARCY_v_4,8, DARCY_v_4,9, DARCY_v_4,10], [DARCY_v_5,0, DARCY_v_5,1, DARCY_v_5,2, DARCY_v_5,3, DARCY_v_5,4, DARCY_v_5,5, DARCY_v_5,6, DARCY_v_5,7, DARCY_v_5,8, DARCY_v_5,9, DARCY_v_5,10], [DARCY_v_6,0, DARCY_v_6,1, DARCY_v_6,2, DARCY_v_6,3, DARCY_v_6,4, DARCY_v_6,5, DARCY_v_6,6, DARCY_v_6,7, DARCY_v_6,8, DARCY_v_6,9, DARCY_v_6,10]]]\n", + "[[[SENSE_h_0,0, SENSE_h_0,1, SENSE_h_0,2, SENSE_h_0,3, SENSE_h_0,4, SENSE_h_0,5, SENSE_h_0,6], [SENSE_h_1,0, SENSE_h_1,1, SENSE_h_1,2, SENSE_h_1,3, SENSE_h_1,4, SENSE_h_1,5, SENSE_h_1,6], [SENSE_h_2,0, SENSE_h_2,1, SENSE_h_2,2, SENSE_h_2,3, SENSE_h_2,4, SENSE_h_2,5, SENSE_h_2,6], [SENSE_h_3,0, SENSE_h_3,1, SENSE_h_3,2, SENSE_h_3,3, SENSE_h_3,4, SENSE_h_3,5, SENSE_h_3,6], [SENSE_h_4,0, SENSE_h_4,1, SENSE_h_4,2, SENSE_h_4,3, SENSE_h_4,4, SENSE_h_4,5, SENSE_h_4,6], [SENSE_h_5,0, SENSE_h_5,1, SENSE_h_5,2, SENSE_h_5,3, SENSE_h_5,4, SENSE_h_5,5, SENSE_h_5,6], [SENSE_h_6,0, SENSE_h_6,1, SENSE_h_6,2, SENSE_h_6,3, SENSE_h_6,4, SENSE_h_6,5, SENSE_h_6,6], [SENSE_h_7,0, SENSE_h_7,1, SENSE_h_7,2, SENSE_h_7,3, SENSE_h_7,4, SENSE_h_7,5, SENSE_h_7,6], [SENSE_h_8,0, SENSE_h_8,1, SENSE_h_8,2, SENSE_h_8,3, SENSE_h_8,4, SENSE_h_8,5, SENSE_h_8,6], [SENSE_h_9,0, SENSE_h_9,1, SENSE_h_9,2, SENSE_h_9,3, SENSE_h_9,4, SENSE_h_9,5, SENSE_h_9,6], [SENSE_h_10,0, SENSE_h_10,1, SENSE_h_10,2, SENSE_h_10,3, SENSE_h_10,4, SENSE_h_10,5, SENSE_h_10,6]], [[SENSE_v_0,0, SENSE_v_0,1, SENSE_v_0,2, SENSE_v_0,3, SENSE_v_0,4, SENSE_v_0,5, SENSE_v_0,6, SENSE_v_0,7, SENSE_v_0,8, SENSE_v_0,9, SENSE_v_0,10], [SENSE_v_1,0, SENSE_v_1,1, SENSE_v_1,2, SENSE_v_1,3, SENSE_v_1,4, SENSE_v_1,5, SENSE_v_1,6, SENSE_v_1,7, SENSE_v_1,8, SENSE_v_1,9, SENSE_v_1,10], [SENSE_v_2,0, SENSE_v_2,1, SENSE_v_2,2, SENSE_v_2,3, SENSE_v_2,4, SENSE_v_2,5, SENSE_v_2,6, SENSE_v_2,7, SENSE_v_2,8, SENSE_v_2,9, SENSE_v_2,10], [SENSE_v_3,0, SENSE_v_3,1, SENSE_v_3,2, SENSE_v_3,3, SENSE_v_3,4, SENSE_v_3,5, SENSE_v_3,6, SENSE_v_3,7, SENSE_v_3,8, SENSE_v_3,9, SENSE_v_3,10], [SENSE_v_4,0, SENSE_v_4,1, SENSE_v_4,2, SENSE_v_4,3, SENSE_v_4,4, SENSE_v_4,5, SENSE_v_4,6, SENSE_v_4,7, SENSE_v_4,8, SENSE_v_4,9, SENSE_v_4,10], [SENSE_v_5,0, SENSE_v_5,1, SENSE_v_5,2, SENSE_v_5,3, SENSE_v_5,4, SENSE_v_5,5, SENSE_v_5,6, SENSE_v_5,7, SENSE_v_5,8, SENSE_v_5,9, SENSE_v_5,10], [SENSE_v_6,0, SENSE_v_6,1, SENSE_v_6,2, SENSE_v_6,3, SENSE_v_6,4, SENSE_v_6,5, SENSE_v_6,6, SENSE_v_6,7, SENSE_v_6,8, SENSE_v_6,9, SENSE_v_6,10]]]\n", + "[[[EMMA_h_0,0, EMMA_h_0,1, EMMA_h_0,2, EMMA_h_0,3, EMMA_h_0,4, EMMA_h_0,5, EMMA_h_0,6, EMMA_h_0,7], [EMMA_h_1,0, EMMA_h_1,1, EMMA_h_1,2, EMMA_h_1,3, EMMA_h_1,4, EMMA_h_1,5, EMMA_h_1,6, EMMA_h_1,7], [EMMA_h_2,0, EMMA_h_2,1, EMMA_h_2,2, EMMA_h_2,3, EMMA_h_2,4, EMMA_h_2,5, EMMA_h_2,6, EMMA_h_2,7], [EMMA_h_3,0, EMMA_h_3,1, EMMA_h_3,2, EMMA_h_3,3, EMMA_h_3,4, EMMA_h_3,5, EMMA_h_3,6, EMMA_h_3,7], [EMMA_h_4,0, EMMA_h_4,1, EMMA_h_4,2, EMMA_h_4,3, EMMA_h_4,4, EMMA_h_4,5, EMMA_h_4,6, EMMA_h_4,7], [EMMA_h_5,0, EMMA_h_5,1, EMMA_h_5,2, EMMA_h_5,3, EMMA_h_5,4, EMMA_h_5,5, EMMA_h_5,6, EMMA_h_5,7], [EMMA_h_6,0, EMMA_h_6,1, EMMA_h_6,2, EMMA_h_6,3, EMMA_h_6,4, EMMA_h_6,5, EMMA_h_6,6, EMMA_h_6,7], [EMMA_h_7,0, EMMA_h_7,1, EMMA_h_7,2, EMMA_h_7,3, EMMA_h_7,4, EMMA_h_7,5, EMMA_h_7,6, EMMA_h_7,7], [EMMA_h_8,0, EMMA_h_8,1, EMMA_h_8,2, EMMA_h_8,3, EMMA_h_8,4, EMMA_h_8,5, EMMA_h_8,6, EMMA_h_8,7], [EMMA_h_9,0, EMMA_h_9,1, EMMA_h_9,2, EMMA_h_9,3, EMMA_h_9,4, EMMA_h_9,5, EMMA_h_9,6, EMMA_h_9,7], [EMMA_h_10,0, EMMA_h_10,1, EMMA_h_10,2, EMMA_h_10,3, EMMA_h_10,4, EMMA_h_10,5, EMMA_h_10,6, EMMA_h_10,7]], [[EMMA_v_0,0, EMMA_v_0,1, EMMA_v_0,2, EMMA_v_0,3, EMMA_v_0,4, EMMA_v_0,5, EMMA_v_0,6, EMMA_v_0,7, EMMA_v_0,8, EMMA_v_0,9, EMMA_v_0,10], [EMMA_v_1,0, EMMA_v_1,1, EMMA_v_1,2, EMMA_v_1,3, EMMA_v_1,4, EMMA_v_1,5, EMMA_v_1,6, EMMA_v_1,7, EMMA_v_1,8, EMMA_v_1,9, EMMA_v_1,10], [EMMA_v_2,0, EMMA_v_2,1, EMMA_v_2,2, EMMA_v_2,3, EMMA_v_2,4, EMMA_v_2,5, EMMA_v_2,6, EMMA_v_2,7, EMMA_v_2,8, EMMA_v_2,9, EMMA_v_2,10], [EMMA_v_3,0, EMMA_v_3,1, EMMA_v_3,2, EMMA_v_3,3, EMMA_v_3,4, EMMA_v_3,5, EMMA_v_3,6, EMMA_v_3,7, EMMA_v_3,8, EMMA_v_3,9, EMMA_v_3,10], [EMMA_v_4,0, EMMA_v_4,1, EMMA_v_4,2, EMMA_v_4,3, EMMA_v_4,4, EMMA_v_4,5, EMMA_v_4,6, EMMA_v_4,7, EMMA_v_4,8, EMMA_v_4,9, EMMA_v_4,10], [EMMA_v_5,0, EMMA_v_5,1, EMMA_v_5,2, EMMA_v_5,3, EMMA_v_5,4, EMMA_v_5,5, EMMA_v_5,6, EMMA_v_5,7, EMMA_v_5,8, EMMA_v_5,9, EMMA_v_5,10], [EMMA_v_6,0, EMMA_v_6,1, EMMA_v_6,2, EMMA_v_6,3, EMMA_v_6,4, EMMA_v_6,5, EMMA_v_6,6, EMMA_v_6,7, EMMA_v_6,8, EMMA_v_6,9, EMMA_v_6,10], [EMMA_v_7,0, EMMA_v_7,1, EMMA_v_7,2, EMMA_v_7,3, EMMA_v_7,4, EMMA_v_7,5, EMMA_v_7,6, EMMA_v_7,7, EMMA_v_7,8, EMMA_v_7,9, EMMA_v_7,10]]]\n", + "[[[ESTATE_h_0,0, ESTATE_h_0,1, ESTATE_h_0,2, ESTATE_h_0,3, ESTATE_h_0,4, ESTATE_h_0,5], [ESTATE_h_1,0, ESTATE_h_1,1, ESTATE_h_1,2, ESTATE_h_1,3, ESTATE_h_1,4, ESTATE_h_1,5], [ESTATE_h_2,0, ESTATE_h_2,1, ESTATE_h_2,2, ESTATE_h_2,3, ESTATE_h_2,4, ESTATE_h_2,5], [ESTATE_h_3,0, ESTATE_h_3,1, ESTATE_h_3,2, ESTATE_h_3,3, ESTATE_h_3,4, ESTATE_h_3,5], [ESTATE_h_4,0, ESTATE_h_4,1, ESTATE_h_4,2, ESTATE_h_4,3, ESTATE_h_4,4, ESTATE_h_4,5], [ESTATE_h_5,0, ESTATE_h_5,1, ESTATE_h_5,2, ESTATE_h_5,3, ESTATE_h_5,4, ESTATE_h_5,5], [ESTATE_h_6,0, ESTATE_h_6,1, ESTATE_h_6,2, ESTATE_h_6,3, ESTATE_h_6,4, ESTATE_h_6,5], [ESTATE_h_7,0, ESTATE_h_7,1, ESTATE_h_7,2, ESTATE_h_7,3, ESTATE_h_7,4, ESTATE_h_7,5], [ESTATE_h_8,0, ESTATE_h_8,1, ESTATE_h_8,2, ESTATE_h_8,3, ESTATE_h_8,4, ESTATE_h_8,5], [ESTATE_h_9,0, ESTATE_h_9,1, ESTATE_h_9,2, ESTATE_h_9,3, ESTATE_h_9,4, ESTATE_h_9,5], [ESTATE_h_10,0, ESTATE_h_10,1, ESTATE_h_10,2, ESTATE_h_10,3, ESTATE_h_10,4, ESTATE_h_10,5]], [[ESTATE_v_0,0, ESTATE_v_0,1, ESTATE_v_0,2, ESTATE_v_0,3, ESTATE_v_0,4, ESTATE_v_0,5, ESTATE_v_0,6, ESTATE_v_0,7, ESTATE_v_0,8, ESTATE_v_0,9, ESTATE_v_0,10], [ESTATE_v_1,0, ESTATE_v_1,1, ESTATE_v_1,2, ESTATE_v_1,3, ESTATE_v_1,4, ESTATE_v_1,5, ESTATE_v_1,6, ESTATE_v_1,7, ESTATE_v_1,8, ESTATE_v_1,9, ESTATE_v_1,10], [ESTATE_v_2,0, ESTATE_v_2,1, ESTATE_v_2,2, ESTATE_v_2,3, ESTATE_v_2,4, ESTATE_v_2,5, ESTATE_v_2,6, ESTATE_v_2,7, ESTATE_v_2,8, ESTATE_v_2,9, ESTATE_v_2,10], [ESTATE_v_3,0, ESTATE_v_3,1, ESTATE_v_3,2, ESTATE_v_3,3, ESTATE_v_3,4, ESTATE_v_3,5, ESTATE_v_3,6, ESTATE_v_3,7, ESTATE_v_3,8, ESTATE_v_3,9, ESTATE_v_3,10], [ESTATE_v_4,0, ESTATE_v_4,1, ESTATE_v_4,2, ESTATE_v_4,3, ESTATE_v_4,4, ESTATE_v_4,5, ESTATE_v_4,6, ESTATE_v_4,7, ESTATE_v_4,8, ESTATE_v_4,9, ESTATE_v_4,10], [ESTATE_v_5,0, ESTATE_v_5,1, ESTATE_v_5,2, ESTATE_v_5,3, ESTATE_v_5,4, ESTATE_v_5,5, ESTATE_v_5,6, ESTATE_v_5,7, ESTATE_v_5,8, ESTATE_v_5,9, ESTATE_v_5,10]]]\n", + "[[[BENNET_h_0,0, BENNET_h_0,1, BENNET_h_0,2, BENNET_h_0,3, BENNET_h_0,4, BENNET_h_0,5], [BENNET_h_1,0, BENNET_h_1,1, BENNET_h_1,2, BENNET_h_1,3, BENNET_h_1,4, BENNET_h_1,5], [BENNET_h_2,0, BENNET_h_2,1, BENNET_h_2,2, BENNET_h_2,3, BENNET_h_2,4, BENNET_h_2,5], [BENNET_h_3,0, BENNET_h_3,1, BENNET_h_3,2, BENNET_h_3,3, BENNET_h_3,4, BENNET_h_3,5], [BENNET_h_4,0, BENNET_h_4,1, BENNET_h_4,2, BENNET_h_4,3, BENNET_h_4,4, BENNET_h_4,5], [BENNET_h_5,0, BENNET_h_5,1, BENNET_h_5,2, BENNET_h_5,3, BENNET_h_5,4, BENNET_h_5,5], [BENNET_h_6,0, BENNET_h_6,1, BENNET_h_6,2, BENNET_h_6,3, BENNET_h_6,4, BENNET_h_6,5], [BENNET_h_7,0, BENNET_h_7,1, BENNET_h_7,2, BENNET_h_7,3, BENNET_h_7,4, BENNET_h_7,5], [BENNET_h_8,0, BENNET_h_8,1, BENNET_h_8,2, BENNET_h_8,3, BENNET_h_8,4, BENNET_h_8,5], [BENNET_h_9,0, BENNET_h_9,1, BENNET_h_9,2, BENNET_h_9,3, BENNET_h_9,4, BENNET_h_9,5], [BENNET_h_10,0, BENNET_h_10,1, BENNET_h_10,2, BENNET_h_10,3, BENNET_h_10,4, BENNET_h_10,5]], [[BENNET_v_0,0, BENNET_v_0,1, BENNET_v_0,2, BENNET_v_0,3, BENNET_v_0,4, BENNET_v_0,5, BENNET_v_0,6, BENNET_v_0,7, BENNET_v_0,8, BENNET_v_0,9, BENNET_v_0,10], [BENNET_v_1,0, BENNET_v_1,1, BENNET_v_1,2, BENNET_v_1,3, BENNET_v_1,4, BENNET_v_1,5, BENNET_v_1,6, BENNET_v_1,7, BENNET_v_1,8, BENNET_v_1,9, BENNET_v_1,10], [BENNET_v_2,0, BENNET_v_2,1, BENNET_v_2,2, BENNET_v_2,3, BENNET_v_2,4, BENNET_v_2,5, BENNET_v_2,6, BENNET_v_2,7, BENNET_v_2,8, BENNET_v_2,9, BENNET_v_2,10], [BENNET_v_3,0, BENNET_v_3,1, BENNET_v_3,2, BENNET_v_3,3, BENNET_v_3,4, BENNET_v_3,5, BENNET_v_3,6, BENNET_v_3,7, BENNET_v_3,8, BENNET_v_3,9, BENNET_v_3,10], [BENNET_v_4,0, BENNET_v_4,1, BENNET_v_4,2, BENNET_v_4,3, BENNET_v_4,4, BENNET_v_4,5, BENNET_v_4,6, BENNET_v_4,7, BENNET_v_4,8, BENNET_v_4,9, BENNET_v_4,10], [BENNET_v_5,0, BENNET_v_5,1, BENNET_v_5,2, BENNET_v_5,3, BENNET_v_5,4, BENNET_v_5,5, BENNET_v_5,6, BENNET_v_5,7, BENNET_v_5,8, BENNET_v_5,9, BENNET_v_5,10]]]\n", + "[[[BATH_h_0,0, BATH_h_0,1, BATH_h_0,2, BATH_h_0,3, BATH_h_0,4, BATH_h_0,5, BATH_h_0,6, BATH_h_0,7], [BATH_h_1,0, BATH_h_1,1, BATH_h_1,2, BATH_h_1,3, BATH_h_1,4, BATH_h_1,5, BATH_h_1,6, BATH_h_1,7], [BATH_h_2,0, BATH_h_2,1, BATH_h_2,2, BATH_h_2,3, BATH_h_2,4, BATH_h_2,5, BATH_h_2,6, BATH_h_2,7], [BATH_h_3,0, BATH_h_3,1, BATH_h_3,2, BATH_h_3,3, BATH_h_3,4, BATH_h_3,5, BATH_h_3,6, BATH_h_3,7], [BATH_h_4,0, BATH_h_4,1, BATH_h_4,2, BATH_h_4,3, BATH_h_4,4, BATH_h_4,5, BATH_h_4,6, BATH_h_4,7], [BATH_h_5,0, BATH_h_5,1, BATH_h_5,2, BATH_h_5,3, BATH_h_5,4, BATH_h_5,5, BATH_h_5,6, BATH_h_5,7], [BATH_h_6,0, BATH_h_6,1, BATH_h_6,2, BATH_h_6,3, BATH_h_6,4, BATH_h_6,5, BATH_h_6,6, BATH_h_6,7], [BATH_h_7,0, BATH_h_7,1, BATH_h_7,2, BATH_h_7,3, BATH_h_7,4, BATH_h_7,5, BATH_h_7,6, BATH_h_7,7], [BATH_h_8,0, BATH_h_8,1, BATH_h_8,2, BATH_h_8,3, BATH_h_8,4, BATH_h_8,5, BATH_h_8,6, BATH_h_8,7], [BATH_h_9,0, BATH_h_9,1, BATH_h_9,2, BATH_h_9,3, BATH_h_9,4, BATH_h_9,5, BATH_h_9,6, BATH_h_9,7], [BATH_h_10,0, BATH_h_10,1, BATH_h_10,2, BATH_h_10,3, BATH_h_10,4, BATH_h_10,5, BATH_h_10,6, BATH_h_10,7]], [[BATH_v_0,0, BATH_v_0,1, BATH_v_0,2, BATH_v_0,3, BATH_v_0,4, BATH_v_0,5, BATH_v_0,6, BATH_v_0,7, BATH_v_0,8, BATH_v_0,9, BATH_v_0,10], [BATH_v_1,0, BATH_v_1,1, BATH_v_1,2, BATH_v_1,3, BATH_v_1,4, BATH_v_1,5, BATH_v_1,6, BATH_v_1,7, BATH_v_1,8, BATH_v_1,9, BATH_v_1,10], [BATH_v_2,0, BATH_v_2,1, BATH_v_2,2, BATH_v_2,3, BATH_v_2,4, BATH_v_2,5, BATH_v_2,6, BATH_v_2,7, BATH_v_2,8, BATH_v_2,9, BATH_v_2,10], [BATH_v_3,0, BATH_v_3,1, BATH_v_3,2, BATH_v_3,3, BATH_v_3,4, BATH_v_3,5, BATH_v_3,6, BATH_v_3,7, BATH_v_3,8, BATH_v_3,9, BATH_v_3,10], [BATH_v_4,0, BATH_v_4,1, BATH_v_4,2, BATH_v_4,3, BATH_v_4,4, BATH_v_4,5, BATH_v_4,6, BATH_v_4,7, BATH_v_4,8, BATH_v_4,9, BATH_v_4,10], [BATH_v_5,0, BATH_v_5,1, BATH_v_5,2, BATH_v_5,3, BATH_v_5,4, BATH_v_5,5, BATH_v_5,6, BATH_v_5,7, BATH_v_5,8, BATH_v_5,9, BATH_v_5,10], [BATH_v_6,0, BATH_v_6,1, BATH_v_6,2, BATH_v_6,3, BATH_v_6,4, BATH_v_6,5, BATH_v_6,6, BATH_v_6,7, BATH_v_6,8, BATH_v_6,9, BATH_v_6,10], [BATH_v_7,0, BATH_v_7,1, BATH_v_7,2, BATH_v_7,3, BATH_v_7,4, BATH_v_7,5, BATH_v_7,6, BATH_v_7,7, BATH_v_7,8, BATH_v_7,9, BATH_v_7,10]]]\n", + "Executed in 1.9492 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║█│█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█│█║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "# Now let's see what the smallest grid we can fit this in is. The longest word is 6 letters, so can't be shorter than this\n", + "solve_crossword(words, 10, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)\n", + "solve_crossword(words, 9, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)\n", + "solve_crossword(words, 8, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)\n", + "solve_crossword(words, 7, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)\n", + "solve_crossword(words, 6, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "OFLdFlaP6g7L", + "outputId": "15710d99-0086-4016-807d-cf63912265e3" + }, + "execution_count": 34, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[[[JANE_h_0,0, JANE_h_0,1, JANE_h_0,2, JANE_h_0,3, JANE_h_0,4, JANE_h_0,5, JANE_h_0,6], [JANE_h_1,0, JANE_h_1,1, JANE_h_1,2, JANE_h_1,3, JANE_h_1,4, JANE_h_1,5, JANE_h_1,6], [JANE_h_2,0, JANE_h_2,1, JANE_h_2,2, JANE_h_2,3, JANE_h_2,4, JANE_h_2,5, JANE_h_2,6], [JANE_h_3,0, JANE_h_3,1, JANE_h_3,2, JANE_h_3,3, JANE_h_3,4, JANE_h_3,5, JANE_h_3,6], [JANE_h_4,0, JANE_h_4,1, JANE_h_4,2, JANE_h_4,3, JANE_h_4,4, JANE_h_4,5, JANE_h_4,6], [JANE_h_5,0, JANE_h_5,1, JANE_h_5,2, JANE_h_5,3, JANE_h_5,4, JANE_h_5,5, JANE_h_5,6], [JANE_h_6,0, JANE_h_6,1, JANE_h_6,2, JANE_h_6,3, JANE_h_6,4, JANE_h_6,5, JANE_h_6,6], [JANE_h_7,0, JANE_h_7,1, JANE_h_7,2, JANE_h_7,3, JANE_h_7,4, JANE_h_7,5, JANE_h_7,6], [JANE_h_8,0, JANE_h_8,1, JANE_h_8,2, JANE_h_8,3, JANE_h_8,4, JANE_h_8,5, JANE_h_8,6], [JANE_h_9,0, JANE_h_9,1, JANE_h_9,2, JANE_h_9,3, JANE_h_9,4, JANE_h_9,5, JANE_h_9,6]], [[JANE_v_0,0, JANE_v_0,1, JANE_v_0,2, JANE_v_0,3, JANE_v_0,4, JANE_v_0,5, JANE_v_0,6, JANE_v_0,7, JANE_v_0,8, JANE_v_0,9], [JANE_v_1,0, JANE_v_1,1, JANE_v_1,2, JANE_v_1,3, JANE_v_1,4, JANE_v_1,5, JANE_v_1,6, JANE_v_1,7, JANE_v_1,8, JANE_v_1,9], [JANE_v_2,0, JANE_v_2,1, JANE_v_2,2, JANE_v_2,3, JANE_v_2,4, JANE_v_2,5, JANE_v_2,6, JANE_v_2,7, JANE_v_2,8, JANE_v_2,9], [JANE_v_3,0, JANE_v_3,1, JANE_v_3,2, JANE_v_3,3, JANE_v_3,4, JANE_v_3,5, JANE_v_3,6, JANE_v_3,7, JANE_v_3,8, JANE_v_3,9], [JANE_v_4,0, JANE_v_4,1, JANE_v_4,2, JANE_v_4,3, JANE_v_4,4, JANE_v_4,5, JANE_v_4,6, JANE_v_4,7, JANE_v_4,8, JANE_v_4,9], [JANE_v_5,0, JANE_v_5,1, JANE_v_5,2, JANE_v_5,3, JANE_v_5,4, JANE_v_5,5, JANE_v_5,6, JANE_v_5,7, JANE_v_5,8, JANE_v_5,9], [JANE_v_6,0, JANE_v_6,1, JANE_v_6,2, JANE_v_6,3, JANE_v_6,4, JANE_v_6,5, JANE_v_6,6, JANE_v_6,7, JANE_v_6,8, JANE_v_6,9]]]\n", + "[[[AUSTEN_h_0,0, AUSTEN_h_0,1, AUSTEN_h_0,2, AUSTEN_h_0,3, AUSTEN_h_0,4], [AUSTEN_h_1,0, AUSTEN_h_1,1, AUSTEN_h_1,2, AUSTEN_h_1,3, AUSTEN_h_1,4], [AUSTEN_h_2,0, AUSTEN_h_2,1, AUSTEN_h_2,2, AUSTEN_h_2,3, AUSTEN_h_2,4], [AUSTEN_h_3,0, AUSTEN_h_3,1, AUSTEN_h_3,2, AUSTEN_h_3,3, AUSTEN_h_3,4], [AUSTEN_h_4,0, AUSTEN_h_4,1, AUSTEN_h_4,2, AUSTEN_h_4,3, AUSTEN_h_4,4], [AUSTEN_h_5,0, AUSTEN_h_5,1, AUSTEN_h_5,2, AUSTEN_h_5,3, AUSTEN_h_5,4], [AUSTEN_h_6,0, AUSTEN_h_6,1, AUSTEN_h_6,2, AUSTEN_h_6,3, AUSTEN_h_6,4], [AUSTEN_h_7,0, AUSTEN_h_7,1, AUSTEN_h_7,2, AUSTEN_h_7,3, AUSTEN_h_7,4], [AUSTEN_h_8,0, AUSTEN_h_8,1, AUSTEN_h_8,2, AUSTEN_h_8,3, AUSTEN_h_8,4], [AUSTEN_h_9,0, AUSTEN_h_9,1, AUSTEN_h_9,2, AUSTEN_h_9,3, AUSTEN_h_9,4]], [[AUSTEN_v_0,0, AUSTEN_v_0,1, AUSTEN_v_0,2, AUSTEN_v_0,3, AUSTEN_v_0,4, AUSTEN_v_0,5, AUSTEN_v_0,6, AUSTEN_v_0,7, AUSTEN_v_0,8, AUSTEN_v_0,9], [AUSTEN_v_1,0, AUSTEN_v_1,1, AUSTEN_v_1,2, AUSTEN_v_1,3, AUSTEN_v_1,4, AUSTEN_v_1,5, AUSTEN_v_1,6, AUSTEN_v_1,7, AUSTEN_v_1,8, AUSTEN_v_1,9], [AUSTEN_v_2,0, AUSTEN_v_2,1, AUSTEN_v_2,2, AUSTEN_v_2,3, AUSTEN_v_2,4, AUSTEN_v_2,5, AUSTEN_v_2,6, AUSTEN_v_2,7, AUSTEN_v_2,8, AUSTEN_v_2,9], [AUSTEN_v_3,0, AUSTEN_v_3,1, AUSTEN_v_3,2, AUSTEN_v_3,3, AUSTEN_v_3,4, AUSTEN_v_3,5, AUSTEN_v_3,6, AUSTEN_v_3,7, AUSTEN_v_3,8, AUSTEN_v_3,9], [AUSTEN_v_4,0, AUSTEN_v_4,1, AUSTEN_v_4,2, AUSTEN_v_4,3, AUSTEN_v_4,4, AUSTEN_v_4,5, AUSTEN_v_4,6, AUSTEN_v_4,7, AUSTEN_v_4,8, AUSTEN_v_4,9]]]\n", + "[[[PRIDE_h_0,0, PRIDE_h_0,1, PRIDE_h_0,2, PRIDE_h_0,3, PRIDE_h_0,4, PRIDE_h_0,5], [PRIDE_h_1,0, PRIDE_h_1,1, PRIDE_h_1,2, PRIDE_h_1,3, PRIDE_h_1,4, PRIDE_h_1,5], [PRIDE_h_2,0, PRIDE_h_2,1, PRIDE_h_2,2, PRIDE_h_2,3, PRIDE_h_2,4, PRIDE_h_2,5], [PRIDE_h_3,0, PRIDE_h_3,1, PRIDE_h_3,2, PRIDE_h_3,3, PRIDE_h_3,4, PRIDE_h_3,5], [PRIDE_h_4,0, PRIDE_h_4,1, PRIDE_h_4,2, PRIDE_h_4,3, PRIDE_h_4,4, PRIDE_h_4,5], [PRIDE_h_5,0, PRIDE_h_5,1, PRIDE_h_5,2, PRIDE_h_5,3, PRIDE_h_5,4, PRIDE_h_5,5], [PRIDE_h_6,0, PRIDE_h_6,1, PRIDE_h_6,2, PRIDE_h_6,3, PRIDE_h_6,4, PRIDE_h_6,5], [PRIDE_h_7,0, PRIDE_h_7,1, PRIDE_h_7,2, PRIDE_h_7,3, PRIDE_h_7,4, PRIDE_h_7,5], [PRIDE_h_8,0, PRIDE_h_8,1, PRIDE_h_8,2, PRIDE_h_8,3, PRIDE_h_8,4, PRIDE_h_8,5], [PRIDE_h_9,0, PRIDE_h_9,1, PRIDE_h_9,2, PRIDE_h_9,3, PRIDE_h_9,4, PRIDE_h_9,5]], [[PRIDE_v_0,0, PRIDE_v_0,1, PRIDE_v_0,2, PRIDE_v_0,3, PRIDE_v_0,4, PRIDE_v_0,5, PRIDE_v_0,6, PRIDE_v_0,7, PRIDE_v_0,8, PRIDE_v_0,9], [PRIDE_v_1,0, PRIDE_v_1,1, PRIDE_v_1,2, PRIDE_v_1,3, PRIDE_v_1,4, PRIDE_v_1,5, PRIDE_v_1,6, PRIDE_v_1,7, PRIDE_v_1,8, PRIDE_v_1,9], [PRIDE_v_2,0, PRIDE_v_2,1, PRIDE_v_2,2, PRIDE_v_2,3, PRIDE_v_2,4, PRIDE_v_2,5, PRIDE_v_2,6, PRIDE_v_2,7, PRIDE_v_2,8, PRIDE_v_2,9], [PRIDE_v_3,0, PRIDE_v_3,1, PRIDE_v_3,2, PRIDE_v_3,3, PRIDE_v_3,4, PRIDE_v_3,5, PRIDE_v_3,6, PRIDE_v_3,7, PRIDE_v_3,8, PRIDE_v_3,9], [PRIDE_v_4,0, PRIDE_v_4,1, PRIDE_v_4,2, PRIDE_v_4,3, PRIDE_v_4,4, PRIDE_v_4,5, PRIDE_v_4,6, PRIDE_v_4,7, PRIDE_v_4,8, PRIDE_v_4,9], [PRIDE_v_5,0, PRIDE_v_5,1, PRIDE_v_5,2, PRIDE_v_5,3, PRIDE_v_5,4, PRIDE_v_5,5, PRIDE_v_5,6, PRIDE_v_5,7, PRIDE_v_5,8, PRIDE_v_5,9]]]\n", + "[[[NOVEL_h_0,0, NOVEL_h_0,1, NOVEL_h_0,2, NOVEL_h_0,3, NOVEL_h_0,4, NOVEL_h_0,5], [NOVEL_h_1,0, NOVEL_h_1,1, NOVEL_h_1,2, NOVEL_h_1,3, NOVEL_h_1,4, NOVEL_h_1,5], [NOVEL_h_2,0, NOVEL_h_2,1, NOVEL_h_2,2, NOVEL_h_2,3, NOVEL_h_2,4, NOVEL_h_2,5], [NOVEL_h_3,0, NOVEL_h_3,1, NOVEL_h_3,2, NOVEL_h_3,3, NOVEL_h_3,4, NOVEL_h_3,5], [NOVEL_h_4,0, NOVEL_h_4,1, NOVEL_h_4,2, NOVEL_h_4,3, NOVEL_h_4,4, NOVEL_h_4,5], [NOVEL_h_5,0, NOVEL_h_5,1, NOVEL_h_5,2, NOVEL_h_5,3, NOVEL_h_5,4, NOVEL_h_5,5], [NOVEL_h_6,0, NOVEL_h_6,1, NOVEL_h_6,2, NOVEL_h_6,3, NOVEL_h_6,4, NOVEL_h_6,5], [NOVEL_h_7,0, NOVEL_h_7,1, NOVEL_h_7,2, NOVEL_h_7,3, NOVEL_h_7,4, NOVEL_h_7,5], [NOVEL_h_8,0, NOVEL_h_8,1, NOVEL_h_8,2, NOVEL_h_8,3, NOVEL_h_8,4, NOVEL_h_8,5], [NOVEL_h_9,0, NOVEL_h_9,1, NOVEL_h_9,2, NOVEL_h_9,3, NOVEL_h_9,4, NOVEL_h_9,5]], [[NOVEL_v_0,0, NOVEL_v_0,1, NOVEL_v_0,2, NOVEL_v_0,3, NOVEL_v_0,4, NOVEL_v_0,5, NOVEL_v_0,6, NOVEL_v_0,7, NOVEL_v_0,8, NOVEL_v_0,9], [NOVEL_v_1,0, NOVEL_v_1,1, NOVEL_v_1,2, NOVEL_v_1,3, NOVEL_v_1,4, NOVEL_v_1,5, NOVEL_v_1,6, NOVEL_v_1,7, NOVEL_v_1,8, NOVEL_v_1,9], [NOVEL_v_2,0, NOVEL_v_2,1, NOVEL_v_2,2, NOVEL_v_2,3, NOVEL_v_2,4, NOVEL_v_2,5, NOVEL_v_2,6, NOVEL_v_2,7, NOVEL_v_2,8, NOVEL_v_2,9], [NOVEL_v_3,0, NOVEL_v_3,1, NOVEL_v_3,2, NOVEL_v_3,3, NOVEL_v_3,4, NOVEL_v_3,5, NOVEL_v_3,6, NOVEL_v_3,7, NOVEL_v_3,8, NOVEL_v_3,9], [NOVEL_v_4,0, NOVEL_v_4,1, NOVEL_v_4,2, NOVEL_v_4,3, NOVEL_v_4,4, NOVEL_v_4,5, NOVEL_v_4,6, NOVEL_v_4,7, NOVEL_v_4,8, NOVEL_v_4,9], [NOVEL_v_5,0, NOVEL_v_5,1, NOVEL_v_5,2, NOVEL_v_5,3, NOVEL_v_5,4, NOVEL_v_5,5, NOVEL_v_5,6, NOVEL_v_5,7, NOVEL_v_5,8, NOVEL_v_5,9]]]\n", + "[[[DARCY_h_0,0, DARCY_h_0,1, DARCY_h_0,2, DARCY_h_0,3, DARCY_h_0,4, DARCY_h_0,5], [DARCY_h_1,0, DARCY_h_1,1, DARCY_h_1,2, DARCY_h_1,3, DARCY_h_1,4, DARCY_h_1,5], [DARCY_h_2,0, DARCY_h_2,1, DARCY_h_2,2, DARCY_h_2,3, DARCY_h_2,4, DARCY_h_2,5], [DARCY_h_3,0, DARCY_h_3,1, DARCY_h_3,2, DARCY_h_3,3, DARCY_h_3,4, DARCY_h_3,5], [DARCY_h_4,0, DARCY_h_4,1, DARCY_h_4,2, DARCY_h_4,3, DARCY_h_4,4, DARCY_h_4,5], [DARCY_h_5,0, DARCY_h_5,1, DARCY_h_5,2, DARCY_h_5,3, DARCY_h_5,4, DARCY_h_5,5], [DARCY_h_6,0, DARCY_h_6,1, DARCY_h_6,2, DARCY_h_6,3, DARCY_h_6,4, DARCY_h_6,5], [DARCY_h_7,0, DARCY_h_7,1, DARCY_h_7,2, DARCY_h_7,3, DARCY_h_7,4, DARCY_h_7,5], [DARCY_h_8,0, DARCY_h_8,1, DARCY_h_8,2, DARCY_h_8,3, DARCY_h_8,4, DARCY_h_8,5], [DARCY_h_9,0, DARCY_h_9,1, DARCY_h_9,2, DARCY_h_9,3, DARCY_h_9,4, DARCY_h_9,5]], [[DARCY_v_0,0, DARCY_v_0,1, DARCY_v_0,2, DARCY_v_0,3, DARCY_v_0,4, DARCY_v_0,5, DARCY_v_0,6, DARCY_v_0,7, DARCY_v_0,8, DARCY_v_0,9], [DARCY_v_1,0, DARCY_v_1,1, DARCY_v_1,2, DARCY_v_1,3, DARCY_v_1,4, DARCY_v_1,5, DARCY_v_1,6, DARCY_v_1,7, DARCY_v_1,8, DARCY_v_1,9], [DARCY_v_2,0, DARCY_v_2,1, DARCY_v_2,2, DARCY_v_2,3, DARCY_v_2,4, DARCY_v_2,5, DARCY_v_2,6, DARCY_v_2,7, DARCY_v_2,8, DARCY_v_2,9], [DARCY_v_3,0, DARCY_v_3,1, DARCY_v_3,2, DARCY_v_3,3, DARCY_v_3,4, DARCY_v_3,5, DARCY_v_3,6, DARCY_v_3,7, DARCY_v_3,8, DARCY_v_3,9], [DARCY_v_4,0, DARCY_v_4,1, DARCY_v_4,2, DARCY_v_4,3, DARCY_v_4,4, DARCY_v_4,5, DARCY_v_4,6, DARCY_v_4,7, DARCY_v_4,8, DARCY_v_4,9], [DARCY_v_5,0, DARCY_v_5,1, DARCY_v_5,2, DARCY_v_5,3, DARCY_v_5,4, DARCY_v_5,5, DARCY_v_5,6, DARCY_v_5,7, DARCY_v_5,8, DARCY_v_5,9]]]\n", + "[[[SENSE_h_0,0, SENSE_h_0,1, SENSE_h_0,2, SENSE_h_0,3, SENSE_h_0,4, SENSE_h_0,5], [SENSE_h_1,0, SENSE_h_1,1, SENSE_h_1,2, SENSE_h_1,3, SENSE_h_1,4, SENSE_h_1,5], [SENSE_h_2,0, SENSE_h_2,1, SENSE_h_2,2, SENSE_h_2,3, SENSE_h_2,4, SENSE_h_2,5], [SENSE_h_3,0, SENSE_h_3,1, SENSE_h_3,2, SENSE_h_3,3, SENSE_h_3,4, SENSE_h_3,5], [SENSE_h_4,0, SENSE_h_4,1, SENSE_h_4,2, SENSE_h_4,3, SENSE_h_4,4, SENSE_h_4,5], [SENSE_h_5,0, SENSE_h_5,1, SENSE_h_5,2, SENSE_h_5,3, SENSE_h_5,4, SENSE_h_5,5], [SENSE_h_6,0, SENSE_h_6,1, SENSE_h_6,2, SENSE_h_6,3, SENSE_h_6,4, SENSE_h_6,5], [SENSE_h_7,0, SENSE_h_7,1, SENSE_h_7,2, SENSE_h_7,3, SENSE_h_7,4, SENSE_h_7,5], [SENSE_h_8,0, SENSE_h_8,1, SENSE_h_8,2, SENSE_h_8,3, SENSE_h_8,4, SENSE_h_8,5], [SENSE_h_9,0, SENSE_h_9,1, SENSE_h_9,2, SENSE_h_9,3, SENSE_h_9,4, SENSE_h_9,5]], [[SENSE_v_0,0, SENSE_v_0,1, SENSE_v_0,2, SENSE_v_0,3, SENSE_v_0,4, SENSE_v_0,5, SENSE_v_0,6, SENSE_v_0,7, SENSE_v_0,8, SENSE_v_0,9], [SENSE_v_1,0, SENSE_v_1,1, SENSE_v_1,2, SENSE_v_1,3, SENSE_v_1,4, SENSE_v_1,5, SENSE_v_1,6, SENSE_v_1,7, SENSE_v_1,8, SENSE_v_1,9], [SENSE_v_2,0, SENSE_v_2,1, SENSE_v_2,2, SENSE_v_2,3, SENSE_v_2,4, SENSE_v_2,5, SENSE_v_2,6, SENSE_v_2,7, SENSE_v_2,8, SENSE_v_2,9], [SENSE_v_3,0, SENSE_v_3,1, SENSE_v_3,2, SENSE_v_3,3, SENSE_v_3,4, SENSE_v_3,5, SENSE_v_3,6, SENSE_v_3,7, SENSE_v_3,8, SENSE_v_3,9], [SENSE_v_4,0, SENSE_v_4,1, SENSE_v_4,2, SENSE_v_4,3, SENSE_v_4,4, SENSE_v_4,5, SENSE_v_4,6, SENSE_v_4,7, SENSE_v_4,8, SENSE_v_4,9], [SENSE_v_5,0, SENSE_v_5,1, SENSE_v_5,2, SENSE_v_5,3, SENSE_v_5,4, SENSE_v_5,5, SENSE_v_5,6, SENSE_v_5,7, SENSE_v_5,8, SENSE_v_5,9]]]\n", + "[[[EMMA_h_0,0, EMMA_h_0,1, EMMA_h_0,2, EMMA_h_0,3, EMMA_h_0,4, EMMA_h_0,5, EMMA_h_0,6], [EMMA_h_1,0, EMMA_h_1,1, EMMA_h_1,2, EMMA_h_1,3, EMMA_h_1,4, EMMA_h_1,5, EMMA_h_1,6], [EMMA_h_2,0, EMMA_h_2,1, EMMA_h_2,2, EMMA_h_2,3, EMMA_h_2,4, EMMA_h_2,5, EMMA_h_2,6], [EMMA_h_3,0, EMMA_h_3,1, EMMA_h_3,2, EMMA_h_3,3, EMMA_h_3,4, EMMA_h_3,5, EMMA_h_3,6], [EMMA_h_4,0, EMMA_h_4,1, EMMA_h_4,2, EMMA_h_4,3, EMMA_h_4,4, EMMA_h_4,5, EMMA_h_4,6], [EMMA_h_5,0, EMMA_h_5,1, EMMA_h_5,2, EMMA_h_5,3, EMMA_h_5,4, EMMA_h_5,5, EMMA_h_5,6], [EMMA_h_6,0, EMMA_h_6,1, EMMA_h_6,2, EMMA_h_6,3, EMMA_h_6,4, EMMA_h_6,5, EMMA_h_6,6], [EMMA_h_7,0, EMMA_h_7,1, EMMA_h_7,2, EMMA_h_7,3, EMMA_h_7,4, EMMA_h_7,5, EMMA_h_7,6], [EMMA_h_8,0, EMMA_h_8,1, EMMA_h_8,2, EMMA_h_8,3, EMMA_h_8,4, EMMA_h_8,5, EMMA_h_8,6], [EMMA_h_9,0, EMMA_h_9,1, EMMA_h_9,2, EMMA_h_9,3, EMMA_h_9,4, EMMA_h_9,5, EMMA_h_9,6]], [[EMMA_v_0,0, EMMA_v_0,1, EMMA_v_0,2, EMMA_v_0,3, EMMA_v_0,4, EMMA_v_0,5, EMMA_v_0,6, EMMA_v_0,7, EMMA_v_0,8, EMMA_v_0,9], [EMMA_v_1,0, EMMA_v_1,1, EMMA_v_1,2, EMMA_v_1,3, EMMA_v_1,4, EMMA_v_1,5, EMMA_v_1,6, EMMA_v_1,7, EMMA_v_1,8, EMMA_v_1,9], [EMMA_v_2,0, EMMA_v_2,1, EMMA_v_2,2, EMMA_v_2,3, EMMA_v_2,4, EMMA_v_2,5, EMMA_v_2,6, EMMA_v_2,7, EMMA_v_2,8, EMMA_v_2,9], [EMMA_v_3,0, EMMA_v_3,1, EMMA_v_3,2, EMMA_v_3,3, EMMA_v_3,4, EMMA_v_3,5, EMMA_v_3,6, EMMA_v_3,7, EMMA_v_3,8, EMMA_v_3,9], [EMMA_v_4,0, EMMA_v_4,1, EMMA_v_4,2, EMMA_v_4,3, EMMA_v_4,4, EMMA_v_4,5, EMMA_v_4,6, EMMA_v_4,7, EMMA_v_4,8, EMMA_v_4,9], [EMMA_v_5,0, EMMA_v_5,1, EMMA_v_5,2, EMMA_v_5,3, EMMA_v_5,4, EMMA_v_5,5, EMMA_v_5,6, EMMA_v_5,7, EMMA_v_5,8, EMMA_v_5,9], [EMMA_v_6,0, EMMA_v_6,1, EMMA_v_6,2, EMMA_v_6,3, EMMA_v_6,4, EMMA_v_6,5, EMMA_v_6,6, EMMA_v_6,7, EMMA_v_6,8, EMMA_v_6,9]]]\n", + "[[[ESTATE_h_0,0, ESTATE_h_0,1, ESTATE_h_0,2, ESTATE_h_0,3, ESTATE_h_0,4], [ESTATE_h_1,0, ESTATE_h_1,1, ESTATE_h_1,2, ESTATE_h_1,3, ESTATE_h_1,4], [ESTATE_h_2,0, ESTATE_h_2,1, ESTATE_h_2,2, ESTATE_h_2,3, ESTATE_h_2,4], [ESTATE_h_3,0, ESTATE_h_3,1, ESTATE_h_3,2, ESTATE_h_3,3, ESTATE_h_3,4], [ESTATE_h_4,0, ESTATE_h_4,1, ESTATE_h_4,2, ESTATE_h_4,3, ESTATE_h_4,4], [ESTATE_h_5,0, ESTATE_h_5,1, ESTATE_h_5,2, ESTATE_h_5,3, ESTATE_h_5,4], [ESTATE_h_6,0, ESTATE_h_6,1, ESTATE_h_6,2, ESTATE_h_6,3, ESTATE_h_6,4], [ESTATE_h_7,0, ESTATE_h_7,1, ESTATE_h_7,2, ESTATE_h_7,3, ESTATE_h_7,4], [ESTATE_h_8,0, ESTATE_h_8,1, ESTATE_h_8,2, ESTATE_h_8,3, ESTATE_h_8,4], [ESTATE_h_9,0, ESTATE_h_9,1, ESTATE_h_9,2, ESTATE_h_9,3, ESTATE_h_9,4]], [[ESTATE_v_0,0, ESTATE_v_0,1, ESTATE_v_0,2, ESTATE_v_0,3, ESTATE_v_0,4, ESTATE_v_0,5, ESTATE_v_0,6, ESTATE_v_0,7, ESTATE_v_0,8, ESTATE_v_0,9], [ESTATE_v_1,0, ESTATE_v_1,1, ESTATE_v_1,2, ESTATE_v_1,3, ESTATE_v_1,4, ESTATE_v_1,5, ESTATE_v_1,6, ESTATE_v_1,7, ESTATE_v_1,8, ESTATE_v_1,9], [ESTATE_v_2,0, ESTATE_v_2,1, ESTATE_v_2,2, ESTATE_v_2,3, ESTATE_v_2,4, ESTATE_v_2,5, ESTATE_v_2,6, ESTATE_v_2,7, ESTATE_v_2,8, ESTATE_v_2,9], [ESTATE_v_3,0, ESTATE_v_3,1, ESTATE_v_3,2, ESTATE_v_3,3, ESTATE_v_3,4, ESTATE_v_3,5, ESTATE_v_3,6, ESTATE_v_3,7, ESTATE_v_3,8, ESTATE_v_3,9], [ESTATE_v_4,0, ESTATE_v_4,1, ESTATE_v_4,2, ESTATE_v_4,3, ESTATE_v_4,4, ESTATE_v_4,5, ESTATE_v_4,6, ESTATE_v_4,7, ESTATE_v_4,8, ESTATE_v_4,9]]]\n", + "[[[BENNET_h_0,0, BENNET_h_0,1, BENNET_h_0,2, BENNET_h_0,3, BENNET_h_0,4], [BENNET_h_1,0, BENNET_h_1,1, BENNET_h_1,2, BENNET_h_1,3, BENNET_h_1,4], [BENNET_h_2,0, BENNET_h_2,1, BENNET_h_2,2, BENNET_h_2,3, BENNET_h_2,4], [BENNET_h_3,0, BENNET_h_3,1, BENNET_h_3,2, BENNET_h_3,3, BENNET_h_3,4], [BENNET_h_4,0, BENNET_h_4,1, BENNET_h_4,2, BENNET_h_4,3, BENNET_h_4,4], [BENNET_h_5,0, BENNET_h_5,1, BENNET_h_5,2, BENNET_h_5,3, BENNET_h_5,4], [BENNET_h_6,0, BENNET_h_6,1, BENNET_h_6,2, BENNET_h_6,3, BENNET_h_6,4], [BENNET_h_7,0, BENNET_h_7,1, BENNET_h_7,2, BENNET_h_7,3, BENNET_h_7,4], [BENNET_h_8,0, BENNET_h_8,1, BENNET_h_8,2, BENNET_h_8,3, BENNET_h_8,4], [BENNET_h_9,0, BENNET_h_9,1, BENNET_h_9,2, BENNET_h_9,3, BENNET_h_9,4]], [[BENNET_v_0,0, BENNET_v_0,1, BENNET_v_0,2, BENNET_v_0,3, BENNET_v_0,4, BENNET_v_0,5, BENNET_v_0,6, BENNET_v_0,7, BENNET_v_0,8, BENNET_v_0,9], [BENNET_v_1,0, BENNET_v_1,1, BENNET_v_1,2, BENNET_v_1,3, BENNET_v_1,4, BENNET_v_1,5, BENNET_v_1,6, BENNET_v_1,7, BENNET_v_1,8, BENNET_v_1,9], [BENNET_v_2,0, BENNET_v_2,1, BENNET_v_2,2, BENNET_v_2,3, BENNET_v_2,4, BENNET_v_2,5, BENNET_v_2,6, BENNET_v_2,7, BENNET_v_2,8, BENNET_v_2,9], [BENNET_v_3,0, BENNET_v_3,1, BENNET_v_3,2, BENNET_v_3,3, BENNET_v_3,4, BENNET_v_3,5, BENNET_v_3,6, BENNET_v_3,7, BENNET_v_3,8, BENNET_v_3,9], [BENNET_v_4,0, BENNET_v_4,1, BENNET_v_4,2, BENNET_v_4,3, BENNET_v_4,4, BENNET_v_4,5, BENNET_v_4,6, BENNET_v_4,7, BENNET_v_4,8, BENNET_v_4,9]]]\n", + "[[[BATH_h_0,0, BATH_h_0,1, BATH_h_0,2, BATH_h_0,3, BATH_h_0,4, BATH_h_0,5, BATH_h_0,6], [BATH_h_1,0, BATH_h_1,1, BATH_h_1,2, BATH_h_1,3, BATH_h_1,4, BATH_h_1,5, BATH_h_1,6], [BATH_h_2,0, BATH_h_2,1, BATH_h_2,2, BATH_h_2,3, BATH_h_2,4, BATH_h_2,5, BATH_h_2,6], [BATH_h_3,0, BATH_h_3,1, BATH_h_3,2, BATH_h_3,3, BATH_h_3,4, BATH_h_3,5, BATH_h_3,6], [BATH_h_4,0, BATH_h_4,1, BATH_h_4,2, BATH_h_4,3, BATH_h_4,4, BATH_h_4,5, BATH_h_4,6], [BATH_h_5,0, BATH_h_5,1, BATH_h_5,2, BATH_h_5,3, BATH_h_5,4, BATH_h_5,5, BATH_h_5,6], [BATH_h_6,0, BATH_h_6,1, BATH_h_6,2, BATH_h_6,3, BATH_h_6,4, BATH_h_6,5, BATH_h_6,6], [BATH_h_7,0, BATH_h_7,1, BATH_h_7,2, BATH_h_7,3, BATH_h_7,4, BATH_h_7,5, BATH_h_7,6], [BATH_h_8,0, BATH_h_8,1, BATH_h_8,2, BATH_h_8,3, BATH_h_8,4, BATH_h_8,5, BATH_h_8,6], [BATH_h_9,0, BATH_h_9,1, BATH_h_9,2, BATH_h_9,3, BATH_h_9,4, BATH_h_9,5, BATH_h_9,6]], [[BATH_v_0,0, BATH_v_0,1, BATH_v_0,2, BATH_v_0,3, BATH_v_0,4, BATH_v_0,5, BATH_v_0,6, BATH_v_0,7, BATH_v_0,8, BATH_v_0,9], [BATH_v_1,0, BATH_v_1,1, BATH_v_1,2, BATH_v_1,3, BATH_v_1,4, BATH_v_1,5, BATH_v_1,6, BATH_v_1,7, BATH_v_1,8, BATH_v_1,9], [BATH_v_2,0, BATH_v_2,1, BATH_v_2,2, BATH_v_2,3, BATH_v_2,4, BATH_v_2,5, BATH_v_2,6, BATH_v_2,7, BATH_v_2,8, BATH_v_2,9], [BATH_v_3,0, BATH_v_3,1, BATH_v_3,2, BATH_v_3,3, BATH_v_3,4, BATH_v_3,5, BATH_v_3,6, BATH_v_3,7, BATH_v_3,8, BATH_v_3,9], [BATH_v_4,0, BATH_v_4,1, BATH_v_4,2, BATH_v_4,3, BATH_v_4,4, BATH_v_4,5, BATH_v_4,6, BATH_v_4,7, BATH_v_4,8, BATH_v_4,9], [BATH_v_5,0, BATH_v_5,1, BATH_v_5,2, BATH_v_5,3, BATH_v_5,4, BATH_v_5,5, BATH_v_5,6, BATH_v_5,7, BATH_v_5,8, BATH_v_5,9], [BATH_v_6,0, BATH_v_6,1, BATH_v_6,2, BATH_v_6,3, BATH_v_6,4, BATH_v_6,5, BATH_v_6,6, BATH_v_6,7, BATH_v_6,8, BATH_v_6,9]]]\n", + "Executed in 1.0347 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╧═╝\n", + "[[[JANE_h_0,0, JANE_h_0,1, JANE_h_0,2, JANE_h_0,3, JANE_h_0,4, JANE_h_0,5], [JANE_h_1,0, JANE_h_1,1, JANE_h_1,2, JANE_h_1,3, JANE_h_1,4, JANE_h_1,5], [JANE_h_2,0, JANE_h_2,1, JANE_h_2,2, JANE_h_2,3, JANE_h_2,4, JANE_h_2,5], [JANE_h_3,0, JANE_h_3,1, JANE_h_3,2, JANE_h_3,3, JANE_h_3,4, JANE_h_3,5], [JANE_h_4,0, JANE_h_4,1, JANE_h_4,2, JANE_h_4,3, JANE_h_4,4, JANE_h_4,5], [JANE_h_5,0, JANE_h_5,1, JANE_h_5,2, JANE_h_5,3, JANE_h_5,4, JANE_h_5,5], [JANE_h_6,0, JANE_h_6,1, JANE_h_6,2, JANE_h_6,3, JANE_h_6,4, JANE_h_6,5], [JANE_h_7,0, JANE_h_7,1, JANE_h_7,2, JANE_h_7,3, JANE_h_7,4, JANE_h_7,5], [JANE_h_8,0, JANE_h_8,1, JANE_h_8,2, JANE_h_8,3, JANE_h_8,4, JANE_h_8,5]], [[JANE_v_0,0, JANE_v_0,1, JANE_v_0,2, JANE_v_0,3, JANE_v_0,4, JANE_v_0,5, JANE_v_0,6, JANE_v_0,7, JANE_v_0,8], [JANE_v_1,0, JANE_v_1,1, JANE_v_1,2, JANE_v_1,3, JANE_v_1,4, JANE_v_1,5, JANE_v_1,6, JANE_v_1,7, JANE_v_1,8], [JANE_v_2,0, JANE_v_2,1, JANE_v_2,2, JANE_v_2,3, JANE_v_2,4, JANE_v_2,5, JANE_v_2,6, JANE_v_2,7, JANE_v_2,8], [JANE_v_3,0, JANE_v_3,1, JANE_v_3,2, JANE_v_3,3, JANE_v_3,4, JANE_v_3,5, JANE_v_3,6, JANE_v_3,7, JANE_v_3,8], [JANE_v_4,0, JANE_v_4,1, JANE_v_4,2, JANE_v_4,3, JANE_v_4,4, JANE_v_4,5, JANE_v_4,6, JANE_v_4,7, JANE_v_4,8], [JANE_v_5,0, JANE_v_5,1, JANE_v_5,2, JANE_v_5,3, JANE_v_5,4, JANE_v_5,5, JANE_v_5,6, JANE_v_5,7, JANE_v_5,8]]]\n", + "[[[AUSTEN_h_0,0, AUSTEN_h_0,1, AUSTEN_h_0,2, AUSTEN_h_0,3], [AUSTEN_h_1,0, AUSTEN_h_1,1, AUSTEN_h_1,2, AUSTEN_h_1,3], [AUSTEN_h_2,0, AUSTEN_h_2,1, AUSTEN_h_2,2, AUSTEN_h_2,3], [AUSTEN_h_3,0, AUSTEN_h_3,1, AUSTEN_h_3,2, AUSTEN_h_3,3], [AUSTEN_h_4,0, AUSTEN_h_4,1, AUSTEN_h_4,2, AUSTEN_h_4,3], [AUSTEN_h_5,0, AUSTEN_h_5,1, AUSTEN_h_5,2, AUSTEN_h_5,3], [AUSTEN_h_6,0, AUSTEN_h_6,1, AUSTEN_h_6,2, AUSTEN_h_6,3], [AUSTEN_h_7,0, AUSTEN_h_7,1, AUSTEN_h_7,2, AUSTEN_h_7,3], [AUSTEN_h_8,0, AUSTEN_h_8,1, AUSTEN_h_8,2, AUSTEN_h_8,3]], [[AUSTEN_v_0,0, AUSTEN_v_0,1, AUSTEN_v_0,2, AUSTEN_v_0,3, AUSTEN_v_0,4, AUSTEN_v_0,5, AUSTEN_v_0,6, AUSTEN_v_0,7, AUSTEN_v_0,8], [AUSTEN_v_1,0, AUSTEN_v_1,1, AUSTEN_v_1,2, AUSTEN_v_1,3, AUSTEN_v_1,4, AUSTEN_v_1,5, AUSTEN_v_1,6, AUSTEN_v_1,7, AUSTEN_v_1,8], [AUSTEN_v_2,0, AUSTEN_v_2,1, AUSTEN_v_2,2, AUSTEN_v_2,3, AUSTEN_v_2,4, AUSTEN_v_2,5, AUSTEN_v_2,6, AUSTEN_v_2,7, AUSTEN_v_2,8], [AUSTEN_v_3,0, AUSTEN_v_3,1, AUSTEN_v_3,2, AUSTEN_v_3,3, AUSTEN_v_3,4, AUSTEN_v_3,5, AUSTEN_v_3,6, AUSTEN_v_3,7, AUSTEN_v_3,8]]]\n", + "[[[PRIDE_h_0,0, PRIDE_h_0,1, PRIDE_h_0,2, PRIDE_h_0,3, PRIDE_h_0,4], [PRIDE_h_1,0, PRIDE_h_1,1, PRIDE_h_1,2, PRIDE_h_1,3, PRIDE_h_1,4], [PRIDE_h_2,0, PRIDE_h_2,1, PRIDE_h_2,2, PRIDE_h_2,3, PRIDE_h_2,4], [PRIDE_h_3,0, PRIDE_h_3,1, PRIDE_h_3,2, PRIDE_h_3,3, PRIDE_h_3,4], [PRIDE_h_4,0, PRIDE_h_4,1, PRIDE_h_4,2, PRIDE_h_4,3, PRIDE_h_4,4], [PRIDE_h_5,0, PRIDE_h_5,1, PRIDE_h_5,2, PRIDE_h_5,3, PRIDE_h_5,4], [PRIDE_h_6,0, PRIDE_h_6,1, PRIDE_h_6,2, PRIDE_h_6,3, PRIDE_h_6,4], [PRIDE_h_7,0, PRIDE_h_7,1, PRIDE_h_7,2, PRIDE_h_7,3, PRIDE_h_7,4], [PRIDE_h_8,0, PRIDE_h_8,1, PRIDE_h_8,2, PRIDE_h_8,3, PRIDE_h_8,4]], [[PRIDE_v_0,0, PRIDE_v_0,1, PRIDE_v_0,2, PRIDE_v_0,3, PRIDE_v_0,4, PRIDE_v_0,5, PRIDE_v_0,6, PRIDE_v_0,7, PRIDE_v_0,8], [PRIDE_v_1,0, PRIDE_v_1,1, PRIDE_v_1,2, PRIDE_v_1,3, PRIDE_v_1,4, PRIDE_v_1,5, PRIDE_v_1,6, PRIDE_v_1,7, PRIDE_v_1,8], [PRIDE_v_2,0, PRIDE_v_2,1, PRIDE_v_2,2, PRIDE_v_2,3, PRIDE_v_2,4, PRIDE_v_2,5, PRIDE_v_2,6, PRIDE_v_2,7, PRIDE_v_2,8], [PRIDE_v_3,0, PRIDE_v_3,1, PRIDE_v_3,2, PRIDE_v_3,3, PRIDE_v_3,4, PRIDE_v_3,5, PRIDE_v_3,6, PRIDE_v_3,7, PRIDE_v_3,8], [PRIDE_v_4,0, PRIDE_v_4,1, PRIDE_v_4,2, PRIDE_v_4,3, PRIDE_v_4,4, PRIDE_v_4,5, PRIDE_v_4,6, PRIDE_v_4,7, PRIDE_v_4,8]]]\n", + "[[[NOVEL_h_0,0, NOVEL_h_0,1, NOVEL_h_0,2, NOVEL_h_0,3, NOVEL_h_0,4], [NOVEL_h_1,0, NOVEL_h_1,1, NOVEL_h_1,2, NOVEL_h_1,3, NOVEL_h_1,4], [NOVEL_h_2,0, NOVEL_h_2,1, NOVEL_h_2,2, NOVEL_h_2,3, NOVEL_h_2,4], [NOVEL_h_3,0, NOVEL_h_3,1, NOVEL_h_3,2, NOVEL_h_3,3, NOVEL_h_3,4], [NOVEL_h_4,0, NOVEL_h_4,1, NOVEL_h_4,2, NOVEL_h_4,3, NOVEL_h_4,4], [NOVEL_h_5,0, NOVEL_h_5,1, NOVEL_h_5,2, NOVEL_h_5,3, NOVEL_h_5,4], [NOVEL_h_6,0, NOVEL_h_6,1, NOVEL_h_6,2, NOVEL_h_6,3, NOVEL_h_6,4], [NOVEL_h_7,0, NOVEL_h_7,1, NOVEL_h_7,2, NOVEL_h_7,3, NOVEL_h_7,4], [NOVEL_h_8,0, NOVEL_h_8,1, NOVEL_h_8,2, NOVEL_h_8,3, NOVEL_h_8,4]], [[NOVEL_v_0,0, NOVEL_v_0,1, NOVEL_v_0,2, NOVEL_v_0,3, NOVEL_v_0,4, NOVEL_v_0,5, NOVEL_v_0,6, NOVEL_v_0,7, NOVEL_v_0,8], [NOVEL_v_1,0, NOVEL_v_1,1, NOVEL_v_1,2, NOVEL_v_1,3, NOVEL_v_1,4, NOVEL_v_1,5, NOVEL_v_1,6, NOVEL_v_1,7, NOVEL_v_1,8], [NOVEL_v_2,0, NOVEL_v_2,1, NOVEL_v_2,2, NOVEL_v_2,3, NOVEL_v_2,4, NOVEL_v_2,5, NOVEL_v_2,6, NOVEL_v_2,7, NOVEL_v_2,8], [NOVEL_v_3,0, NOVEL_v_3,1, NOVEL_v_3,2, NOVEL_v_3,3, NOVEL_v_3,4, NOVEL_v_3,5, NOVEL_v_3,6, NOVEL_v_3,7, NOVEL_v_3,8], [NOVEL_v_4,0, NOVEL_v_4,1, NOVEL_v_4,2, NOVEL_v_4,3, NOVEL_v_4,4, NOVEL_v_4,5, NOVEL_v_4,6, NOVEL_v_4,7, NOVEL_v_4,8]]]\n", + "[[[DARCY_h_0,0, DARCY_h_0,1, DARCY_h_0,2, DARCY_h_0,3, DARCY_h_0,4], [DARCY_h_1,0, DARCY_h_1,1, DARCY_h_1,2, DARCY_h_1,3, DARCY_h_1,4], [DARCY_h_2,0, DARCY_h_2,1, DARCY_h_2,2, DARCY_h_2,3, DARCY_h_2,4], [DARCY_h_3,0, DARCY_h_3,1, DARCY_h_3,2, DARCY_h_3,3, DARCY_h_3,4], [DARCY_h_4,0, DARCY_h_4,1, DARCY_h_4,2, DARCY_h_4,3, DARCY_h_4,4], [DARCY_h_5,0, DARCY_h_5,1, DARCY_h_5,2, DARCY_h_5,3, DARCY_h_5,4], [DARCY_h_6,0, DARCY_h_6,1, DARCY_h_6,2, DARCY_h_6,3, DARCY_h_6,4], [DARCY_h_7,0, DARCY_h_7,1, DARCY_h_7,2, DARCY_h_7,3, DARCY_h_7,4], [DARCY_h_8,0, DARCY_h_8,1, DARCY_h_8,2, DARCY_h_8,3, DARCY_h_8,4]], [[DARCY_v_0,0, DARCY_v_0,1, DARCY_v_0,2, DARCY_v_0,3, DARCY_v_0,4, DARCY_v_0,5, DARCY_v_0,6, DARCY_v_0,7, DARCY_v_0,8], [DARCY_v_1,0, DARCY_v_1,1, DARCY_v_1,2, DARCY_v_1,3, DARCY_v_1,4, DARCY_v_1,5, DARCY_v_1,6, DARCY_v_1,7, DARCY_v_1,8], [DARCY_v_2,0, DARCY_v_2,1, DARCY_v_2,2, DARCY_v_2,3, DARCY_v_2,4, DARCY_v_2,5, DARCY_v_2,6, DARCY_v_2,7, DARCY_v_2,8], [DARCY_v_3,0, DARCY_v_3,1, DARCY_v_3,2, DARCY_v_3,3, DARCY_v_3,4, DARCY_v_3,5, DARCY_v_3,6, DARCY_v_3,7, DARCY_v_3,8], [DARCY_v_4,0, DARCY_v_4,1, DARCY_v_4,2, DARCY_v_4,3, DARCY_v_4,4, DARCY_v_4,5, DARCY_v_4,6, DARCY_v_4,7, DARCY_v_4,8]]]\n", + "[[[SENSE_h_0,0, SENSE_h_0,1, SENSE_h_0,2, SENSE_h_0,3, SENSE_h_0,4], [SENSE_h_1,0, SENSE_h_1,1, SENSE_h_1,2, SENSE_h_1,3, SENSE_h_1,4], [SENSE_h_2,0, SENSE_h_2,1, SENSE_h_2,2, SENSE_h_2,3, SENSE_h_2,4], [SENSE_h_3,0, SENSE_h_3,1, SENSE_h_3,2, SENSE_h_3,3, SENSE_h_3,4], [SENSE_h_4,0, SENSE_h_4,1, SENSE_h_4,2, SENSE_h_4,3, SENSE_h_4,4], [SENSE_h_5,0, SENSE_h_5,1, SENSE_h_5,2, SENSE_h_5,3, SENSE_h_5,4], [SENSE_h_6,0, SENSE_h_6,1, SENSE_h_6,2, SENSE_h_6,3, SENSE_h_6,4], [SENSE_h_7,0, SENSE_h_7,1, SENSE_h_7,2, SENSE_h_7,3, SENSE_h_7,4], [SENSE_h_8,0, SENSE_h_8,1, SENSE_h_8,2, SENSE_h_8,3, SENSE_h_8,4]], [[SENSE_v_0,0, SENSE_v_0,1, SENSE_v_0,2, SENSE_v_0,3, SENSE_v_0,4, SENSE_v_0,5, SENSE_v_0,6, SENSE_v_0,7, SENSE_v_0,8], [SENSE_v_1,0, SENSE_v_1,1, SENSE_v_1,2, SENSE_v_1,3, SENSE_v_1,4, SENSE_v_1,5, SENSE_v_1,6, SENSE_v_1,7, SENSE_v_1,8], [SENSE_v_2,0, SENSE_v_2,1, SENSE_v_2,2, SENSE_v_2,3, SENSE_v_2,4, SENSE_v_2,5, SENSE_v_2,6, SENSE_v_2,7, SENSE_v_2,8], [SENSE_v_3,0, SENSE_v_3,1, SENSE_v_3,2, SENSE_v_3,3, SENSE_v_3,4, SENSE_v_3,5, SENSE_v_3,6, SENSE_v_3,7, SENSE_v_3,8], [SENSE_v_4,0, SENSE_v_4,1, SENSE_v_4,2, SENSE_v_4,3, SENSE_v_4,4, SENSE_v_4,5, SENSE_v_4,6, SENSE_v_4,7, SENSE_v_4,8]]]\n", + "[[[EMMA_h_0,0, EMMA_h_0,1, EMMA_h_0,2, EMMA_h_0,3, EMMA_h_0,4, EMMA_h_0,5], [EMMA_h_1,0, EMMA_h_1,1, EMMA_h_1,2, EMMA_h_1,3, EMMA_h_1,4, EMMA_h_1,5], [EMMA_h_2,0, EMMA_h_2,1, EMMA_h_2,2, EMMA_h_2,3, EMMA_h_2,4, EMMA_h_2,5], [EMMA_h_3,0, EMMA_h_3,1, EMMA_h_3,2, EMMA_h_3,3, EMMA_h_3,4, EMMA_h_3,5], [EMMA_h_4,0, EMMA_h_4,1, EMMA_h_4,2, EMMA_h_4,3, EMMA_h_4,4, EMMA_h_4,5], [EMMA_h_5,0, EMMA_h_5,1, EMMA_h_5,2, EMMA_h_5,3, EMMA_h_5,4, EMMA_h_5,5], [EMMA_h_6,0, EMMA_h_6,1, EMMA_h_6,2, EMMA_h_6,3, EMMA_h_6,4, EMMA_h_6,5], [EMMA_h_7,0, EMMA_h_7,1, EMMA_h_7,2, EMMA_h_7,3, EMMA_h_7,4, EMMA_h_7,5], [EMMA_h_8,0, EMMA_h_8,1, EMMA_h_8,2, EMMA_h_8,3, EMMA_h_8,4, EMMA_h_8,5]], [[EMMA_v_0,0, EMMA_v_0,1, EMMA_v_0,2, EMMA_v_0,3, EMMA_v_0,4, EMMA_v_0,5, EMMA_v_0,6, EMMA_v_0,7, EMMA_v_0,8], [EMMA_v_1,0, EMMA_v_1,1, EMMA_v_1,2, EMMA_v_1,3, EMMA_v_1,4, EMMA_v_1,5, EMMA_v_1,6, EMMA_v_1,7, EMMA_v_1,8], [EMMA_v_2,0, EMMA_v_2,1, EMMA_v_2,2, EMMA_v_2,3, EMMA_v_2,4, EMMA_v_2,5, EMMA_v_2,6, EMMA_v_2,7, EMMA_v_2,8], [EMMA_v_3,0, EMMA_v_3,1, EMMA_v_3,2, EMMA_v_3,3, EMMA_v_3,4, EMMA_v_3,5, EMMA_v_3,6, EMMA_v_3,7, EMMA_v_3,8], [EMMA_v_4,0, EMMA_v_4,1, EMMA_v_4,2, EMMA_v_4,3, EMMA_v_4,4, EMMA_v_4,5, EMMA_v_4,6, EMMA_v_4,7, EMMA_v_4,8], [EMMA_v_5,0, EMMA_v_5,1, EMMA_v_5,2, EMMA_v_5,3, EMMA_v_5,4, EMMA_v_5,5, EMMA_v_5,6, EMMA_v_5,7, EMMA_v_5,8]]]\n", + "[[[ESTATE_h_0,0, ESTATE_h_0,1, ESTATE_h_0,2, ESTATE_h_0,3], [ESTATE_h_1,0, ESTATE_h_1,1, ESTATE_h_1,2, ESTATE_h_1,3], [ESTATE_h_2,0, ESTATE_h_2,1, ESTATE_h_2,2, ESTATE_h_2,3], [ESTATE_h_3,0, ESTATE_h_3,1, ESTATE_h_3,2, ESTATE_h_3,3], [ESTATE_h_4,0, ESTATE_h_4,1, ESTATE_h_4,2, ESTATE_h_4,3], [ESTATE_h_5,0, ESTATE_h_5,1, ESTATE_h_5,2, ESTATE_h_5,3], [ESTATE_h_6,0, ESTATE_h_6,1, ESTATE_h_6,2, ESTATE_h_6,3], [ESTATE_h_7,0, ESTATE_h_7,1, ESTATE_h_7,2, ESTATE_h_7,3], [ESTATE_h_8,0, ESTATE_h_8,1, ESTATE_h_8,2, ESTATE_h_8,3]], [[ESTATE_v_0,0, ESTATE_v_0,1, ESTATE_v_0,2, ESTATE_v_0,3, ESTATE_v_0,4, ESTATE_v_0,5, ESTATE_v_0,6, ESTATE_v_0,7, ESTATE_v_0,8], [ESTATE_v_1,0, ESTATE_v_1,1, ESTATE_v_1,2, ESTATE_v_1,3, ESTATE_v_1,4, ESTATE_v_1,5, ESTATE_v_1,6, ESTATE_v_1,7, ESTATE_v_1,8], [ESTATE_v_2,0, ESTATE_v_2,1, ESTATE_v_2,2, ESTATE_v_2,3, ESTATE_v_2,4, ESTATE_v_2,5, ESTATE_v_2,6, ESTATE_v_2,7, ESTATE_v_2,8], [ESTATE_v_3,0, ESTATE_v_3,1, ESTATE_v_3,2, ESTATE_v_3,3, ESTATE_v_3,4, ESTATE_v_3,5, ESTATE_v_3,6, ESTATE_v_3,7, ESTATE_v_3,8]]]\n", + "[[[BENNET_h_0,0, BENNET_h_0,1, BENNET_h_0,2, BENNET_h_0,3], [BENNET_h_1,0, BENNET_h_1,1, BENNET_h_1,2, BENNET_h_1,3], [BENNET_h_2,0, BENNET_h_2,1, BENNET_h_2,2, BENNET_h_2,3], [BENNET_h_3,0, BENNET_h_3,1, BENNET_h_3,2, BENNET_h_3,3], [BENNET_h_4,0, BENNET_h_4,1, BENNET_h_4,2, BENNET_h_4,3], [BENNET_h_5,0, BENNET_h_5,1, BENNET_h_5,2, BENNET_h_5,3], [BENNET_h_6,0, BENNET_h_6,1, BENNET_h_6,2, BENNET_h_6,3], [BENNET_h_7,0, BENNET_h_7,1, BENNET_h_7,2, BENNET_h_7,3], [BENNET_h_8,0, BENNET_h_8,1, BENNET_h_8,2, BENNET_h_8,3]], [[BENNET_v_0,0, BENNET_v_0,1, BENNET_v_0,2, BENNET_v_0,3, BENNET_v_0,4, BENNET_v_0,5, BENNET_v_0,6, BENNET_v_0,7, BENNET_v_0,8], [BENNET_v_1,0, BENNET_v_1,1, BENNET_v_1,2, BENNET_v_1,3, BENNET_v_1,4, BENNET_v_1,5, BENNET_v_1,6, BENNET_v_1,7, BENNET_v_1,8], [BENNET_v_2,0, BENNET_v_2,1, BENNET_v_2,2, BENNET_v_2,3, BENNET_v_2,4, BENNET_v_2,5, BENNET_v_2,6, BENNET_v_2,7, BENNET_v_2,8], [BENNET_v_3,0, BENNET_v_3,1, BENNET_v_3,2, BENNET_v_3,3, BENNET_v_3,4, BENNET_v_3,5, BENNET_v_3,6, BENNET_v_3,7, BENNET_v_3,8]]]\n", + "[[[BATH_h_0,0, BATH_h_0,1, BATH_h_0,2, BATH_h_0,3, BATH_h_0,4, BATH_h_0,5], [BATH_h_1,0, BATH_h_1,1, BATH_h_1,2, BATH_h_1,3, BATH_h_1,4, BATH_h_1,5], [BATH_h_2,0, BATH_h_2,1, BATH_h_2,2, BATH_h_2,3, BATH_h_2,4, BATH_h_2,5], [BATH_h_3,0, BATH_h_3,1, BATH_h_3,2, BATH_h_3,3, BATH_h_3,4, BATH_h_3,5], [BATH_h_4,0, BATH_h_4,1, BATH_h_4,2, BATH_h_4,3, BATH_h_4,4, BATH_h_4,5], [BATH_h_5,0, BATH_h_5,1, BATH_h_5,2, BATH_h_5,3, BATH_h_5,4, BATH_h_5,5], [BATH_h_6,0, BATH_h_6,1, BATH_h_6,2, BATH_h_6,3, BATH_h_6,4, BATH_h_6,5], [BATH_h_7,0, BATH_h_7,1, BATH_h_7,2, BATH_h_7,3, BATH_h_7,4, BATH_h_7,5], [BATH_h_8,0, BATH_h_8,1, BATH_h_8,2, BATH_h_8,3, BATH_h_8,4, BATH_h_8,5]], [[BATH_v_0,0, BATH_v_0,1, BATH_v_0,2, BATH_v_0,3, BATH_v_0,4, BATH_v_0,5, BATH_v_0,6, BATH_v_0,7, BATH_v_0,8], [BATH_v_1,0, BATH_v_1,1, BATH_v_1,2, BATH_v_1,3, BATH_v_1,4, BATH_v_1,5, BATH_v_1,6, BATH_v_1,7, BATH_v_1,8], [BATH_v_2,0, BATH_v_2,1, BATH_v_2,2, BATH_v_2,3, BATH_v_2,4, BATH_v_2,5, BATH_v_2,6, BATH_v_2,7, BATH_v_2,8], [BATH_v_3,0, BATH_v_3,1, BATH_v_3,2, BATH_v_3,3, BATH_v_3,4, BATH_v_3,5, BATH_v_3,6, BATH_v_3,7, BATH_v_3,8], [BATH_v_4,0, BATH_v_4,1, BATH_v_4,2, BATH_v_4,3, BATH_v_4,4, BATH_v_4,5, BATH_v_4,6, BATH_v_4,7, BATH_v_4,8], [BATH_v_5,0, BATH_v_5,1, BATH_v_5,2, BATH_v_5,3, BATH_v_5,4, BATH_v_5,5, BATH_v_5,6, BATH_v_5,7, BATH_v_5,8]]]\n", + "Executed in 0.8997 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╝\n", + "[[[JANE_h_0,0, JANE_h_0,1, JANE_h_0,2, JANE_h_0,3, JANE_h_0,4], [JANE_h_1,0, JANE_h_1,1, JANE_h_1,2, JANE_h_1,3, JANE_h_1,4], [JANE_h_2,0, JANE_h_2,1, JANE_h_2,2, JANE_h_2,3, JANE_h_2,4], [JANE_h_3,0, JANE_h_3,1, JANE_h_3,2, JANE_h_3,3, JANE_h_3,4], [JANE_h_4,0, JANE_h_4,1, JANE_h_4,2, JANE_h_4,3, JANE_h_4,4], [JANE_h_5,0, JANE_h_5,1, JANE_h_5,2, JANE_h_5,3, JANE_h_5,4], [JANE_h_6,0, JANE_h_6,1, JANE_h_6,2, JANE_h_6,3, JANE_h_6,4], [JANE_h_7,0, JANE_h_7,1, JANE_h_7,2, JANE_h_7,3, JANE_h_7,4]], [[JANE_v_0,0, JANE_v_0,1, JANE_v_0,2, JANE_v_0,3, JANE_v_0,4, JANE_v_0,5, JANE_v_0,6, JANE_v_0,7], [JANE_v_1,0, JANE_v_1,1, JANE_v_1,2, JANE_v_1,3, JANE_v_1,4, JANE_v_1,5, JANE_v_1,6, JANE_v_1,7], [JANE_v_2,0, JANE_v_2,1, JANE_v_2,2, JANE_v_2,3, JANE_v_2,4, JANE_v_2,5, JANE_v_2,6, JANE_v_2,7], [JANE_v_3,0, JANE_v_3,1, JANE_v_3,2, JANE_v_3,3, JANE_v_3,4, JANE_v_3,5, JANE_v_3,6, JANE_v_3,7], [JANE_v_4,0, JANE_v_4,1, JANE_v_4,2, JANE_v_4,3, JANE_v_4,4, JANE_v_4,5, JANE_v_4,6, JANE_v_4,7]]]\n", + "[[[AUSTEN_h_0,0, AUSTEN_h_0,1, AUSTEN_h_0,2], [AUSTEN_h_1,0, AUSTEN_h_1,1, AUSTEN_h_1,2], [AUSTEN_h_2,0, AUSTEN_h_2,1, AUSTEN_h_2,2], [AUSTEN_h_3,0, AUSTEN_h_3,1, AUSTEN_h_3,2], [AUSTEN_h_4,0, AUSTEN_h_4,1, AUSTEN_h_4,2], [AUSTEN_h_5,0, AUSTEN_h_5,1, AUSTEN_h_5,2], [AUSTEN_h_6,0, AUSTEN_h_6,1, AUSTEN_h_6,2], [AUSTEN_h_7,0, AUSTEN_h_7,1, AUSTEN_h_7,2]], [[AUSTEN_v_0,0, AUSTEN_v_0,1, AUSTEN_v_0,2, AUSTEN_v_0,3, AUSTEN_v_0,4, AUSTEN_v_0,5, AUSTEN_v_0,6, AUSTEN_v_0,7], [AUSTEN_v_1,0, AUSTEN_v_1,1, AUSTEN_v_1,2, AUSTEN_v_1,3, AUSTEN_v_1,4, AUSTEN_v_1,5, AUSTEN_v_1,6, AUSTEN_v_1,7], [AUSTEN_v_2,0, AUSTEN_v_2,1, AUSTEN_v_2,2, AUSTEN_v_2,3, AUSTEN_v_2,4, AUSTEN_v_2,5, AUSTEN_v_2,6, AUSTEN_v_2,7]]]\n", + "[[[PRIDE_h_0,0, PRIDE_h_0,1, PRIDE_h_0,2, PRIDE_h_0,3], [PRIDE_h_1,0, PRIDE_h_1,1, PRIDE_h_1,2, PRIDE_h_1,3], [PRIDE_h_2,0, PRIDE_h_2,1, PRIDE_h_2,2, PRIDE_h_2,3], [PRIDE_h_3,0, PRIDE_h_3,1, PRIDE_h_3,2, PRIDE_h_3,3], [PRIDE_h_4,0, PRIDE_h_4,1, PRIDE_h_4,2, PRIDE_h_4,3], [PRIDE_h_5,0, PRIDE_h_5,1, PRIDE_h_5,2, PRIDE_h_5,3], [PRIDE_h_6,0, PRIDE_h_6,1, PRIDE_h_6,2, PRIDE_h_6,3], [PRIDE_h_7,0, PRIDE_h_7,1, PRIDE_h_7,2, PRIDE_h_7,3]], [[PRIDE_v_0,0, PRIDE_v_0,1, PRIDE_v_0,2, PRIDE_v_0,3, PRIDE_v_0,4, PRIDE_v_0,5, PRIDE_v_0,6, PRIDE_v_0,7], [PRIDE_v_1,0, PRIDE_v_1,1, PRIDE_v_1,2, PRIDE_v_1,3, PRIDE_v_1,4, PRIDE_v_1,5, PRIDE_v_1,6, PRIDE_v_1,7], [PRIDE_v_2,0, PRIDE_v_2,1, PRIDE_v_2,2, PRIDE_v_2,3, PRIDE_v_2,4, PRIDE_v_2,5, PRIDE_v_2,6, PRIDE_v_2,7], [PRIDE_v_3,0, PRIDE_v_3,1, PRIDE_v_3,2, PRIDE_v_3,3, PRIDE_v_3,4, PRIDE_v_3,5, PRIDE_v_3,6, PRIDE_v_3,7]]]\n", + "[[[NOVEL_h_0,0, NOVEL_h_0,1, NOVEL_h_0,2, NOVEL_h_0,3], [NOVEL_h_1,0, NOVEL_h_1,1, NOVEL_h_1,2, NOVEL_h_1,3], [NOVEL_h_2,0, NOVEL_h_2,1, NOVEL_h_2,2, NOVEL_h_2,3], [NOVEL_h_3,0, NOVEL_h_3,1, NOVEL_h_3,2, NOVEL_h_3,3], [NOVEL_h_4,0, NOVEL_h_4,1, NOVEL_h_4,2, NOVEL_h_4,3], [NOVEL_h_5,0, NOVEL_h_5,1, NOVEL_h_5,2, NOVEL_h_5,3], [NOVEL_h_6,0, NOVEL_h_6,1, NOVEL_h_6,2, NOVEL_h_6,3], [NOVEL_h_7,0, NOVEL_h_7,1, NOVEL_h_7,2, NOVEL_h_7,3]], [[NOVEL_v_0,0, NOVEL_v_0,1, NOVEL_v_0,2, NOVEL_v_0,3, NOVEL_v_0,4, NOVEL_v_0,5, NOVEL_v_0,6, NOVEL_v_0,7], [NOVEL_v_1,0, NOVEL_v_1,1, NOVEL_v_1,2, NOVEL_v_1,3, NOVEL_v_1,4, NOVEL_v_1,5, NOVEL_v_1,6, NOVEL_v_1,7], [NOVEL_v_2,0, NOVEL_v_2,1, NOVEL_v_2,2, NOVEL_v_2,3, NOVEL_v_2,4, NOVEL_v_2,5, NOVEL_v_2,6, NOVEL_v_2,7], [NOVEL_v_3,0, NOVEL_v_3,1, NOVEL_v_3,2, NOVEL_v_3,3, NOVEL_v_3,4, NOVEL_v_3,5, NOVEL_v_3,6, NOVEL_v_3,7]]]\n", + "[[[DARCY_h_0,0, DARCY_h_0,1, DARCY_h_0,2, DARCY_h_0,3], [DARCY_h_1,0, DARCY_h_1,1, DARCY_h_1,2, DARCY_h_1,3], [DARCY_h_2,0, DARCY_h_2,1, DARCY_h_2,2, DARCY_h_2,3], [DARCY_h_3,0, DARCY_h_3,1, DARCY_h_3,2, DARCY_h_3,3], [DARCY_h_4,0, DARCY_h_4,1, DARCY_h_4,2, DARCY_h_4,3], [DARCY_h_5,0, DARCY_h_5,1, DARCY_h_5,2, DARCY_h_5,3], [DARCY_h_6,0, DARCY_h_6,1, DARCY_h_6,2, DARCY_h_6,3], [DARCY_h_7,0, DARCY_h_7,1, DARCY_h_7,2, DARCY_h_7,3]], [[DARCY_v_0,0, DARCY_v_0,1, DARCY_v_0,2, DARCY_v_0,3, DARCY_v_0,4, DARCY_v_0,5, DARCY_v_0,6, DARCY_v_0,7], [DARCY_v_1,0, DARCY_v_1,1, DARCY_v_1,2, DARCY_v_1,3, DARCY_v_1,4, DARCY_v_1,5, DARCY_v_1,6, DARCY_v_1,7], [DARCY_v_2,0, DARCY_v_2,1, DARCY_v_2,2, DARCY_v_2,3, DARCY_v_2,4, DARCY_v_2,5, DARCY_v_2,6, DARCY_v_2,7], [DARCY_v_3,0, DARCY_v_3,1, DARCY_v_3,2, DARCY_v_3,3, DARCY_v_3,4, DARCY_v_3,5, DARCY_v_3,6, DARCY_v_3,7]]]\n", + "[[[SENSE_h_0,0, SENSE_h_0,1, SENSE_h_0,2, SENSE_h_0,3], [SENSE_h_1,0, SENSE_h_1,1, SENSE_h_1,2, SENSE_h_1,3], [SENSE_h_2,0, SENSE_h_2,1, SENSE_h_2,2, SENSE_h_2,3], [SENSE_h_3,0, SENSE_h_3,1, SENSE_h_3,2, SENSE_h_3,3], [SENSE_h_4,0, SENSE_h_4,1, SENSE_h_4,2, SENSE_h_4,3], [SENSE_h_5,0, SENSE_h_5,1, SENSE_h_5,2, SENSE_h_5,3], [SENSE_h_6,0, SENSE_h_6,1, SENSE_h_6,2, SENSE_h_6,3], [SENSE_h_7,0, SENSE_h_7,1, SENSE_h_7,2, SENSE_h_7,3]], [[SENSE_v_0,0, SENSE_v_0,1, SENSE_v_0,2, SENSE_v_0,3, SENSE_v_0,4, SENSE_v_0,5, SENSE_v_0,6, SENSE_v_0,7], [SENSE_v_1,0, SENSE_v_1,1, SENSE_v_1,2, SENSE_v_1,3, SENSE_v_1,4, SENSE_v_1,5, SENSE_v_1,6, SENSE_v_1,7], [SENSE_v_2,0, SENSE_v_2,1, SENSE_v_2,2, SENSE_v_2,3, SENSE_v_2,4, SENSE_v_2,5, SENSE_v_2,6, SENSE_v_2,7], [SENSE_v_3,0, SENSE_v_3,1, SENSE_v_3,2, SENSE_v_3,3, SENSE_v_3,4, SENSE_v_3,5, SENSE_v_3,6, SENSE_v_3,7]]]\n", + "[[[EMMA_h_0,0, EMMA_h_0,1, EMMA_h_0,2, EMMA_h_0,3, EMMA_h_0,4], [EMMA_h_1,0, EMMA_h_1,1, EMMA_h_1,2, EMMA_h_1,3, EMMA_h_1,4], [EMMA_h_2,0, EMMA_h_2,1, EMMA_h_2,2, EMMA_h_2,3, EMMA_h_2,4], [EMMA_h_3,0, EMMA_h_3,1, EMMA_h_3,2, EMMA_h_3,3, EMMA_h_3,4], [EMMA_h_4,0, EMMA_h_4,1, EMMA_h_4,2, EMMA_h_4,3, EMMA_h_4,4], [EMMA_h_5,0, EMMA_h_5,1, EMMA_h_5,2, EMMA_h_5,3, EMMA_h_5,4], [EMMA_h_6,0, EMMA_h_6,1, EMMA_h_6,2, EMMA_h_6,3, EMMA_h_6,4], [EMMA_h_7,0, EMMA_h_7,1, EMMA_h_7,2, EMMA_h_7,3, EMMA_h_7,4]], [[EMMA_v_0,0, EMMA_v_0,1, EMMA_v_0,2, EMMA_v_0,3, EMMA_v_0,4, EMMA_v_0,5, EMMA_v_0,6, EMMA_v_0,7], [EMMA_v_1,0, EMMA_v_1,1, EMMA_v_1,2, EMMA_v_1,3, EMMA_v_1,4, EMMA_v_1,5, EMMA_v_1,6, EMMA_v_1,7], [EMMA_v_2,0, EMMA_v_2,1, EMMA_v_2,2, EMMA_v_2,3, EMMA_v_2,4, EMMA_v_2,5, EMMA_v_2,6, EMMA_v_2,7], [EMMA_v_3,0, EMMA_v_3,1, EMMA_v_3,2, EMMA_v_3,3, EMMA_v_3,4, EMMA_v_3,5, EMMA_v_3,6, EMMA_v_3,7], [EMMA_v_4,0, EMMA_v_4,1, EMMA_v_4,2, EMMA_v_4,3, EMMA_v_4,4, EMMA_v_4,5, EMMA_v_4,6, EMMA_v_4,7]]]\n", + "[[[ESTATE_h_0,0, ESTATE_h_0,1, ESTATE_h_0,2], [ESTATE_h_1,0, ESTATE_h_1,1, ESTATE_h_1,2], [ESTATE_h_2,0, ESTATE_h_2,1, ESTATE_h_2,2], [ESTATE_h_3,0, ESTATE_h_3,1, ESTATE_h_3,2], [ESTATE_h_4,0, ESTATE_h_4,1, ESTATE_h_4,2], [ESTATE_h_5,0, ESTATE_h_5,1, ESTATE_h_5,2], [ESTATE_h_6,0, ESTATE_h_6,1, ESTATE_h_6,2], [ESTATE_h_7,0, ESTATE_h_7,1, ESTATE_h_7,2]], [[ESTATE_v_0,0, ESTATE_v_0,1, ESTATE_v_0,2, ESTATE_v_0,3, ESTATE_v_0,4, ESTATE_v_0,5, ESTATE_v_0,6, ESTATE_v_0,7], [ESTATE_v_1,0, ESTATE_v_1,1, ESTATE_v_1,2, ESTATE_v_1,3, ESTATE_v_1,4, ESTATE_v_1,5, ESTATE_v_1,6, ESTATE_v_1,7], [ESTATE_v_2,0, ESTATE_v_2,1, ESTATE_v_2,2, ESTATE_v_2,3, ESTATE_v_2,4, ESTATE_v_2,5, ESTATE_v_2,6, ESTATE_v_2,7]]]\n", + "[[[BENNET_h_0,0, BENNET_h_0,1, BENNET_h_0,2], [BENNET_h_1,0, BENNET_h_1,1, BENNET_h_1,2], [BENNET_h_2,0, BENNET_h_2,1, BENNET_h_2,2], [BENNET_h_3,0, BENNET_h_3,1, BENNET_h_3,2], [BENNET_h_4,0, BENNET_h_4,1, BENNET_h_4,2], [BENNET_h_5,0, BENNET_h_5,1, BENNET_h_5,2], [BENNET_h_6,0, BENNET_h_6,1, BENNET_h_6,2], [BENNET_h_7,0, BENNET_h_7,1, BENNET_h_7,2]], [[BENNET_v_0,0, BENNET_v_0,1, BENNET_v_0,2, BENNET_v_0,3, BENNET_v_0,4, BENNET_v_0,5, BENNET_v_0,6, BENNET_v_0,7], [BENNET_v_1,0, BENNET_v_1,1, BENNET_v_1,2, BENNET_v_1,3, BENNET_v_1,4, BENNET_v_1,5, BENNET_v_1,6, BENNET_v_1,7], [BENNET_v_2,0, BENNET_v_2,1, BENNET_v_2,2, BENNET_v_2,3, BENNET_v_2,4, BENNET_v_2,5, BENNET_v_2,6, BENNET_v_2,7]]]\n", + "[[[BATH_h_0,0, BATH_h_0,1, BATH_h_0,2, BATH_h_0,3, BATH_h_0,4], [BATH_h_1,0, BATH_h_1,1, BATH_h_1,2, BATH_h_1,3, BATH_h_1,4], [BATH_h_2,0, BATH_h_2,1, BATH_h_2,2, BATH_h_2,3, BATH_h_2,4], [BATH_h_3,0, BATH_h_3,1, BATH_h_3,2, BATH_h_3,3, BATH_h_3,4], [BATH_h_4,0, BATH_h_4,1, BATH_h_4,2, BATH_h_4,3, BATH_h_4,4], [BATH_h_5,0, BATH_h_5,1, BATH_h_5,2, BATH_h_5,3, BATH_h_5,4], [BATH_h_6,0, BATH_h_6,1, BATH_h_6,2, BATH_h_6,3, BATH_h_6,4], [BATH_h_7,0, BATH_h_7,1, BATH_h_7,2, BATH_h_7,3, BATH_h_7,4]], [[BATH_v_0,0, BATH_v_0,1, BATH_v_0,2, BATH_v_0,3, BATH_v_0,4, BATH_v_0,5, BATH_v_0,6, BATH_v_0,7], [BATH_v_1,0, BATH_v_1,1, BATH_v_1,2, BATH_v_1,3, BATH_v_1,4, BATH_v_1,5, BATH_v_1,6, BATH_v_1,7], [BATH_v_2,0, BATH_v_2,1, BATH_v_2,2, BATH_v_2,3, BATH_v_2,4, BATH_v_2,5, BATH_v_2,6, BATH_v_2,7], [BATH_v_3,0, BATH_v_3,1, BATH_v_3,2, BATH_v_3,3, BATH_v_3,4, BATH_v_3,5, BATH_v_3,6, BATH_v_3,7], [BATH_v_4,0, BATH_v_4,1, BATH_v_4,2, BATH_v_4,3, BATH_v_4,4, BATH_v_4,5, BATH_v_4,6, BATH_v_4,7]]]\n", + "Executed in 0.8943 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╝\n", + "[[[JANE_h_0,0, JANE_h_0,1, JANE_h_0,2, JANE_h_0,3], [JANE_h_1,0, JANE_h_1,1, JANE_h_1,2, JANE_h_1,3], [JANE_h_2,0, JANE_h_2,1, JANE_h_2,2, JANE_h_2,3], [JANE_h_3,0, JANE_h_3,1, JANE_h_3,2, JANE_h_3,3], [JANE_h_4,0, JANE_h_4,1, JANE_h_4,2, JANE_h_4,3], [JANE_h_5,0, JANE_h_5,1, JANE_h_5,2, JANE_h_5,3], [JANE_h_6,0, JANE_h_6,1, JANE_h_6,2, JANE_h_6,3]], [[JANE_v_0,0, JANE_v_0,1, JANE_v_0,2, JANE_v_0,3, JANE_v_0,4, JANE_v_0,5, JANE_v_0,6], [JANE_v_1,0, JANE_v_1,1, JANE_v_1,2, JANE_v_1,3, JANE_v_1,4, JANE_v_1,5, JANE_v_1,6], [JANE_v_2,0, JANE_v_2,1, JANE_v_2,2, JANE_v_2,3, JANE_v_2,4, JANE_v_2,5, JANE_v_2,6], [JANE_v_3,0, JANE_v_3,1, JANE_v_3,2, JANE_v_3,3, JANE_v_3,4, JANE_v_3,5, JANE_v_3,6]]]\n", + "[[[AUSTEN_h_0,0, AUSTEN_h_0,1], [AUSTEN_h_1,0, AUSTEN_h_1,1], [AUSTEN_h_2,0, AUSTEN_h_2,1], [AUSTEN_h_3,0, AUSTEN_h_3,1], [AUSTEN_h_4,0, AUSTEN_h_4,1], [AUSTEN_h_5,0, AUSTEN_h_5,1], [AUSTEN_h_6,0, AUSTEN_h_6,1]], [[AUSTEN_v_0,0, AUSTEN_v_0,1, AUSTEN_v_0,2, AUSTEN_v_0,3, AUSTEN_v_0,4, AUSTEN_v_0,5, AUSTEN_v_0,6], [AUSTEN_v_1,0, AUSTEN_v_1,1, AUSTEN_v_1,2, AUSTEN_v_1,3, AUSTEN_v_1,4, AUSTEN_v_1,5, AUSTEN_v_1,6]]]\n", + "[[[PRIDE_h_0,0, PRIDE_h_0,1, PRIDE_h_0,2], [PRIDE_h_1,0, PRIDE_h_1,1, PRIDE_h_1,2], [PRIDE_h_2,0, PRIDE_h_2,1, PRIDE_h_2,2], [PRIDE_h_3,0, PRIDE_h_3,1, PRIDE_h_3,2], [PRIDE_h_4,0, PRIDE_h_4,1, PRIDE_h_4,2], [PRIDE_h_5,0, PRIDE_h_5,1, PRIDE_h_5,2], [PRIDE_h_6,0, PRIDE_h_6,1, PRIDE_h_6,2]], [[PRIDE_v_0,0, PRIDE_v_0,1, PRIDE_v_0,2, PRIDE_v_0,3, PRIDE_v_0,4, PRIDE_v_0,5, PRIDE_v_0,6], [PRIDE_v_1,0, PRIDE_v_1,1, PRIDE_v_1,2, PRIDE_v_1,3, PRIDE_v_1,4, PRIDE_v_1,5, PRIDE_v_1,6], [PRIDE_v_2,0, PRIDE_v_2,1, PRIDE_v_2,2, PRIDE_v_2,3, PRIDE_v_2,4, PRIDE_v_2,5, PRIDE_v_2,6]]]\n", + "[[[NOVEL_h_0,0, NOVEL_h_0,1, NOVEL_h_0,2], [NOVEL_h_1,0, NOVEL_h_1,1, NOVEL_h_1,2], [NOVEL_h_2,0, NOVEL_h_2,1, NOVEL_h_2,2], [NOVEL_h_3,0, NOVEL_h_3,1, NOVEL_h_3,2], [NOVEL_h_4,0, NOVEL_h_4,1, NOVEL_h_4,2], [NOVEL_h_5,0, NOVEL_h_5,1, NOVEL_h_5,2], [NOVEL_h_6,0, NOVEL_h_6,1, NOVEL_h_6,2]], [[NOVEL_v_0,0, NOVEL_v_0,1, NOVEL_v_0,2, NOVEL_v_0,3, NOVEL_v_0,4, NOVEL_v_0,5, NOVEL_v_0,6], [NOVEL_v_1,0, NOVEL_v_1,1, NOVEL_v_1,2, NOVEL_v_1,3, NOVEL_v_1,4, NOVEL_v_1,5, NOVEL_v_1,6], [NOVEL_v_2,0, NOVEL_v_2,1, NOVEL_v_2,2, NOVEL_v_2,3, NOVEL_v_2,4, NOVEL_v_2,5, NOVEL_v_2,6]]]\n", + "[[[DARCY_h_0,0, DARCY_h_0,1, DARCY_h_0,2], [DARCY_h_1,0, DARCY_h_1,1, DARCY_h_1,2], [DARCY_h_2,0, DARCY_h_2,1, DARCY_h_2,2], [DARCY_h_3,0, DARCY_h_3,1, DARCY_h_3,2], [DARCY_h_4,0, DARCY_h_4,1, DARCY_h_4,2], [DARCY_h_5,0, DARCY_h_5,1, DARCY_h_5,2], [DARCY_h_6,0, DARCY_h_6,1, DARCY_h_6,2]], [[DARCY_v_0,0, DARCY_v_0,1, DARCY_v_0,2, DARCY_v_0,3, DARCY_v_0,4, DARCY_v_0,5, DARCY_v_0,6], [DARCY_v_1,0, DARCY_v_1,1, DARCY_v_1,2, DARCY_v_1,3, DARCY_v_1,4, DARCY_v_1,5, DARCY_v_1,6], [DARCY_v_2,0, DARCY_v_2,1, DARCY_v_2,2, DARCY_v_2,3, DARCY_v_2,4, DARCY_v_2,5, DARCY_v_2,6]]]\n", + "[[[SENSE_h_0,0, SENSE_h_0,1, SENSE_h_0,2], [SENSE_h_1,0, SENSE_h_1,1, SENSE_h_1,2], [SENSE_h_2,0, SENSE_h_2,1, SENSE_h_2,2], [SENSE_h_3,0, SENSE_h_3,1, SENSE_h_3,2], [SENSE_h_4,0, SENSE_h_4,1, SENSE_h_4,2], [SENSE_h_5,0, SENSE_h_5,1, SENSE_h_5,2], [SENSE_h_6,0, SENSE_h_6,1, SENSE_h_6,2]], [[SENSE_v_0,0, SENSE_v_0,1, SENSE_v_0,2, SENSE_v_0,3, SENSE_v_0,4, SENSE_v_0,5, SENSE_v_0,6], [SENSE_v_1,0, SENSE_v_1,1, SENSE_v_1,2, SENSE_v_1,3, SENSE_v_1,4, SENSE_v_1,5, SENSE_v_1,6], [SENSE_v_2,0, SENSE_v_2,1, SENSE_v_2,2, SENSE_v_2,3, SENSE_v_2,4, SENSE_v_2,5, SENSE_v_2,6]]]\n", + "[[[EMMA_h_0,0, EMMA_h_0,1, EMMA_h_0,2, EMMA_h_0,3], [EMMA_h_1,0, EMMA_h_1,1, EMMA_h_1,2, EMMA_h_1,3], [EMMA_h_2,0, EMMA_h_2,1, EMMA_h_2,2, EMMA_h_2,3], [EMMA_h_3,0, EMMA_h_3,1, EMMA_h_3,2, EMMA_h_3,3], [EMMA_h_4,0, EMMA_h_4,1, EMMA_h_4,2, EMMA_h_4,3], [EMMA_h_5,0, EMMA_h_5,1, EMMA_h_5,2, EMMA_h_5,3], [EMMA_h_6,0, EMMA_h_6,1, EMMA_h_6,2, EMMA_h_6,3]], [[EMMA_v_0,0, EMMA_v_0,1, EMMA_v_0,2, EMMA_v_0,3, EMMA_v_0,4, EMMA_v_0,5, EMMA_v_0,6], [EMMA_v_1,0, EMMA_v_1,1, EMMA_v_1,2, EMMA_v_1,3, EMMA_v_1,4, EMMA_v_1,5, EMMA_v_1,6], [EMMA_v_2,0, EMMA_v_2,1, EMMA_v_2,2, EMMA_v_2,3, EMMA_v_2,4, EMMA_v_2,5, EMMA_v_2,6], [EMMA_v_3,0, EMMA_v_3,1, EMMA_v_3,2, EMMA_v_3,3, EMMA_v_3,4, EMMA_v_3,5, EMMA_v_3,6]]]\n", + "[[[ESTATE_h_0,0, ESTATE_h_0,1], [ESTATE_h_1,0, ESTATE_h_1,1], [ESTATE_h_2,0, ESTATE_h_2,1], [ESTATE_h_3,0, ESTATE_h_3,1], [ESTATE_h_4,0, ESTATE_h_4,1], [ESTATE_h_5,0, ESTATE_h_5,1], [ESTATE_h_6,0, ESTATE_h_6,1]], [[ESTATE_v_0,0, ESTATE_v_0,1, ESTATE_v_0,2, ESTATE_v_0,3, ESTATE_v_0,4, ESTATE_v_0,5, ESTATE_v_0,6], [ESTATE_v_1,0, ESTATE_v_1,1, ESTATE_v_1,2, ESTATE_v_1,3, ESTATE_v_1,4, ESTATE_v_1,5, ESTATE_v_1,6]]]\n", + "[[[BENNET_h_0,0, BENNET_h_0,1], [BENNET_h_1,0, BENNET_h_1,1], [BENNET_h_2,0, BENNET_h_2,1], [BENNET_h_3,0, BENNET_h_3,1], [BENNET_h_4,0, BENNET_h_4,1], [BENNET_h_5,0, BENNET_h_5,1], [BENNET_h_6,0, BENNET_h_6,1]], [[BENNET_v_0,0, BENNET_v_0,1, BENNET_v_0,2, BENNET_v_0,3, BENNET_v_0,4, BENNET_v_0,5, BENNET_v_0,6], [BENNET_v_1,0, BENNET_v_1,1, BENNET_v_1,2, BENNET_v_1,3, BENNET_v_1,4, BENNET_v_1,5, BENNET_v_1,6]]]\n", + "[[[BATH_h_0,0, BATH_h_0,1, BATH_h_0,2, BATH_h_0,3], [BATH_h_1,0, BATH_h_1,1, BATH_h_1,2, BATH_h_1,3], [BATH_h_2,0, BATH_h_2,1, BATH_h_2,2, BATH_h_2,3], [BATH_h_3,0, BATH_h_3,1, BATH_h_3,2, BATH_h_3,3], [BATH_h_4,0, BATH_h_4,1, BATH_h_4,2, BATH_h_4,3], [BATH_h_5,0, BATH_h_5,1, BATH_h_5,2, BATH_h_5,3], [BATH_h_6,0, BATH_h_6,1, BATH_h_6,2, BATH_h_6,3]], [[BATH_v_0,0, BATH_v_0,1, BATH_v_0,2, BATH_v_0,3, BATH_v_0,4, BATH_v_0,5, BATH_v_0,6], [BATH_v_1,0, BATH_v_1,1, BATH_v_1,2, BATH_v_1,3, BATH_v_1,4, BATH_v_1,5, BATH_v_1,6], [BATH_v_2,0, BATH_v_2,1, BATH_v_2,2, BATH_v_2,3, BATH_v_2,4, BATH_v_2,5, BATH_v_2,6], [BATH_v_3,0, BATH_v_3,1, BATH_v_3,2, BATH_v_3,3, BATH_v_3,4, BATH_v_3,5, BATH_v_3,6]]]\n", + "Executed in 0.6775 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╗\n", + "║█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█║\n", + "╚═╧═╧═╧═╧═╧═╧═╝\n", + "[[[JANE_h_0,0, JANE_h_0,1, JANE_h_0,2], [JANE_h_1,0, JANE_h_1,1, JANE_h_1,2], [JANE_h_2,0, JANE_h_2,1, JANE_h_2,2], [JANE_h_3,0, JANE_h_3,1, JANE_h_3,2], [JANE_h_4,0, JANE_h_4,1, JANE_h_4,2], [JANE_h_5,0, JANE_h_5,1, JANE_h_5,2]], [[JANE_v_0,0, JANE_v_0,1, JANE_v_0,2, JANE_v_0,3, JANE_v_0,4, JANE_v_0,5], [JANE_v_1,0, JANE_v_1,1, JANE_v_1,2, JANE_v_1,3, JANE_v_1,4, JANE_v_1,5], [JANE_v_2,0, JANE_v_2,1, JANE_v_2,2, JANE_v_2,3, JANE_v_2,4, JANE_v_2,5]]]\n", + "[[[AUSTEN_h_0,0], [AUSTEN_h_1,0], [AUSTEN_h_2,0], [AUSTEN_h_3,0], [AUSTEN_h_4,0], [AUSTEN_h_5,0]], [[AUSTEN_v_0,0, AUSTEN_v_0,1, AUSTEN_v_0,2, AUSTEN_v_0,3, AUSTEN_v_0,4, AUSTEN_v_0,5]]]\n", + "[[[PRIDE_h_0,0, PRIDE_h_0,1], [PRIDE_h_1,0, PRIDE_h_1,1], [PRIDE_h_2,0, PRIDE_h_2,1], [PRIDE_h_3,0, PRIDE_h_3,1], [PRIDE_h_4,0, PRIDE_h_4,1], [PRIDE_h_5,0, PRIDE_h_5,1]], [[PRIDE_v_0,0, PRIDE_v_0,1, PRIDE_v_0,2, PRIDE_v_0,3, PRIDE_v_0,4, PRIDE_v_0,5], [PRIDE_v_1,0, PRIDE_v_1,1, PRIDE_v_1,2, PRIDE_v_1,3, PRIDE_v_1,4, PRIDE_v_1,5]]]\n", + "[[[NOVEL_h_0,0, NOVEL_h_0,1], [NOVEL_h_1,0, NOVEL_h_1,1], [NOVEL_h_2,0, NOVEL_h_2,1], [NOVEL_h_3,0, NOVEL_h_3,1], [NOVEL_h_4,0, NOVEL_h_4,1], [NOVEL_h_5,0, NOVEL_h_5,1]], [[NOVEL_v_0,0, NOVEL_v_0,1, NOVEL_v_0,2, NOVEL_v_0,3, NOVEL_v_0,4, NOVEL_v_0,5], [NOVEL_v_1,0, NOVEL_v_1,1, NOVEL_v_1,2, NOVEL_v_1,3, NOVEL_v_1,4, NOVEL_v_1,5]]]\n", + "[[[DARCY_h_0,0, DARCY_h_0,1], [DARCY_h_1,0, DARCY_h_1,1], [DARCY_h_2,0, DARCY_h_2,1], [DARCY_h_3,0, DARCY_h_3,1], [DARCY_h_4,0, DARCY_h_4,1], [DARCY_h_5,0, DARCY_h_5,1]], [[DARCY_v_0,0, DARCY_v_0,1, DARCY_v_0,2, DARCY_v_0,3, DARCY_v_0,4, DARCY_v_0,5], [DARCY_v_1,0, DARCY_v_1,1, DARCY_v_1,2, DARCY_v_1,3, DARCY_v_1,4, DARCY_v_1,5]]]\n", + "[[[SENSE_h_0,0, SENSE_h_0,1], [SENSE_h_1,0, SENSE_h_1,1], [SENSE_h_2,0, SENSE_h_2,1], [SENSE_h_3,0, SENSE_h_3,1], [SENSE_h_4,0, SENSE_h_4,1], [SENSE_h_5,0, SENSE_h_5,1]], [[SENSE_v_0,0, SENSE_v_0,1, SENSE_v_0,2, SENSE_v_0,3, SENSE_v_0,4, SENSE_v_0,5], [SENSE_v_1,0, SENSE_v_1,1, SENSE_v_1,2, SENSE_v_1,3, SENSE_v_1,4, SENSE_v_1,5]]]\n", + "[[[EMMA_h_0,0, EMMA_h_0,1, EMMA_h_0,2], [EMMA_h_1,0, EMMA_h_1,1, EMMA_h_1,2], [EMMA_h_2,0, EMMA_h_2,1, EMMA_h_2,2], [EMMA_h_3,0, EMMA_h_3,1, EMMA_h_3,2], [EMMA_h_4,0, EMMA_h_4,1, EMMA_h_4,2], [EMMA_h_5,0, EMMA_h_5,1, EMMA_h_5,2]], [[EMMA_v_0,0, EMMA_v_0,1, EMMA_v_0,2, EMMA_v_0,3, EMMA_v_0,4, EMMA_v_0,5], [EMMA_v_1,0, EMMA_v_1,1, EMMA_v_1,2, EMMA_v_1,3, EMMA_v_1,4, EMMA_v_1,5], [EMMA_v_2,0, EMMA_v_2,1, EMMA_v_2,2, EMMA_v_2,3, EMMA_v_2,4, EMMA_v_2,5]]]\n", + "[[[ESTATE_h_0,0], [ESTATE_h_1,0], [ESTATE_h_2,0], [ESTATE_h_3,0], [ESTATE_h_4,0], [ESTATE_h_5,0]], [[ESTATE_v_0,0, ESTATE_v_0,1, ESTATE_v_0,2, ESTATE_v_0,3, ESTATE_v_0,4, ESTATE_v_0,5]]]\n", + "[[[BENNET_h_0,0], [BENNET_h_1,0], [BENNET_h_2,0], [BENNET_h_3,0], [BENNET_h_4,0], [BENNET_h_5,0]], [[BENNET_v_0,0, BENNET_v_0,1, BENNET_v_0,2, BENNET_v_0,3, BENNET_v_0,4, BENNET_v_0,5]]]\n", + "[[[BATH_h_0,0, BATH_h_0,1, BATH_h_0,2], [BATH_h_1,0, BATH_h_1,1, BATH_h_1,2], [BATH_h_2,0, BATH_h_2,1, BATH_h_2,2], [BATH_h_3,0, BATH_h_3,1, BATH_h_3,2], [BATH_h_4,0, BATH_h_4,1, BATH_h_4,2], [BATH_h_5,0, BATH_h_5,1, BATH_h_5,2]], [[BATH_v_0,0, BATH_v_0,1, BATH_v_0,2, BATH_v_0,3, BATH_v_0,4, BATH_v_0,5], [BATH_v_1,0, BATH_v_1,1, BATH_v_1,2, BATH_v_1,3, BATH_v_1,4, BATH_v_1,5], [BATH_v_2,0, BATH_v_2,1, BATH_v_2,2, BATH_v_2,3, BATH_v_2,4, BATH_v_2,5]]]\n", + "Executed in 0.5988 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╗\n", + "║█│█│█│█│█│█║\n", + "║█│█│█│█│█│█║\n", + "║█│█│█│█│█│█║\n", + "║█│█│█│█│█│█║\n", + "║█│█│█│█│█│█║\n", + "║█│█│█│█│█│█║\n", + "╚═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "These were some random words that I chose for an example. Here are some real words from an NY Times mini puzzle. You can see that it recovers the minimal possible solution correctly." + ], + "metadata": { + "id": "kFQs0iZXmipH" + } + }, + { + "cell_type": "code", + "source": [ + "words2 = ['GUM','TAB','ERA','END','IRA','MAP','TIMWALZ','ONE','ELI','COATS','POPHITS', \\\n", + " 'GOT', 'PIE','BIZ','SPA','ALLSTAR','UNICORN','MEMOPAD','WAH','TEATIME']\n", + "solve_crossword(words2, 7, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "sDmahKMoldGi", + "outputId": "959eeb9a-3633-4b86-b94b-c80eb0f0f73a" + }, + "execution_count": 35, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[[[GUM_h_0,0, GUM_h_0,1, GUM_h_0,2, GUM_h_0,3, GUM_h_0,4], [GUM_h_1,0, GUM_h_1,1, GUM_h_1,2, GUM_h_1,3, GUM_h_1,4], [GUM_h_2,0, GUM_h_2,1, GUM_h_2,2, GUM_h_2,3, GUM_h_2,4], [GUM_h_3,0, GUM_h_3,1, GUM_h_3,2, GUM_h_3,3, GUM_h_3,4], [GUM_h_4,0, GUM_h_4,1, GUM_h_4,2, GUM_h_4,3, GUM_h_4,4], [GUM_h_5,0, GUM_h_5,1, GUM_h_5,2, GUM_h_5,3, GUM_h_5,4], [GUM_h_6,0, GUM_h_6,1, GUM_h_6,2, GUM_h_6,3, GUM_h_6,4]], [[GUM_v_0,0, GUM_v_0,1, GUM_v_0,2, GUM_v_0,3, GUM_v_0,4, GUM_v_0,5, GUM_v_0,6], [GUM_v_1,0, GUM_v_1,1, GUM_v_1,2, GUM_v_1,3, GUM_v_1,4, GUM_v_1,5, GUM_v_1,6], [GUM_v_2,0, GUM_v_2,1, GUM_v_2,2, GUM_v_2,3, GUM_v_2,4, GUM_v_2,5, GUM_v_2,6], [GUM_v_3,0, GUM_v_3,1, GUM_v_3,2, GUM_v_3,3, GUM_v_3,4, GUM_v_3,5, GUM_v_3,6], [GUM_v_4,0, GUM_v_4,1, GUM_v_4,2, GUM_v_4,3, GUM_v_4,4, GUM_v_4,5, GUM_v_4,6]]]\n", + "[[[TAB_h_0,0, TAB_h_0,1, TAB_h_0,2, TAB_h_0,3, TAB_h_0,4], [TAB_h_1,0, TAB_h_1,1, TAB_h_1,2, TAB_h_1,3, TAB_h_1,4], [TAB_h_2,0, TAB_h_2,1, TAB_h_2,2, TAB_h_2,3, TAB_h_2,4], [TAB_h_3,0, TAB_h_3,1, TAB_h_3,2, TAB_h_3,3, TAB_h_3,4], [TAB_h_4,0, TAB_h_4,1, TAB_h_4,2, TAB_h_4,3, TAB_h_4,4], [TAB_h_5,0, TAB_h_5,1, TAB_h_5,2, TAB_h_5,3, TAB_h_5,4], [TAB_h_6,0, TAB_h_6,1, TAB_h_6,2, TAB_h_6,3, TAB_h_6,4]], [[TAB_v_0,0, TAB_v_0,1, TAB_v_0,2, TAB_v_0,3, TAB_v_0,4, TAB_v_0,5, TAB_v_0,6], [TAB_v_1,0, TAB_v_1,1, TAB_v_1,2, TAB_v_1,3, TAB_v_1,4, TAB_v_1,5, TAB_v_1,6], [TAB_v_2,0, TAB_v_2,1, TAB_v_2,2, TAB_v_2,3, TAB_v_2,4, TAB_v_2,5, TAB_v_2,6], [TAB_v_3,0, TAB_v_3,1, TAB_v_3,2, TAB_v_3,3, TAB_v_3,4, TAB_v_3,5, TAB_v_3,6], [TAB_v_4,0, TAB_v_4,1, TAB_v_4,2, TAB_v_4,3, TAB_v_4,4, TAB_v_4,5, TAB_v_4,6]]]\n", + "[[[ERA_h_0,0, ERA_h_0,1, ERA_h_0,2, ERA_h_0,3, ERA_h_0,4], [ERA_h_1,0, ERA_h_1,1, ERA_h_1,2, ERA_h_1,3, ERA_h_1,4], [ERA_h_2,0, ERA_h_2,1, ERA_h_2,2, ERA_h_2,3, ERA_h_2,4], [ERA_h_3,0, ERA_h_3,1, ERA_h_3,2, ERA_h_3,3, ERA_h_3,4], [ERA_h_4,0, ERA_h_4,1, ERA_h_4,2, ERA_h_4,3, ERA_h_4,4], [ERA_h_5,0, ERA_h_5,1, ERA_h_5,2, ERA_h_5,3, ERA_h_5,4], [ERA_h_6,0, ERA_h_6,1, ERA_h_6,2, ERA_h_6,3, ERA_h_6,4]], [[ERA_v_0,0, ERA_v_0,1, ERA_v_0,2, ERA_v_0,3, ERA_v_0,4, ERA_v_0,5, ERA_v_0,6], [ERA_v_1,0, ERA_v_1,1, ERA_v_1,2, ERA_v_1,3, ERA_v_1,4, ERA_v_1,5, ERA_v_1,6], [ERA_v_2,0, ERA_v_2,1, ERA_v_2,2, ERA_v_2,3, ERA_v_2,4, ERA_v_2,5, ERA_v_2,6], [ERA_v_3,0, ERA_v_3,1, ERA_v_3,2, ERA_v_3,3, ERA_v_3,4, ERA_v_3,5, ERA_v_3,6], [ERA_v_4,0, ERA_v_4,1, ERA_v_4,2, ERA_v_4,3, ERA_v_4,4, ERA_v_4,5, ERA_v_4,6]]]\n", + "[[[END_h_0,0, END_h_0,1, END_h_0,2, END_h_0,3, END_h_0,4], [END_h_1,0, END_h_1,1, END_h_1,2, END_h_1,3, END_h_1,4], [END_h_2,0, END_h_2,1, END_h_2,2, END_h_2,3, END_h_2,4], [END_h_3,0, END_h_3,1, END_h_3,2, END_h_3,3, END_h_3,4], [END_h_4,0, END_h_4,1, END_h_4,2, END_h_4,3, END_h_4,4], [END_h_5,0, END_h_5,1, END_h_5,2, END_h_5,3, END_h_5,4], [END_h_6,0, END_h_6,1, END_h_6,2, END_h_6,3, END_h_6,4]], [[END_v_0,0, END_v_0,1, END_v_0,2, END_v_0,3, END_v_0,4, END_v_0,5, END_v_0,6], [END_v_1,0, END_v_1,1, END_v_1,2, END_v_1,3, END_v_1,4, END_v_1,5, END_v_1,6], [END_v_2,0, END_v_2,1, END_v_2,2, END_v_2,3, END_v_2,4, END_v_2,5, END_v_2,6], [END_v_3,0, END_v_3,1, END_v_3,2, END_v_3,3, END_v_3,4, END_v_3,5, END_v_3,6], [END_v_4,0, END_v_4,1, END_v_4,2, END_v_4,3, END_v_4,4, END_v_4,5, END_v_4,6]]]\n", + "[[[IRA_h_0,0, IRA_h_0,1, IRA_h_0,2, IRA_h_0,3, IRA_h_0,4], [IRA_h_1,0, IRA_h_1,1, IRA_h_1,2, IRA_h_1,3, IRA_h_1,4], [IRA_h_2,0, IRA_h_2,1, IRA_h_2,2, IRA_h_2,3, IRA_h_2,4], [IRA_h_3,0, IRA_h_3,1, IRA_h_3,2, IRA_h_3,3, IRA_h_3,4], [IRA_h_4,0, IRA_h_4,1, IRA_h_4,2, IRA_h_4,3, IRA_h_4,4], [IRA_h_5,0, IRA_h_5,1, IRA_h_5,2, IRA_h_5,3, IRA_h_5,4], [IRA_h_6,0, IRA_h_6,1, IRA_h_6,2, IRA_h_6,3, IRA_h_6,4]], [[IRA_v_0,0, IRA_v_0,1, IRA_v_0,2, IRA_v_0,3, IRA_v_0,4, IRA_v_0,5, IRA_v_0,6], [IRA_v_1,0, IRA_v_1,1, IRA_v_1,2, IRA_v_1,3, IRA_v_1,4, IRA_v_1,5, IRA_v_1,6], [IRA_v_2,0, IRA_v_2,1, IRA_v_2,2, IRA_v_2,3, IRA_v_2,4, IRA_v_2,5, IRA_v_2,6], [IRA_v_3,0, IRA_v_3,1, IRA_v_3,2, IRA_v_3,3, IRA_v_3,4, IRA_v_3,5, IRA_v_3,6], [IRA_v_4,0, IRA_v_4,1, IRA_v_4,2, IRA_v_4,3, IRA_v_4,4, IRA_v_4,5, IRA_v_4,6]]]\n", + "[[[MAP_h_0,0, MAP_h_0,1, MAP_h_0,2, MAP_h_0,3, MAP_h_0,4], [MAP_h_1,0, MAP_h_1,1, MAP_h_1,2, MAP_h_1,3, MAP_h_1,4], [MAP_h_2,0, MAP_h_2,1, MAP_h_2,2, MAP_h_2,3, MAP_h_2,4], [MAP_h_3,0, MAP_h_3,1, MAP_h_3,2, MAP_h_3,3, MAP_h_3,4], [MAP_h_4,0, MAP_h_4,1, MAP_h_4,2, MAP_h_4,3, MAP_h_4,4], [MAP_h_5,0, MAP_h_5,1, MAP_h_5,2, MAP_h_5,3, MAP_h_5,4], [MAP_h_6,0, MAP_h_6,1, MAP_h_6,2, MAP_h_6,3, MAP_h_6,4]], [[MAP_v_0,0, MAP_v_0,1, MAP_v_0,2, MAP_v_0,3, MAP_v_0,4, MAP_v_0,5, MAP_v_0,6], [MAP_v_1,0, MAP_v_1,1, MAP_v_1,2, MAP_v_1,3, MAP_v_1,4, MAP_v_1,5, MAP_v_1,6], [MAP_v_2,0, MAP_v_2,1, MAP_v_2,2, MAP_v_2,3, MAP_v_2,4, MAP_v_2,5, MAP_v_2,6], [MAP_v_3,0, MAP_v_3,1, MAP_v_3,2, MAP_v_3,3, MAP_v_3,4, MAP_v_3,5, MAP_v_3,6], [MAP_v_4,0, MAP_v_4,1, MAP_v_4,2, MAP_v_4,3, MAP_v_4,4, MAP_v_4,5, MAP_v_4,6]]]\n", + "[[[TIMWALZ_h_0,0], [TIMWALZ_h_1,0], [TIMWALZ_h_2,0], [TIMWALZ_h_3,0], [TIMWALZ_h_4,0], [TIMWALZ_h_5,0], [TIMWALZ_h_6,0]], [[TIMWALZ_v_0,0, TIMWALZ_v_0,1, TIMWALZ_v_0,2, TIMWALZ_v_0,3, TIMWALZ_v_0,4, TIMWALZ_v_0,5, TIMWALZ_v_0,6]]]\n", + "[[[ONE_h_0,0, ONE_h_0,1, ONE_h_0,2, ONE_h_0,3, ONE_h_0,4], [ONE_h_1,0, ONE_h_1,1, ONE_h_1,2, ONE_h_1,3, ONE_h_1,4], [ONE_h_2,0, ONE_h_2,1, ONE_h_2,2, ONE_h_2,3, ONE_h_2,4], [ONE_h_3,0, ONE_h_3,1, ONE_h_3,2, ONE_h_3,3, ONE_h_3,4], [ONE_h_4,0, ONE_h_4,1, ONE_h_4,2, ONE_h_4,3, ONE_h_4,4], [ONE_h_5,0, ONE_h_5,1, ONE_h_5,2, ONE_h_5,3, ONE_h_5,4], [ONE_h_6,0, ONE_h_6,1, ONE_h_6,2, ONE_h_6,3, ONE_h_6,4]], [[ONE_v_0,0, ONE_v_0,1, ONE_v_0,2, ONE_v_0,3, ONE_v_0,4, ONE_v_0,5, ONE_v_0,6], [ONE_v_1,0, ONE_v_1,1, ONE_v_1,2, ONE_v_1,3, ONE_v_1,4, ONE_v_1,5, ONE_v_1,6], [ONE_v_2,0, ONE_v_2,1, ONE_v_2,2, ONE_v_2,3, ONE_v_2,4, ONE_v_2,5, ONE_v_2,6], [ONE_v_3,0, ONE_v_3,1, ONE_v_3,2, ONE_v_3,3, ONE_v_3,4, ONE_v_3,5, ONE_v_3,6], [ONE_v_4,0, ONE_v_4,1, ONE_v_4,2, ONE_v_4,3, ONE_v_4,4, ONE_v_4,5, ONE_v_4,6]]]\n", + "[[[ELI_h_0,0, ELI_h_0,1, ELI_h_0,2, ELI_h_0,3, ELI_h_0,4], [ELI_h_1,0, ELI_h_1,1, ELI_h_1,2, ELI_h_1,3, ELI_h_1,4], [ELI_h_2,0, ELI_h_2,1, ELI_h_2,2, ELI_h_2,3, ELI_h_2,4], [ELI_h_3,0, ELI_h_3,1, ELI_h_3,2, ELI_h_3,3, ELI_h_3,4], [ELI_h_4,0, ELI_h_4,1, ELI_h_4,2, ELI_h_4,3, ELI_h_4,4], [ELI_h_5,0, ELI_h_5,1, ELI_h_5,2, ELI_h_5,3, ELI_h_5,4], [ELI_h_6,0, ELI_h_6,1, ELI_h_6,2, ELI_h_6,3, ELI_h_6,4]], [[ELI_v_0,0, ELI_v_0,1, ELI_v_0,2, ELI_v_0,3, ELI_v_0,4, ELI_v_0,5, ELI_v_0,6], [ELI_v_1,0, ELI_v_1,1, ELI_v_1,2, ELI_v_1,3, ELI_v_1,4, ELI_v_1,5, ELI_v_1,6], [ELI_v_2,0, ELI_v_2,1, ELI_v_2,2, ELI_v_2,3, ELI_v_2,4, ELI_v_2,5, ELI_v_2,6], [ELI_v_3,0, ELI_v_3,1, ELI_v_3,2, ELI_v_3,3, ELI_v_3,4, ELI_v_3,5, ELI_v_3,6], [ELI_v_4,0, ELI_v_4,1, ELI_v_4,2, ELI_v_4,3, ELI_v_4,4, ELI_v_4,5, ELI_v_4,6]]]\n", + "[[[COATS_h_0,0, COATS_h_0,1, COATS_h_0,2], [COATS_h_1,0, COATS_h_1,1, COATS_h_1,2], [COATS_h_2,0, COATS_h_2,1, COATS_h_2,2], [COATS_h_3,0, COATS_h_3,1, COATS_h_3,2], [COATS_h_4,0, COATS_h_4,1, COATS_h_4,2], [COATS_h_5,0, COATS_h_5,1, COATS_h_5,2], [COATS_h_6,0, COATS_h_6,1, COATS_h_6,2]], [[COATS_v_0,0, COATS_v_0,1, COATS_v_0,2, COATS_v_0,3, COATS_v_0,4, COATS_v_0,5, COATS_v_0,6], [COATS_v_1,0, COATS_v_1,1, COATS_v_1,2, COATS_v_1,3, COATS_v_1,4, COATS_v_1,5, COATS_v_1,6], [COATS_v_2,0, COATS_v_2,1, COATS_v_2,2, COATS_v_2,3, COATS_v_2,4, COATS_v_2,5, COATS_v_2,6]]]\n", + "[[[POPHITS_h_0,0], [POPHITS_h_1,0], [POPHITS_h_2,0], [POPHITS_h_3,0], [POPHITS_h_4,0], [POPHITS_h_5,0], [POPHITS_h_6,0]], [[POPHITS_v_0,0, POPHITS_v_0,1, POPHITS_v_0,2, POPHITS_v_0,3, POPHITS_v_0,4, POPHITS_v_0,5, POPHITS_v_0,6]]]\n", + "[[[GOT_h_0,0, GOT_h_0,1, GOT_h_0,2, GOT_h_0,3, GOT_h_0,4], [GOT_h_1,0, GOT_h_1,1, GOT_h_1,2, GOT_h_1,3, GOT_h_1,4], [GOT_h_2,0, GOT_h_2,1, GOT_h_2,2, GOT_h_2,3, GOT_h_2,4], [GOT_h_3,0, GOT_h_3,1, GOT_h_3,2, GOT_h_3,3, GOT_h_3,4], [GOT_h_4,0, GOT_h_4,1, GOT_h_4,2, GOT_h_4,3, GOT_h_4,4], [GOT_h_5,0, GOT_h_5,1, GOT_h_5,2, GOT_h_5,3, GOT_h_5,4], [GOT_h_6,0, GOT_h_6,1, GOT_h_6,2, GOT_h_6,3, GOT_h_6,4]], [[GOT_v_0,0, GOT_v_0,1, GOT_v_0,2, GOT_v_0,3, GOT_v_0,4, GOT_v_0,5, GOT_v_0,6], [GOT_v_1,0, GOT_v_1,1, GOT_v_1,2, GOT_v_1,3, GOT_v_1,4, GOT_v_1,5, GOT_v_1,6], [GOT_v_2,0, GOT_v_2,1, GOT_v_2,2, GOT_v_2,3, GOT_v_2,4, GOT_v_2,5, GOT_v_2,6], [GOT_v_3,0, GOT_v_3,1, GOT_v_3,2, GOT_v_3,3, GOT_v_3,4, GOT_v_3,5, GOT_v_3,6], [GOT_v_4,0, GOT_v_4,1, GOT_v_4,2, GOT_v_4,3, GOT_v_4,4, GOT_v_4,5, GOT_v_4,6]]]\n", + "[[[PIE_h_0,0, PIE_h_0,1, PIE_h_0,2, PIE_h_0,3, PIE_h_0,4], [PIE_h_1,0, PIE_h_1,1, PIE_h_1,2, PIE_h_1,3, PIE_h_1,4], [PIE_h_2,0, PIE_h_2,1, PIE_h_2,2, PIE_h_2,3, PIE_h_2,4], [PIE_h_3,0, PIE_h_3,1, PIE_h_3,2, PIE_h_3,3, PIE_h_3,4], [PIE_h_4,0, PIE_h_4,1, PIE_h_4,2, PIE_h_4,3, PIE_h_4,4], [PIE_h_5,0, PIE_h_5,1, PIE_h_5,2, PIE_h_5,3, PIE_h_5,4], [PIE_h_6,0, PIE_h_6,1, PIE_h_6,2, PIE_h_6,3, PIE_h_6,4]], [[PIE_v_0,0, PIE_v_0,1, PIE_v_0,2, PIE_v_0,3, PIE_v_0,4, PIE_v_0,5, PIE_v_0,6], [PIE_v_1,0, PIE_v_1,1, PIE_v_1,2, PIE_v_1,3, PIE_v_1,4, PIE_v_1,5, PIE_v_1,6], [PIE_v_2,0, PIE_v_2,1, PIE_v_2,2, PIE_v_2,3, PIE_v_2,4, PIE_v_2,5, PIE_v_2,6], [PIE_v_3,0, PIE_v_3,1, PIE_v_3,2, PIE_v_3,3, PIE_v_3,4, PIE_v_3,5, PIE_v_3,6], [PIE_v_4,0, PIE_v_4,1, PIE_v_4,2, PIE_v_4,3, PIE_v_4,4, PIE_v_4,5, PIE_v_4,6]]]\n", + "[[[BIZ_h_0,0, BIZ_h_0,1, BIZ_h_0,2, BIZ_h_0,3, BIZ_h_0,4], [BIZ_h_1,0, BIZ_h_1,1, BIZ_h_1,2, BIZ_h_1,3, BIZ_h_1,4], [BIZ_h_2,0, BIZ_h_2,1, BIZ_h_2,2, BIZ_h_2,3, BIZ_h_2,4], [BIZ_h_3,0, BIZ_h_3,1, BIZ_h_3,2, BIZ_h_3,3, BIZ_h_3,4], [BIZ_h_4,0, BIZ_h_4,1, BIZ_h_4,2, BIZ_h_4,3, BIZ_h_4,4], [BIZ_h_5,0, BIZ_h_5,1, BIZ_h_5,2, BIZ_h_5,3, BIZ_h_5,4], [BIZ_h_6,0, BIZ_h_6,1, BIZ_h_6,2, BIZ_h_6,3, BIZ_h_6,4]], [[BIZ_v_0,0, BIZ_v_0,1, BIZ_v_0,2, BIZ_v_0,3, BIZ_v_0,4, BIZ_v_0,5, BIZ_v_0,6], [BIZ_v_1,0, BIZ_v_1,1, BIZ_v_1,2, BIZ_v_1,3, BIZ_v_1,4, BIZ_v_1,5, BIZ_v_1,6], [BIZ_v_2,0, BIZ_v_2,1, BIZ_v_2,2, BIZ_v_2,3, BIZ_v_2,4, BIZ_v_2,5, BIZ_v_2,6], [BIZ_v_3,0, BIZ_v_3,1, BIZ_v_3,2, BIZ_v_3,3, BIZ_v_3,4, BIZ_v_3,5, BIZ_v_3,6], [BIZ_v_4,0, BIZ_v_4,1, BIZ_v_4,2, BIZ_v_4,3, BIZ_v_4,4, BIZ_v_4,5, BIZ_v_4,6]]]\n", + "[[[SPA_h_0,0, SPA_h_0,1, SPA_h_0,2, SPA_h_0,3, SPA_h_0,4], [SPA_h_1,0, SPA_h_1,1, SPA_h_1,2, SPA_h_1,3, SPA_h_1,4], [SPA_h_2,0, SPA_h_2,1, SPA_h_2,2, SPA_h_2,3, SPA_h_2,4], [SPA_h_3,0, SPA_h_3,1, SPA_h_3,2, SPA_h_3,3, SPA_h_3,4], [SPA_h_4,0, SPA_h_4,1, SPA_h_4,2, SPA_h_4,3, SPA_h_4,4], [SPA_h_5,0, SPA_h_5,1, SPA_h_5,2, SPA_h_5,3, SPA_h_5,4], [SPA_h_6,0, SPA_h_6,1, SPA_h_6,2, SPA_h_6,3, SPA_h_6,4]], [[SPA_v_0,0, SPA_v_0,1, SPA_v_0,2, SPA_v_0,3, SPA_v_0,4, SPA_v_0,5, SPA_v_0,6], [SPA_v_1,0, SPA_v_1,1, SPA_v_1,2, SPA_v_1,3, SPA_v_1,4, SPA_v_1,5, SPA_v_1,6], [SPA_v_2,0, SPA_v_2,1, SPA_v_2,2, SPA_v_2,3, SPA_v_2,4, SPA_v_2,5, SPA_v_2,6], [SPA_v_3,0, SPA_v_3,1, SPA_v_3,2, SPA_v_3,3, SPA_v_3,4, SPA_v_3,5, SPA_v_3,6], [SPA_v_4,0, SPA_v_4,1, SPA_v_4,2, SPA_v_4,3, SPA_v_4,4, SPA_v_4,5, SPA_v_4,6]]]\n", + "[[[ALLSTAR_h_0,0], [ALLSTAR_h_1,0], [ALLSTAR_h_2,0], [ALLSTAR_h_3,0], [ALLSTAR_h_4,0], [ALLSTAR_h_5,0], [ALLSTAR_h_6,0]], [[ALLSTAR_v_0,0, ALLSTAR_v_0,1, ALLSTAR_v_0,2, ALLSTAR_v_0,3, ALLSTAR_v_0,4, ALLSTAR_v_0,5, ALLSTAR_v_0,6]]]\n", + "[[[UNICORN_h_0,0], [UNICORN_h_1,0], [UNICORN_h_2,0], [UNICORN_h_3,0], [UNICORN_h_4,0], [UNICORN_h_5,0], [UNICORN_h_6,0]], [[UNICORN_v_0,0, UNICORN_v_0,1, UNICORN_v_0,2, UNICORN_v_0,3, UNICORN_v_0,4, UNICORN_v_0,5, UNICORN_v_0,6]]]\n", + "[[[MEMOPAD_h_0,0], [MEMOPAD_h_1,0], [MEMOPAD_h_2,0], [MEMOPAD_h_3,0], [MEMOPAD_h_4,0], [MEMOPAD_h_5,0], [MEMOPAD_h_6,0]], [[MEMOPAD_v_0,0, MEMOPAD_v_0,1, MEMOPAD_v_0,2, MEMOPAD_v_0,3, MEMOPAD_v_0,4, MEMOPAD_v_0,5, MEMOPAD_v_0,6]]]\n", + "[[[WAH_h_0,0, WAH_h_0,1, WAH_h_0,2, WAH_h_0,3, WAH_h_0,4], [WAH_h_1,0, WAH_h_1,1, WAH_h_1,2, WAH_h_1,3, WAH_h_1,4], [WAH_h_2,0, WAH_h_2,1, WAH_h_2,2, WAH_h_2,3, WAH_h_2,4], [WAH_h_3,0, WAH_h_3,1, WAH_h_3,2, WAH_h_3,3, WAH_h_3,4], [WAH_h_4,0, WAH_h_4,1, WAH_h_4,2, WAH_h_4,3, WAH_h_4,4], [WAH_h_5,0, WAH_h_5,1, WAH_h_5,2, WAH_h_5,3, WAH_h_5,4], [WAH_h_6,0, WAH_h_6,1, WAH_h_6,2, WAH_h_6,3, WAH_h_6,4]], [[WAH_v_0,0, WAH_v_0,1, WAH_v_0,2, WAH_v_0,3, WAH_v_0,4, WAH_v_0,5, WAH_v_0,6], [WAH_v_1,0, WAH_v_1,1, WAH_v_1,2, WAH_v_1,3, WAH_v_1,4, WAH_v_1,5, WAH_v_1,6], [WAH_v_2,0, WAH_v_2,1, WAH_v_2,2, WAH_v_2,3, WAH_v_2,4, WAH_v_2,5, WAH_v_2,6], [WAH_v_3,0, WAH_v_3,1, WAH_v_3,2, WAH_v_3,3, WAH_v_3,4, WAH_v_3,5, WAH_v_3,6], [WAH_v_4,0, WAH_v_4,1, WAH_v_4,2, WAH_v_4,3, WAH_v_4,4, WAH_v_4,5, WAH_v_4,6]]]\n", + "[[[TEATIME_h_0,0], [TEATIME_h_1,0], [TEATIME_h_2,0], [TEATIME_h_3,0], [TEATIME_h_4,0], [TEATIME_h_5,0], [TEATIME_h_6,0]], [[TEATIME_v_0,0, TEATIME_v_0,1, TEATIME_v_0,2, TEATIME_v_0,3, TEATIME_v_0,4, TEATIME_v_0,5, TEATIME_v_0,6]]]\n", + "Executed in 6.0718 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╗\n", + "║█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█║\n", + "╚═╧═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Further work for keeners:\n", + "\n", + "1. Technically, we should also put in a constraint to stop two words starting in the same place. For example, the current solver would allow the words 'EAT' and 'EATEN' to start in the same place and overlap. Add this constraint.\n", + "\n", + "2. As proposed, this is fairly impractical to help make crosswords. It would be more useful to have a superset of answers and allow the SAT solver to choose from these to make a sensible crossword. You would then ask the SAT solver to build an crossword of size $N$ that included at least $W$ words. Add this feature.\n" + ], + "metadata": { + "id": "c2ZiS3OgzHXW" + } + } + ] +} \ No newline at end of file diff --git a/Trees/SAT_Crossword_Answers.ipynb b/Trees/SAT_Crossword_Answers.ipynb new file mode 100644 index 0000000..618a211 --- /dev/null +++ b/Trees/SAT_Crossword_Answers.ipynb @@ -0,0 +1,911 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyMplmIQrTkWc/1oVZK7PhOy", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "QjHXD27ieTS-" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Crosswords with SAT\n", + "\n", + "The purpose of this Python notebook is to use investigate using SAT to find a valid arrangement of known answers in a crossword puzzle.\n", + "\n", + "You should have completed the notebook on SAT constructions before attempting this notebook. Note: this exercise is pretty hard. Expect it to take a while!\n", + "\n", + "You can save a local copy of this notebook in your Google account and work through it in Colab (recommended) or you can download the notebook and run it locally using Jupyter notebook or similar.\n", + "\n", + "Contact me at iclimbtreesmail@gmail.com if you find any mistakes or have any suggestions." + ], + "metadata": { + "id": "jtMs90veeZIn" + } + }, + { + "cell_type": "code", + "source": [ + "# Install relevant packages\n", + "!pip install z3-solver\n", + "from z3 import *\n", + "import numpy as np\n", + "import time" + ], + "metadata": { + "id": "mF6ngqCses3n", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "68804cab-5138-4ed6-c87b-4d27717acc8d" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Requirement already satisfied: z3-solver in /usr/local/lib/python3.11/dist-packages (4.14.1.0)\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "First let's write some code to visualize a crossword problem. We'll represent the crossword as a ndarray of integers where each integer represents a letter index and zero represents a blank spot. " + ], + "metadata": { + "id": "3A5_7mByYrur" + } + }, + { + "cell_type": "code", + "source": [ + "puzzle = ['ALCOVE NSEC MIC',\n", + " 'LEANED ALTO ADO',\n", + " 'LAVALAMPOON CON',\n", + " 'ASSN EKG GABLE',\n", + " ' DENTI MEMO ',\n", + " ' AEOLIANHARPOON',\n", + " 'MOANER SAX SKUA',\n", + " 'ERS MVP TWI PTS',\n", + " 'OTTO AUS ESPRIT',\n", + " 'WAILINGWALLOON ',\n", + " ' NARA IDLES ',\n", + " 'REDYE UMA ECHO',\n", + " 'ARI FILMBUFFOON',\n", + " 'JOE UTNE SLOPPY',\n", + " 'ASS LEAR CORTEX']\n", + "\n", + "# Convert to a list of lists\n", + "for i in range(len(puzzle)):\n", + " puzzle[i] = [char for char in puzzle[i]]\n", + "\n", + "# Represent the puzzle as integers in a grid\n", + "puzzle_as_integers = np.zeros((len(puzzle), len(puzzle[0])), dtype=int)\n", + "for i in range(len(puzzle)):\n", + " for j in range(len(puzzle[i])):\n", + " if puzzle[i][j] == ' ':\n", + " puzzle_as_integers[i][j] = 0\n", + " else:\n", + " puzzle_as_integers[i][j] = ord(puzzle[i][j]) - ord('A') + 1\n", + "\n", + "print(puzzle)\n", + "print(puzzle_as_integers)" + ], + "metadata": { + "id": "cvGNbKkf-Qix", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "7ed1d16a-7cd8-4bdd-b790-26fdac322ab9" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[['A', 'L', 'C', 'O', 'V', 'E', ' ', 'N', 'S', 'E', 'C', ' ', 'M', 'I', 'C'], ['L', 'E', 'A', 'N', 'E', 'D', ' ', 'A', 'L', 'T', 'O', ' ', 'A', 'D', 'O'], ['L', 'A', 'V', 'A', 'L', 'A', 'M', 'P', 'O', 'O', 'N', ' ', 'C', 'O', 'N'], ['A', 'S', 'S', 'N', ' ', ' ', 'E', 'K', 'G', ' ', 'G', 'A', 'B', 'L', 'E'], [' ', ' ', ' ', 'D', 'E', 'N', 'T', 'I', ' ', 'M', 'E', 'M', 'O', ' ', ' '], [' ', 'A', 'E', 'O', 'L', 'I', 'A', 'N', 'H', 'A', 'R', 'P', 'O', 'O', 'N'], ['M', 'O', 'A', 'N', 'E', 'R', ' ', 'S', 'A', 'X', ' ', 'S', 'K', 'U', 'A'], ['E', 'R', 'S', ' ', 'M', 'V', 'P', ' ', 'T', 'W', 'I', ' ', 'P', 'T', 'S'], ['O', 'T', 'T', 'O', ' ', 'A', 'U', 'S', ' ', 'E', 'S', 'P', 'R', 'I', 'T'], ['W', 'A', 'I', 'L', 'I', 'N', 'G', 'W', 'A', 'L', 'L', 'O', 'O', 'N', ' '], [' ', ' ', 'N', 'A', 'R', 'A', ' ', 'I', 'D', 'L', 'E', 'S', ' ', ' ', ' '], ['R', 'E', 'D', 'Y', 'E', ' ', 'U', 'M', 'A', ' ', ' ', 'E', 'C', 'H', 'O'], ['A', 'R', 'I', ' ', 'F', 'I', 'L', 'M', 'B', 'U', 'F', 'F', 'O', 'O', 'N'], ['J', 'O', 'E', ' ', 'U', 'T', 'N', 'E', ' ', 'S', 'L', 'O', 'P', 'P', 'Y'], ['A', 'S', 'S', ' ', 'L', 'E', 'A', 'R', ' ', 'C', 'O', 'R', 'T', 'E', 'X']]\n", + "[[ 1 12 3 15 22 5 0 14 19 5 3 0 13 9 3]\n", + " [12 5 1 14 5 4 0 1 12 20 15 0 1 4 15]\n", + " [12 1 22 1 12 1 13 16 15 15 14 0 3 15 14]\n", + " [ 1 19 19 14 0 0 5 11 7 0 7 1 2 12 5]\n", + " [ 0 0 0 4 5 14 20 9 0 13 5 13 15 0 0]\n", + " [ 0 1 5 15 12 9 1 14 8 1 18 16 15 15 14]\n", + " [13 15 1 14 5 18 0 19 1 24 0 19 11 21 1]\n", + " [ 5 18 19 0 13 22 16 0 20 23 9 0 16 20 19]\n", + " [15 20 20 15 0 1 21 19 0 5 19 16 18 9 20]\n", + " [23 1 9 12 9 14 7 23 1 12 12 15 15 14 0]\n", + " [ 0 0 14 1 18 1 0 9 4 12 5 19 0 0 0]\n", + " [18 5 4 25 5 0 21 13 1 0 0 5 3 8 15]\n", + " [ 1 18 9 0 6 9 12 13 2 21 6 6 15 15 14]\n", + " [10 15 5 0 21 20 14 5 0 19 12 15 16 16 25]\n", + " [ 1 19 19 0 12 5 1 18 0 3 15 18 20 5 24]]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Let's write a routine that draws this out nicely" + ], + "metadata": { + "id": "v7UbEiIjYdxj" + } + }, + { + "cell_type": "code", + "source": [ + "def draw_crossword(puzzle_as_integers):\n", + "\n", + " # Find number of rows and columns\n", + " n_rows = puzzle_as_integers.shape[0]\n", + " n_cols = puzzle_as_integers.shape[1]\n", + "\n", + " # Draw the top row\n", + " print(\"╔\", end=\"\")\n", + " for i in range(n_cols-1):\n", + " print(\"═╤\", end=\"\")\n", + " print(\"═╗\")\n", + "\n", + " for c_row in range(n_rows):\n", + " print(\"║\", end=\"\")\n", + " for c_col in range(n_cols):\n", + " if puzzle_as_integers[c_row][c_col] == 0:\n", + " print(u\"\\u2588\", end=\"\") # Use block character for blank spaces\n", + " else:\n", + " print(chr(puzzle_as_integers[c_row][c_col] + ord('A') - 1), end=\"\")\n", + " if(c_col < n_cols-1):\n", + " print(\"│\", end=\"\")\n", + " print(\"║\")\n", + "\n", + "\n", + " # Draw the bottom row\n", + " print(\"╚\", end=\"\")\n", + " for i in range(n_cols-1):\n", + " print(\"═╧\", end=\"\")\n", + " print(\"═╝\")\n", + "\n", + "draw_crossword(puzzle_as_integers)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "gdJakiT6TrIU", + "outputId": "b89a05aa-a6b2-4fd1-ac77-60a435a30c91" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║A│L│C│O│V│E│█│N│S│E│C│█│M│I│C║\n", + "║L│E│A│N│E│D│█│A│L│T│O│█│A│D│O║\n", + "║L│A│V│A│L│A│M│P│O│O│N│█│C│O│N║\n", + "║A│S│S│N│█│█│E│K│G│█│G│A│B│L│E║\n", + "║█│█│█│D│E│N│T│I│█│M│E│M│O│█│█║\n", + "║█│A│E│O│L│I│A│N│H│A│R│P│O│O│N║\n", + "║M│O│A│N│E│R│█│S│A│X│█│S│K│U│A║\n", + "║E│R│S│█│M│V│P│█│T│W│I│█│P│T│S║\n", + "║O│T│T│O│█│A│U│S│█│E│S│P│R│I│T║\n", + "║W│A│I│L│I│N│G│W│A│L│L│O│O│N│█║\n", + "║█│█│N│A│R│A│█│I│D│L│E│S│█│█│█║\n", + "║R│E│D│Y│E│█│U│M│A│█│█│E│C│H│O║\n", + "║A│R│I│█│F│I│L│M│B│U│F│F│O│O│N║\n", + "║J│O│E│█│U│T│N│E│█│S│L│O│P│P│Y║\n", + "║A│S│S│█│L│E│A│R│█│C│O│R│T│E│X║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╧═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "The goal of this notebook will be to take a set of words and create a crossword layout like this in an $n \\times n$ grid. We'll start with just a small set of clues. " + ], + "metadata": { + "id": "yDJLSLNtaSOL" + } + }, + { + "cell_type": "code", + "source": [ + "words = ['JANE','AUSTEN','PRIDE','NOVEL','DARCY','SENSE','EMMA','ESTATE','BENNET','BATH']" + ], + "metadata": { + "id": "NkNMg4GuYt-h" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "This routine takes the words, the grid size and various sets of constraints (which we'll develop one at a time). It then runs the solver and displays the crossword." + ], + "metadata": { + "id": "NdMkEZVl_Dci" + } + }, + { + "cell_type": "code", + "source": [ + "def solve_crossword (words, grid_size, add_constraint_set1, add_constraint_set2=None, add_constraint_set3=None, add_constraint_set4=None ):\n", + "\n", + " # Fail if longest string length is not large enough to fit in grid\n", + " longest_string_length = max(len(word) for word in words)\n", + " if (longest_string_length > grid_size):\n", + " print(\"Grid too small, no solution\")\n", + " return ;\n", + "\n", + " start_time = time.time()\n", + " # Set up the SAT solver\n", + " s = Solver()\n", + "\n", + " # This is a dictionary indexed by the word itself that contains the possible start\n", + " # positions of that word, and whether the word is horizontal or vertical\n", + " # The number of possible positions depend on the grid size as the word cannot exceed\n", + " # grid.\n", + " placement_vars = {word: [[[z3.Bool(f'{word}_{orientation}_{y},{x}')\n", + " for x in range(grid_size-len(word)+1 if orientation=='h' else grid_size )]\n", + " for y in range(grid_size-len(word)+1 if orientation=='v' else grid_size )]\n", + " for orientation in ['h', 'v']]\n", + " for word in words}\n", + "\n", + " # We will also define variables that indicate which letter is at which position\n", + " # There are 27 possible characters (26 letters and a blank)\n", + " # The variable x_i,j,k says that letter k is at position (i,j) in the grid\n", + " letter_posns = [[[ z3.Bool(\"x_{%d,%d,%d}\"%((i,j,k))) for k in range(0,27)] for j in range(0,grid_size) ] for i in range(0,grid_size) ]\n", + "\n", + " # Add the first set of constraints\n", + " s = add_constraint_set1(s, placement_vars, letter_posns, words, grid_size)\n", + " # Add the second set of constraints if present\n", + " if add_constraint_set2 is not None:\n", + " s = add_constraint_set2(s, placement_vars, letter_posns, words, grid_size)\n", + " # Add the third set of constraints if present\n", + " if add_constraint_set3 is not None:\n", + " s = add_constraint_set3(s, placement_vars, letter_posns, words, grid_size)\n", + " # Add the fourth set of constraints if present\n", + " if add_constraint_set4 is not None:\n", + " s = add_constraint_set4(s, placement_vars, letter_posns, words, grid_size)\n", + "\n", + " # Check if it's SAT (creates the model)\n", + " sat_result = s.check()\n", + " print(f\"Executed in {time.time()-start_time:.4f} seconds\")\n", + " print(sat_result)\n", + "\n", + " # If it is then draw crossword, otherwise return\n", + " if sat_result == z3.sat:\n", + " result = s.model()\n", + " # Retrieve the letter position variables in the solution as [0,1] values\n", + " x_vals = np.array([[[int(bool(result[z3.Bool(\"x_{%d,%d,%d}\" % (i, j, k))])) for k in range(0,27)] for j in range(0,grid_size) ] for i in range(0,grid_size) ] )\n", + "\n", + " # Find the position of the true value -- this is now a 2D grid with a 0 where there is a space and a value 1-26 representing a letter\n", + " solution = np.argmax(x_vals, axis=2)\n", + " # Draw the solution\n", + " draw_crossword(solution)\n", + " else:\n", + " print(\"No solution\")" + ], + "metadata": { + "id": "Ijuo4HdSefPk" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Here's a couple of helpful routines that we can make use of" + ], + "metadata": { + "id": "BBD_gK-q_AaT" + } + }, + { + "cell_type": "code", + "source": [ + "# Takes a list of z3.Bool variables and returns constraints\n", + "# ensuring that there is exactly one true\n", + "def exactly_one(x):\n", + " return PbEq([(i,1) for i in x],1)\n", + "\n", + "# Converts a word in capital letters to its indices so 'ABD' becomes [1,2,4]\n", + "def letter_to_index(word):\n", + " return [ord(char) - ord('A') + 1 for char in word]" + ], + "metadata": { + "id": "O5p-8Ul6cvsk" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Let's work on the first set of constraints. \n", + "\n", + "\n", + "1. Each word can only appear at one valid position\n", + "2. Each position in the grid can have only a single letter present\n", + "3. The letters implied by the word positions must agree where the words overlap\n", + "\n" + ], + "metadata": { + "id": "VT-QNf-FHClF" + } + }, + { + "cell_type": "code", + "source": [ + "def add_constraint_set1(s, placement_vars, letter_posns, words, grid_size):\n", + " # Constraint 1: Each word can only be placed in exactly one position\n", + " for word in words:\n", + " # Flatten the possible positions into a list\n", + " flat_list = [item for sublist1 in placement_vars[word] for sublist2 in sublist1 for item in sublist2]\n", + " # Add the constraint that exactly one must be true\n", + " s.add(exactly_one(flat_list))\n", + "\n", + " # Constraint 2: Each grid position can only have one letter present\n", + " for i in range(0,grid_size):\n", + " for j in range(0,grid_size):\n", + " s.add(exactly_one(letter_posns[i][j]))\n", + "\n", + " # Constraint 3: If a word is in a given position and orientation, the letters at the\n", + " # appropriate grid positions must correspond (uses the routine letter_to_index() defined above)\n", + " for word in words:\n", + " for i in range(0,grid_size):\n", + " for j in range(0,grid_size-len(word)+1):\n", + " for letter_index in range(0,len(word)):\n", + " s.add(Implies(placement_vars[word][0][i][j], letter_posns[i][j+letter_index][letter_to_index(word)[letter_index]]))\n", + " for i in range(0,grid_size-len(word)+1):\n", + " for j in range(0,grid_size):\n", + " for letter_index in range(0,len(word)):\n", + " s.add(Implies(placement_vars[word][1][i][j], letter_posns[i+letter_index][j][letter_to_index(word)[letter_index]]))\n", + "\n", + " return s" + ], + "metadata": { + "id": "NdjsISBCFr6K" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Let's test this routine so far\n", + "solve_crossword(words, 10, add_constraint_set1)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "ksTQlgvnHiqK", + "outputId": "d453c0b1-9507-4adf-9336-f38e4e6ff5ad" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║A│█│█│E│S│T│A│T│E│S║\n", + "║U│B│E│N│N│E│T│O│J│C║\n", + "║S│F│S│V│E│Z│A│Z│A│G║\n", + "║T│E│H│X│V│B│R│D│N│T║\n", + "║E│S│E│N│S│E│C│A│E│A║\n", + "║N│T│O│█│█│K│Y│R│█│█║\n", + "║█│█│P│R│I│D│E│C│B│B║\n", + "║B│Z│A│B│B│J│I│Y│Q│█║\n", + "║J│B│A│T│H│B│E│M│M│A║\n", + "║N│O│V│E│L│A│E│B│U│S║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "You can see that the words are all in there, but we don't have blank spaces where we should. We need to add two further constraints to improve matters\n", + "\n", + "1. Horizontal words must have a blank space or an edge to the left and right of their positions. Vertical words must have a blank space or and edge above or below their positions.\n", + "2. Any position that is not part of a word should be blank\n", + "\n" + ], + "metadata": { + "id": "SlzqglXAH1wG" + } + }, + { + "cell_type": "code", + "source": [ + "def add_constraint_set2(s, placement_vars, letter_posns, words, grid_size):\n", + " # Constraint 1: Horizontal words must either start in the first column or have a 0 to their left\n", + " # Horizontal words must either finish in the last column of have a 0 to their right\n", + " # Vertical words must either start in the first row or have a 0 above them\n", + " # Vertical words must either end in the last row of have a 0 below them\n", + " for word in words:\n", + " # Horizontal words\n", + " for i in range(grid_size):\n", + " for j in range(1, grid_size - len(word)+1 ):\n", + " # Check for border or blank square before the word starts\n", + " s.add(Implies(placement_vars[word][0][i][j], letter_posns[i][j-1][0]))\n", + " s.add(Implies(placement_vars[word][0][i][j-1], letter_posns[i][j+len(word)-1][0]))\n", + "\n", + " # Vertical words\n", + " for i in range(1,grid_size - len(word)+1 ):\n", + " for j in range(grid_size):\n", + " # Check blank square before the word starts\n", + " s.add(Implies(placement_vars[word][1][i][j], letter_posns[i-1][j][0]))\n", + " s.add(Implies(placement_vars[word][1][i-1][j], letter_posns[i+len(word)-1][j][0]))\n", + "\n", + " # Constraint 2: Any position in the crossword grid that is not part of a word must be a blank space\n", + " # This stops random characters appearing outside the solution\n", + " for i in range(grid_size):\n", + " for j in range(grid_size):\n", + " # Create a list of placement variables that affect the current square\n", + " relevant_placements = []\n", + " for word in words:\n", + " # Horizontal words\n", + " for col in range(grid_size - len(word) + 1):\n", + " if j >= col and j < col + len(word):\n", + " relevant_placements.append(placement_vars[word][0][i][col])\n", + "\n", + " # Vertical words\n", + " for row in range(grid_size - len(word) + 1):\n", + " if i >= row and i < row + len(word):\n", + " relevant_placements.append(placement_vars[word][1][row][j])\n", + "\n", + "\n", + " # If none of the relevant placements are true, the square must be blank\n", + " s.add(Implies(Not(Or(relevant_placements)), letter_posns[i][j][0]))\n", + "\n", + " return s" + ], + "metadata": { + "id": "7iHXNe_0F7ej" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Let's test this routine so far\n", + "solve_crossword(words, 10, add_constraint_set1, add_constraint_set2)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "lPEcYEIbItHp", + "outputId": "b6a560d2-9962-4be1-bc8b-86b853c77c64" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│█│█│█│█│█│█│█│█║\n", + "║█│█│P│█│█│█│E│█│█│█║\n", + "║█│█│R│A│█│B│S│█│█│█║\n", + "║█│N│I│U│█│E│T│█│S│D║\n", + "║B│O│D│S│J│N│A│E│E│A║\n", + "║A│V│E│T│A│N│T│M│N│R║\n", + "║T│E│█│E│N│E│E│M│S│C║\n", + "║H│L│█│N│E│T│█│A│E│Y║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "That's an improvement, but it's not perfect; every letter is now part of either a horizontal or a vertical word. However, when there are several vertical words adjacent to each other and we read horizontally across these words, we get nonsense. We can fix this by adding another constraint:\n", + "\n", + "* If a letter is in a horizontal word, it is either inside a vertical word as well *OR* it has a blank square (or the edge of the grid) above and below it.\n", + "* If a letter is in a vertical word, it is either inside a horizontal word as well *OR* it has a blank square (or the edge of the grid) to the left and the right of it." + ], + "metadata": { + "id": "2X4guZwZI_JW" + } + }, + { + "cell_type": "code", + "source": [ + "def add_constraint_set3(s, placement_vars, letter_posns, words, grid_size):\n", + " # Constraint 1: If a letter is in a horizontal word, it either\n", + " # -- is inside a vertical word as well\n", + " # -- has a blank (or edge) above and below it\n", + " # If a letter in a vertical word exists, it is either\n", + " # -- is inside a horizontal word too\n", + " # -- has a blank (or edge) to the left and to the right of it.\n", + " for i in range(0,grid_size):\n", + " for j in range(0,grid_size):\n", + " relevant_placements_horz = []\n", + " relevant_placements_vert = []\n", + " for word in words:\n", + " for j2 in range (max(0,j-len(word)+1), min(j+1,grid_size-len(word)+1)):\n", + " relevant_placements_horz.append(placement_vars[word][0][i][j2])\n", + " for i2 in range(max(0,i-len(word)+1), min(i+1,grid_size-len(word)+1)):\n", + " relevant_placements_vert.append(placement_vars[word][1][i2][j])\n", + " in_horizontal_word = Or(relevant_placements_horz)\n", + " in_vertical_word = Or(relevant_placements_vert)\n", + "\n", + " if(i == 0):\n", + " above_and_below_are_blank = letter_posns[i+1][j][0]\n", + " else:\n", + " if(i == grid_size-1):\n", + " above_and_below_are_blank = letter_posns[i-1][j][0]\n", + " else:\n", + " above_and_below_are_blank = And(letter_posns[i-1][j][0],letter_posns[i+1][j][0])\n", + "\n", + " if(j == 0):\n", + " left_and_right_are_blank = letter_posns[i][j+1][0]\n", + " else:\n", + " if(j == grid_size-1):\n", + " left_and_right_are_blank = letter_posns[i][j-1][0]\n", + " else:\n", + " left_and_right_are_blank = And(letter_posns[i][j-1][0],letter_posns[i][j+1][0])\n", + " s.add(Implies(in_horizontal_word, Or(in_vertical_word, above_and_below_are_blank)))\n", + " s.add(Implies(in_vertical_word, Or(in_horizontal_word, left_and_right_are_blank)))\n", + "\n", + " return s" + ], + "metadata": { + "id": "4LSgimAjGQdT" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Let's see how this improves things\n", + "solve_crossword(words, 10, add_constraint_set1, add_constraint_set2, add_constraint_set3)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "8HHqCWMzKCTL", + "outputId": "76f583ca-fcd5-4f67-9373-8a29596564ff" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║D│A│R│C│Y│█│E│M│M│A║\n", + "║█│█│█│█│█│█│S│█│█│█║\n", + "║█│B│E│N│N│E│T│█│█│█║\n", + "║█│█│█│█│█│█│A│█│█│█║\n", + "║J│A│N│E│█│█│T│█│█│█║\n", + "║█│█│█│█│█│S│E│N│S│E║\n", + "║N│O│V│E│L│█│█│█│█│█║\n", + "║█│█│█│█│█│P│R│I│D│E║\n", + "║B│A│T│H│█│█│█│█│█│█║\n", + "║█│█│█│█│A│U│S│T│E│N║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "This looks like it's working better, but we now have the problem that words do not all connect to each other. Hence, we have to add a final constraint that all of the letters are connected.\n", + "\n", + "First we form an $N\\times N$ adjacency matrix which the value is true if word $i$ intersects with word $j$. Then we use the 'is_fully_connected' construction that we developed in the notebook on SAT constructions." + ], + "metadata": { + "id": "R69urJyN9wlc" + } + }, + { + "cell_type": "code", + "source": [ + "def is_fully_connected(s, adjacency):\n", + " # Size of the adjacency matrix\n", + " n_components = len(adjacency)\n", + " # We'll construct a N x N x log[N] array of variables\n", + " # The NxN variables in the first layer represent A, the variables in the second layer represent B and so on\n", + " n_layers = math.ceil(math.log(n_components,2))+1\n", + " connected = [[[ z3.Bool(\"conn_{%d,%d,%d}\"%((i,j,n))) for n in range(0, n_layers)] for j in range(0, n_components) ] for i in range(0, n_components) ]\n", + "\n", + " # Constraint 1\n", + " # The value in the top layer of the connected structure is equal to the adjacency matrix\n", + " for i in range(n_components):\n", + " for j in range(n_components):\n", + " s.add(connected[i][j][0]==adjacency[i][j])\n", + "\n", + " # Constraint 2\n", + " # Value at position [i,j] in layer n is value at position [i,j] of the binary matrix product of layer n-1 with itself\n", + " for n in range(1,n_layers):\n", + " for i in range(n_components):\n", + " for j in range(n_components):\n", + " matrix_entry_ij = False\n", + " for k in range(n_components):\n", + " matrix_entry_ij = Or(matrix_entry_ij, And(connected[i][k][n-1],connected[k][j][n-1]))\n", + " s.add(connected[i][j][n]==matrix_entry_ij)\n", + "\n", + " # Constraint 3 -- any row of column of the matrix should be full of ones at the end (everything is connected)\n", + " for i in range(n_components):\n", + " s.add(connected[i][0][n_layers-1])\n", + "\n", + " return s" + ], + "metadata": { + "id": "2uPPXENwLugr" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Helper routine that returns true if the current word is at position (i,j) in the grid\n", + "def word_at_position_ij(i,j, placement_vars, word, grid_size):\n", + "\n", + " relevant_placements = [] ;\n", + " # Deal with horizontal words first\n", + " for horz_pos in range(np.max([0, j-len(word)+1]), np.min([j+1, grid_size-len(word)+1])):\n", + " # First the horizontal words\n", + " relevant_placements.append(placement_vars[word][0][i][horz_pos])\n", + " # Then the vertical words\n", + " for vert_pos in range(np.max([0, i-len(word)+1]), np.min([i+1, grid_size-len(word)+1])):\n", + " relevant_placements.append(placement_vars[word][1][vert_pos][j])\n", + "\n", + " return Or(relevant_placements) ;" + ], + "metadata": { + "id": "FhJPmmAOV3AS" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def add_constraint_set4(s, placement_vars, letter_posns, words, grid_size):\n", + " # First lets create a new variable that represents the adjacency matrix of the words\n", + " adjacency = [[ z3.Bool(\"adj_{%d,%d}\"%((i,j))) for j in range(0, len(words)) ] for i in range(0, len(words)) ]\n", + "\n", + " # Run through each word\n", + " for c_w1 in range(len(words)):\n", + " for c_w2 in range(c_w1, len(words)):\n", + " # If indices are the same then adjacency is true\n", + " if c_w1 == c_w2:\n", + " s.add(adjacency[c_w1][c_w2])\n", + "\n", + " word1 = words[c_w1]\n", + " word2 = words[c_w2]\n", + " words_intersect = False\n", + " # Run through each position in the grid\n", + " for i in range(grid_size):\n", + " for j in range(grid_size):\n", + " # Words interset if both at a given position for any position\n", + " words_intersect = Or(words_intersect, And(word_at_position_ij(i,j, placement_vars, word1, grid_size), word_at_position_ij(i,j, placement_vars, word2, grid_size)))\n", + " # Set value and symmetric value of adjacency matrix\n", + " s.add(adjacency[c_w1][c_w2] == words_intersect)\n", + " s.add(adjacency[c_w2][c_w1] == adjacency[c_w1][c_w2])\n", + "\n", + " # Add the constraint that the adjacency matrix must be fully connected\n", + " s = is_fully_connected(s, adjacency)\n", + " return s" + ], + "metadata": { + "id": "Xmzv8g_-RIid" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Let's see how this improves things (took 32 seconds to run for me, but might be longer)\n", + "solve_crossword(words, 11, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "M7pPhYTfaLEd", + "outputId": "fe616dd9-caca-4aea-e94c-fefaf5fe0ec4" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Executed in 32.3613 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║█│█│█│█│█│█│█│█│█│█│█║\n", + "║J│█│█│█│█│█│█│█│█│█│█║\n", + "║A│█│█│B│█│█│█│█│█│█│P║\n", + "║N│O│V│E│L│█│█│█│█│█│R║\n", + "║E│█│█│N│█│█│█│B│█│█│I║\n", + "║█│█│█│N│█│S│█│A│█│█│D║\n", + "║D│█│█│E│█│E│S│T│A│T│E║\n", + "║A│U│S│T│E│N│█│H│█│█│█║\n", + "║R│█│█│█│█│S│█│█│█│█│█║\n", + "║C│█│█│█│█│E│M│M│A│█│█║\n", + "║Y│█│█│█│█│█│█│█│█│█│█║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "# Now let's see what the smallest grid we can fit this in is. The longest word is 6 letters, so can't be shorter than this\n", + "solve_crossword(words, 10, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)\n", + "solve_crossword(words, 9, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)\n", + "solve_crossword(words, 8, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)\n", + "solve_crossword(words, 7, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)\n", + "solve_crossword(words, 6, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "OFLdFlaP6g7L", + "outputId": "d7d8b512-84ef-4b48-a9f9-9912cd01622b" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Executed in 139.9588 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║█│█│█│█│█│█│█│█│B│█║\n", + "║█│█│█│█│█│J│█│█│E│█║\n", + "║D│█│█│█│█│A│█│█│N│█║\n", + "║A│U│S│T│E│N│█│█│N│█║\n", + "║R│█│█│█│S│E│N│S│E│█║\n", + "║C│█│█│█│T│█│O│█│T│█║\n", + "║Y│█│█│█│A│█│V│█│█│B║\n", + "║█│█│█│█│T│█│E│M│M│A║\n", + "║P│R│I│D│E│█│L│█│█│T║\n", + "║█│█│█│█│█│█│█│█│█│H║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╧═╝\n", + "Executed in 261.2188 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╤═╤═╗\n", + "║█│█│█│█│B│A│T│H│█║\n", + "║P│█│█│█│E│█│█│█│█║\n", + "║R│█│A│█│N│█│█│S│█║\n", + "║I│█│U│█│N│O│V│E│L║\n", + "║D│█│S│█│E│█│█│N│█║\n", + "║E│S│T│A│T│E│█│S│█║\n", + "║█│█│E│█│█│M│█│E│█║\n", + "║J│A│N│E│█│M│█│█│█║\n", + "║█│█│█│█│D│A│R│C│Y║\n", + "╚═╧═╧═╧═╧═╧═╧═╧═╧═╝\n", + "Executed in 199.8628 seconds\n", + "unsat\n", + "No solution\n", + "Executed in 4.0513 seconds\n", + "unsat\n", + "No solution\n", + "Executed in 1.7816 seconds\n", + "unsat\n", + "No solution\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "These were some random words that I chose for an example. Here are some real words from an NY Times mini puzzle. You can see that it recovers the minimal possible solution correctly." + ], + "metadata": { + "id": "kFQs0iZXmipH" + } + }, + { + "cell_type": "code", + "source": [ + "words2 = ['GUM','TAB','ERA','END','IRA','MAP','TIMWALZ','ONE','ELI','COATS','POPHITS', \\\n", + " 'GOT', 'PIE','BIZ','SPA','ALLSTAR','UNICORN','MEMOPAD','WAH','TEATIME']\n", + "solve_crossword(words2, 7, add_constraint_set1, add_constraint_set2, add_constraint_set3, add_constraint_set4)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "sDmahKMoldGi", + "outputId": "0cf09d11-d14f-431b-e6e8-c08a4e7ea201" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Executed in 13.5120 seconds\n", + "sat\n", + "╔═╤═╤═╤═╤═╤═╤═╗\n", + "║G│U│M│█│T│A│B║\n", + "║O│N│E│█│E│L│I║\n", + "║T│I│M│W│A│L│Z║\n", + "║█│C│O│A│T│S│█║\n", + "║P│O│P│H│I│T│S║\n", + "║I│R│A│█│M│A│P║\n", + "║E│N│D│█│E│R│A║\n", + "╚═╧═╧═╧═╧═╧═╧═╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Further work for keeners:\n", + "\n", + "1. Technically, we should also put in a constraint to stop two words starting in the same place. For example, the current solver would allow the words 'EAT' and 'EATEN' to start in the same place and overlap. Add this constraint.\n", + "\n", + "2. As proposed, this is fairly impractical to help make crosswords. It would be more useful to have a superset of answers and allow the SAT solver to choose from these to make a sensible crossword. You would then ask the SAT solver to build an crossword of size $N$ that included at least $W$ words. Add this feature.\n" + ], + "metadata": { + "id": "c2ZiS3OgzHXW" + } + } + ] +} \ No newline at end of file diff --git a/Trees/SAT_Graph_Coloring.ipynb b/Trees/SAT_Graph_Coloring.ipynb new file mode 100644 index 0000000..9cc3656 --- /dev/null +++ b/Trees/SAT_Graph_Coloring.ipynb @@ -0,0 +1,275 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyPST5sXc/A8YZYz0OFEURdn", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "QjHXD27ieTS-" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Graph coloring\n", + "\n", + "The purpose of this Python notebook is to use investigate using SAT for graph coloring problems. We'll aim to color a map of the United States in such a way that no two adjacent states have the same color. We'll both aim to find a valid color labelling and to determine the minimum number of colors for which this is possible.\n", + "\n", + "You should have completed the notebook on SAT constructions before attempting this notebook.\n", + "Work through the cells below, running each cell in turn. In various places you will see the words \"TODO\". Follow the instructions at these places and write code to complete the functions.\n", + "\n", + "You can save a local copy of this notebook in your Google account and work through it in Colab (recommended) or you can download the notebook and run it locally using Jupyter notebook or similar. If you are using CoLab, we recommend that turn off AI autocomplete (under cog icon in top-right corner), which will give you the answers and defeat the purpose of the exercise.\n", + "\n", + "A fully working version of this notebook with the complete answers can be found [here](https://colab.research.google.com/github/udlbook/udlbook/blob/main/Trees/SAT_Graph_Coloring_Answers.ipynb).\n", + "\n", + "Contact me at iclimbtreesmail@gmail.com if you find any mistakes or have any suggestions." + ], + "metadata": { + "id": "jtMs90veeZIn" + } + }, + { + "cell_type": "code", + "source": [ + "# Install relevant packages\n", + "!pip install z3-solver\n", + "from z3 import *\n", + "import numpy as np\n", + "import geopandas as gpd\n", + "import matplotlib.pyplot as plt" + ], + "metadata": { + "id": "mF6ngqCses3n" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "First let's download a map of the United States and form a 49x49 matrix representing the adjacencies of the lower 48 states. There will be a one in the matrix if two states are adjacent and a zero otherwise." + ], + "metadata": { + "id": "fMXNLIZGQzby" + } + }, + { + "cell_type": "code", + "source": [ + "# Download map of the US\n", + "!wget https://github.com/udlbook/udlbook/raw/refs/heads/main/Trees/cb_2018_us_state_500k.zip\n", + "!unzip -o cb_2018_us_state_500k.zip\n", + "# Read shape data\n", + "# Read the shapefile into a GeoDataFrame\n", + "try:\n", + " states = gpd.read_file(\"cb_2018_us_state_500k.shp\")\n", + "except FileNotFoundError:\n", + " print(\"Shapefile not found. Please make sure 'cb_2018_us_state_500k.shp' is in the current directory.\")\n", + " exit()\n", + "\n", + "# Reatain just lower 48 states and DC to make it easier to draw\n", + "lower48 = states[~states['NAME'].isin(['Alaska', 'Hawaii', 'American Samoa', 'Virgin Islands', \\\n", + " 'Commonwealth of the Northern Mariana Islands', 'Guam', \\\n", + " 'Puerto Rico', 'United States Virgin Islands', 'District of Columbia'])]\n", + "\n", + "# Reset the index to remove gaps\n", + "lower48 = lower48.reset_index(drop=True)" + ], + "metadata": { + "id": "Xfg3YopGffZn" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Get adjacency matrix for remaining 48 states plus DC.\n", + "\n", + "# Get the number of states\n", + "num_states = len(lower48)\n", + "\n", + "# Create an empty adjacency matrix\n", + "adjacency_matrix = np.zeros((num_states, num_states), dtype=int)\n", + "\n", + "# Iterate through pairs of states\n", + "for i in range(num_states):\n", + " for j in range(i + 1, num_states):\n", + " # Check if the geometries of the two states intersect\n", + " if lower48.geometry[i].intersects(lower48.geometry[j]):\n", + " adjacency_matrix[i, j] = 1\n", + " adjacency_matrix[j, i] = 1 # Adjacency matrix is symmetric" + ], + "metadata": { + "id": "W5YOa3-kD04W" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's write a rountine that draws the map given a set of 49 input colors" + ], + "metadata": { + "id": "xi2FRkqpRHmk" + } + }, + { + "cell_type": "code", + "source": [ + "def draw_map(lower48, color_indices):\n", + "\n", + " # Define a list of colors\n", + " colors = ['#316879', '#f47a60', '#7fe7dc', '#ced7d8', '#424b4f', '#773c23', '#aec3cf']\n", + "\n", + " # Assign a random color to each state\n", + " lower48['color'] = [colors[color_indices[i]] for i in range(len(lower48))]\n", + "\n", + " # Create the plot\n", + " fig, ax = plt.subplots(1, 1, figsize=(10, 6))\n", + "\n", + " # Plot the states with the assigned colors\n", + " lower48.plot(color=lower48['color'], cmap=None, categorical=True, legend=False, ax=ax, edgecolor='0.8')\n", + "\n", + " # Customize the plot (optional)\n", + " ax.set_axis_off()\n", + "\n", + " # Display the plot\n", + " plt.show()" + ], + "metadata": { + "id": "F9s4LoceWm1p" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Test the drawing routine with random colors\n", + "color_indices = np.random.randint(0, 7, size=48)\n", + "draw_map(lower48, color_indices)" + ], + "metadata": { + "id": "X8GL1sbfSq4z" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's write a routine to color the map in such a way that no two adjacent states have the same color" + ], + "metadata": { + "id": "3A5_7mByYrur" + } + }, + { + "cell_type": "code", + "source": [ + "def exactly_one(x):\n", + " return PbEq([(i,1) for i in x],1)\n", + "\n", + "def graph_coloring(adjacency_matrix, n_colors):\n", + " # Create an n_state by n_color structure (list of lists) of binary variables\n", + " # There will be a 1 in a given position if that state has that particular color\n", + " n_state = adjacency_matrix.shape[0]\n", + " x = [[ z3.Bool(\"x_{%d,%d}\"%((i,j))) for j in range(0,n_colors) ] for i in range(0,n_state) ]\n", + " # Set up the sat solver\n", + " s = Solver()\n", + "\n", + " # Add constraint one: each state can only have one color\n", + " # For each row of the matrix, exactly one variable is true\n", + " # TODO Replace this code. You should call the routine \"exactly_one\" above.\n", + " # Hint. X[i] contains the i'th row of the table\n", + " s.add(Not(x[0][0]))\n", + "\n", + " # Add constraint two. Two adjacent states cannot have the same color\n", + " # If there is a one in the adjacency matrix at position i,j, then variable\n", + " # x[i, color] and x[j, color] cannot both be true for every color\n", + " s.add(x[1][0])\n", + "\n", + "\n", + " # Check if it's SAT (creates the model)\n", + " sat_result = s.check()\n", + " print(sat_result)\n", + "\n", + " # If it isn't then return\n", + " if sat_result == z3.sat:\n", + " result = s.model()\n", + " x_vals = np.array([[int(bool(result[z3.Bool(\"x_{%d,%d}\" % (i, j))])) for j in range(7)] for i in range(48)])\n", + " color_indices = np.argmax(x_vals, axis=1)\n", + " draw_map(lower48, color_indices)\n", + "\n", + "\n" + ], + "metadata": { + "id": "dRmdNKGdZRks" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Call the routine to compute the graph coloring with seven colors\n", + "graph_coloring(adjacency_matrix, 7)" + ], + "metadata": { + "id": "0HeFo62NZfBp" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# TODO Find the minimum number of colors needed for a valid solution\n", + "# Add some code here" + ], + "metadata": { + "id": "Qe629Lui0nlW" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "You can find more about the minimum number of colors required for a planar graph like this in this [Wikipedia page](https://en.wikipedia.org/wiki/Four_color_theorem)" + ], + "metadata": { + "id": "RPkY-lHh11i_" + } + } + ] +} \ No newline at end of file diff --git a/Trees/SAT_Graph_Coloring_Answers.ipynb b/Trees/SAT_Graph_Coloring_Answers.ipynb new file mode 100644 index 0000000..705612e --- /dev/null +++ b/Trees/SAT_Graph_Coloring_Answers.ipynb @@ -0,0 +1,279 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyOAcjSfaesYEf3X4ld/x0Qh", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "QjHXD27ieTS-" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Graph coloring\n", + "\n", + "The purpose of this Python notebook is to use investigate using SAT for graph coloring problems. We'll aim to color a map of the United States in such a way that no two adjacent states have the same color. We'll both aim to find a valid color labelling and to determine the minimum number of colors for which this is possible.\n", + "\n", + "You should have completed the notebook on SAT constructions before attempting this notebook.\n", + "\n", + "You can save a local copy of this notebook in your Google account and work through it in Colab (recommended) or you can download the notebook and run it locally using Jupyter notebook or similar.\n", + "\n", + "Contact me at iclimbtreesmail@gmail.com if you find any mistakes or have any suggestions." + ], + "metadata": { + "id": "jtMs90veeZIn" + } + }, + { + "cell_type": "code", + "source": [ + "# Install relevant packages\n", + "!pip install z3-solver\n", + "from z3 import *\n", + "import numpy as np\n", + "from itertools import combinations\n", + "import geopandas as gpd\n", + "import matplotlib.pyplot as plt" + ], + "metadata": { + "id": "mF6ngqCses3n" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "First let's download a map of the United States and form a 49x49 matrix representing the adjacencies of the lower 48 states. There will be a one in the matrix if two states are adjacent and a zero otherwise." + ], + "metadata": { + "id": "fMXNLIZGQzby" + } + }, + { + "cell_type": "code", + "source": [ + "# Download map of the US\n", + "!wget https://github.com/udlbook/udlbook/raw/refs/heads/main/Trees/cb_2018_us_state_500k.zip\n", + "!unzip -o cb_2018_us_state_500k.zip\n", + "# Read shape data\n", + "# Read the shapefile into a GeoDataFrame\n", + "try:\n", + " states = gpd.read_file(\"cb_2018_us_state_500k.shp\")\n", + "except FileNotFoundError:\n", + " print(\"Shapefile not found. Please make sure 'cb_2018_us_state_500k.shp' is in the current directory.\")\n", + " exit()\n", + "\n", + "# Reatain just lower 48 states and DC to make it easier to draw\n", + "lower48 = states[~states['NAME'].isin(['Alaska', 'Hawaii', 'American Samoa', 'Virgin Islands', \\\n", + " 'Commonwealth of the Northern Mariana Islands', 'Guam', \\\n", + " 'Puerto Rico', 'United States Virgin Islands', 'District of Columbia'])]\n", + "\n", + "# Reset the index to remove gaps\n", + "lower48 = lower48.reset_index(drop=True)" + ], + "metadata": { + "id": "Xfg3YopGffZn" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Get adjacency matrix for remaining 48 states plus DC.\n", + "\n", + "# Get the number of states\n", + "num_states = len(lower48)\n", + "\n", + "# Create an empty adjacency matrix\n", + "adjacency_matrix = np.zeros((num_states, num_states), dtype=int)\n", + "\n", + "# Iterate through pairs of states\n", + "for i in range(num_states):\n", + " for j in range(i + 1, num_states):\n", + " # Check if the geometries of the two states intersect\n", + " if lower48.geometry[i].intersects(lower48.geometry[j]):\n", + " adjacency_matrix[i, j] = 1\n", + " adjacency_matrix[j, i] = 1 # Adjacency matrix is symmetric" + ], + "metadata": { + "id": "W5YOa3-kD04W" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's write a rountine that draws the map given a set of 49 input colors" + ], + "metadata": { + "id": "xi2FRkqpRHmk" + } + }, + { + "cell_type": "code", + "source": [ + "def draw_map(lower48, color_indices):\n", + "\n", + " # Define a list of colors\n", + " colors = ['#316879', '#f47a60', '#7fe7dc', '#ced7d8', '#424b4f', '#773c23', '#aec3cf']\n", + "\n", + " # Assign a random color to each state\n", + " lower48['color'] = [colors[color_indices[i]] for i in range(len(lower48))]\n", + "\n", + " # Create the plot\n", + " fig, ax = plt.subplots(1, 1, figsize=(10, 6))\n", + "\n", + " # Plot the states with the assigned colors\n", + " lower48.plot(color=lower48['color'], cmap=None, categorical=True, legend=False, ax=ax, edgecolor='0.8')\n", + "\n", + " # Customize the plot (optional)\n", + " ax.set_axis_off()\n", + "\n", + " # Display the plot\n", + " plt.show()" + ], + "metadata": { + "id": "F9s4LoceWm1p" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Test the drawing routine with random colors\n", + "color_indices = np.random.randint(0, 7, size=48)\n", + "draw_map(lower48, color_indices)" + ], + "metadata": { + "id": "X8GL1sbfSq4z" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's write a routine to color the map in such a way that no two adjacent states have the same color" + ], + "metadata": { + "id": "3A5_7mByYrur" + } + }, + { + "cell_type": "code", + "source": [ + "def exactly_one(x):\n", + " return PbEq([(i,1) for i in x],1)\n", + "\n", + "def graph_coloring(adjacency_matrix, n_colors):\n", + " # Create an n_state by n_color structure (list of lists) of binary variables\n", + " # There will be a 1 in a given position if that state has that particular color\n", + " n_state = adjacency_matrix.shape[0]\n", + " x = [[ z3.Bool(\"x_{%d,%d}\"%((i,j))) for j in range(0,n_colors) ] for i in range(0,n_state) ]\n", + " # Set up the sat solver\n", + " s = Solver()\n", + "\n", + " # Add constraint one: each state can only have one color\n", + " # For each row of the matrix, exactly one variable is true\n", + " for c_state in range(0,n_state):\n", + " s.add(exactly_one(x[c_state]))\n", + "\n", + " # Add constraint two. Two adjacent states cannot have the same color\n", + " # If there is a one in the adjacency matrix at position i,j, then variable\n", + " # x[i, color] and x[j, color] cannot both be true for every color\n", + " for c_state in range(0,n_state):\n", + " for c_neighbor in range(c_state+1,n_state):\n", + " if adjacency_matrix[c_state,c_neighbor]:\n", + " for c_color in range(0,n_colors):\n", + " s.add(Or(Not(x[c_state][c_color]),Not(x[c_neighbor][c_color])))\n", + "\n", + " # Check if it's SAT (creates the model)\n", + " sat_result = s.check()\n", + " print(sat_result)\n", + "\n", + " # If it isn't then return\n", + " if sat_result == z3.sat:\n", + " result = s.model()\n", + " x_vals = np.array([[int(bool(result[z3.Bool(\"x_{%d,%d}\" % (i, j))])) for j in range(7)] for i in range(48)])\n", + " color_indices = np.argmax(x_vals, axis=1)\n", + " draw_map(lower48, color_indices)\n", + "\n", + "\n", + "" + ], + "metadata": { + "id": "dRmdNKGdZRks" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Call the routine to compute the graph coloring with seven colors\n", + "graph_coloring(adjacency_matrix, 7)" + ], + "metadata": { + "id": "0HeFo62NZfBp" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Find the minimum numbers required to color\n", + "graph_coloring(adjacency_matrix, 4)\n", + "graph_coloring(adjacency_matrix, 3)\n", + "\n", + "# The minimum number is four colors" + ], + "metadata": { + "id": "Qe629Lui0nlW" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "You can find more about the minimum number of colors required for a planar graph like this in this [Wikipedia page](https://en.wikipedia.org/wiki/Four_color_theorem)" + ], + "metadata": { + "id": "RPkY-lHh11i_" + } + } + ] +} \ No newline at end of file diff --git a/Trees/SAT_Sudoku.ipynb b/Trees/SAT_Sudoku.ipynb new file mode 100644 index 0000000..d005b63 --- /dev/null +++ b/Trees/SAT_Sudoku.ipynb @@ -0,0 +1,270 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyNm1191WQYfb72RCYRciluw", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "QjHXD27ieTS-" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Solving Soduku problems with SAT\n", + "\n", + "The purpose of this Python notebook is to use investigate using SAT to solve Sudoku problems. \n", + "\n", + "You should have completed the notebook on SAT constructions before attempting this notebook.\n", + "\n", + "You should have completed the notebook on SAT constructions before attempting this notebook. Work through the cells below, running each cell in turn. In various places you will see the words \"TODO\". Follow the instructions at these places and write code to complete the functions.\n", + "\n", + "You can save a local copy of this notebook in your Google account and work through it in Colab (recommended) or you can download the notebook and run it locally using Jupyter notebook or similar. If you are using CoLab, we recommend that turn off AI autocomplete (under cog icon in top-right corner), which will give you the answers and defeat the purpose of the exercise.\n", + "\n", + "A fully working version of this notebook with the complete answers can be found [here](https://colab.research.google.com/github/udlbook/udlbook/blob/main/Trees/SAT_Sudoku_Answers.ipynb).\n", + "\n", + "Contact me at iclimbtreesmail@gmail.com if you find any mistakes or have any suggestions." + ], + "metadata": { + "id": "jtMs90veeZIn" + } + }, + { + "cell_type": "code", + "source": [ + "# Install relevant packages\n", + "!pip install z3-solver\n", + "from z3 import *\n", + "import numpy as np" + ], + "metadata": { + "id": "mF6ngqCses3n" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's define a Sudoku problem. We'll make a 9x9 matrix use zeros to represent unknown values." + ], + "metadata": { + "id": "3A5_7mByYrur" + } + }, + { + "cell_type": "code", + "source": [ + "puzzle = [[0,0,9,4,2,0,0,6,0],\n", + " [0,7,0,9,0,5,3,0,2],\n", + " [5,0,0,0,0,3,0,9,0],\n", + " [0,0,0,8,0,1,0,2,0],\n", + " [2,6,0,0,0,0,0,5,1],\n", + " [0,1,8,2,0,0,4,0,0],\n", + " [3,8,0,0,0,4,0,1,9],\n", + " [0,9,4,0,3,0,6,8,5],\n", + " [0,2,1,0,0,8,0,3,0]]" + ], + "metadata": { + "id": "cvGNbKkf-Qix" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def expandLine(line):\n", + " return line[0]+line[5:9].join([line[1:5]*2]*3)+line[9:13]\n", + "\n", + "def drawPuzzle(puzzle):\n", + " line0 = expandLine(\"╔═══╤═══╦═══╗\")\n", + " line1 = expandLine(\"║ . │ . ║ . ║\")\n", + " line2 = expandLine(\"╟───┼───╫───╢\")\n", + " line3 = expandLine(\"╠═══╪═══╬═══╣\")\n", + " line4 = expandLine(\"╚═══╧═══╩═══╝\")\n", + "\n", + " symbol = \" 123456789\"\n", + " nums = [ [\"\"]+[symbol[n] for n in row] for row in puzzle ]\n", + " print(line0)\n", + " for r in range(1,9+1):\n", + " print( \"\".join(n+s for n,s in zip(nums[r-1],line1.split(\".\"))) )\n", + " print([line2,line3,line4][(r%9==0)+(r%3==0)])" + ], + "metadata": { + "id": "0EkPFukj_-qc" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "drawPuzzle(puzzle)" + ], + "metadata": { + "id": "CB0QFnLODWTu" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Takes a list of z3.Bool variables and returns constraints\n", + "# ensuring that there is exactly one true\n", + "def exactly_one(x):\n", + " return PbEq([(i,1) for i in x],1)\n", + "\n", + "def solve_soduku(puzzle):\n", + " # Create a 9 x 9 x 9 structure containing Boolean variables\n", + " # If position (i,j,k) is true, this means position (i,j) in the puzzle matrix\n", + " # takes color k+1\n", + " x = [[[ z3.Bool(\"x_{%d,%d,%d}\"%((i,j,k))) for k in range(0,9)] for j in range(0,9) ] for i in range(0,9) ]\n", + " # Set up the sat solver\n", + " s = Solver()\n", + "\n", + " # Add constraint one: each position where the entry is already known\n", + " # should be set to true. Don't forget that the indices of x start at zero not one.\n", + " # TODO: Replace this code.\n", + " s.add(x[1][0])\n", + "\n", + "\n", + " # Add constraint two: each position in the matrix can have only one color\n", + " # For fixed i and j, only one value of k can be true\n", + " # TODO: Replace this code.\n", + " s.add(x[1][0])\n", + "\n", + " # Add constraint three: each row of the table can only have each value once\n", + " # TODO: Replace this code.\n", + " s.add(x[1][0])\n", + "\n", + " # Add constraint four: each column of the table can only have each value once\n", + " # TODO: Replace this code.\n", + " s.add(x[1][0])\n", + "\n", + " # Add constraint five: each 3x3 region can only have each value once\n", + " # TODO: Replace this code.\n", + " s.add(x[1][0])\n", + "\n", + " # Check if it's SAT (creates the model)\n", + " sat_result = s.check()\n", + " print(sat_result)\n", + "\n", + " # If it is then print out solution, otherwise return\n", + " if sat_result == z3.sat:\n", + " result = s.model()\n", + " x_vals = np.array([[[int(bool(result[z3.Bool(\"x_{%d,%d,%d}\" % (i, j, k))])) for k in range(9)] for j in range(9)] for i in range(9)] )\n", + " solution = np.argmax(x_vals, axis=2) + 1\n", + " drawPuzzle(solution)\n", + " else:\n", + " print(\"No solution\")\n", + "\n" + ], + "metadata": { + "id": "dRmdNKGdZRks" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Call the routine to solve the Soduku puzzle\n", + "solve_soduku(puzzle)" + ], + "metadata": { + "id": "0HeFo62NZfBp" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's try it with a more difficult example. Try to solve this yourself and you'll see that it's pretty hard." + ], + "metadata": { + "id": "vL-sDmF7tnxa" + } + }, + { + "cell_type": "code", + "source": [ + "puzzle2 = [[0,5,0,0,0,9,0,8,0],\n", + " [7,0,0,0,0,3,6,0,5],\n", + " [0,0,0,0,6,0,0,1,0],\n", + " [0,0,0,0,0,0,0,6,0],\n", + " [0,1,0,0,3,0,4,0,9],\n", + " [0,0,2,0,0,7,0,0,0],\n", + " [0,9,0,0,4,0,5,0,3],\n", + " [1,0,0,9,0,0,0,0,0],\n", + " [0,0,0,0,0,0,0,0,8]]\n", + "drawPuzzle(puzzle2)\n", + "solve_soduku(puzzle2)" + ], + "metadata": { + "id": "3SWS2QUqtlNI" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Finally, let's modify the last puzzle so it is (non-obviously) impossible to solve. I've just added a 3 to position (3,3) on the grid. It looks innoccuous, but actually makes the puzzle unsolvable." + ], + "metadata": { + "id": "opc4hxI2ugU7" + } + }, + { + "cell_type": "code", + "source": [ + "puzzle3 = [[0,5,0,0,0,9,0,8,0],\n", + " [7,0,0,0,0,3,6,0,5],\n", + " [0,0,3,0,6,0,0,1,0],\n", + " [0,0,0,0,0,0,0,6,0],\n", + " [0,1,0,0,3,0,4,0,9],\n", + " [0,0,2,0,0,7,0,0,0],\n", + " [0,9,0,0,4,0,5,0,3],\n", + " [1,0,0,9,0,0,0,0,0],\n", + " [0,0,0,0,0,0,0,0,8]]\n", + "drawPuzzle(puzzle3)\n", + "solve_soduku(puzzle3)" + ], + "metadata": { + "id": "0lfUKX66ubj0" + }, + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file diff --git a/Trees/SAT_Sudoku_Answers.ipynb b/Trees/SAT_Sudoku_Answers.ipynb new file mode 100644 index 0000000..81eee4a --- /dev/null +++ b/Trees/SAT_Sudoku_Answers.ipynb @@ -0,0 +1,433 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyPtj9hlWODaIsW0bHgGf3Hg", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "QjHXD27ieTS-" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Solving Soduku problems with SAT\n", + "\n", + "The purpose of this Python notebook is to use investigate using SAT to solve Sudoku problems. \n", + "\n", + "You should have completed the notebook on SAT constructions before attempting this notebook.\n", + "\n", + "You can save a local copy of this notebook in your Google account and work through it in Colab (recommended) or you can download the notebook and run it locally using Jupyter notebook or similar.\n", + "\n", + "Contact me at iclimbtreesmail@gmail.com if you find any mistakes or have any suggestions." + ], + "metadata": { + "id": "jtMs90veeZIn" + } + }, + { + "cell_type": "code", + "source": [ + "# Install relevant packages\n", + "!pip install z3-solver\n", + "from z3 import *\n", + "import numpy as np" + ], + "metadata": { + "id": "mF6ngqCses3n", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "f3748d40-b6bd-4e8e-f082-f8668ac33720" + }, + "execution_count": 1, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Collecting z3-solver\n", + " Downloading z3_solver-4.14.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (602 bytes)\n", + "Downloading z3_solver-4.14.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (29.5 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m29.5/29.5 MB\u001b[0m \u001b[31m19.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hInstalling collected packages: z3-solver\n", + "Successfully installed z3-solver-4.14.1.0\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's define a Sudoku problem. We'll make a 9x9 matrix use zeros to represent unknown values." + ], + "metadata": { + "id": "3A5_7mByYrur" + } + }, + { + "cell_type": "code", + "source": [ + "puzzle = [[0,0,9,4,2,0,0,6,0],\n", + " [0,7,0,9,0,5,3,0,2],\n", + " [5,0,0,0,0,3,0,9,0],\n", + " [0,0,0,8,0,1,0,2,0],\n", + " [2,6,0,0,0,0,0,5,1],\n", + " [0,1,8,2,0,0,4,0,0],\n", + " [3,8,0,0,0,4,0,1,9],\n", + " [0,9,4,0,3,0,6,8,5],\n", + " [0,2,1,0,0,8,0,3,0]]" + ], + "metadata": { + "id": "cvGNbKkf-Qix" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def expandLine(line):\n", + " return line[0]+line[5:9].join([line[1:5]*2]*3)+line[9:13]\n", + "\n", + "def drawPuzzle(puzzle):\n", + " line0 = expandLine(\"╔═══╤═══╦═══╗\")\n", + " line1 = expandLine(\"║ . │ . ║ . ║\")\n", + " line2 = expandLine(\"╟───┼───╫───╢\")\n", + " line3 = expandLine(\"╠═══╪═══╬═══╣\")\n", + " line4 = expandLine(\"╚═══╧═══╩═══╝\")\n", + "\n", + " symbol = \" 123456789\"\n", + " nums = [ [\"\"]+[symbol[n] for n in row] for row in puzzle ]\n", + " print(line0)\n", + " for r in range(1,9+1):\n", + " print( \"\".join(n+s for n,s in zip(nums[r-1],line1.split(\".\"))) )\n", + " print([line2,line3,line4][(r%9==0)+(r%3==0)])" + ], + "metadata": { + "id": "0EkPFukj_-qc" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "drawPuzzle(puzzle)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "CB0QFnLODWTu", + "outputId": "3698caa2-e0a2-466a-a542-5e141d7b6bce" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "╔═══╤═══╤═══╦═══╤═══╤═══╦═══╤═══╤═══╗\n", + "║ │ │ 9 ║ 4 │ 2 │ ║ │ 6 │ ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ │ 7 │ ║ 9 │ │ 5 ║ 3 │ │ 2 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 5 │ │ ║ │ │ 3 ║ │ 9 │ ║\n", + "╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣\n", + "║ │ │ ║ 8 │ │ 1 ║ │ 2 │ ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 2 │ 6 │ ║ │ │ ║ │ 5 │ 1 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ │ 1 │ 8 ║ 2 │ │ ║ 4 │ │ ║\n", + "╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣\n", + "║ 3 │ 8 │ ║ │ │ 4 ║ │ 1 │ 9 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ │ 9 │ 4 ║ │ 3 │ ║ 6 │ 8 │ 5 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ │ 2 │ 1 ║ │ │ 8 ║ │ 3 │ ║\n", + "╚═══╧═══╧═══╩═══╧═══╧═══╩═══╧═══╧═══╝\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "# Takes a list of z3.Bool variables and returns constraints\n", + "# ensuring that there is exactly one true\n", + "def exactly_one(x):\n", + " return PbEq([(i,1) for i in x],1)\n", + "\n", + "def solve_soduku(puzzle):\n", + " # Create a 9 x 9 x 9 structure containing Boolean variables\n", + " # If position (i,j,k) is true, this means position (i,j) in the puzzle matrix\n", + " # takes color k\n", + " x = [[[ z3.Bool(\"x_{%d,%d,%d}\"%((i,j,k))) for k in range(0,9)] for j in range(0,9) ] for i in range(0,9) ]\n", + " # Set up the sat solver\n", + " s = Solver()\n", + "\n", + " # Add constraint one: each position where the entry is already known\n", + " # should be set to true.\n", + " for i in range(0,9):\n", + " for j in range(0,9):\n", + " if puzzle[i][j] > 0:\n", + " s.add(x[i][j][puzzle[i][j]-1] == True)\n", + "\n", + "\n", + " # Add constraint two: each position in the matrix can have only one color\n", + " # For fixed i and j, only one value of k can be true\n", + " for i in range(0,9):\n", + " for j in range(0,9):\n", + " s.add(exactly_one([x[i][j][k] for k in range(0,9)]))\n", + "\n", + " # Add constraint three: each row of the table can only have each value once\n", + " for i in range(0,9):\n", + " for k in range(0,9):\n", + " s.add(exactly_one([x[i][j][k] for j in range(0,9)]))\n", + "\n", + " # Add constraint four: each column of the table can only have each value once\n", + " for j in range(0,9):\n", + " for k in range(0,9):\n", + " s.add(exactly_one([x[i][j][k] for i in range(0,9)]))\n", + "\n", + " # Add constraint five: each 3x3 region can only have each value once\n", + " for i in range(0,9,3):\n", + " for j in range(0,9,3):\n", + " for k in range(0,9):\n", + " s.add(exactly_one([x[i+a][j+b][k] for a in range(0,3) for b in range(0,3)]))\n", + "\n", + " # Check if it's SAT (creates the model)\n", + " sat_result = s.check()\n", + " print(sat_result)\n", + "\n", + " # If it isn't then return\n", + " if sat_result == z3.sat:\n", + " result = s.model()\n", + " x_vals = np.array([[[int(bool(result[z3.Bool(\"x_{%d,%d,%d}\" % (i, j, k))])) for k in range(9)] for j in range(9)] for i in range(9)] )\n", + " solution = np.argmax(x_vals, axis=2) + 1\n", + " drawPuzzle(solution)\n", + " else:\n", + " print(\"No solution\")\n", + "\n" + ], + "metadata": { + "id": "dRmdNKGdZRks" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Call the routine to solve the Soduku puzzle\n", + "solve_soduku(puzzle)" + ], + "metadata": { + "id": "0HeFo62NZfBp", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "8840695c-8ce0-4ce1-93ef-d68d1c7098ed" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "sat\n", + "╔═══╤═══╤═══╦═══╤═══╤═══╦═══╤═══╤═══╗\n", + "║ 1 │ 3 │ 9 ║ 4 │ 2 │ 7 ║ 5 │ 6 │ 8 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 8 │ 7 │ 6 ║ 9 │ 1 │ 5 ║ 3 │ 4 │ 2 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 5 │ 4 │ 2 ║ 6 │ 8 │ 3 ║ 1 │ 9 │ 7 ║\n", + "╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣\n", + "║ 4 │ 5 │ 3 ║ 8 │ 7 │ 1 ║ 9 │ 2 │ 6 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 2 │ 6 │ 7 ║ 3 │ 4 │ 9 ║ 8 │ 5 │ 1 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 9 │ 1 │ 8 ║ 2 │ 5 │ 6 ║ 4 │ 7 │ 3 ║\n", + "╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣\n", + "║ 3 │ 8 │ 5 ║ 7 │ 6 │ 4 ║ 2 │ 1 │ 9 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 7 │ 9 │ 4 ║ 1 │ 3 │ 2 ║ 6 │ 8 │ 5 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 6 │ 2 │ 1 ║ 5 │ 9 │ 8 ║ 7 │ 3 │ 4 ║\n", + "╚═══╧═══╧═══╩═══╧═══╧═══╩═══╧═══╧═══╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's try it with a more difficult example. Try to solve this yourself and you'll see that it's pretty hard." + ], + "metadata": { + "id": "vL-sDmF7tnxa" + } + }, + { + "cell_type": "code", + "source": [ + "puzzle2 = [[0,5,0,0,0,9,0,8,0],\n", + " [7,0,0,0,0,3,6,0,5],\n", + " [0,0,0,0,6,0,0,1,0],\n", + " [0,0,0,0,0,0,0,6,0],\n", + " [0,1,0,0,3,0,4,0,9],\n", + " [0,0,2,0,0,7,0,0,0],\n", + " [0,9,0,0,4,0,5,0,3],\n", + " [1,0,0,9,0,0,0,0,0],\n", + " [0,0,0,0,0,0,0,0,8]]\n", + "drawPuzzle(puzzle2)\n", + "solve_soduku(puzzle2)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "3SWS2QUqtlNI", + "outputId": "8c7419ff-70b2-4322-81c0-2f8d8727de72" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "╔═══╤═══╤═══╦═══╤═══╤═══╦═══╤═══╤═══╗\n", + "║ │ 5 │ ║ │ │ 9 ║ │ 8 │ ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 7 │ │ ║ │ │ 3 ║ 6 │ │ 5 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ │ │ ║ │ 6 │ ║ │ 1 │ ║\n", + "╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣\n", + "║ │ │ ║ │ │ ║ │ 6 │ ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ │ 1 │ ║ │ 3 │ ║ 4 │ │ 9 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ │ │ 2 ║ │ │ 7 ║ │ │ ║\n", + "╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣\n", + "║ │ 9 │ ║ │ 4 │ ║ 5 │ │ 3 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 1 │ │ ║ 9 │ │ ║ │ │ ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ │ │ ║ │ │ ║ │ │ 8 ║\n", + "╚═══╧═══╧═══╩═══╧═══╧═══╩═══╧═══╧═══╝\n", + "sat\n", + "╔═══╤═══╤═══╦═══╤═══╤═══╦═══╤═══╤═══╗\n", + "║ 6 │ 5 │ 1 ║ 7 │ 2 │ 9 ║ 3 │ 8 │ 4 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 7 │ 8 │ 9 ║ 4 │ 1 │ 3 ║ 6 │ 2 │ 5 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 3 │ 2 │ 4 ║ 8 │ 6 │ 5 ║ 9 │ 1 │ 7 ║\n", + "╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣\n", + "║ 9 │ 3 │ 5 ║ 1 │ 8 │ 4 ║ 7 │ 6 │ 2 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 8 │ 1 │ 7 ║ 2 │ 3 │ 6 ║ 4 │ 5 │ 9 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 4 │ 6 │ 2 ║ 5 │ 9 │ 7 ║ 8 │ 3 │ 1 ║\n", + "╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣\n", + "║ 2 │ 9 │ 8 ║ 6 │ 4 │ 1 ║ 5 │ 7 │ 3 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 1 │ 7 │ 3 ║ 9 │ 5 │ 8 ║ 2 │ 4 │ 6 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 5 │ 4 │ 6 ║ 3 │ 7 │ 2 ║ 1 │ 9 │ 8 ║\n", + "╚═══╧═══╧═══╩═══╧═══╧═══╩═══╧═══╧═══╝\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Finally, let's modify the last puzzle so it is (non-obviously) impossible to solve. I've just added a 3 to position (3,3) on the grid. It looks innoccuous, but actually makes the puzzle unsolvable." + ], + "metadata": { + "id": "opc4hxI2ugU7" + } + }, + { + "cell_type": "code", + "source": [ + "puzzle3 = [[0,5,0,0,0,9,0,8,0],\n", + " [7,0,0,0,0,3,6,0,5],\n", + " [0,0,3,0,6,0,0,1,0],\n", + " [0,0,0,0,0,0,0,6,0],\n", + " [0,1,0,0,3,0,4,0,9],\n", + " [0,0,2,0,0,7,0,0,0],\n", + " [0,9,0,0,4,0,5,0,3],\n", + " [1,0,0,9,0,0,0,0,0],\n", + " [0,0,0,0,0,0,0,0,8]]\n", + "drawPuzzle(puzzle3)\n", + "solve_soduku(puzzle3)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "0lfUKX66ubj0", + "outputId": "e74941c8-addd-46fd-d908-34b0d975fd71" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "╔═══╤═══╤═══╦═══╤═══╤═══╦═══╤═══╤═══╗\n", + "║ │ 5 │ ║ │ │ 9 ║ │ 8 │ ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 7 │ │ ║ │ │ 3 ║ 6 │ │ 5 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ │ │ 3 ║ │ 6 │ ║ │ 1 │ ║\n", + "╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣\n", + "║ │ │ ║ │ │ ║ │ 6 │ ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ │ 1 │ ║ │ 3 │ ║ 4 │ │ 9 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ │ │ 2 ║ │ │ 7 ║ │ │ ║\n", + "╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣\n", + "║ │ 9 │ ║ │ 4 │ ║ 5 │ │ 3 ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ 1 │ │ ║ 9 │ │ ║ │ │ ║\n", + "╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n", + "║ │ │ ║ │ │ ║ │ │ 8 ║\n", + "╚═══╧═══╧═══╩═══╧═══╧═══╩═══╧═══╧═══╝\n", + "unsat\n", + "No solution\n" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/Trees/SAT_Z3.ipynb b/Trees/SAT_Z3.ipynb new file mode 100644 index 0000000..829132c --- /dev/null +++ b/Trees/SAT_Z3.ipynb @@ -0,0 +1,264 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyOQ/cPvllkwhCPc062L9r/d", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "jv83sxEU2Ig0" + } + }, + { + "cell_type": "markdown", + "source": [ + "# The Z3 library\n", + "\n", + "The purpose of this Python notebook is to use the Z3 library to solve simple SAT problems. \n", + "\n", + "Work through the cells below, running each cell in turn. In various places you will see the words \"TODO\". Follow the instructions at these places and write code to complete the functions.\n", + "\n", + "You can save a local copy of this notebook in your Google account and work through it in Colab (recommended) or you can download the notebook and run it locally using Jupyter notebook or similar. If you are using CoLab, we recommend that turn off AI autocomplete (under cog icon in top-right corner), which will give you the answers and defeat the purpose of the exercise.\n", + "\n", + "A fully working version of this notebook with the complete answers can be found [here](https://colab.research.google.com/github/udlbook/udlbook/blob/main/Trees/SAT_Z3_Answers.ipynb).\n", + "\n", + "Contact me at iclimbtreesmail@gmail.com if you find any mistakes or have any suggestions." + ], + "metadata": { + "id": "IsBSW40O20Hv" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "WZYb15hc19es" + }, + "outputs": [], + "source": [ + "!pip install z3-solver\n", + "from z3 import *\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Example 1\n", + "\n", + "Now let's define and implement a Boolean logic formula. We'll use the one from the Tseitin transormation example in the text.\n", + "\n", + "$$\\phi:= ((x_{1} \\lor x_{2}) \\Leftrightarrow x_{3}) \\Rightarrow (\\lnot x_{4}).$$" + ], + "metadata": { + "id": "aVj9RmuB3ZWJ" + } + }, + { + "cell_type": "markdown", + "source": [ + "First we define the variables:" + ], + "metadata": { + "id": "Amv5G3VR3zIJ" + } + }, + { + "cell_type": "code", + "source": [ + "x1 = Bool('x1')\n", + "x2 = Bool('x2')\n", + "x3 = Bool('x3')\n", + "x4 = Bool('x4')" + ], + "metadata": { + "id": "f0esE71E94fm" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Then we define the Boolean logic formula" + ], + "metadata": { + "id": "0IIRG5ZWIZV6" + } + }, + { + "cell_type": "code", + "source": [ + "phi = Implies(Or(x1, x2)==x3, Not(x4))" + ], + "metadata": { + "id": "C0HGBOjI99Ex" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Next, we add the logic formula to the solver. If we have multiple constraints, connected by AND functions, we could add them one at a time." + ], + "metadata": { + "id": "2Y_1zYOZIoZI" + } + }, + { + "cell_type": "code", + "source": [ + "s = Solver()\n", + "s.add(phi)" + ], + "metadata": { + "id": "zQksy42u-qom" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's see if this logic formula is satisfiable" + ], + "metadata": { + "id": "2lIQSZO8IzOu" + } + }, + { + "cell_type": "code", + "source": [ + "print(s.check())" + ], + "metadata": { + "id": "fraGSSzV_wTO" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "We see that it is. Now lets get it to return a set of variables that satisfies the formula" + ], + "metadata": { + "id": "BPR2s_otI-ca" + } + }, + { + "cell_type": "code", + "source": [ + "print(s.model())" + ], + "metadata": { + "id": "kT3GlhUwI8IN" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Note that it does not print a value for $x_1$ because the formula is true regardless of the value of $x_1$" + ], + "metadata": { + "id": "YL-seZBWJOtG" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Example 2\n", + "\n", + "Let's use Z3 if this formula is SAT or UNSAT:\n", + "\n", + "$$\\phi_2:= \\bigl(x_{1}\\Rightarrow (\\lnot x_{2}\\land x_{3})\\bigr) \\land \\bigl(x_{2} \\Leftrightarrow (\\lnot x_{3} \\lor x_{1})\\bigr).$$" + ], + "metadata": { + "id": "pVAFYHwFJbRw" + } + }, + { + "cell_type": "code", + "source": [ + "# TODO Use Z3 to test if this is SAT or UNSAT\n", + "# 1. Define the formula\n", + "# 2. Create a new\n", + "# 3. Add the formula to the solver\n", + "# 4. Check if SAT or UNSAT\n", + "# 5. If SAT, then print out a sat of variables that satisfies the formula\n" + ], + "metadata": { + "id": "zqdfz1MxJNk1" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Example 3\n", + "\n", + "Let's add another clause to the formula and use Z3 if this formula is SAT or UNSAT:\n", + "\n", + "$$\\phi_1:= \\bigl(x_{1}\\Rightarrow (\\lnot x_{2}\\land x_{3})\\bigr) \\land \\bigl(x_{2} \\Leftrightarrow (\\lnot x_{3} \\lor x_{1})\\bigr)\\land\\lnot \\bigl(\\lnot x_3 \\Rightarrow x_2\\bigr).$$" + ], + "metadata": { + "id": "QAfr332sLyHH" + } + }, + { + "cell_type": "code", + "source": [ + "# TODO Use Z3 to test if this is SAT or UNSAT\n", + "# 1. Define the formula\n", + "# 2. Create a new\n", + "# 3. Add the formula to the solver\n", + "# 4. Check if SAT or UNSAT\n", + "# 5. If SAT, then print out a sat of variables that satisfies the formula\n" + ], + "metadata": { + "id": "fPMvltcyMBGQ" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "You should fined that this is UNSAT. We have added too many constraints and the three terms that are ANDed together are never simultaneously true." + ], + "metadata": { + "id": "ZFW5Z_oWM9TE" + } + } + ] +} \ No newline at end of file diff --git a/Trees/SAT_Z3_Answers.ipynb b/Trees/SAT_Z3_Answers.ipynb new file mode 100644 index 0000000..1404dfe --- /dev/null +++ b/Trees/SAT_Z3_Answers.ipynb @@ -0,0 +1,335 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyNkmY28x40aqtuZo3iefHDD", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "jv83sxEU2Ig0" + } + }, + { + "cell_type": "markdown", + "source": [ + "# The Z3 library\n", + "\n", + "The purpose of this Python notebook is to use the Z3 library to solve simple SAT problems. \n", + "\n", + "You can save a local copy of this notebook in your Google account and work through it in Colab (recommended) or you can download the notebook and run it locally using Jupyter notebook or similar.\n", + "\n", + "Contact me at iclimbtreesmail@gmail.com if you find any mistakes or have any suggestions." + ], + "metadata": { + "id": "IsBSW40O20Hv" + } + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "WZYb15hc19es", + "outputId": "3861e4fb-3f57-4ca3-aaa4-03f85c57ea7a" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Collecting z3-solver\n", + " Downloading z3_solver-4.14.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (602 bytes)\n", + "Downloading z3_solver-4.14.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (29.5 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m29.5/29.5 MB\u001b[0m \u001b[31m31.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hInstalling collected packages: z3-solver\n", + "Successfully installed z3-solver-4.14.1.0\n" + ] + } + ], + "source": [ + "!pip install z3-solver\n", + "from z3 import *\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Example 1\n", + "\n", + "Now let's define and implement a Boolean logic formula. We'll use the one from the Tseitin transormation example in the text.\n", + "\n", + "$$\\phi:= ((x_{1} \\lor x_{2}) \\Leftrightarrow x_{3}) \\Rightarrow (\\lnot x_{4}).$$" + ], + "metadata": { + "id": "aVj9RmuB3ZWJ" + } + }, + { + "cell_type": "markdown", + "source": [ + "First we define the variables:" + ], + "metadata": { + "id": "Amv5G3VR3zIJ" + } + }, + { + "cell_type": "code", + "source": [ + "x1 = Bool('x1')\n", + "x2 = Bool('x2')\n", + "x3 = Bool('x3')\n", + "x4 = Bool('x4')" + ], + "metadata": { + "id": "f0esE71E94fm" + }, + "execution_count": 2, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Then we define the Boolean logic formula" + ], + "metadata": { + "id": "0IIRG5ZWIZV6" + } + }, + { + "cell_type": "code", + "source": [ + "phi = Implies(Or(x1, x2)==x3, Not(x4))" + ], + "metadata": { + "id": "C0HGBOjI99Ex" + }, + "execution_count": 14, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Next, we add the logic formula to the solver. If we have multiple constraints, connected by AND functions, we could add them one at a time." + ], + "metadata": { + "id": "2Y_1zYOZIoZI" + } + }, + { + "cell_type": "code", + "source": [ + "s = Solver()\n", + "s.add(phi)" + ], + "metadata": { + "id": "zQksy42u-qom" + }, + "execution_count": 17, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's see if this logic formula is satisfiable" + ], + "metadata": { + "id": "2lIQSZO8IzOu" + } + }, + { + "cell_type": "code", + "source": [ + "print(s.check())" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "fraGSSzV_wTO", + "outputId": "6a9668da-b6c5-4c01-ab15-6c2ba324ca3e" + }, + "execution_count": 18, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "sat\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "We see that it is. Now lets get it to return a set of variables that satisfies the formula" + ], + "metadata": { + "id": "BPR2s_otI-ca" + } + }, + { + "cell_type": "code", + "source": [ + "print(s.model())" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "kT3GlhUwI8IN", + "outputId": "222d7c56-e5dc-48c6-f73d-1490d55e6577" + }, + "execution_count": 19, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[x3 = False, x2 = True, x4 = False]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Note that it does not print a value for $x_1$ because the formula is true regardless of the value of $x_1$" + ], + "metadata": { + "id": "YL-seZBWJOtG" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Example 2\n", + "\n", + "Let's use Z3 if this formula is SAT or UNSAT:\n", + "\n", + "$$\\phi_2:= \\bigl(x_{1}\\Rightarrow (\\lnot x_{2}\\land x_{3})\\bigr) \\land \\bigl(x_{2} \\Leftrightarrow (\\lnot x_{3} \\lor x_{1})\\bigr).$$" + ], + "metadata": { + "id": "pVAFYHwFJbRw" + } + }, + { + "cell_type": "code", + "source": [ + "# TODO Use Z3 to test if this is SAT or UNSAT\n", + "# 1. Define the formula\n", + "# 2. Create a new\n", + "# 3. Add the formula to the solver\n", + "# 4. Check if SAT or UNSAT\n", + "# 5. If SAT, then print out a sat of variables that satisfies the formula\n", + "phi2 = And(Implies(x1, And(Not(x2),x3))), x2 == Or(Not(x3), x1)\n", + "S2 = Solver()\n", + "S2.add(phi2)\n", + "print(S2.check())\n", + "print(S2.model())" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "zqdfz1MxJNk1", + "outputId": "4eb0d556-243e-4071-dac6-811726df7abe" + }, + "execution_count": 24, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "sat\n", + "[x3 = True, x2 = False]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Example 3\n", + "\n", + "Let's add another clause to the formula and use Z3 if this formula is SAT or UNSAT:\n", + "\n", + "$$\\phi_1:= \\bigl(x_{1}\\Rightarrow (\\lnot x_{2}\\land x_{3})\\bigr) \\land \\bigl(x_{2} \\Leftrightarrow (\\lnot x_{3} \\lor x_{1})\\bigr)\\land\\lnot \\bigl(\\lnot x_3 \\Rightarrow x_2\\bigr).$$" + ], + "metadata": { + "id": "QAfr332sLyHH" + } + }, + { + "cell_type": "code", + "source": [ + "# TODO Use Z3 to test if this is SAT or UNSAT\n", + "# 1. Define the formula\n", + "# 2. Create a new\n", + "# 3. Add the formula to the solver\n", + "# 4. Check if SAT or UNSAT\n", + "# 5. If SAT, then print out a sat of variables that satisfies the formula\n", + "phi3 = And(And(Implies(x1, And(Not(x2),x3))), x2 == Or(Not(x3), x1),Not(Implies(Not(x3), x2)))\n", + "S3 = Solver()\n", + "S3.add(phi3)\n", + "print(S3.check())" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "fPMvltcyMBGQ", + "outputId": "24fca173-8df8-43f4-eed0-7791c2c50ddc" + }, + "execution_count": 27, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "unsat\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "You should fined that this is UNSAT. We have added too many constraints and the three terms that are ANDed together are never simultaneously true." + ], + "metadata": { + "id": "ZFW5Z_oWM9TE" + } + } + ] +} \ No newline at end of file diff --git a/Trees/cb_2018_us_state_500k.zip b/Trees/cb_2018_us_state_500k.zip new file mode 100644 index 0000000..a4a8c06 Binary files /dev/null and b/Trees/cb_2018_us_state_500k.zip differ diff --git a/UDL_Answer_Booklet_Students.pdf b/UDL_Answer_Booklet_Students.pdf index 962ec34..a9a8bad 100644 Binary files a/UDL_Answer_Booklet_Students.pdf and b/UDL_Answer_Booklet_Students.pdf differ diff --git a/UDL_Errata.pdf b/UDL_Errata.pdf index 60e39a0..977034f 100644 Binary files a/UDL_Errata.pdf and b/UDL_Errata.pdf differ