Newer
Older
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"# Constraint Satisfaction Problems (CSPs)\n",
"\n",
"This IPy notebook acts as supporting material for topics covered in **Chapter 6 Constraint Satisfaction Problems** of the book* Artificial Intelligence: A Modern Approach*. We make use of the implementations in **csp.py** module. Even though this notebook includes a brief summary of the main topics familiarity with the material present in the book is expected. We will look at some visualizations and solve some of the CSP problems described in the book. Let us import everything from the csp module to get started."
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from csp import *"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Review\n",
"\n",
"CSPs are a special kind of search problems. Here we don't treat the space as a black box but the state has a particular form and we use that to our advantage to tweak our algorithms to be more suited to the problems. A CSP State is defined by a set of variables which can take values from corresponding domains. These variables can take only certain values in their domains to satisfy the constraints. A set of assignments which satisfies all constraints passes the goal test. Let us start by exploring the CSP class which we will use to model our CSPs. You can keep the popup open and read the main page to get a better idea of the code.\n"
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"%psource CSP"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The __ _ _init_ _ __ method parameters specify the CSP. Variable can be passed as a list of strings or integers. Domains are passed as dict where key specify the variables and value specify the domains. The variables are passed as an empty list. Variables are extracted from the keys of the domain dictionary. Neighbor is a dict of variables that essentially describes the constraint graph. Here each variable key has a list its value which are the variables that are constraint along with it. The constraint parameter should be a function **f(A, a, B, b**) that **returns true** if neighbors A, B **satisfy the constraint** when they have values **A=a, B=b**. We have additional parameters like nassings which is incremented each time an assignment is made when calling the assign method. You can read more about the methods and parameters in the class doc string. We will talk more about them as we encounter their use. Let us jump to an example."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Graph Coloring\n",
"\n",
"We use the graph coloring problem as our running example for demonstrating the different algorithms in the **csp module**. The idea of map coloring problem is that the adjacent nodes (those connected by edges) should not have the same color throughout the graph. The graph can be colored using a fixed number of colors. Here each node is a variable and the values are the colors that can be assigned to them. Given that the domain will be the same for all our nodes we use a custom dict defined by the **UniversalDict** class. The **UniversalDict** Class takes in a parameter which it returns as value for all the keys of the dict. It is very similar to **defaultdict** in Python except that it does not support item assignment."
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"['R', 'G', 'B']"
]
},
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"s = UniversalDict(['R','G','B'])\n",
"s[5]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For our CSP we also need to define a constraint function **f(A, a, B, b)**. In this what we need is that the neighbors must not have the same color. This is defined in the function **different_values_constraint** of the module."
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"%psource different_values_constraint"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The CSP class takes neighbors in the form of a Dict. The module specifies a simple helper function named **parse_neighbors** which allows to take input in the form of strings and return a Dict of the form compatible with the **CSP Class**."
]
},
{
"cell_type": "code",
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The **MapColoringCSP** function creates and returns a CSP with the above constraint function and states. The variables our the keys of the neighbors dict and the constraint is the one specified by the **different_values_constratint** function. **australia**, **usa** and **france** are three CSPs that have been created using **MapColoringCSP**. **australia** corresponds to ** Figure 6.1 ** in the book."
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"%psource MapColoringCSP"
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(<csp.CSP at 0x7fa2d0593a58>,\n",
" <csp.CSP at 0x7fa2d0599ac8>,\n",
" <csp.CSP at 0x7fa2d0523080>)"
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"australia, usa, france"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Helper Functions\n",
"\n",
"We will now implement few helper functions that will help us visualize the Coloring Problem. We will make some modifications to the existing Classes and Functions for additional book keeping. To begin with we modify the **assign** and **unassign** methods in the **CSP** to add a copy of the assignment to the **assingment_history**. We call this new class **InstruCSP**. This would allow us to see how the assignment evolves over time."
]
},
{
"cell_type": "code",
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import copy\n",
"class InstruCSP(CSP):\n",
" \n",
" def __init__(self, variables, domains, neighbors, constraints):\n",
" super().__init__(variables, domains, neighbors, constraints)\n",
" self.assingment_history = []\n",
" \n",
" def assign(self, var, val, assignment):\n",
" super().assign(var,val, assignment)\n",
" self.assingment_history.append(copy.deepcopy(assignment))\n",
" \n",
" def unassign(self, var, assignment):\n",
" super().unassign(var,assignment)\n",
" self.assingment_history.append(copy.deepcopy(assignment)) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we modify the **MapColoringCSP** function to use the **InstruCSP**. "
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def ModMapColoringCSP(colors, neighbors):\n",
" if isinstance(neighbors, str):\n",
" neighbors = parse_neighbors(neighbors)\n",
" return InstruCSP(list(neighbors.keys()), UniversalDict(colors), neighbors,\n",
" different_values_constraint)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We will now use the france graph for plotting purposes. The **parse_neighbors** function is used for parsing them."
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"neighbors = parse_neighbors(\"\"\"AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA\n",
" AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO\n",
" CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR:\n",
" MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO:\n",
" PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA:\n",
" AU BO FC PA LR\"\"\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we are ready to create an InstruCSP instance for our problem."
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"coloring_problem1 = ModMapColoringCSP('RGBY', neighbors)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Backtracking Search\n",
"\n",
"For solving a CSP the main issue with Naive search algorithms is that they can continue expanding obviously wrong paths. In backtracking search, we check constraints as we go. Backtracking is just the above idea combined with the fact that we are dealing with one variable at a time. Backtracking Search is implemented in the repository as the function **backtracking_search**. This is the same as **Figure 6.5** in the book. The function takes as input a CSP and few other optional parameters which can be used to further speed it up. The function returns the correct assignment if it satisfies the goal. We will discuss these later. Let us solve our **coloring_problem1** with **backtracking_search**.\n"
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"result = backtracking_search(coloring_problem1)"
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": false
},
"source": [
"result # A dictonary of assingments."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let us also check the number of assingments made."
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": false
},
"source": [
"coloring_problem1.nassigns"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let us check the total number of assingments and unassingments which is the lentgh ofour assingment history."
]
},
{
"cell_type": "code",
"metadata": {
"collapsed": false
},
"source": [
"len(coloring_problem1.assingment_history)"
]
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Visualization\n",
"\n",
"Next, we define some functions to create the visualisation from the assingment_history of **coloring_problem1**. The reader need not concern himself with the code that immediately follows as it is the usage of Matplotib with IPython Widgets. If you are interested in reading more about these visit [ipywidgets.readthedocs.io](http://ipywidgets.readthedocs.io). We will be using the **networkx** library to generate graphs. These graphs can be treated as the graph that needs to be colored or as a constraint graph for this problem. If interested you can read a dead simple tutorial [here](https://www.udacity.com/wiki/creating-network-graphs-with-python). We start by importing the necessary libraries and initializing matplotlib inline.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import networkx as nx\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The ipython widgets we will be using require the plots in the form of a step function such that there is a graph corresponding to each value. We define the **make_update_step_function** which return such a function. It takes in as inputs the neighbors/graph along with an instance of the **InstruCSP**. This will be more clear with the example below. If this sounds confusing do not worry this is not the part of the core material and our only goal is to help you visualize how the process works."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def make_update_step_function(graph, instru_csp):\n",
" \n",
" def draw_graph(graph):\n",
" # create networkx graph\n",
" G=nx.Graph(graph)\n",
" # draw graph\n",
" pos = nx.spring_layout(G,k=0.15)\n",
" return (G, pos)\n",
" \n",
" G, pos = draw_graph(graph)\n",
" \n",
" def update_step(iteration):\n",
" # here iteration is the index of the assingment_history we want to visualize.\n",
" current = instru_csp.assingment_history[iteration]\n",
" # We convert the particular assingment to a default dict so that the color for nodes which \n",
" # have not been assigned defaults to black.\n",
" current = defaultdict(lambda: 'Black', current)\n",
"\n",
" # Now we use colors in the list and default to black otherwise.\n",
" colors = [current[node] for node in G.node.keys()]\n",
" # Finally drawing the nodes.\n",
" nx.draw(G, pos, node_color=colors, node_size=500)\n",
"\n",
" labels = {label:label for label in G.node}\n",
" # Labels shifted by offset so as to not overlap nodes.\n",
" label_pos = {key:[value[0], value[1]+0.03] for key, value in pos.items()}\n",
" nx.draw_networkx_labels(G, label_pos, labels, font_size=20)\n",
"\n",
" # show graph\n",
" plt.show()\n",
"\n",
" return update_step # <-- this is a function\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally let us plot our problem. We first use the function above to obtain a step function."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"step_func = make_update_step_function(neighbors, coloring_problem1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next we set the canvas size."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"matplotlib.rcParams['figure.figsize'] = (18.0, 18.0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally our plot using ipywidget slider and matplotib. You can move the slider to experiment and see the coloring change. It is also possible to move the slider using arrow keys or to jump to the value by directly editing the number with a double click."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import ipywidgets as widgets\n",
"from IPython.display import display\n",
"\n",
"iteration_slider = widgets.IntSlider(min=0, max=len(coloring_problem1.assingment_history)-1, step=1, value=0)\n",
"w=widgets.interactive(step_func,iteration=iteration_slider)\n",
"display(w)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
"state": {
"11c257bf5afd4dc085bc8625f2f5b064": {
"views": []
},
"2a5d565045c54a1aa994bd1f4d20598e": {
"views": []
},
"2d85cab81ee44791a94c94dfc338dbb2": {
"views": []
},
"3274aeea4b0b48c48ad4fc3292e5a9db": {
"views": []
},
"32f7a26654264000801324cd162b3736": {
"views": []
},
"3bdbe39cfebe45bd8f567a9eea384ae0": {
"views": []
},
"51076ed152d44022b5198b97fb41d079": {
"views": []
},
"57e00a3004bd4f6daa3594ad1235203a": {
"views": []
},
"6309ade1ff624145b66134ff05478ed7": {
"views": []
},
"641e3e122b7b401da4ffe4cfa8ef491e": {
"views": []
},
"7139845f3d75490382a04b4edf9d52f1": {
"views": []
},
"72798785fb3840f0bf54ce8e43da385a": {
"views": []
},
"73c1ce651784464fbf4a4b77d01d13f6": {
"views": []
},
"74c14cd38b594a73a6690ceec29fa82b": {
"views": []
},
"757fdae1fc99468890645b38a1ade51a": {
"views": []
},
"7c47d1ae17fe42c3bfca9a8643b5b5e7": {
"views": []
},
"7cadfb57eb9e4ca69f38edd7a0871003": {
"views": []
},
"8155171d610a4a4193e4b85d8c33a645": {
"views": []
},
"81bf3789a7d6487d8c97bbbaa2fb10e5": {
"views": []
},
"90e417c92a4f45408065bccdeceade40": {
"views": []
},
"93fdec2526be4434868b9a5ae72d6a68": {
"views": []
},
"99e6ce2b4591444caa39228ad478622d": {
"views": []
},
"9b3ce605a2fe43ea9efd9a2523934a78": {
"views": []
},
"9ce18c6af15846cbbd1dbccb0d7f7acd": {
"views": []
},
"a21b1344ddc64c928a5ecc9f9448e3a6": {
"views": []
},
"a3be52e2f5fe497ba0a38089b3408dcd": {
"views": []
},
"a63d0abd1b014dcfb76c3602b7daa3bf": {
"views": []
},
"a833f96aaaa8423cb23fcdcd9d50b7ef": {
"views": []
},
"ad62d3f676ee4dcc9d2c13bccd4c9ba5": {
"views": []
},
"ae8536dbdab94589bff99f542a84cbd2": {
"views": []
},
"b1679e217ef64dfeb70f20a73aecf9ce": {
"views": []
},
"b4e2bb7ccec84be2bcf4e66d8c7fe531": {
"views": []
},
"bad5f393034f467b88d9948b3658bb79": {
"views": []
},
"c0e8d394e38a4c3eaf5e780baefa26b8": {
"views": []
},
"c3dbc0a876044adea6a983d887a182fa": {
"views": []
},
"c425298ee6e0473fac87abb3f93b96f9": {
"views": []
},
"cfcb6ce7a19f4581a4b5d7865cded856": {
"views": []
},
"dff08c132aee450087e607174f2e47c5": {
"views": []
},
"e8827da62e204484ac7b783629e684a8": {
"views": []
},
"eba28e17a6bf45d69ee25e86ed55e313": {
"views": []
},
"f2119b193f2b45e095b41a7db2b3eadf": {
"views": []
},
"f2bb5a744c004774a9d480e58684c045": {
"views": []
},
"f45d16e50aa345e2b80ad07c98f9b66a": {
"views": []
},
"f4719605f006430fa9a1a2f3b5961a43": {
"views": []
}
},