Ticket #11379: trac_11379_hole_bugsl.patch
File trac_11379_hole_bugsl.patch, 20.1 KB (added by , 8 years ago) 


sage/combinat/tiling.py
# HG changeset patch # User Sebastien Labbe <slabqc at gmail.com> # Date 1311186705 14400 # Node ID 3e72dcebcc0306b36879a81248131ab108d645fb # Parent 81265924135136acf22976e2e5b525f22a76b112 #11379: Fix hole bug. Fix animation bug. diff git a/sage/combinat/tiling.py b/sage/combinat/tiling.py
a b 1 1 r""" 2 2 Tiling Solver 3 3 4 Finding a tiling of a box into nonintersectingpolyominoes.4 Tiling a ndimensional box into nonintersecting ndimensional polyominoes. 5 5 6 6 This uses dancing links code which is in Sage. Dancing links were 7 7 originally introduced by Donald Knuth in 2000 [1]. In particular, Knuth 8 8 used dancing links to solve tilings of a region by 2D pentaminos. Here we 9 extend the method for any dimension. 9 extend the method to any dimension. 10 11 In particular, the :mod:`sage.games.quantumino` module is based on 12 the Tiling Solver and allows to solve the 3d Quantumino puzzle. 10 13 11 14 This module defines two classes: 12 15 … … The following is a puzzle owned by Flore 89 92 sage: L.append(Polyomino([(0,1),(0,2),(0,3),(1,0),(1,1),(1,2)])) 90 93 sage: L.append(Polyomino([(0,0),(0,1),(0,2),(1,0),(1,1),(1,2)])) 91 94 92 By default, rotations are allowed and reflections are not. In this case, if93 reflections are not allowed,there are no solution::95 By default, rotations are allowed and reflections are not. In this case, 96 there are no solution:: 94 97 95 98 sage: T = TilingSolver(L, (8,8)) 96 99 sage: T.number_of_solutions() # long time (2.5 s) 97 100 0 98 101 99 Allow reflections, solve the puzzle and show one solution:: 102 If reflections are allowed, there are solutions. Solve the puzzle and show 103 one solution:: 100 104 101 105 sage: T = TilingSolver(L, (8,8), reflection=True) 102 106 sage: solution = T.solve().next() 103 sage: G = sum([piece.show2d( ) for piece in solution], Graphics())107 sage: G = sum([piece.show2d(size=0.85) for piece in solution], Graphics()) 104 108 sage: G.show(aspect_ratio=1) 105 109 106 Let's compute the number of solutions::110 Compute the number of solutions:: 107 111 108 sage: T.number_of_solutions() # long time ( 6s the first time)112 sage: T.number_of_solutions() # long time (2.6s) 109 113 328 110 114 111 115 3d Puzzle 112 116  113 117 114 The same thing done in 3d withoutallowing reflections this time::118 The same thing done in 3d *without* allowing reflections this time:: 115 119 116 120 sage: from sage.combinat.tiling import Polyomino, TilingSolver 117 121 sage: L = [] … … Solve the puzzle and show one solution:: 130 134 131 135 sage: T = TilingSolver(L, (8,8,1)) 132 136 sage: solution = T.solve().next() 133 sage: G = sum([piece.show3d( ) for piece in solution], Graphics())137 sage: G = sum([piece.show3d(size=0.85) for piece in solution], Graphics()) 134 138 sage: G.show(aspect_ratio=1, viewer='tachyon') 135 139 136 140 Let's compute the number of solutions:: … … Donald Knuth [1] considered the problem 162 166 Animation of Donald Knuth's dancing links 163 167  164 168 165 ::169 Animation of the solutions:: 166 170 167 171 sage: from sage.combinat.tiling import Polyomino, TilingSolver 168 172 sage: Y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='yellow') … … Animation of Donald Knuth's dancing link 170 174 sage: a = T.animate(stop=40) # long time 171 175 sage: a # long time 172 176 Animation with 40 frames 173 sage: a.show() # not tested 177 sage: a.show() # not tested  requires convert command 178 179 Incremental animation of the solutions (one piece is removed/added at a time):: 180 181 sage: a = T.animate('incremental', stop=40) # long time 182 sage: a # long time 183 Animation with 40 frames 184 sage: a.show(delay=50, iterations=1) # not tested  requires convert command 174 185 175 186 5d Easy Example 176 187  … … class Polyomino(SageObject): 787 798 last = s[1] 788 799 if last == 1 and all(f == 0 for f in firsts): 789 800 yield (P + Q) / 2.0 801 def center(self): 802 r""" 803 Return the center of the polyomino. 790 804 791 def show3d(self, size=0.75): 805 EXAMPLES:: 806 807 sage: from sage.combinat.tiling import Polyomino 808 sage: p = Polyomino([(0,0,0),(0,0,1)]) 809 sage: p.center() 810 (0, 0, 1/2) 811 812 In 3d:: 813 814 sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink') 815 sage: p.center() 816 (4/5, 4/5, 1/5) 817 818 In 2d:: 819 820 sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)]) 821 sage: p.center() 822 (3/4, 3/4) 823 """ 824 return sum(vector(t) for t in self) / len(self) 825 826 def show3d(self, size=1): 792 827 r""" 793 828 Returns a 3d Graphic object representing the polyomino. 794 829 795 .. NOTE::796 797 For now this is simply a bunch of intersecting cubes. It could798 be improved to give better results.799 800 830 INPUT: 801 831 802 832  ``self``  a polyomino of dimension 3 803  ``size``  number (optional, default: ``0.75``), the size of the 804 ``1 \times 1 \times 1`` cubes 833  ``size``  number (optional, default: ``1``), the size of each 834 ``1 \times 1 \times 1`` cube. This does a homothety with respect 835 to the center of the polyomino. 805 836 806 837 EXAMPLES:: 807 838 … … class Polyomino(SageObject): 812 843 assert self._dimension == 3, "To show a polyomino in 3d, its dimension must be 3." 813 844 G = Graphics() 814 845 for p in self: 815 G += cube(p, color=self._color, size=size) 816 for m in self.middle_of_neighbor_coords(): 817 G += cube(m, color=self._color, size=size) 846 G += cube(p, color=self._color) 847 center = self.center() 848 G = G.translate(center) 849 G = G.scale(size) 850 G = G.translate(center) 818 851 return G 819 852 820 def show2d(self, size= 0.75):853 def show2d(self, size=1): 821 854 r""" 822 855 Returns a 2d Graphic object representing the polyomino. 823 856 824 .. NOTE::825 826 For now this is simply a bunch of intersecting cubes. It could827 be improved to give better results.828 829 857 INPUT: 830 858 831 859  ``self``  a polyomino of dimension 2 832  ``size``  number (optional, default: ``0.75``), the size of the 833 ``1 \times 1 \times 1`` cubes 860  ``size``  number (optional, default: ``1``), the size of each 861 ``1 \times 1`` square. This does a homothety with respect 862 to the center of the polyomino. 834 863 835 864 EXAMPLES:: 836 865 … … class Polyomino(SageObject): 839 868 sage: p.show2d() # long time (0.5s) 840 869 """ 841 870 assert self._dimension == 2, "To show a polyomino in 2d, its dimension must be 2." 871 center = self.center() 872 transformed = [size * (vector(t)  center) + center for t in self] 842 873 h = size / 2.0 843 874 G = Graphics() 844 for a,b in self: 845 G += polygon([(ah,bh), (a+h,bh), (a+h,b+h), (ah,b+h), (ah,bh)], color=self._color) 846 for a,b in self.middle_of_neighbor_coords(): 875 for a,b in transformed: 847 876 G += polygon([(ah,bh), (a+h,bh), (a+h,b+h), (ah,b+h), (ah,bh)], color=self._color) 848 877 return G 849 878 … … class TilingSolver(SageObject): 1115 1144 return dict( (i+number_of_pieces,c) for i,c in enumerate(self.space()) ) 1116 1145 1117 1146 @cached_method 1118 def rows(self , verbose=False):1147 def rows(self): 1119 1148 r""" 1120 1149 Creation of the rows 1121 1150 … … class TilingSolver(SageObject): 1146 1175 """ 1147 1176 coord_to_int = self.coord_to_int_dict() 1148 1177 rows = [] 1149 self._starting_rows = [] 1178 self._starting_rows = [] # indices of the first row for each piece 1150 1179 for i,p in enumerate(self._pieces): 1151 1180 self._starting_rows.append(len(rows)) 1152 1181 if self._rotation and self._reflection: … … class TilingSolver(SageObject): 1164 1193 for q in it: 1165 1194 rows.append([i] + [coord_to_int[coord] for coord in q]) 1166 1195 self._starting_rows.append(len(rows)) 1167 if verbose:1168 print "Number of rows : %s" % len(rows)1169 print "Number of distinct rows : %s" % len(set(tuple(sorted(row)) for row in rows))1170 1196 return rows 1171 1197 1172 1198 def nrows_per_piece(self): … … class TilingSolver(SageObject): 1239 1265 while x.search() == 1: 1240 1266 yield x.get_solution() 1241 1267 1242 def dlx_common_p art_solutions(self):1268 def dlx_common_prefix_solutions(self): 1243 1269 r""" 1244 1270 Return an iterator over the row indices of solutions and of partial 1245 solutions, i.e. the common p artof two consecutive solutions.1271 solutions, i.e. the common prefix of two consecutive solutions. 1246 1272 1247 1273 The purpose is to illustrate the backtracking and construct an 1248 1274 animation of the evolution of solutions. … … class TilingSolver(SageObject): 1260 1286 sage: T = TilingSolver([p,q,r], box=(1,1,6)) 1261 1287 sage: list(T.dlx_solutions()) 1262 1288 [[0, 7, 14], [0, 12, 10], [6, 13, 5], [6, 14, 2], [11, 9, 5], [11, 10, 3]] 1263 sage: list(T.dlx_common_p art_solutions())1289 sage: list(T.dlx_common_prefix_solutions()) 1264 1290 [[0, 7, 14], [0], [0, 12, 10], [], [6, 13, 5], [6], [6, 14, 2], [], [11, 9, 5], [11], [11, 10, 3]] 1291 1292 :: 1293 1294 sage: from sage.combinat.tiling import TilingSolver, Polyomino 1295 sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='yellow') 1296 sage: T = TilingSolver([y], box=(5,10), reusable=True, reflection=True) 1297 sage: for a in T.dlx_common_prefix_solutions(): a 1298 [18, 119, 68, 33, 48, 23, 8, 73, 58, 43] 1299 [18, 119, 68, 33, 48] 1300 [18, 119, 68, 33, 48, 133, 8, 73, 168, 43] 1301 [18, 119] 1302 [18, 119, 178, 143, 48, 23, 8, 73, 58, 43] 1303 [18, 119, 178, 143, 48] 1304 [18, 119, 178, 143, 48, 133, 8, 73, 168, 43] 1305 [18, 119, 178] 1306 [18, 119, 178, 164, 152, 73, 8, 133, 159, 147] 1307 [] 1308 [74, 19, 177, 33, 49, 134, 109, 72, 58, 42] 1309 [74, 19, 177] 1310 [74, 19, 177, 54, 151, 72, 109, 134, 160, 37] 1311 [74, 19, 177, 54, 151] 1312 [74, 19, 177, 54, 151, 182, 109, 134, 160, 147] 1313 [74] 1314 [74, 129, 177, 164, 151, 72, 109, 134, 160, 37] 1315 [74, 129, 177, 164, 151] 1316 [74, 129, 177, 164, 151, 182, 109, 134, 160, 147] 1265 1317 """ 1266 1318 it = self.dlx_solutions() 1267 1319 B = it.next() 1268 1320 while True: 1269 1321 yield B 1270 1322 A, B = B, it.next() 1271 common = []1323 common_prefix = [] 1272 1324 for a,b in itertools.izip(A,B): 1273 1325 if a == b: 1274 common.append(a) 1275 yield common 1326 common_prefix.append(a) 1327 else: 1328 break 1329 yield common_prefix 1276 1330 1277 1331 def dlx_incremental_solutions(self): 1278 1332 r""" … … class TilingSolver(SageObject): 1300 1354 [[0, 7, 14], [0, 12, 10], [6, 13, 5], [6, 14, 2], [11, 9, 5], [11, 10, 3]] 1301 1355 sage: list(T.dlx_incremental_solutions()) 1302 1356 [[0, 7, 14], [0, 7], [0], [0, 12], [0, 12, 10], [0, 12], [0], [], [6], [6, 13], [6, 13, 5], [6, 13], [6], [6, 14], [6, 14, 2], [6, 14], [6], [], [11], [11, 9], [11, 9, 5], [11, 9], [11], [11, 10], [11, 10, 3]] 1357 1358 :: 1359 1360 sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='yellow') 1361 sage: T = TilingSolver([y], box=(5,10), reusable=True, reflection=True) 1362 sage: for a in T.dlx_solutions(): a 1363 [18, 119, 68, 33, 48, 23, 8, 73, 58, 43] 1364 [18, 119, 68, 33, 48, 133, 8, 73, 168, 43] 1365 [18, 119, 178, 143, 48, 23, 8, 73, 58, 43] 1366 [18, 119, 178, 143, 48, 133, 8, 73, 168, 43] 1367 [18, 119, 178, 164, 152, 73, 8, 133, 159, 147] 1368 [74, 19, 177, 33, 49, 134, 109, 72, 58, 42] 1369 [74, 19, 177, 54, 151, 72, 109, 134, 160, 37] 1370 [74, 19, 177, 54, 151, 182, 109, 134, 160, 147] 1371 [74, 129, 177, 164, 151, 72, 109, 134, 160, 37] 1372 [74, 129, 177, 164, 151, 182, 109, 134, 160, 147] 1373 sage: len(list(T.dlx_incremental_solutions())) 1374 123 1303 1375 """ 1304 1376 it = self.dlx_solutions() 1305 1377 B = it.next() 1306 1378 while True: 1307 1379 yield B 1308 1380 A, B = B, it.next() 1309 common = [] 1310 for i in reversed(xrange(len(A))): 1311 if A[i] == B[i]: 1381 common_prefix = 0 1382 for a,b in itertools.izip(A,B): 1383 if a == b: 1384 common_prefix += 1 1385 else: 1312 1386 break 1313 else: 1314 yield A[:i] 1315 else: 1316 i = 1 1317 for j in xrange(i+2, len(A)): 1387 for i in xrange(1,len(A)common_prefix): 1388 yield A[:i] 1389 for j in xrange(common_prefix, len(B)): 1318 1390 yield B[:j] 1319 1391 1320 1392 def solve(self, partial=None): … … class TilingSolver(SageObject): 1329 1401 following: 1330 1402 1331 1403  ``None``  include only complete solution 1332  ``'common '``  common partbetween two consecutive solutions1404  ``'common_prefix'``  common prefix between two consecutive solutions 1333 1405  ``'incremental'``  one piece change at a time 1334 1406 1335 1407 OUTPUT: … … class TilingSolver(SageObject): 1359 1431 1360 1432 Including the partial solutions:: 1361 1433 1362 sage: it = T.solve(partial='common ')1434 sage: it = T.solve(partial='common_prefix') 1363 1435 sage: for p in it.next(): p 1364 1436 Polyomino: [(0, 0, 0)], Color: gray 1365 1437 Polyomino: [(0, 0, 1), (0, 0, 2)], Color: gray … … class TilingSolver(SageObject): 1406 1478 rows = self.rows() 1407 1479 if partial is None: 1408 1480 it = self.dlx_solutions() 1409 elif partial == 'common ':1410 it = self.dlx_common_p art_solutions()1481 elif partial == 'common_prefix': 1482 it = self.dlx_common_prefix_solutions() 1411 1483 elif partial == 'incremental': 1412 1484 it = self.dlx_incremental_solutions() 1413 1485 else: 1414 1486 raise ValueError("Unknown value for partial (=%s)" % partial) 1415 1487 for solution in it: 1416 p entos = []1488 pieces = [] 1417 1489 for row_number in solution: 1418 1490 row = rows[row_number] 1419 1491 if self._reusable: … … class TilingSolver(SageObject): 1426 1498 no = row[0] 1427 1499 coords = [int_to_coord[i] for i in row[1:]] 1428 1500 p = Polyomino(coords, color=self._pieces[no].color()) 1429 p entos.append(p)1430 yield p entos1501 pieces.append(p) 1502 yield pieces 1431 1503 1432 1504 def number_of_solutions(self): 1433 1505 r""" … … class TilingSolver(SageObject): 1461 1533 N += 1 1462 1534 return N 1463 1535 1464 def animate(self, partial= 'incremental', stop=None):1536 def animate(self, partial=None, stop=None, size=0.9): 1465 1537 r""" 1466 1538 Return an animation of evolving solutions. 1467 1539 … … class TilingSolver(SageObject): 1471 1543 include partial (incomplete) solutions. It can be one of the 1472 1544 following: 1473 1545 1474  ``None``  include only complete solution 1475  ``'common '``  common partbetween two consecutive solutions1546  ``None``  include only complete solutions 1547  ``'common_prefix'``  common prefix between two consecutive solutions 1476 1548  ``'incremental'``  one piece change at a time 1477 1549 1478 1550  ``stop``  integer (optional, default:``None``), number of frames 1551 1552  ``size``  number (optional, default: ``0.9``), the size of each 1553 ``1 \times 1`` square. This does a homothety with respect 1554 to the center of each polyomino. 1479 1555 1480 1556 EXAMPLES:: 1481 1557 1482 1558 sage: from sage.combinat.tiling import Polyomino, TilingSolver 1483 sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color=' yellow')1559 sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='cyan') 1484 1560 sage: T = TilingSolver([y], box=(5,10), reusable=True, reflection=True) 1485 sage: a = T.animate() # long time (2s) 1561 sage: a = T.animate() 1562 sage: a 1563 Animation with 10 frames 1564 1565 Include partial solutions (common prefix between two consecutive 1566 solutions):: 1567 1568 sage: a = T.animate('common_prefix') 1569 sage: a 1570 Animation with 19 frames 1571 1572 Incremental solutions (one piece removed or added at a time):: 1573 1574 sage: a = T.animate('incremental') # long time (2s) 1486 1575 sage: a # long time (2s) 1487 Animation with 42 frames 1488 sage: a.show() # not tested 1576 Animation with 123 frames 1489 1577 1490 1578 :: 1491 1579 1492 sage: a = T.animate('common') # not tested 1493 sage: a # not tested 1494 Animation with 19 frames 1580 sage: a.show() # optional  requires convert command 1495 1581 1496 Without partial solutions:: 1582 The ``show`` function takes arguments to specify the delay between 1583 frames (measured in hundredths of a second, default value 20) and 1584 the number of iterations (default value 0, which means to iterate 1585 forever). To iterate 4 times with half a second between each frame:: 1497 1586 1498 sage: a = T.animate(None) # not tested 1499 sage: a # not tested 1500 Animation with 10 frames 1587 sage: a.show(delay=50, iterations=4) # optional 1501 1588 1502 1589 Limit the number of frames:: 1503 1590 1504 sage: a = T.animate('incremental', 13)# not tested1591 sage: a = T.animate('incremental', stop=13) # not tested 1505 1592 sage: a # not tested 1506 1593 Animation with 13 frames 1507 1594 """ … … class TilingSolver(SageObject): 1509 1596 if dimension == 2: 1510 1597 it = self.solve(partial=partial) 1511 1598 it = itertools.islice(it, stop) 1512 L = [sum([piece.show2d( ) for piece in solution], Graphics()) for solution in it]1599 L = [sum([piece.show2d(size) for piece in solution], Graphics()) for solution in it] 1513 1600 xmax, ymax = self._box 1514 1601 a = Animation(L, xmin=0, ymin=0, xmax=xmax, ymax=ymax, aspect_ratio=1) 1515 1602 return a 1516 1603 elif dimension == 3: 1517 raise NotImplementedError(" Animation must be implemented in Jmol first")1604 raise NotImplementedError("3d Animation must be implemented in Jmol first") 1518 1605 else: 1519 1606 raise NotImplementedError("Dimension must be 2 or 3 in order to make an animation") 1520 1607 
sage/games/quantumino.py
diff git a/sage/games/quantumino.py b/sage/games/quantumino.py
a b Mathematique in Montreal by Franco Salio 11 11 12 12 The solution uses the dancing links code which is in Sage and is based on 13 13 the more general code available in the module :mod:`sage.combinat.tiling`. 14 Dancing links were originally introduced by Donald Knuth in 2000 [3]. In 15 particular, Knuth used dancing links to solve tilings of a region by 2D 16 pentaminos. Here we extend the method for 3D pentaminos. 14 Dancing links were originally introduced by Donald Knuth in 2000 15 (`arXiv:cs/0011047 <http://arxiv.org/abs/cs/0011047>`_). In particular, 16 Knuth used dancing links to solve tilings of a region by 2D pentaminos. 17 Here we extend the method for 3D pentaminos. 17 18 18 19 This module defines two classes : 19 20 … … class QuantuminoSolver(SageObject): 562 563 sage: QuantuminoSolver(4, box=(3,2,2)).number_of_solutions() 563 564 0 564 565 565 ::566 This computation takes several days:: 566 567 567 568 sage: QuantuminoSolver(0).number_of_solutions() # not tested 568 569 ??? hundreds of millions ???