├── queen.png ├── EightQueens.gif ├── CODEOWNERS ├── IMAGE_LICENSE.md ├── README.md ├── LICENSE ├── util.py ├── Sympy_Intro.ipynb └── AIND-Constraint_Satisfaction.ipynb /queen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/AIND-Constraint_Satisfaction/HEAD/queen.png -------------------------------------------------------------------------------- /EightQueens.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/AIND-Constraint_Satisfaction/HEAD/EightQueens.gif -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | s is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in 5 | # the repo. 6 | * @cgearhart @luisguiserrano 7 | 8 | -------------------------------------------------------------------------------- /IMAGE_LICENSE.md: -------------------------------------------------------------------------------- 1 | ("Chess qdt45.svg")[https://commons.wikimedia.org/wiki/File:Chess_qdt45.svg] by [Colin M.L. Burnett](https://en.wikipedia.org/wiki/User:Cburnett) is licensed under [CC Attribution-Share Alike 3.0 Unported](https://creativecommons.org/licenses/by-sa/3.0/deed.en). -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | In this exercise you will explore Constraint Satisfaction Problems by implementing the N-Queens problem using symbolic constraints in a Jupyter notebook, and solving it using the Backtracking Search algorithm from AIMA. 2 | 3 | To launch the notebook, run the following command from a terminal with anaconda3 installed and on the application path: 4 | 5 | jupyter notebook AIND-Constraint_Satisfaction.ipynb 6 | # Archival Note 7 | This repository is deprecated; therefore, we are going to archive it. However, learners will be able to fork it to their personal Github account but cannot submit PRs to this repository. If you have any issues or suggestions to make, feel free to: 8 | - Utilize the https://knowledge.udacity.com/ forum to seek help on content-specific issues. 9 | - Submit a support ticket along with the link to your forked repository if (learners are) blocked for other reasons. Here are the links for the [retail consumers](https://udacity.zendesk.com/hc/en-us/requests/new) and [enterprise learners](https://udacityenterprise.zendesk.com/hc/en-us/requests/new?ticket_form_id=360000279131). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Udacity, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | 2 | import matplotlib as mpl 3 | import matplotlib.pyplot as plt 4 | import numpy as np 5 | 6 | from sympy import * 7 | 8 | 9 | def constraint(name, expr): 10 | """Augment the sympy Function class by attaching a symbolic expression 11 | and making the function evaluatable by patching the .subs() method. 12 | 13 | Parameters 14 | ---------- 15 | name : str 16 | A string defining the name of the function 17 | 18 | expr : sympy.Expr 19 | A symbolic expression that can be evaluated for a particular 20 | assignment to determine whether the constraint is True, False, 21 | or undetermined 22 | 23 | Returns 24 | ------- 25 | sympy.Function or sympy.Expr 26 | If the expression still has unassigned free variables, then a 27 | Function is returned with an attached Expr object; otherwise 28 | the expression is returned unchanged 29 | """ 30 | if not len(expr.free_symbols): 31 | return expr 32 | func = Function(name)(*expr.free_symbols) 33 | setattr(func, "expr", expr) 34 | setattr(func, "subs", lambda *a, **b: constraint(name, expr.subs(*a, **b))) 35 | setattr(func, "_subs", lambda *a, **b: expr.subs(*a, **b)) 36 | return func 37 | 38 | 39 | def displayBoard(locations, shape): 40 | """Draw a chessboard with queens placed at each position specified 41 | by the assignment. 42 | 43 | Parameters 44 | ---------- 45 | locations : list 46 | The locations list should contain one element for each queen 47 | of the chessboard containing a tuple (r, c) indicating the 48 | row and column coordinates of a queen to draw on the board. 49 | 50 | shape : integer 51 | The number of cells in each dimension of the board (e.g., 52 | shape=3 indicates a 3x3 board) 53 | 54 | Returns 55 | ------- 56 | matplotlib.figure.Figure 57 | The handle to the figure containing the board and queens 58 | """ 59 | r = c = shape 60 | cmap = mpl.colors.ListedColormap(['#f5ecce', '#614532']) 61 | img = mpl.image.imread('queen.png').astype(np.float) 62 | boxprops = {"facecolor": "none", "edgecolor": "none"} 63 | 64 | x, y = np.meshgrid(range(c), range(r)) 65 | plt.matshow(x % 2 ^ y % 2, cmap=cmap) 66 | plt.axis("off") # eliminate borders from plot 67 | 68 | fig = plt.gcf() 69 | fig.set_size_inches([r, c]) 70 | scale = fig.get_dpi() / max(img.shape) 71 | ax = plt.gca() 72 | for y, x in set(locations): 73 | box = mpl.offsetbox.OffsetImage(img, zoom=scale) 74 | ab = mpl.offsetbox.AnnotationBbox(box, (y, x), bboxprops=boxprops) 75 | ax.add_artist(ab) 76 | 77 | plt.show() 78 | return fig 79 | -------------------------------------------------------------------------------- /Sympy_Intro.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Warmup - Introduction to SymPy\n", 8 | "This lab exercise uses the [SymPy](http://www.sympy.org/en/index.html) symbolic math library to model constraints in the problem. To do that, we will use symbols (`sympy.Symbol`), functions (`sympy.Function`), and expressions (`sympy.Expr`) from sympy, and then we'll combine the function and expression classes to make constraints -- evaluatable symbolic functions.\n", 9 | "\n", 10 | "In this warmup, you will be introduced to the syntax and functionality of SymPy:\n", 11 | "- [Example 1](#Example-1:-Symbols): Creating [symbols](http://docs.sympy.org/dev/modules/core.html#module-sympy.core.symbol)\n", 12 | "- [Example 2](#Example-2:-Expressions): Creating & manipulating [expressions](http://docs.sympy.org/dev/modules/core.html#id16) with [arithmetic & logical operators](http://docs.sympy.org/dev/modules/core.html#sympy-core)\n", 13 | "- [Example 3](#Example-3:-Symbolic-substitution-and-expression-evaluation): Symbolic [substitution & evaluation](http://docs.sympy.org/dev/modules/core.html#subs)\n", 14 | "- [Exercises](#SymPy-Exercises): Creating & manipulating constraints & [functions](http://docs.sympy.org/dev/modules/functions/index.html)\n", 15 | "\n", 16 | "(See a list of common \"gotchas\" for sympy in their documentation: http://docs.sympy.org/dev/gotchas.html)\n", 17 | "\n", 18 | "Start by reading and running the example cells, then complete the steps in the warmup cell." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "metadata": { 25 | "collapsed": true 26 | }, 27 | "outputs": [], 28 | "source": [ 29 | "import matplotlib as mpl\n", 30 | "import matplotlib.pyplot as plt\n", 31 | "\n", 32 | "from util import constraint\n", 33 | "from IPython.display import display\n", 34 | "from sympy import *\n", 35 | "init_printing()" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "## Example 1: Symbols\n", 43 | "**Sympy provides the `Symbol` class to create symbolic variables. Create individual symbols by calling the constructor with a symbol name.** (Tip: Use the `display()` function to pretty-print symbolic terms.)" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "metadata": { 50 | "collapsed": false 51 | }, 52 | "outputs": [], 53 | "source": [ 54 | "x = Symbol('x')\n", 55 | "display(x)" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "**You can also create symbols from an iterable sequence using the `symbols()` function.**" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "metadata": { 69 | "collapsed": false 70 | }, 71 | "outputs": [], 72 | "source": [ 73 | "i, j, k = symbols(['i', 'j', 'k']) # use implicit unpacking to associate multiple symbols with variables\n", 74 | "display((i, j, k))" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "**`symbols()` can also create subscripted sequences of symbolic variables.**" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": null, 87 | "metadata": { 88 | "collapsed": false 89 | }, 90 | "outputs": [], 91 | "source": [ 92 | "X = symbols(\"X:3\")\n", 93 | "display(X)" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "metadata": {}, 99 | "source": [ 100 | "## Example 2: Expressions\n", 101 | "\n", 102 | "**A symbol is the most basic expression.** (Tip: Jupyter notebooks show information about objects\n", 103 | "using the `?` magic function)" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": null, 109 | "metadata": { 110 | "collapsed": false 111 | }, 112 | "outputs": [], 113 | "source": [ 114 | "x = Symbol('x')\n", 115 | "x?\n", 116 | "display(x)" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "**You can also define expressions with relations between symbols.** (However, notice that expressions have no _names_...)" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": null, 129 | "metadata": { 130 | "collapsed": false 131 | }, 132 | "outputs": [], 133 | "source": [ 134 | "x, y = symbols('x y')\n", 135 | "or_relation = x | y\n", 136 | "or_relation?\n", 137 | "display(or_relation)" 138 | ] 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "metadata": {}, 143 | "source": [ 144 | "**Also, not all operators can be used in expressions. The equal sign (=) performs assignment in python, so it cannot be used to make expressions. Using `=` assigns a new python variable to an existing reference.**" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": { 151 | "collapsed": false 152 | }, 153 | "outputs": [], 154 | "source": [ 155 | "x, y = symbols(\"x y\")\n", 156 | "y = x # now y references the same symbolic object as x\n", 157 | "display(y) # display(y) == x ??!" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "metadata": {}, 163 | "source": [ 164 | "**Use `sympy.Eq` for symbolic equality expressions:** (Tip: there are lots of expressions in the [sympy docs](http://docs.sympy.org/dev/modules/core.html#sympy-core))" 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": null, 170 | "metadata": { 171 | "collapsed": false 172 | }, 173 | "outputs": [], 174 | "source": [ 175 | "x, z = symbols(\"x z\")\n", 176 | "display(Eq(z, x))" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "**Sympy overloads standard python operators so that arithmetic and logical expressions can be constructed directly between symbolic objects.**" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": null, 189 | "metadata": { 190 | "collapsed": false 191 | }, 192 | "outputs": [], 193 | "source": [ 194 | "x, y, z = symbols(\"x y z\")\n", 195 | "display([x**2, x - y, Ne(x, y), (~x & y & z)])" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "metadata": {}, 201 | "source": [ 202 | "## Example 3: Symbolic substitution and expression evaluation\n", 203 | "\n", 204 | "**Given an original expression...**" 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": null, 210 | "metadata": { 211 | "collapsed": false 212 | }, 213 | "outputs": [], 214 | "source": [ 215 | "x, y, z = symbols(\"x y z\")\n", 216 | "relation = Eq(x, y)\n", 217 | "display(relation)" 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "metadata": {}, 223 | "source": [ 224 | "**Symbolic variables can be replaced by other variables, or by concrete values.** (Tip: use positional arguments in the `subs()` method to replace one symbol)" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": null, 230 | "metadata": { 231 | "collapsed": false 232 | }, 233 | "outputs": [], 234 | "source": [ 235 | "display(relation.subs(x, z)) # Use positional arguments to substitute a single symbol" 236 | ] 237 | }, 238 | { 239 | "cell_type": "markdown", 240 | "metadata": {}, 241 | "source": [ 242 | "**But keep in mind that substitution returns a _copy_ of the expression -- it doesn't operate in-place.** (Tip: as a result, you can use substitution on one expression bound to generic variables to generate new instances bound to specific variables.)\n", 243 | "\n", 244 | "Look at what happens when we bind new variables to our equality relation:" 245 | ] 246 | }, 247 | { 248 | "cell_type": "code", 249 | "execution_count": null, 250 | "metadata": { 251 | "collapsed": false 252 | }, 253 | "outputs": [], 254 | "source": [ 255 | "a = symbols(\"a:5\")\n", 256 | "b = symbols(\"b:5\")\n", 257 | "display([relation.subs({x: _a, y: _b}) for _a, _b in zip(a, b)])" 258 | ] 259 | }, 260 | { 261 | "cell_type": "markdown", 262 | "metadata": {}, 263 | "source": [ 264 | "**Symbol substitution returns an expression.** (Recall that Symbols _are_ expressions)." 265 | ] 266 | }, 267 | { 268 | "cell_type": "code", 269 | "execution_count": null, 270 | "metadata": { 271 | "collapsed": false 272 | }, 273 | "outputs": [], 274 | "source": [ 275 | "print(type(relation), type(relation.subs(x, z)))\n", 276 | "print(type(relation) == type(relation.subs(x, z)))" 277 | ] 278 | }, 279 | { 280 | "cell_type": "markdown", 281 | "metadata": {}, 282 | "source": [ 283 | "**But substituting values for all symbols returns a value type.** (Tip: You can substitute multiple symbols in the `subs()` command by providing a mapping (dict) from current symbols to new symbols or values.)" 284 | ] 285 | }, 286 | { 287 | "cell_type": "code", 288 | "execution_count": null, 289 | "metadata": { 290 | "collapsed": false 291 | }, 292 | "outputs": [], 293 | "source": [ 294 | "print(type(relation), type(relation.subs({x: 0, y: 1})))\n", 295 | "print(type(relation) != type(relation.subs({x: 0, y: 1})))" 296 | ] 297 | }, 298 | { 299 | "cell_type": "markdown", 300 | "metadata": {}, 301 | "source": [ 302 | "## Example 4: Constraints\n", 303 | "\n", 304 | "**Constraints are a construct of this lab exercise (not part of sympy) that combine symbolic Functions with Expressions for evaluation. The `constraint()` function (defined in the `util` module) takes a name and an expression and returns a \"named expression\" -- a constraint.**" 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": null, 310 | "metadata": { 311 | "collapsed": false 312 | }, 313 | "outputs": [], 314 | "source": [ 315 | "x, y = symbols(['x', 'y'])\n", 316 | "sameAs = constraint(\"SameAs\", Eq(x, y))\n", 317 | "display(sameAs)" 318 | ] 319 | }, 320 | { 321 | "cell_type": "markdown", 322 | "metadata": {}, 323 | "source": [ 324 | "**Constraints are evaluated using the .subs method, just like an expression. If the resulting\n", 325 | "expression has unbound (free) symbols, then the result is a new constraint.**" 326 | ] 327 | }, 328 | { 329 | "cell_type": "code", 330 | "execution_count": null, 331 | "metadata": { 332 | "collapsed": false 333 | }, 334 | "outputs": [], 335 | "source": [ 336 | "display(sameAs.subs(x, 0), type(sameAs.subs(x, 0)))" 337 | ] 338 | }, 339 | { 340 | "cell_type": "markdown", 341 | "metadata": {}, 342 | "source": [ 343 | "**If the resulting expression has no free symbols, then the result is only the evaluated expression.**" 344 | ] 345 | }, 346 | { 347 | "cell_type": "code", 348 | "execution_count": null, 349 | "metadata": { 350 | "collapsed": false 351 | }, 352 | "outputs": [], 353 | "source": [ 354 | "display(sameAs.subs({x: 0, y: 0}), type(sameAs.subs({x: 0, y: 0})))" 355 | ] 356 | }, 357 | { 358 | "cell_type": "markdown", 359 | "metadata": {}, 360 | "source": [ 361 | "## SymPy Exercises\n", 362 | "Complete the following exercises to check your understanding of sympy symbols, expressions, and constraints:" 363 | ] 364 | }, 365 | { 366 | "cell_type": "markdown", 367 | "metadata": {}, 368 | "source": [ 369 | "**Question 1:** Create an array of subscripted symbols A0, A1, A2 stored in a variable named `A`" 370 | ] 371 | }, 372 | { 373 | "cell_type": "code", 374 | "execution_count": null, 375 | "metadata": { 376 | "collapsed": false 377 | }, 378 | "outputs": [], 379 | "source": [ 380 | "A = None\n", 381 | "\n", 382 | "# test for completion\n", 383 | "assert(len(A) == 3)\n", 384 | "assert(all([type(v) == Symbol for v in A]))\n", 385 | "print(\"All tests passed!\")" 386 | ] 387 | }, 388 | { 389 | "cell_type": "markdown", 390 | "metadata": {}, 391 | "source": [ 392 | "**Question 2:** Create an expression E with two generic symbols (e.g., \"a\" and \"b\", etc.) that represents logical XOR" 393 | ] 394 | }, 395 | { 396 | "cell_type": "code", 397 | "execution_count": null, 398 | "metadata": { 399 | "collapsed": false 400 | }, 401 | "outputs": [], 402 | "source": [ 403 | "E = None\n", 404 | "\n", 405 | "# test for completion\n", 406 | "_vars = E.free_symbols\n", 407 | "assert(len(_vars) == 2)\n", 408 | "xor_table = {(0, 0): False, (0, 1): True, (1, 0): True, (1, 1): False}\n", 409 | "assert(all(E.subs(zip(_vars, vals)) == truth for vals, truth in xor_table.items()))\n", 410 | "print(\"All tests passed!\")" 411 | ] 412 | }, 413 | { 414 | "cell_type": "markdown", 415 | "metadata": {}, 416 | "source": [ 417 | "**Question 3:** Create a constraint MaxAbsDiff with three generic arguments to test abs(a - b) < c, and create a copy of the constraint such that it tests abs(A[0] - A[1]) < A[2] from Q1" 418 | ] 419 | }, 420 | { 421 | "cell_type": "code", 422 | "execution_count": null, 423 | "metadata": { 424 | "collapsed": false 425 | }, 426 | "outputs": [], 427 | "source": [ 428 | "maxAbsDiff = None \n", 429 | "maxAbsDiff_copy = None\n", 430 | "\n", 431 | "# test for completion\n", 432 | "assert(maxAbsDiff.free_symbols != maxAbsDiff_copy.free_symbols)\n", 433 | "assert(len(maxAbsDiff_copy.free_symbols) == len(maxAbsDiff_copy.args))\n", 434 | "inputs = {(0, 6, 7): True, (6, 0, 7): True, (7, 6, 0): False}\n", 435 | "assert(all(maxAbsDiff_copy.subs(zip(A[:3], vals)) == truth for vals, truth in inputs.items()))\n", 436 | "print(\"All tests passed!\")" 437 | ] 438 | }, 439 | { 440 | "cell_type": "markdown", 441 | "metadata": {}, 442 | "source": [ 443 | "**(Optional) Question 4:** Create a constraint AllDiff accepting the symbols in A as arguments, returning True if they are all different, and False if any pair is the same" 444 | ] 445 | }, 446 | { 447 | "cell_type": "code", 448 | "execution_count": null, 449 | "metadata": { 450 | "collapsed": false 451 | }, 452 | "outputs": [], 453 | "source": [ 454 | "allDiff = None\n", 455 | "\n", 456 | "inputs = (([0, 1, 2], True), ([1, 1, 1], False), ([0, 1, 1], False))\n", 457 | "assert(all(allDiff.subs(zip(A, vals)) == truth for vals, truth in inputs))\n", 458 | "print(\"All tests passed!\")" 459 | ] 460 | } 461 | ], 462 | "metadata": { 463 | "anaconda-cloud": {}, 464 | "kernelspec": { 465 | "display_name": "Python [conda env:aind]", 466 | "language": "python", 467 | "name": "conda-env-aind-py" 468 | }, 469 | "language_info": { 470 | "codemirror_mode": { 471 | "name": "ipython", 472 | "version": 3 473 | }, 474 | "file_extension": ".py", 475 | "mimetype": "text/x-python", 476 | "name": "python", 477 | "nbconvert_exporter": "python", 478 | "pygments_lexer": "ipython3", 479 | "version": "3.5.2" 480 | } 481 | }, 482 | "nbformat": 4, 483 | "nbformat_minor": 1 484 | } 485 | -------------------------------------------------------------------------------- /AIND-Constraint_Satisfaction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Constraint Satisfaction Problems Lab\n", 8 | "\n", 9 | "## Introduction\n", 10 | "Constraint Satisfaction is a technique for solving problems by expressing limits on the values of each variable in the solution with mathematical constraints. We've used constraints before -- constraints in the Sudoku project are enforced implicitly by filtering the legal values for each box, and the planning project represents constraints as arcs connecting nodes in the planning graph -- but in this lab exercise we will use a symbolic math library to explicitly construct binary constraints and then use Backtracking to solve the N-queens problem (which is a generalization [8-queens problem](https://en.wikipedia.org/wiki/Eight_queens_puzzle)). Using symbolic constraints should make it easier to visualize and reason about the constraints (especially for debugging), but comes with a performance penalty.\n", 11 | "\n", 12 | "![8-queens puzzle solution](EightQueens.gif)\n", 13 | "\n", 14 | "Briefly, the 8-queens problem asks you to place 8 queens on a standard 8x8 chessboard such that none of the queens are in \"check\" (i.e., no two queens occupy the same row, column, or diagonal). The N-queens problem generalizes the puzzle to to any size square board.\n", 15 | "\n", 16 | "## I. Lab Overview\n", 17 | "Students should read through the code and the wikipedia page (or other resources) to understand the N-queens problem, then:\n", 18 | "\n", 19 | "0. Complete the warmup exercises in the [Sympy_Intro notebook](Sympy_Intro.ipynb) to become familiar with they sympy library and symbolic representation for constraints\n", 20 | "0. Implement the [NQueensCSP class](#II.-Representing-the-N-Queens-Problem) to develop an efficient encoding of the N-queens problem and explicitly generate the constraints bounding the solution\n", 21 | "0. Write the [search functions](#III.-Backtracking-Search) for recursive backtracking, and use them to solve the N-queens problem\n", 22 | "0. (Optional) Conduct [additional experiments](#IV.-Experiments-%28Optional%29) with CSPs and various modifications to the search order (minimum remaining values, least constraining value, etc.)" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "metadata": { 29 | "collapsed": false 30 | }, 31 | "outputs": [], 32 | "source": [ 33 | "import matplotlib as mpl\n", 34 | "import matplotlib.pyplot as plt\n", 35 | "\n", 36 | "from util import constraint, displayBoard\n", 37 | "from sympy import *\n", 38 | "from IPython.display import display\n", 39 | "init_printing()\n", 40 | "%matplotlib inline" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "## II. Representing the N-Queens Problem\n", 48 | "There are many acceptable ways to represent the N-queens problem, but one convenient way is to recognize that one of the constraints (either the row or column constraint) can be enforced implicitly by the encoding. If we represent a solution as an array with N elements, then each position in the array can represent a column of the board, and the value at each position can represent which row the queen is placed on.\n", 49 | "\n", 50 | "In this encoding, we only need a constraint to make sure that no two queens occupy the same row, and one to make sure that no two queens occupy the same diagonal.\n", 51 | "\n", 52 | "### Define Symbolic Expressions for the Problem Constraints\n", 53 | "Before implementing the board class, we need to construct the symbolic constraints that will be used in the CSP. Declare any symbolic terms required, and then declare two generic constraint generators:\n", 54 | "- `diffRow` - generate constraints that return True if the two arguments do not match\n", 55 | "- `diffDiag` - generate constraints that return True if two arguments are not on the same diagonal (Hint: you can easily test whether queens in two columns are on the same diagonal by testing if the difference in the number of rows and the number of columns match)\n", 56 | "\n", 57 | "Both generators should produce binary constraints (i.e., each should have two free symbols) once they're bound to specific variables in the CSP. For example, Eq((a + b), (b + c)) is not a binary constraint, but Eq((a + b), (b + c)).subs(b, 1) _is_ a binary constraint because one of the terms has been bound to a constant, so there are only two free variables remaining. " 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": { 64 | "collapsed": false 65 | }, 66 | "outputs": [], 67 | "source": [ 68 | "# Declare any required symbolic variables\n", 69 | "raise NotImplementedError(\"TODO: declare symbolic variables for the constraint generators\")\n", 70 | "\n", 71 | "# Define diffRow and diffDiag constraints\n", 72 | "raise NotImplementedError(\"TODO: create the diffRow and diffDiag constraint generators\")\n", 73 | "diffRow = constraint(...)\n", 74 | "diffRow = constraint(...)" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": null, 80 | "metadata": { 81 | "collapsed": false 82 | }, 83 | "outputs": [], 84 | "source": [ 85 | "# Test diffRow and diffDiag\n", 86 | "_x = symbols(\"x:3\")\n", 87 | "\n", 88 | "# generate a diffRow instance for testing\n", 89 | "raise NotImplementedError(\"TODO: use your diffRow constraint to generate a diffRow constraint for _x[0] and _x[1]\")\n", 90 | "diffRow_test = None\n", 91 | "\n", 92 | "assert(len(diffRow_test.free_symbols) == 2)\n", 93 | "assert(diffRow_test.subs({_x[0]: 0, _x[1]: 1}) == True)\n", 94 | "assert(diffRow_test.subs({_x[0]: 0, _x[1]: 0}) == False)\n", 95 | "assert(diffRow_test.subs({_x[0]: 0}) != False) # partial assignment is not false\n", 96 | "print(\"Passed all diffRow tests.\")\n", 97 | "\n", 98 | "# generate a diffDiag instance for testing\n", 99 | "raise NotImplementedError(\"TODO: use your diffDiag constraint to generate a diffDiag constraint for _x[0] and _x[2]\")\n", 100 | "diffDiag_test = None\n", 101 | "\n", 102 | "assert(len(diffDiag_test.free_symbols) == 2)\n", 103 | "assert(diffDiag_test.subs({_x[0]: 0, _x[2]: 2}) == False)\n", 104 | "assert(diffDiag_test.subs({_x[0]: 0, _x[2]: 0}) == True)\n", 105 | "assert(diffDiag_test.subs({_x[0]: 0}) != False) # partial assignment is not false\n", 106 | "print(\"Passed all diffDiag tests.\")" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": {}, 112 | "source": [ 113 | "### The N-Queens CSP Class\n", 114 | "Implement the CSP class as described above, with constraints to make sure each queen is on a different row and different diagonal than every other queen, and a variable for each column defining the row that containing a queen in that column." 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": { 121 | "collapsed": false 122 | }, 123 | "outputs": [], 124 | "source": [ 125 | "class NQueensCSP:\n", 126 | " \"\"\"CSP representation of the N-queens problem\n", 127 | " \n", 128 | " Parameters\n", 129 | " ----------\n", 130 | " N : Integer\n", 131 | " The side length of a square chess board to use for the problem, and\n", 132 | " the number of queens that must be placed on the board\n", 133 | " \"\"\"\n", 134 | " def __init__(self, N):\n", 135 | " raise NotImplementedError(\"TODO: declare symbolic variables in self._vars in the CSP constructor\")\n", 136 | " _vars = None\n", 137 | " _domain = set(range(N))\n", 138 | " self.size = N\n", 139 | " self.variables = _vars\n", 140 | " self.domains = {v: _domain for v in _vars}\n", 141 | " self._constraints = {x: set() for x in _vars}\n", 142 | "\n", 143 | " # add constraints - for each pair of variables xi and xj, create\n", 144 | " # a diffRow(xi, xj) and a diffDiag(xi, xj) instance, and add them\n", 145 | " # to the self._constraints dictionary keyed to both xi and xj;\n", 146 | " # (i.e., add them to both self._constraints[xi] and self._constraints[xj])\n", 147 | " raise NotImplementedError(\"TODO: add constraints in self._constraints in the CSP constructor\")\n", 148 | " \n", 149 | " @property\n", 150 | " def constraints(self):\n", 151 | " \"\"\"Read-only list of constraints -- cannot be used for evaluation \"\"\"\n", 152 | " constraints = set()\n", 153 | " for _cons in self._constraints.values():\n", 154 | " constraints |= _cons\n", 155 | " return list(constraints)\n", 156 | " \n", 157 | " def is_complete(self, assignment):\n", 158 | " \"\"\"An assignment is complete if it is consistent, and all constraints\n", 159 | " are satisfied.\n", 160 | " \n", 161 | " Hint: Backtracking search checks consistency of each assignment, so checking\n", 162 | " for completeness can be done very efficiently\n", 163 | " \n", 164 | " Parameters\n", 165 | " ----------\n", 166 | " assignment : dict(sympy.Symbol: Integer)\n", 167 | " An assignment of values to variables that have previously been checked\n", 168 | " for consistency with the CSP constraints\n", 169 | " \"\"\"\n", 170 | " raise NotImplementedError(\"TODO: implement the is_complete() method of the CSP\")\n", 171 | " \n", 172 | " def is_consistent(self, var, value, assignment):\n", 173 | " \"\"\"Check consistency of a proposed variable assignment\n", 174 | " \n", 175 | " self._constraints[x] returns a set of constraints that involve variable `x`.\n", 176 | " An assignment is consistent unless the assignment it causes a constraint to\n", 177 | " return False (partial assignments are always consistent).\n", 178 | " \n", 179 | " Parameters\n", 180 | " ----------\n", 181 | " var : sympy.Symbol\n", 182 | " One of the symbolic variables in the CSP\n", 183 | " \n", 184 | " value : Numeric\n", 185 | " A valid value (i.e., in the domain of) the variable `var` for assignment\n", 186 | "\n", 187 | " assignment : dict(sympy.Symbol: Integer)\n", 188 | " A dictionary mapping CSP variables to row assignment of each queen\n", 189 | " \n", 190 | " \"\"\"\n", 191 | " raise NotImplementedError(\"TODO: implement the is_consistent() method of the CSP\")\n", 192 | " \n", 193 | " \n", 194 | " def inference(self, var, value):\n", 195 | " \"\"\"Perform logical inference based on proposed variable assignment\n", 196 | " \n", 197 | " Returns an empty dictionary by default; function can be overridden to\n", 198 | " check arc-, path-, or k-consistency; returning None signals \"failure\".\n", 199 | " \n", 200 | " Parameters\n", 201 | " ----------\n", 202 | " var : sympy.Symbol\n", 203 | " One of the symbolic variables in the CSP\n", 204 | " \n", 205 | " value : Integer\n", 206 | " A valid value (i.e., in the domain of) the variable `var` for assignment\n", 207 | " \n", 208 | " Returns\n", 209 | " -------\n", 210 | " dict(sympy.Symbol: Integer) or None\n", 211 | " A partial set of values mapped to variables in the CSP based on inferred\n", 212 | " constraints from previous mappings, or None to indicate failure\n", 213 | " \"\"\"\n", 214 | " # TODO (Optional): Implement this function based on AIMA discussion\n", 215 | " return {}\n", 216 | " \n", 217 | " def show(self, assignment):\n", 218 | " \"\"\"Display a chessboard with queens drawn in the locations specified by an\n", 219 | " assignment\n", 220 | " \n", 221 | " Parameters\n", 222 | " ----------\n", 223 | " assignment : dict(sympy.Symbol: Integer)\n", 224 | " A dictionary mapping CSP variables to row assignment of each queen\n", 225 | " \n", 226 | " \"\"\"\n", 227 | " locations = [(i, assignment[j]) for i, j in enumerate(self.variables)\n", 228 | " if assignment.get(j, None) is not None]\n", 229 | " displayBoard(locations, self.size)" 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "metadata": { 235 | "collapsed": true 236 | }, 237 | "source": [ 238 | "## III. Backtracking Search\n", 239 | "Implement the [backtracking search](https://github.com/aimacode/aima-pseudocode/blob/master/md/Backtracking-Search.md) algorithm (required) and helper functions (optional) from the AIMA text. " 240 | ] 241 | }, 242 | { 243 | "cell_type": "code", 244 | "execution_count": null, 245 | "metadata": { 246 | "collapsed": false 247 | }, 248 | "outputs": [], 249 | "source": [ 250 | "def select(csp, assignment):\n", 251 | " \"\"\"Choose an unassigned variable in a constraint satisfaction problem \"\"\"\n", 252 | " # TODO (Optional): Implement a more sophisticated selection routine from AIMA\n", 253 | " for var in csp.variables:\n", 254 | " if var not in assignment:\n", 255 | " return var\n", 256 | " return None\n", 257 | "\n", 258 | "def order_values(var, assignment, csp):\n", 259 | " \"\"\"Select the order of the values in the domain of a variable for checking during search;\n", 260 | " the default is lexicographically.\n", 261 | " \"\"\"\n", 262 | " # TODO (Optional): Implement a more sophisticated search ordering routine from AIMA\n", 263 | " return csp.domains[var]\n", 264 | "\n", 265 | "def backtracking_search(csp):\n", 266 | " \"\"\"Helper function used to initiate backtracking search \"\"\"\n", 267 | " return backtrack({}, csp)\n", 268 | "\n", 269 | "def backtrack(assignment, csp):\n", 270 | " \"\"\"Perform backtracking search for a valid assignment to a CSP\n", 271 | " \n", 272 | " Parameters\n", 273 | " ----------\n", 274 | " assignment : dict(sympy.Symbol: Integer)\n", 275 | " An partial set of values mapped to variables in the CSP\n", 276 | " \n", 277 | " csp : CSP\n", 278 | " A problem encoded as a CSP. Interface should include csp.variables, csp.domains,\n", 279 | " csp.inference(), csp.is_consistent(), and csp.is_complete().\n", 280 | " \n", 281 | " Returns\n", 282 | " -------\n", 283 | " dict(sympy.Symbol: Integer) or None\n", 284 | " A partial set of values mapped to variables in the CSP, or None to indicate failure\n", 285 | " \"\"\"\n", 286 | " raise NotImplementedError(\"TODO: complete the backtrack function\")" 287 | ] 288 | }, 289 | { 290 | "cell_type": "markdown", 291 | "metadata": {}, 292 | "source": [ 293 | "### Solve the CSP\n", 294 | "With backtracking implemented, now you can use it to solve instances of the problem. We've started with the classical 8-queen version, but you can try other sizes as well. Boards larger than 12x12 may take some time to solve because sympy is slow in the way its being used here, and because the selection and value ordering methods haven't been implemented. See if you can implement any of the techniques in the AIMA text to speed up the solver!" 295 | ] 296 | }, 297 | { 298 | "cell_type": "code", 299 | "execution_count": null, 300 | "metadata": { 301 | "collapsed": false 302 | }, 303 | "outputs": [], 304 | "source": [ 305 | "num_queens = 8\n", 306 | "csp = NQueensCSP(num_queens)\n", 307 | "var = csp.variables[0]\n", 308 | "print(\"CSP problems have variables, each variable has a domain, and the problem has a list of constraints.\")\n", 309 | "print(\"Showing the variables for the N-Queens CSP:\")\n", 310 | "display(csp.variables)\n", 311 | "print(\"Showing domain for {}:\".format(var))\n", 312 | "display(csp.domains[var])\n", 313 | "print(\"And showing the constraints for {}:\".format(var))\n", 314 | "display(csp._constraints[var])\n", 315 | "\n", 316 | "print(\"Solving N-Queens CSP...\")\n", 317 | "assn = backtracking_search(csp)\n", 318 | "if assn is not None:\n", 319 | " csp.show(assn)\n", 320 | " print(\"Solution found:\\n{!s}\".format(assn))\n", 321 | "else:\n", 322 | " print(\"No solution found.\")" 323 | ] 324 | }, 325 | { 326 | "cell_type": "markdown", 327 | "metadata": {}, 328 | "source": [ 329 | "## IV. Experiments (Optional)\n", 330 | "For each optional experiment, discuss the answers to these questions on the forum: Do you expect this change to be more efficient, less efficient, or the same? Why or why not? Is your prediction correct? What metric did you compare (e.g., time, space, nodes visited, etc.)?\n", 331 | "\n", 332 | "- Implement a _bad_ N-queens solver: generate & test candidate solutions one at a time until a valid solution is found. For example, represent the board as an array with $N^2$ elements, and let each element be True if there is a queen in that box, and False if it is empty. Use an $N^2$-bit counter to generate solutions, then write a function to check if each solution is valid. Notice that this solution doesn't require any of the techniques we've applied to other problems -- there is no DFS or backtracking, nor constraint propagation, or even explicitly defined variables.\n", 333 | "- Use more complex constraints -- i.e., generalize the binary constraint RowDiff to an N-ary constraint AllRowsDiff, etc., -- and solve the problem again.\n", 334 | "- Rewrite the CSP class to use forward checking to restrict the domain of each variable as new values are assigned.\n", 335 | "- The sympy library isn't very fast, so this version of the CSP doesn't work well on boards bigger than about 12x12. Write a new representation of the problem class that uses constraint functions (like the Sudoku project) to implicitly track constraint satisfaction through the restricted domain of each variable. How much larger can you solve?\n", 336 | "- Create your own CSP!" 337 | ] 338 | } 339 | ], 340 | "metadata": { 341 | "anaconda-cloud": {}, 342 | "kernelspec": { 343 | "display_name": "Python [conda env:aind]", 344 | "language": "python", 345 | "name": "conda-env-aind-py" 346 | }, 347 | "language_info": { 348 | "codemirror_mode": { 349 | "name": "ipython", 350 | "version": 3 351 | }, 352 | "file_extension": ".py", 353 | "mimetype": "text/x-python", 354 | "name": "python", 355 | "nbconvert_exporter": "python", 356 | "pygments_lexer": "ipython3", 357 | "version": "3.5.2" 358 | } 359 | }, 360 | "nbformat": 4, 361 | "nbformat_minor": 1 362 | } 363 | --------------------------------------------------------------------------------