Ticket #11379: trac_11379_hole_bug-sl.patch

File trac_11379_hole_bug-sl.patch, 20.1 KB (added by slabbe, 7 years ago)

Applies over the precedent patches.

  • 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  
    11r"""
    22Tiling Solver
    33
    4 Finding a tiling of a box into non-intersecting polyominoes.
     4Tiling a n-dimensional box into non-intersecting n-dimensional polyominoes.
    55
    66This uses dancing links code which is in Sage.  Dancing links were
    77originally introduced by Donald Knuth in 2000 [1]. In particular, Knuth
    88used dancing links to solve tilings of a region by 2D pentaminos.  Here we
    9 extend the method for any dimension.
     9extend the method to any dimension.
     10
     11In particular, the :mod:`sage.games.quantumino` module is based on
     12the Tiling Solver and allows to solve the 3d Quantumino puzzle.
    1013
    1114This module defines two classes:
    1215
    The following is a puzzle owned by Flore 
    8992    sage: L.append(Polyomino([(0,1),(0,2),(0,3),(1,0),(1,1),(1,2)]))
    9093    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(1,0),(1,1),(1,2)]))
    9194
    92 By default, rotations are allowed and reflections are not. In this case, if
    93 reflections are not allowed, there are no solution::
     95By default, rotations are allowed and reflections are not. In this case,
     96there are no solution::
    9497
    9598    sage: T = TilingSolver(L, (8,8))
    9699    sage: T.number_of_solutions()                             # long time (2.5 s)
    97100    0
    98101
    99 Allow reflections, solve the puzzle and show one solution::
     102If reflections are allowed, there are solutions. Solve the puzzle and show
     103one solution::
    100104
    101105    sage: T = TilingSolver(L, (8,8), reflection=True)
    102106    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())
    104108    sage: G.show(aspect_ratio=1)
    105109
    106 Let's compute the number of solutions::
     110Compute the number of solutions::
    107111
    108     sage: T.number_of_solutions()                              # long time (6s the first time)
     112    sage: T.number_of_solutions()                              # long time (2.6s)
    109113    328
    110114
    1111153d Puzzle
    112116---------
    113117
    114 The same thing done in 3d without allowing reflections this time::
     118The same thing done in 3d *without* allowing reflections this time::
    115119
    116120    sage: from sage.combinat.tiling import Polyomino, TilingSolver
    117121    sage: L = []
    Solve the puzzle and show one solution:: 
    130134
    131135    sage: T = TilingSolver(L, (8,8,1))
    132136    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())
    134138    sage: G.show(aspect_ratio=1, viewer='tachyon')
    135139
    136140Let's compute the number of solutions::
    Donald Knuth [1] considered the problem  
    162166Animation of Donald Knuth's dancing links
    163167-----------------------------------------
    164168
    165 ::
     169Animation of the solutions::
    166170
    167171    sage: from sage.combinat.tiling import Polyomino, TilingSolver
    168172    sage: Y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='yellow')
    Animation of Donald Knuth's dancing link 
    170174    sage: a = T.animate(stop=40)            # long time
    171175    sage: a                                 # long time
    172176    Animation with 40 frames
    173     sage: a.show()                          # not tested
     177    sage: a.show()                          # not tested  - requires convert command
     178
     179Incremental 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
    174185
    1751865d Easy Example
    176187---------------
    class Polyomino(SageObject): 
    787798            last = s[-1]
    788799            if last == 1 and all(f == 0 for f in firsts):
    789800                yield (P + Q) / 2.0
     801    def center(self):
     802        r"""
     803        Return the center of the polyomino.
    790804
    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):
    792827        r"""
    793828        Returns a 3d Graphic object representing the polyomino.
    794829
    795         .. NOTE::
    796 
    797             For now this is simply a bunch of intersecting cubes. It could
    798             be improved to give better results.
    799 
    800830        INPUT:
    801831
    802832        - ``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.
    805836
    806837        EXAMPLES::
    807838
    class Polyomino(SageObject): 
    812843        assert self._dimension == 3, "To show a polyomino in 3d, its dimension must be 3."
    813844        G = Graphics()
    814845        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)
    818851        return G
    819852
    820     def show2d(self, size=0.75):
     853    def show2d(self, size=1):
    821854        r"""
    822855        Returns a 2d Graphic object representing the polyomino.
    823856
    824         .. NOTE::
    825 
    826             For now this is simply a bunch of intersecting cubes. It could
    827             be improved to give better results.
    828 
    829857        INPUT:
    830858
    831859        - ``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.
    834863
    835864        EXAMPLES::
    836865
    class Polyomino(SageObject): 
    839868            sage: p.show2d()              # long time (0.5s)
    840869        """
    841870        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]
    842873        h = size / 2.0
    843874        G = Graphics()
    844         for a,b in self:
    845             G += polygon([(a-h,b-h), (a+h,b-h), (a+h,b+h), (a-h,b+h), (a-h,b-h)], color=self._color)
    846         for a,b in self.middle_of_neighbor_coords():
     875        for a,b in transformed:
    847876            G += polygon([(a-h,b-h), (a+h,b-h), (a+h,b+h), (a-h,b+h), (a-h,b-h)], color=self._color)
    848877        return G
    849878
    class TilingSolver(SageObject): 
    11151144            return dict( (i+number_of_pieces,c) for i,c in enumerate(self.space()) )
    11161145
    11171146    @cached_method
    1118     def rows(self, verbose=False):
     1147    def rows(self):
    11191148        r"""
    11201149        Creation of the rows
    11211150
    class TilingSolver(SageObject): 
    11461175        """
    11471176        coord_to_int = self.coord_to_int_dict()
    11481177        rows = []
    1149         self._starting_rows = []
     1178        self._starting_rows = [] # indices of the first row for each piece
    11501179        for i,p in enumerate(self._pieces):
    11511180            self._starting_rows.append(len(rows))
    11521181            if self._rotation and self._reflection:
    class TilingSolver(SageObject): 
    11641193                for q in it:
    11651194                    rows.append([i] + [coord_to_int[coord] for coord in q])
    11661195        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))
    11701196        return rows
    11711197
    11721198    def nrows_per_piece(self):
    class TilingSolver(SageObject): 
    12391265        while x.search() == 1:
    12401266            yield x.get_solution()
    12411267
    1242     def dlx_common_part_solutions(self):
     1268    def dlx_common_prefix_solutions(self):
    12431269        r"""
    12441270        Return an iterator over the row indices of solutions and of partial
    1245         solutions, i.e. the common part of two consecutive solutions.
     1271        solutions, i.e. the common prefix of two consecutive solutions.
    12461272
    12471273        The purpose is to illustrate the backtracking and construct an
    12481274        animation of the evolution of solutions.
    class TilingSolver(SageObject): 
    12601286            sage: T = TilingSolver([p,q,r], box=(1,1,6))
    12611287            sage: list(T.dlx_solutions())
    12621288            [[0, 7, 14], [0, 12, 10], [6, 13, 5], [6, 14, 2], [11, 9, 5], [11, 10, 3]]
    1263             sage: list(T.dlx_common_part_solutions())
     1289            sage: list(T.dlx_common_prefix_solutions())
    12641290            [[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]
    12651317        """
    12661318        it = self.dlx_solutions()
    12671319        B = it.next()
    12681320        while True:
    12691321            yield B
    12701322            A, B = B, it.next()
    1271             common = []
     1323            common_prefix = []
    12721324            for a,b in itertools.izip(A,B):
    12731325                if a == b:
    1274                     common.append(a)
    1275             yield common
     1326                    common_prefix.append(a)
     1327                else:
     1328                    break
     1329            yield common_prefix
    12761330
    12771331    def dlx_incremental_solutions(self):
    12781332        r"""
    class TilingSolver(SageObject): 
    13001354            [[0, 7, 14], [0, 12, 10], [6, 13, 5], [6, 14, 2], [11, 9, 5], [11, 10, 3]]
    13011355            sage: list(T.dlx_incremental_solutions())
    13021356            [[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
    13031375        """
    13041376        it = self.dlx_solutions()
    13051377        B = it.next()
    13061378        while True:
    13071379            yield B
    13081380            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:
    13121386                    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)):
    13181390                yield B[:j]
    13191391
    13201392    def solve(self, partial=None):
    class TilingSolver(SageObject): 
    13291401          following:
    13301402         
    13311403          - ``None`` - include only complete solution
    1332           - ``'common'`` - common part between two consecutive solutions
     1404          - ``'common_prefix'`` - common prefix between two consecutive solutions
    13331405          - ``'incremental'`` - one piece change at a time
    13341406       
    13351407        OUTPUT:
    class TilingSolver(SageObject): 
    13591431
    13601432        Including the partial solutions::
    13611433
    1362             sage: it = T.solve(partial='common')
     1434            sage: it = T.solve(partial='common_prefix')
    13631435            sage: for p in it.next(): p
    13641436            Polyomino: [(0, 0, 0)], Color: gray
    13651437            Polyomino: [(0, 0, 1), (0, 0, 2)], Color: gray
    class TilingSolver(SageObject): 
    14061478        rows = self.rows()
    14071479        if partial is None:
    14081480            it = self.dlx_solutions()
    1409         elif partial == 'common':
    1410             it = self.dlx_common_part_solutions()
     1481        elif partial == 'common_prefix':
     1482            it = self.dlx_common_prefix_solutions()
    14111483        elif partial == 'incremental':
    14121484            it = self.dlx_incremental_solutions()
    14131485        else:
    14141486            raise ValueError("Unknown value for partial (=%s)" % partial)
    14151487        for solution in it:
    1416             pentos = []
     1488            pieces = []
    14171489            for row_number in solution:
    14181490                row = rows[row_number]
    14191491                if self._reusable:
    class TilingSolver(SageObject): 
    14261498                    no = row[0]
    14271499                    coords = [int_to_coord[i] for i in row[1:]]
    14281500                    p = Polyomino(coords, color=self._pieces[no].color())
    1429                 pentos.append(p)
    1430             yield pentos
     1501                pieces.append(p)
     1502            yield pieces
    14311503
    14321504    def number_of_solutions(self):
    14331505        r"""
    class TilingSolver(SageObject): 
    14611533            N += 1
    14621534        return N
    14631535
    1464     def animate(self, partial='incremental', stop=None):
     1536    def animate(self, partial=None, stop=None, size=0.9):
    14651537        r"""
    14661538        Return an animation of evolving solutions.
    14671539
    class TilingSolver(SageObject): 
    14711543          include partial (incomplete) solutions. It can be one of the
    14721544          following:
    14731545         
    1474           - ``None`` - include only complete solution
    1475           - ``'common'`` - common part between two consecutive solutions
     1546          - ``None`` - include only complete solutions
     1547          - ``'common_prefix'`` - common prefix between two consecutive solutions
    14761548          - ``'incremental'`` - one piece change at a time
    14771549
    14781550        - ``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.
    14791555       
    14801556        EXAMPLES::
    14811557
    14821558            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')
    14841560            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)
    14861575            sage: a                                 # long time (2s)
    1487             Animation with 42 frames
    1488             sage: a.show()                          # not tested
     1576            Animation with 123 frames
    14891577
    14901578        ::
    14911579
    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
    14951581
    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::
    14971586
    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
    15011588
    15021589        Limit the number of frames::
    15031590
    1504             sage: a = T.animate('incremental', 13)          # not tested
     1591            sage: a = T.animate('incremental', stop=13)     # not tested
    15051592            sage: a                                         # not tested
    15061593            Animation with 13 frames
    15071594        """
    class TilingSolver(SageObject): 
    15091596        if dimension == 2:
    15101597            it = self.solve(partial=partial)
    15111598            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]
    15131600            xmax, ymax = self._box
    15141601            a = Animation(L, xmin=0, ymin=0, xmax=xmax, ymax=ymax, aspect_ratio=1)
    15151602            return a
    15161603        elif dimension == 3:
    1517             raise NotImplementedError("Animation must be implemented in Jmol first")
     1604            raise NotImplementedError("3d Animation must be implemented in Jmol first")
    15181605        else:
    15191606            raise NotImplementedError("Dimension must be 2 or 3 in order to make an animation")
    15201607
  • sage/games/quantumino.py

    diff --git a/sage/games/quantumino.py b/sage/games/quantumino.py
    a b Mathematique in Montreal by Franco Salio 
    1111
    1212The solution uses the dancing links code which is in Sage and is based on
    1313the 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.
     14Dancing links were originally introduced by Donald Knuth in 2000
     15(`arXiv:cs/0011047 <http://arxiv.org/abs/cs/0011047>`_). In particular,
     16Knuth used dancing links to solve tilings of a region by 2D pentaminos.
     17Here we extend the method for 3D pentaminos.
    1718
    1819This module defines two classes :
    1920
    class QuantuminoSolver(SageObject): 
    562563            sage: QuantuminoSolver(4, box=(3,2,2)).number_of_solutions()
    563564            0
    564565
    565         ::
     566        This computation takes several days::
    566567
    567568            sage: QuantuminoSolver(0).number_of_solutions()                # not tested
    568569            ??? hundreds of millions ???