Ticket #11379: trac_11379_quantamino-total-sl.patch

File trac_11379_quantamino-total-sl.patch, 87.0 KB (added by rbeezer, 10 years ago)

Standalone, comprehensive patch

  • doc/en/reference/combinat/index.rst

    # HG changeset patch
    # User Sebastien Labbe <slabqc at gmail.com>
    # Date 1306440157 14400
    # Node ID c7272c05107751335ca6a301d31a1e4d6406e44b
    # Parent  8532a2ad1e558cbc91ddaaa6b7cc79956dd1e8ba
    11379: Family Games America's Quantumino solver
    
    diff --git a/doc/en/reference/combinat/index.rst b/doc/en/reference/combinat/index.rst
    a b  
    3636   ../sage/combinat/skew_partition
    3737   ../sage/combinat/subset
    3838   ../sage/combinat/subword
     39   ../sage/combinat/tiling
    3940   ../sage/combinat/tuple
    4041
    4142   algebra
  • doc/en/reference/games.rst

    diff --git a/doc/en/reference/games.rst b/doc/en/reference/games.rst
    a b  
    77.. toctree::
    88   :maxdepth: 2
    99
    10    sage/games/sudoku
    11  No newline at end of file
     10   sage/games/sudoku
     11   sage/games/quantumino
  • new file sage/combinat/tiling.py

    diff --git a/sage/combinat/tiling.py b/sage/combinat/tiling.py
    new file mode 100644
    - +  
     1r"""
     2Tiling Solver
     3
     4Tiling a n-dimensional box into non-intersecting n-dimensional polyominoes.
     5
     6This uses dancing links code which is in Sage.  Dancing links were
     7originally introduced by Donald Knuth in 2000 [1]. In particular, Knuth
     8used dancing links to solve tilings of a region by 2D pentaminos.  Here we
     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.
     13
     14This module defines two classes:
     15
     16- :class:`sage.combinat.tiling.Polyomino` class, to represent polyominoes
     17  in arbitrary dimension. The goal of this class is to return all the
     18  rotated, reflected and/or translated copies of a polyomino that are
     19  contained in a certain box.
     20
     21- :class:`sage.combinat.tiling.TilingSolver` class, to solve the general
     22  problem of tiling a rectangular `n`-dimensional box with a set of
     23  `n`-dimensional polyominoes. One can specify if rotations and reflections
     24  are allowed or not and if pieces can be reused or not. This class convert
     25  the tiling data into rows of a matrix that are passed to the DLX solver.
     26  It also allows to compute the number of solutions.
     27
     28AUTHOR:
     29
     30    - Sebastien Labbe, June 2011
     31
     32EXAMPLES:
     33
     342d Easy Example
     35---------------
     36
     37Here is a 2d example. Let's try to fill the `3 \times 2` rectangle with a
     38`1 \times 2` rectangle and a `2 \times 2` square. Obviously, there are two
     39solutions::
     40
     41    sage: from sage.combinat.tiling import TilingSolver, Polyomino
     42    sage: p = Polyomino([(0,0), (0,1)])
     43    sage: q = Polyomino([(0,0), (0,1), (1,0), (1,1)])
     44    sage: T = TilingSolver([p,q], box=[3,2])
     45    sage: it = T.solve()
     46    sage: it.next()
     47    [Polyomino: [(0, 0), (0, 1), (1, 0), (1, 1)], Color: gray, Polyomino: [(2, 0), (2, 1)], Color: gray]
     48    sage: it.next()
     49    [Polyomino: [(1, 0), (1, 1), (2, 0), (2, 1)], Color: gray, Polyomino: [(0, 0), (0, 1)], Color: gray]
     50    sage: it.next()
     51    Traceback (most recent call last):
     52    ...
     53    StopIteration
     54    sage: T.number_of_solutions()
     55    2
     56
     571d Easy Example
     58---------------
     59
     60Here is an easy one dimensional example where we try to tile a stick of
     61length 6 with three sticks of length 1, 2 and 3. There are six solutions::
     62
     63    sage: p = Polyomino([[0]])
     64    sage: q = Polyomino([[0],[1]])
     65    sage: r = Polyomino([[0],[1],[2]])
     66    sage: T = TilingSolver([p,q,r], box=[6])
     67    sage: len(T.rows())
     68    15
     69    sage: it = T.solve()
     70    sage: it.next()
     71    [Polyomino: [(0,)], Color: gray, Polyomino: [(1,), (2,)], Color: gray, Polyomino: [(3,), (4,), (5,)], Color: gray]
     72    sage: it.next()
     73    [Polyomino: [(0,)], Color: gray, Polyomino: [(1,), (2,), (3,)], Color: gray, Polyomino: [(4,), (5,)], Color: gray]
     74    sage: T.number_of_solutions()
     75    6
     76
     772d Puzzle allowing reflections
     78------------------------------
     79
     80The following is a puzzle owned by Florent Hivert::
     81
     82    sage: from sage.combinat.tiling import Polyomino, TilingSolver
     83    sage: L = []
     84    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,2),(1,3)], 'yellow'))
     85    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,2)], "black"))
     86    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,3)], "gray"))
     87    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,3)],"cyan"))
     88    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,1)],"red"))
     89    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,1),(1,2)],"blue"))
     90    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,1),(1,3)],"green"))
     91    sage: L.append(Polyomino([(0,1),(0,2),(0,3),(1,0),(1,1),(1,3)],"magenta"))
     92    sage: L.append(Polyomino([(0,1),(0,2),(0,3),(1,0),(1,1),(1,2)],"orange"))
     93    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(1,0),(1,1),(1,2)],"pink"))
     94
     95By default, rotations are allowed and reflections are not. In this case,
     96there are no solution::
     97
     98    sage: T = TilingSolver(L, (8,8))
     99    sage: T.number_of_solutions()                             # long time (2.5 s)
     100    0
     101
     102If reflections are allowed, there are solutions. Solve the puzzle and show
     103one solution::
     104
     105    sage: T = TilingSolver(L, (8,8), reflection=True)
     106    sage: solution = T.solve().next()
     107    sage: G = sum([piece.show2d() for piece in solution], Graphics())
     108    sage: G.show(aspect_ratio=1, axes=False)
     109
     110Compute the number of solutions::
     111
     112    sage: T.number_of_solutions()                              # long time (2.6s)
     113    328
     114
     115Create a animation of all the solutions::
     116
     117    sage: a = T.animate()                            # not tested
     118    sage: a                                          # not tested
     119    Animation with 328 frames
     120
     1213d Puzzle
     122---------
     123
     124The same thing done in 3d *without* allowing reflections this time::
     125
     126    sage: from sage.combinat.tiling import Polyomino, TilingSolver
     127    sage: L = []
     128    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,2,0),(1,3,0)]))
     129    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,2,0)]))
     130    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,3,0)]))
     131    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,3,0)]))
     132    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0)]))
     133    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,1,0),(1,2,0)]))
     134    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,1,0),(1,3,0)]))
     135    sage: L.append(Polyomino([(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,3,0)]))
     136    sage: L.append(Polyomino([(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,2,0)]))
     137    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(1,0,0),(1,1,0),(1,2,0)]))
     138
     139Solve the puzzle and show one solution::
     140
     141    sage: T = TilingSolver(L, (8,8,1))
     142    sage: solution = T.solve().next()
     143    sage: G = sum([piece.show3d(size=0.85) for piece in solution], Graphics())
     144    sage: G.show(aspect_ratio=1, viewer='tachyon')
     145
     146Let's compute the number of solutions::
     147
     148    sage: T.number_of_solutions()                              # long time (3s)
     149    328
     150
     151Donald Knuth example : the Y pentamino
     152--------------------------------------
     153
     154Donald Knuth [1] considered the problem of packing 45 Y pentominoes into a
     155`15 \times 15` square::
     156
     157    sage: from sage.combinat.tiling import Polyomino, TilingSolver
     158    sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)])
     159    sage: T = TilingSolver([y], box=(5,10), reusable=True, reflection=True)
     160    sage: T.number_of_solutions()
     161    10
     162    sage: solution = T.solve().next()
     163    sage: G = sum([piece.show2d() for piece in solution], Graphics())
     164    sage: G.show(aspect_ratio=1)
     165
     166::
     167
     168    sage: T = TilingSolver([y], box=(15,15), reusable=True, reflection=True)
     169    sage: T.number_of_solutions()                      #not tested
     170    212
     171
     172Animation of Donald Knuth's dancing links
     173-----------------------------------------
     174
     175Animation of the solutions::
     176
     177    sage: from sage.combinat.tiling import Polyomino, TilingSolver
     178    sage: Y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='yellow')
     179    sage: T = TilingSolver([Y], box=(15,15), reusable=True, reflection=True)
     180    sage: a = T.animate(stop=40)            # long time
     181    sage: a                                 # long time
     182    Animation with 40 frames
     183    sage: a.show()                          # not tested  - requires convert command
     184
     185Incremental animation of the solutions (one piece is removed/added at a time)::
     186
     187    sage: a = T.animate('incremental', stop=40)   # long time
     188    sage: a                                       # long time
     189    Animation with 40 frames
     190    sage: a.show(delay=50, iterations=1)     # not tested  - requires convert command
     191
     1925d Easy Example
     193---------------
     194
     195Here is a 5d example. Let's try to fill the `2 \times 2 \times 2 \times 2
     196\times 2` rectangle with reusable `1 \times 1 \times 1 \times 1 \times 1`
     197rectangles. Obviously, there is one solution::
     198
     199    sage: from sage.combinat.tiling import Polyomino, TilingSolver
     200    sage: p = Polyomino([(0,0,0,0,0)])
     201    sage: T = TilingSolver([p], box=(2,2,2,2,2), reusable=True)
     202    sage: rows = T.rows()                               # long time (3s)
     203    sage: rows                                          # long time (fast)
     204    [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17], [18], [19], [20], [21], [22], [23], [24], [25], [26], [27], [28], [29], [30], [31]]
     205    sage: T.number_of_solutions()                       # long time (fast)
     206    1
     207
     208REFERENCES:
     209
     210- [1] Knuth, Donald (2000). "Dancing links". `arXiv:cs/0011047
     211  <http://arxiv.org/abs/cs/0011047>`_.
     212
     213"""
     214#*****************************************************************************
     215#       Copyright (C) 2011 Sebastien Labbe <slabqc@gmail.com>
     216#
     217#  Distributed under the terms of the GNU General Public License (GPL)
     218#  as published by the Free Software Foundation; either version 2 of
     219#  the License, or (at your option) any later version. 
     220#                  http://www.gnu.org/licenses/ 
     221#*****************************************************************************
     222import itertools
     223from sage.structure.sage_object import SageObject
     224from sage.misc.cachefunc import cached_method, cached_function
     225from sage.misc.misc import prod
     226from sage.combinat.all import WeylGroup
     227from sage.plot.plot import Graphics
     228from sage.plot.polygon import polygon
     229from sage.plot.line import line
     230from sage.plot.circle import circle
     231from sage.modules.free_module_element import vector
     232from sage.plot.plot3d.platonic import cube
     233from sage.plot.animate import Animation
     234from sage.misc.mrange import xmrange
     235
     236############################
     237# Orthogonal transformations
     238############################
     239@cached_function
     240def orthogonal_transformation(n, orientation_preserving=True):
     241    r"""
     242    Return the list of orthogonal transformation matrices in the
     243    `n`-dimensional vector space.
     244
     245    INPUT:
     246
     247    - ``n``` - positive integer, dimension of the space
     248    - ``orientation_preserving`` - bool (optional, default: ``True``),
     249      whether the orientation is preserved
     250
     251    OUTPUT:
     252
     253        list of matrices
     254
     255    EXAMPLES::
     256
     257        sage: from sage.combinat.tiling import orthogonal_transformation
     258        sage: orthogonal_transformation(2)
     259        [
     260        [1 0]  [ 0 -1]  [ 0  1]  [-1  0]
     261        [0 1], [ 1  0], [-1  0], [ 0 -1]
     262        ]
     263        sage: orthogonal_transformation(2, orientation_preserving=False)
     264        [
     265        [1 0]  [0 1]  [ 0 -1]  [-1  0]  [ 1  0]  [ 0  1]  [ 0 -1]  [-1  0]
     266        [0 1], [1 0], [ 1  0], [ 0  1], [ 0 -1], [-1  0], [-1  0], [ 0 -1]
     267        ]
     268        sage: orthogonal_transformation(3)
     269        [
     270        [1 0 0]  [0 0 1]  [ 0  0 -1]  [-1  0  0]  [ 0 -1  0]  [0 1 0]
     271        [0 1 0]  [1 0 0]  [ 0  1  0]  [ 0  0  1]  [ 1  0  0]  [0 0 1]
     272        [0 0 1], [0 1 0], [ 1  0  0], [ 0  1  0], [ 0  0  1], [1 0 0],
     273        <BLANKLINE>
     274        [ 1  0  0]  [ 0  0  1]  [ 0  0 -1]  [-1  0  0]  [ 0 -1  0]  [ 0  1  0]
     275        [ 0  0 -1]  [ 0 -1  0]  [-1  0  0]  [ 0 -1  0]  [ 0  0 -1]  [-1  0  0]
     276        [ 0  1  0], [ 1  0  0], [ 0  1  0], [ 0  0  1], [ 1  0  0], [ 0  0  1],
     277        <BLANKLINE>
     278        [ 0  1  0]  [ 0  0  1]  [ 0  0 -1]  [ 0 -1  0]  [-1  0  0]  [ 1  0  0]
     279        [ 1  0  0]  [ 0  1  0]  [ 1  0  0]  [ 0  0  1]  [ 0  1  0]  [ 0  0  1]
     280        [ 0  0 -1], [-1  0  0], [ 0 -1  0], [-1  0  0], [ 0  0 -1], [ 0 -1  0],
     281        <BLANKLINE>
     282        [ 0  1  0]  [ 0  0  1]  [ 0  0 -1]  [ 0 -1  0]  [-1  0  0]  [ 1  0  0]
     283        [ 0  0 -1]  [-1  0  0]  [ 0 -1  0]  [-1  0  0]  [ 0  0 -1]  [ 0 -1  0]
     284        [-1  0  0], [ 0 -1  0], [-1  0  0], [ 0  0 -1], [ 0 -1  0], [ 0  0 -1]
     285        ]
     286
     287    TESTS::
     288
     289        sage: orthogonal_transformation(1)
     290        [[1]]
     291        sage: orthogonal_transformation(0)
     292        Traceback (most recent call last):
     293        ...
     294        ValueError: ['B', 0] is not a valid cartan type
     295    """
     296    if orientation_preserving:
     297        return [w.matrix() for w in WeylGroup(['B', n]) if w.matrix().det() == 1]
     298    else:
     299        return [w.matrix() for w in WeylGroup(['B', n])]
     300
     301##############################
     302# Class Polyomino
     303##############################
     304class Polyomino(SageObject):
     305    r"""
     306    Return the polyomino defined by a set of coordinates.
     307
     308    The polyomino is the union of the unit square (or cube, or n-cube)
     309    centered at those coordinates. Such an object should be connected, but
     310    the code do not make this assumption.
     311
     312    INPUT:
     313
     314    - ``coords`` - iterable of tuple
     315    - ``color`` - string (optional, default: ``'gray'``), the color
     316
     317    EXAMPLES::
     318       
     319        sage: from sage.combinat.tiling import Polyomino
     320        sage: Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     321        Polyomino: [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)], Color: blue
     322    """
     323    def __init__(self, coords, color='gray'):
     324        r"""
     325        INPUT:
     326
     327        - ``coords`` - iterable of tuple
     328        - ``color`` - string (optional, default: ``'gray'``), the color
     329
     330        EXAMPLES::
     331           
     332            sage: from sage.combinat.tiling import Polyomino
     333            sage: Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     334            Polyomino: [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)], Color: blue
     335
     336        ::
     337
     338            sage: from sage.combinat.tiling import Polyomino
     339            sage: Polyomino([(0,0), (1,0), (2,0)])
     340            Polyomino: [(0, 0), (1, 0), (2, 0)], Color: gray
     341        """
     342        assert isinstance(color, str)
     343        self._color = color
     344        self._blocs = frozenset(tuple(c) for c in coords)
     345        assert len(self._blocs) != 0, "Polyomino must be non empty"
     346        dimension_set = set(len(a) for a in self._blocs)
     347        assert len(dimension_set) <= 1, "coord must be all of the same dimension"
     348        self._dimension = dimension_set.pop()
     349
     350    def __repr__(self):
     351        r"""
     352        String representation.
     353
     354        EXAMPLES::
     355
     356            sage: from sage.combinat.tiling import Polyomino
     357            sage: Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='red')
     358            Polyomino: [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)], Color: red
     359        """
     360        s = "Polyomino: %s, " % sorted(self._blocs)
     361        s += "Color: %s" % self._color
     362        return s
     363
     364    def __hash__(self):
     365        r"""
     366        EXAMPLES::
     367
     368            sage: from sage.combinat.tiling import Polyomino
     369            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     370            sage: hash(p)     # random
     371            2059134902
     372        """
     373        return hash(self._blocs)
     374
     375    def __eq__(self, other):
     376        r"""
     377        Return whether self is equal to other.
     378
     379        INPUT:
     380
     381        - ``other`` - a polyomino
     382
     383        OUTPUT:
     384
     385            boolean
     386
     387        EXAMPLES::
     388
     389            sage: from sage.combinat.tiling import Polyomino
     390            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     391            sage: q = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='red')
     392            sage: p == q
     393            True
     394            sage: r = Polyomino([(0,0,0), (0,1,0), (1,1,0)], color='blue')
     395            sage: p == r
     396            False
     397        """
     398        return self._blocs == other._blocs
     399
     400    def __ne__(self, other):
     401        r"""
     402        Return whether self is not equal to other.
     403
     404        INPUT:
     405
     406        - ``other`` - a polyomino
     407
     408        OUTPUT:
     409
     410            boolean
     411
     412        EXAMPLES::
     413
     414            sage: from sage.combinat.tiling import Polyomino
     415            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     416            sage: q = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='red')
     417            sage: p != q
     418            False
     419            sage: r = Polyomino([(0,0,0), (0,1,0), (1,1,0)], color='blue')
     420            sage: p != r
     421            True
     422        """
     423        return self._blocs != other._blocs
     424
     425    def __iter__(self):
     426        r"""
     427        EXAMPLES::
     428
     429            sage: from sage.combinat.tiling import Polyomino
     430            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     431            sage: it = iter(p)
     432            sage: it.next()
     433            (1, 1, 0)
     434        """
     435        return iter(self._blocs)
     436
     437    def color(self):
     438        r"""
     439        Return the color of the polyomino.
     440
     441        EXAMPLES::
     442
     443            sage: from sage.combinat.tiling import Polyomino
     444            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     445            sage: p.color()
     446            'blue'
     447        """
     448        return self._color
     449
     450    def __len__(self):
     451        r"""
     452        Return the size of the polyomino, i.e. the number of n-dimensional
     453        unit cubes.
     454
     455        EXAMPLES::
     456
     457            sage: from sage.combinat.tiling import Polyomino
     458            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     459            sage: len(p)
     460            4
     461        """
     462        return len(self._blocs)
     463
     464    def orthogonals(self, orientation_preserving=True):
     465        r"""
     466        Iterator over the images of self under orthogonal transformations.
     467
     468        .. NOTE::
     469
     470            No guarantee of unicity.
     471
     472        INPUT:
     473
     474        - ``orientation_preserving`` - bool (optional, default: ``True``),
     475          whether the orientation is preserved
     476
     477        OUTPUT:
     478
     479            iterator of Polyomino
     480
     481        EXAMPLES::
     482
     483            sage: from sage.combinat.tiling import Polyomino
     484            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     485            sage: L = list(p.orthogonals())
     486            sage: len(L)
     487            24
     488            sage: L = list(p.orthogonals(False))
     489            sage: len(L)
     490            48
     491        """
     492        return (m * self for m in orthogonal_transformation(self._dimension, orientation_preserving))
     493
     494    def canonical_orthogonals(self, orientation_preserving=True):
     495        r"""
     496        Iterator over the image of self under orthogonal transformations
     497        where the coordinates are all positive and minimal.
     498
     499        .. NOTE::
     500
     501            No guarentee of unicity.
     502
     503        INPUT:
     504
     505        - ``orientation_preserving`` - bool (optional, default: ``True``),
     506          whether the orientation is preserved
     507
     508        OUTPUT:
     509
     510            iterator of Polyomino
     511
     512        EXAMPLES::
     513
     514            sage: from sage.combinat.tiling import Polyomino
     515            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     516            sage: L = list(p.canonical_orthogonals())
     517            sage: len(L)
     518            24
     519
     520        They might not be all different::
     521
     522            sage: s = set(p.canonical_orthogonals())
     523            sage: len(s)
     524            12
     525
     526        With the non orientation-preserving::
     527
     528            sage: s = set(p.canonical_orthogonals(False))
     529            sage: len(s)
     530            24
     531        """
     532        for q in self.orthogonals(orientation_preserving):
     533            yield q.canonical()
     534
     535    def canonical(self):
     536        r"""
     537        Returns the translated copy of self having minimal and positive
     538        coordinates
     539
     540        EXAMPLES::
     541
     542            sage: from sage.combinat.tiling import Polyomino
     543            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     544            sage: p
     545            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     546            sage: p.canonical()
     547            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     548
     549        TESTS::
     550           
     551            sage: p
     552            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     553            sage: p + (3,4,5)
     554            Polyomino: [(3, 4, 5), (4, 4, 5), (4, 5, 5), (4, 5, 6), (4, 6, 5)], Color: deeppink
     555            sage: (p + (3,4,5)).canonical()
     556            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     557        """
     558        minxyz,maxxyz = self.bounding_box()
     559        return self - minxyz
     560
     561    def __sub__(self, v):
     562        r"""
     563        Return a translated copy of self by the opposite of the
     564        vector v.
     565
     566        INPUT:
     567
     568        - ``v`` - tuple
     569
     570        OUPUT:
     571
     572            polyomino
     573
     574        EXAMPLES::
     575
     576            sage: from sage.combinat.tiling import Polyomino
     577            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     578            sage: p - (2,2,2)
     579            Polyomino: [(-2, -2, -2), (-1, -2, -2), (-1, -1, -2), (-1, -1, -1), (-1, 0, -2)], Color: deeppink
     580        """
     581        if not len(v) == self._dimension:
     582            raise ValueError, "Dimension of input vector must match the dimension of the polyomino"
     583        v = vector(v)
     584        return Polyomino([vector(p)-v for p in self], color=self._color)
     585
     586    def __add__(self, v):
     587        r"""
     588        Return a translated copy of self by the vector v.
     589
     590        INPUT:
     591
     592        - ``v`` - tuple
     593
     594        OUPUT:
     595
     596            polyomino
     597
     598        EXAMPLES::
     599
     600            sage: from sage.combinat.tiling import Polyomino
     601            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     602            sage: p + (2,2,2)
     603            Polyomino: [(2, 2, 2), (3, 2, 2), (3, 3, 2), (3, 3, 3), (3, 4, 2)], Color: deeppink
     604        """
     605        if not len(v) == self._dimension:
     606            raise ValueError, "Dimension of input vector must match the dimension of the polyomino"
     607        v = vector(v)
     608        return Polyomino([vector(p)+v for p in self], color=self._color)
     609
     610    def __rmul__(self, m):
     611        r"""
     612        Return the image of the polyomino under the application of the
     613        matrix m.
     614
     615        INPUT:
     616
     617        - ``m`` - square matrix, matching the dimension of self.
     618
     619        OUPUT:
     620
     621            Polyomino
     622
     623        EXAMPLES::
     624
     625            sage: from sage.combinat.tiling import Polyomino
     626            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     627            sage: m = matrix(3, [1,0,0,0,1,0,0,0,1])
     628            sage: m * p
     629            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     630            sage: m = matrix(3, [1,0,0,0,0,-1,0,1,0])
     631            sage: m * p
     632            Polyomino: [(0, 0, 0), (1, -1, 1), (1, 0, 0), (1, 0, 1), (1, 0, 2)], Color: deeppink
     633
     634        TESTS::
     635
     636            sage: m = matrix(2, [1,0,0,1])
     637            sage: m * p
     638            Traceback (most recent call last):
     639            ...
     640            ValueError: Dimension of input matrix must match the dimension of the polyomino
     641        """
     642        if not m.nrows() == m.ncols() == self._dimension:
     643            raise ValueError, "Dimension of input matrix must match the dimension of the polyomino"
     644        return Polyomino([m * vector(p) for p in self], color=self._color)
     645
     646    def bounding_box(self):
     647        r"""
     648        EXAMPLES::
     649
     650            sage: from sage.combinat.tiling import Polyomino
     651            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     652            sage: p.bounding_box()
     653            [[0, 0, 0], [1, 2, 1]]
     654        """
     655        zipped_coords = zip(*self)
     656        return [map(min, zipped_coords), map(max, zipped_coords)]
     657
     658    def translated(self, box):
     659        r"""
     660        Returns an iterator over the translated images of self inside a
     661        box.
     662
     663        INPUT:
     664
     665        - ``box`` - tuple, size of the box
     666
     667        OUTPUT:
     668           
     669            iterator of 3D polyominoes
     670
     671        EXAMPLES::
     672           
     673            sage: from sage.combinat.tiling import Polyomino
     674            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     675            sage: for t in p.translated(box=(5,8,2)): t
     676            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     677            Polyomino: [(0, 1, 0), (1, 1, 0), (1, 2, 0), (1, 2, 1), (1, 3, 0)], Color: deeppink
     678            Polyomino: [(0, 2, 0), (1, 2, 0), (1, 3, 0), (1, 3, 1), (1, 4, 0)], Color: deeppink
     679            Polyomino: [(0, 3, 0), (1, 3, 0), (1, 4, 0), (1, 4, 1), (1, 5, 0)], Color: deeppink
     680            Polyomino: [(0, 4, 0), (1, 4, 0), (1, 5, 0), (1, 5, 1), (1, 6, 0)], Color: deeppink
     681            Polyomino: [(0, 5, 0), (1, 5, 0), (1, 6, 0), (1, 6, 1), (1, 7, 0)], Color: deeppink
     682            Polyomino: [(1, 0, 0), (2, 0, 0), (2, 1, 0), (2, 1, 1), (2, 2, 0)], Color: deeppink
     683            Polyomino: [(1, 1, 0), (2, 1, 0), (2, 2, 0), (2, 2, 1), (2, 3, 0)], Color: deeppink
     684            Polyomino: [(1, 2, 0), (2, 2, 0), (2, 3, 0), (2, 3, 1), (2, 4, 0)], Color: deeppink
     685            Polyomino: [(1, 3, 0), (2, 3, 0), (2, 4, 0), (2, 4, 1), (2, 5, 0)], Color: deeppink
     686            Polyomino: [(1, 4, 0), (2, 4, 0), (2, 5, 0), (2, 5, 1), (2, 6, 0)], Color: deeppink
     687            Polyomino: [(1, 5, 0), (2, 5, 0), (2, 6, 0), (2, 6, 1), (2, 7, 0)], Color: deeppink
     688            Polyomino: [(2, 0, 0), (3, 0, 0), (3, 1, 0), (3, 1, 1), (3, 2, 0)], Color: deeppink
     689            Polyomino: [(2, 1, 0), (3, 1, 0), (3, 2, 0), (3, 2, 1), (3, 3, 0)], Color: deeppink
     690            Polyomino: [(2, 2, 0), (3, 2, 0), (3, 3, 0), (3, 3, 1), (3, 4, 0)], Color: deeppink
     691            Polyomino: [(2, 3, 0), (3, 3, 0), (3, 4, 0), (3, 4, 1), (3, 5, 0)], Color: deeppink
     692            Polyomino: [(2, 4, 0), (3, 4, 0), (3, 5, 0), (3, 5, 1), (3, 6, 0)], Color: deeppink
     693            Polyomino: [(2, 5, 0), (3, 5, 0), (3, 6, 0), (3, 6, 1), (3, 7, 0)], Color: deeppink
     694            Polyomino: [(3, 0, 0), (4, 0, 0), (4, 1, 0), (4, 1, 1), (4, 2, 0)], Color: deeppink
     695            Polyomino: [(3, 1, 0), (4, 1, 0), (4, 2, 0), (4, 2, 1), (4, 3, 0)], Color: deeppink
     696            Polyomino: [(3, 2, 0), (4, 2, 0), (4, 3, 0), (4, 3, 1), (4, 4, 0)], Color: deeppink
     697            Polyomino: [(3, 3, 0), (4, 3, 0), (4, 4, 0), (4, 4, 1), (4, 5, 0)], Color: deeppink
     698            Polyomino: [(3, 4, 0), (4, 4, 0), (4, 5, 0), (4, 5, 1), (4, 6, 0)], Color: deeppink
     699            Polyomino: [(3, 5, 0), (4, 5, 0), (4, 6, 0), (4, 6, 1), (4, 7, 0)], Color: deeppink
     700
     701        This method is independant of the translation of the polyomino::
     702
     703            sage: q = Polyomino([(0,0,0), (1,0,0)])
     704            sage: list(q.translated((2,2,1)))
     705            [Polyomino: [(0, 0, 0), (1, 0, 0)], Color: gray, Polyomino: [(0, 1, 0), (1, 1, 0)], Color: gray]
     706            sage: q = Polyomino([(34,7,-9), (35,7,-9)])
     707            sage: list(q.translated((2,2,1)))
     708            [Polyomino: [(0, 0, 0), (1, 0, 0)], Color: gray, Polyomino: [(0, 1, 0), (1, 1, 0)], Color: gray]
     709
     710        Inside smaller boxes::
     711           
     712            sage: list(p.translated(box=(2,2,3)))
     713            []
     714            sage: list(p.translated(box=(2,3,2)))
     715            [Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink]
     716            sage: list(p.translated(box=(3,2,2)))
     717            []
     718            sage: list(p.translated(box=(1,1,1)))
     719            []
     720            sage: list(p.translated(box=(1,1,-1)))
     721            []
     722        """
     723        if not len(box) == self._dimension:
     724            raise ValueError, "Dimension of input box must match the dimension of the polyomino"
     725        minxyz,maxxyz = map(vector, self.bounding_box())
     726        size = maxxyz - minxyz
     727        cano = self.canonical()
     728        for v in xmrange(vector(box) - vector(size), tuple):
     729            yield cano + v
     730
     731    def translated_orthogonals(self, box, orientation_preserving=True):
     732        r"""
     733        Return the translated and rotated of self that lies in the box.
     734
     735        INPUT:
     736
     737        - ``box`` - tuple of size three, size of the box
     738        - ``orientation_preserving`` - bool (optional, default: ``True``),
     739          whether the orientation is preserved
     740
     741        EXAMPLES::
     742
     743            sage: from sage.combinat.tiling import Polyomino
     744            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     745            sage: L = list(p.translated_orthogonals(box=(5,8,2)))
     746            sage: len(L)
     747            360
     748
     749        ::
     750
     751            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,2,0),(1,2,1)], color='orange')
     752            sage: L = list(p.translated_orthogonals(box=(5,8,2)))
     753            sage: len(L)
     754            180
     755
     756        ::
     757
     758            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,2,0),(1,2,1)], color='orange')
     759            sage: L = list(p.translated_orthogonals((5,8,2), False))
     760            sage: len(L)
     761            360
     762        """
     763        if not len(box) == self._dimension:
     764            raise ValueError, "Dimension of input box must match the dimension of the polyomino"
     765        all_distinct_cano = set(self.canonical_orthogonals(orientation_preserving))
     766        for cano in all_distinct_cano:
     767            for t in cano.translated(box=box):
     768                yield t
     769
     770
     771    def neighbor_edges(self):
     772        r"""
     773        Return an iterator over the pairs of neighbor coordinates of the
     774        polyomino.
     775
     776        Two points `P` and `Q` are neighbor if `P - Q` has one coordinate
     777        equal to `+1` or `-1` and zero everywhere else.
     778
     779        EXAMPLES::
     780
     781            sage: from sage.combinat.tiling import Polyomino
     782            sage: p = Polyomino([(0,0,0),(0,0,1)])
     783            sage: list(sorted(edge) for edge in p.neighbor_edges())
     784            [[(0, 0, 0), (0, 0, 1)]]
     785
     786        In 3d::
     787
     788            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     789            sage: L = sorted(sorted(edge) for edge in p.neighbor_edges())
     790            sage: for a in L: a
     791            [(0, 0, 0), (1, 0, 0)]
     792            [(1, 0, 0), (1, 1, 0)]
     793            [(1, 1, 0), (1, 1, 1)]
     794            [(1, 1, 0), (1, 2, 0)]
     795
     796        In 2d::
     797
     798            sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)])
     799            sage: L = sorted(sorted(edge) for edge in p.neighbor_edges())
     800            sage: for a in L: a
     801            [(0, 0), (1, 0)]
     802            [(1, 0), (1, 1)]
     803            [(1, 1), (1, 2)]
     804        """
     805        for P, Q in itertools.combinations(self, 2):
     806            P, Q = vector(P), vector(Q)
     807            s = sorted(map(abs, Q-P))
     808            firsts = s[:-1]
     809            last = s[-1]
     810            if last == 1 and all(f == 0 for f in firsts):
     811                yield P, Q
     812
     813    def center(self):
     814        r"""
     815        Return the center of the polyomino.
     816
     817        EXAMPLES::
     818
     819            sage: from sage.combinat.tiling import Polyomino
     820            sage: p = Polyomino([(0,0,0),(0,0,1)])
     821            sage: p.center()
     822            (0, 0, 1/2)
     823
     824        In 3d::
     825
     826            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     827            sage: p.center()
     828            (4/5, 4/5, 1/5)
     829
     830        In 2d::
     831
     832            sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)])
     833            sage: p.center()
     834            (3/4, 3/4)
     835        """
     836        return sum(vector(t) for t in self) / len(self)
     837
     838    def boundary(self):
     839        r"""
     840        Return the boundary of a 2D polyomino.
     841
     842        INPUT:
     843
     844        - ``self`` - a 2D polyomino
     845
     846        OUTPUT:
     847
     848        - list of edges (an edge is a pair of adjacent 2D coordinates)
     849
     850        EXAMPLES::
     851
     852            sage: from sage.combinat.tiling import Polyomino
     853            sage: p = Polyomino([(0,0), (1,0), (0,1), (1,1)])
     854            sage: p.boundary()
     855            [((0.5, 1.5), (1.5, 1.5)), ((-0.5, -0.5), (0.5, -0.5)), ((0.5, -0.5), (1.5, -0.5)), ((-0.5, 1.5), (0.5, 1.5)), ((-0.5, 0.5), (-0.5, 1.5)), ((-0.5, -0.5), (-0.5, 0.5)), ((1.5, 0.5), (1.5, 1.5)), ((1.5, -0.5), (1.5, 0.5))]
     856            sage: len(_)
     857            8
     858            sage: p = Polyomino([(5,5)])
     859            sage: p.boundary()
     860            [((4.5, 5.5), (5.5, 5.5)), ((4.5, 4.5), (5.5, 4.5)), ((4.5, 4.5), (4.5, 5.5)), ((5.5, 4.5), (5.5, 5.5))]
     861        """
     862        if self._dimension != 2:
     863            raise NotImplementedError("The method boundary is currently implemented "
     864                                      "only for dimension 2")
     865        from collections import defaultdict
     866        horizontal = defaultdict(int)
     867        vertical = defaultdict(int)
     868        for a in self:
     869            x,y = a = tuple(a)
     870            horizontal[a] += 1
     871            vertical[a] += 1
     872            horizontal[(x,y+1)] -= 1
     873            vertical[(x+1,y)] -= 1
     874        edges = []
     875        h = 0.5
     876        for (x,y), coeff in horizontal.iteritems():
     877            if coeff != 0:
     878                edges.append(((x-h,y-h),(x+h,y-h)))
     879        for (x,y), coeff in vertical.iteritems():
     880            if coeff != 0:
     881                edges.append(((x-h,y-h),(x-h,y+h)))
     882        return edges
     883
     884
     885    def show3d(self, size=1):
     886        r"""
     887        Returns a 3d Graphic object representing the polyomino.
     888
     889        INPUT:
     890
     891        - ``self`` - a polyomino of dimension 3
     892        - ``size`` - number (optional, default: ``1``), the size of each
     893          ``1 \times 1 \times 1`` cube. This does a homothety with respect
     894          to the center of the polyomino.
     895
     896        EXAMPLES::
     897
     898            sage: from sage.combinat.tiling import Polyomino
     899            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     900            sage: p.show3d()
     901        """
     902        assert self._dimension == 3, "Dimension of the polyomino must be 3."
     903        G = Graphics()
     904        for p in self:
     905            G += cube(p, color=self._color)
     906        center = self.center()
     907        G = G.translate(-center)
     908        G = G.scale(size)
     909        G = G.translate(center)
     910        return G
     911
     912    def show2d(self, size=0.7, color='black', thickness=1):
     913        r"""
     914        Returns a 2d Graphic object representing the polyomino.
     915
     916        INPUT:
     917
     918        - ``self`` - a polyomino of dimension 2
     919        - ``size`` - number (optional, default: ``0.7``), the size of each
     920          square.
     921        - ``color`` - color (optional, default: ``'black'``), color of
     922          the boundary line.
     923        - ``thickness`` - number (optional, default: ``1``), how thick the
     924          boundary line is.
     925
     926        EXAMPLES::
     927
     928            sage: from sage.combinat.tiling import Polyomino
     929            sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)], color='deeppink')
     930            sage: p.show2d()              # long time (0.5s)
     931        """
     932        assert self._dimension == 2, "Dimension of the polyomino must be 2."
     933        h = size / 2.0
     934        G = Graphics()
     935        for a,b in self:
     936            G += circle((a,b), h, fill=True, color=self._color)
     937        k = h / 2.0
     938        for P,Q in self.neighbor_edges():
     939            a,b = (P + Q) / 2.0
     940            G += polygon([(a-k,b-k), (a+k,b-k), (a+k,b+k), (a-k,b+k), (a-k,b-k)], color=self._color)
     941        for edge in self.boundary():
     942            G += line(edge, color=color, thickness=thickness)
     943        return G
     944
     945#######################
     946# General tiling solver
     947#######################
     948class TilingSolver(SageObject):
     949    r"""
     950    Tiling solver
     951
     952    Solve the problem of tiling a rectangular box with a certain number
     953    of pieces, called polyominoes, where each polyomino must be used
     954    exactly once.
     955
     956    INPUT:
     957
     958    - ``pieces`` - iterable of Polyominoes
     959    - ``box`` - tuple, size of the box
     960    - ``rotation`` - bool (optional, default: ``True``), whether to allow
     961      rotations
     962    - ``reflection`` - bool (optional, default: ``False``), whether to allow
     963      reflections
     964    - ``reusable`` - bool (optional, default: ``False``), whether to allow
     965      the pieces to be reused
     966
     967    EXAMPLES:
     968
     969    By default, rotations are allowed and reflections are not allowed::
     970
     971        sage: from sage.combinat.tiling import TilingSolver, Polyomino
     972        sage: p = Polyomino([(0,0,0)])
     973        sage: q = Polyomino([(0,0,0), (0,0,1)])
     974        sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     975        sage: T = TilingSolver([p,q,r], box=(1,1,6))
     976        sage: T
     977        Tiling solver of 3 pieces into the box (1, 1, 6)
     978        Rotation allowed: True
     979        Reflection allowed: False
     980        Reusing pieces allowed: False
     981
     982    Solutions are given by an iterator::
     983
     984        sage: it = T.solve()
     985        sage: for p in it.next(): p
     986        Polyomino: [(0, 0, 0)], Color: gray
     987        Polyomino: [(0, 0, 1), (0, 0, 2)], Color: gray
     988        Polyomino: [(0, 0, 3), (0, 0, 4), (0, 0, 5)], Color: gray
     989
     990    Another solution::
     991
     992        sage: for p in it.next(): p
     993        Polyomino: [(0, 0, 0)], Color: gray
     994        Polyomino: [(0, 0, 1), (0, 0, 2), (0, 0, 3)], Color: gray
     995        Polyomino: [(0, 0, 4), (0, 0, 5)], Color: gray
     996
     997    TESTS::
     998
     999        sage: T = TilingSolver([p,q,r], box=(1,1,6), rotation=False, reflection=True)
     1000        Traceback (most recent call last):
     1001        ...
     1002        NotImplementedError: When reflection is allowed and rotation is not allowed
     1003    """
     1004    def __init__(self, pieces, box, rotation=True, reflection=False, reusable=False):
     1005        r"""
     1006        Constructor.
     1007
     1008        EXAMPLES::
     1009
     1010            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1011            sage: p = Polyomino([(0,0,0)])
     1012            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1013            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1014            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1015            sage: T
     1016            Tiling solver of 3 pieces into the box (1, 1, 6)
     1017            Rotation allowed: True
     1018            Reflection allowed: False
     1019            Reusing pieces allowed: False
     1020        """
     1021        self._pieces = pieces
     1022        self._box = box
     1023        self._rotation = rotation
     1024        self._reflection = reflection
     1025        if not self._rotation and self._reflection:
     1026            raise NotImplementedError, "When reflection is allowed and rotation is not allowed"
     1027        self._reusable = reusable
     1028        self._starting_rows = None    # the starting row of each piece
     1029
     1030    def __repr__(self):
     1031        r"""
     1032        String representation
     1033
     1034        EXAMPLES::
     1035
     1036            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1037            sage: p = Polyomino([(0,0,0)])
     1038            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1039            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1040            sage: TilingSolver([p,q,r], box=(1,1,6))
     1041            Tiling solver of 3 pieces into the box (1, 1, 6)
     1042            Rotation allowed: True
     1043            Reflection allowed: False
     1044            Reusing pieces allowed: False
     1045
     1046        """
     1047        N = len(self._pieces)
     1048        s = "Tiling solver of %s pieces into the box %s\n" % (N, self._box)
     1049        s += "Rotation allowed: %s\n" % self._rotation
     1050        s += "Reflection allowed: %s\n" % self._reflection
     1051        s += "Reusing pieces allowed: %s" % self._reusable
     1052        return s
     1053
     1054    def is_suitable(self):
     1055        r"""
     1056        Return whether the volume of the box is equal to sum of the volume
     1057        of the polyominoes and the number of rows sent to the DLX solver is
     1058        larger than zero.
     1059
     1060        If these conditions are not verified, then the problem is not suitable
     1061        in the sense that there are no solution.
     1062
     1063        .. NOTE::
     1064
     1065            The DLX solver throws a Segmentation Fault when the
     1066            number of rows is zero::
     1067               
     1068                sage: from sage.combinat.matrices.dancing_links import dlx_solver
     1069                sage: rows = []
     1070                sage: x = dlx_solver(rows)
     1071                sage: x.search()        # not tested
     1072                BOOM !!!
     1073
     1074        EXAMPLES::
     1075
     1076            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1077            sage: p = Polyomino([(0,0,0)])
     1078            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1079            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1080            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1081            sage: T.is_suitable()
     1082            True
     1083            sage: T = TilingSolver([p,q,r], box=(1,1,7))
     1084            sage: T.is_suitable()
     1085            False
     1086        """
     1087        if self._reusable:
     1088            return len(self.rows()) != 0
     1089        else:
     1090            return (sum(len(p) for p in self.pieces()) == prod(self._box)
     1091                    and len(self.rows()) != 0)
     1092
     1093    def pieces(self):
     1094        r"""
     1095        Return the list of pieces.
     1096
     1097        OUTPUT:
     1098           
     1099            list of 3D polyominoes
     1100       
     1101        EXAMPLES::
     1102
     1103            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1104            sage: p = Polyomino([(0,0,0)])
     1105            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1106            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1107            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1108            sage: for p in T._pieces: p
     1109            Polyomino: [(0, 0, 0)], Color: gray
     1110            Polyomino: [(0, 0, 0), (0, 0, 1)], Color: gray
     1111            Polyomino: [(0, 0, 0), (0, 0, 1), (0, 0, 2)], Color: gray
     1112        """
     1113        return self._pieces
     1114
     1115    def space(self):
     1116        r"""
     1117        Returns an iterator over all the non negative integer coordinates
     1118        contained in the box.
     1119
     1120        EXAMPLES::
     1121
     1122            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1123            sage: p = Polyomino([(0,0,0)])
     1124            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1125            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1126            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1127            sage: list(T.space())
     1128            [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 0, 4), (0, 0, 5)]
     1129        """
     1130        return xmrange(self._box, tuple)
     1131
     1132    @cached_method
     1133    def coord_to_int_dict(self):
     1134        r"""
     1135        Returns a dictionary mapping coordinates to integers.
     1136
     1137        OUTPUT:
     1138
     1139            dict
     1140
     1141        EXAMPLES::
     1142           
     1143            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1144            sage: p = Polyomino([(0,0,0)])
     1145            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1146            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1147            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1148            sage: A = T.coord_to_int_dict()
     1149            sage: sorted(A.iteritems())
     1150            [((0, 0, 0), 3), ((0, 0, 1), 4), ((0, 0, 2), 5), ((0, 0, 3), 6), ((0, 0, 4), 7), ((0, 0, 5), 8)]
     1151
     1152        Reusable pieces::
     1153
     1154            sage: p = Polyomino([(0,0), (0,1)])
     1155            sage: q = Polyomino([(0,0), (0,1), (1,0), (1,1)])
     1156            sage: T = TilingSolver([p,q], box=[3,2], reusable=True)
     1157            sage: B = T.coord_to_int_dict()
     1158            sage: sorted(B.iteritems())
     1159            [((0, 0), 0), ((0, 1), 1), ((1, 0), 2), ((1, 1), 3), ((2, 0), 4), ((2, 1), 5)]
     1160        """
     1161        if self._reusable:
     1162            return dict( (c,i) for i,c in enumerate(self.space()) )
     1163        else:
     1164            number_of_pieces = len(self._pieces)
     1165            return dict( (c,i+number_of_pieces) for i,c in enumerate(self.space()) )
     1166
     1167    @cached_method
     1168    def int_to_coord_dict(self):
     1169        r"""
     1170        Returns a dictionary mapping integers to coordinates.
     1171
     1172        EXAMPLES::
     1173           
     1174            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1175            sage: p = Polyomino([(0,0,0)])
     1176            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1177            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1178            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1179            sage: B = T.int_to_coord_dict()
     1180            sage: sorted(B.iteritems())
     1181            [(3, (0, 0, 0)), (4, (0, 0, 1)), (5, (0, 0, 2)), (6, (0, 0, 3)), (7, (0, 0, 4)), (8, (0, 0, 5))]
     1182
     1183        Reusable pieces::
     1184
     1185            sage: from sage.combinat.tiling import Polyomino, TilingSolver
     1186            sage: p = Polyomino([(0,0), (0,1)])
     1187            sage: q = Polyomino([(0,0), (0,1), (1,0), (1,1)])
     1188            sage: T = TilingSolver([p,q], box=[3,2], reusable=True)
     1189            sage: B = T.int_to_coord_dict()
     1190            sage: sorted(B.iteritems())
     1191            [(0, (0, 0)), (1, (0, 1)), (2, (1, 0)), (3, (1, 1)), (4, (2, 0)), (5, (2, 1))]
     1192
     1193        TESTS:
     1194
     1195        The methods ``int_to_coord_dict`` and ``coord_to_int_dict`` returns
     1196        dictionary that are inverse of each other::
     1197
     1198            sage: A = T.coord_to_int_dict()
     1199            sage: B = T.int_to_coord_dict()
     1200            sage: all(A[B[i]] == i for i in B)
     1201            True
     1202            sage: all(B[A[i]] == i for i in A)
     1203            True
     1204
     1205        """
     1206        if self._reusable:
     1207            return dict( (i,c) for i,c in enumerate(self.space()) )
     1208        else:
     1209            number_of_pieces = len(self._pieces)
     1210            return dict( (i+number_of_pieces,c) for i,c in enumerate(self.space()) )
     1211
     1212    @cached_method
     1213    def rows(self):
     1214        r"""
     1215        Creation of the rows
     1216
     1217        EXAMPLES::
     1218           
     1219            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1220            sage: p = Polyomino([(0,0,0)])
     1221            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1222            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1223            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1224            sage: rows = T.rows()
     1225            sage: for row in rows: row
     1226            [0, 3]
     1227            [0, 4]
     1228            [0, 5]
     1229            [0, 6]
     1230            [0, 7]
     1231            [0, 8]
     1232            [1, 3, 4]
     1233            [1, 4, 5]
     1234            [1, 5, 6]
     1235            [1, 6, 7]
     1236            [1, 8, 7]
     1237            [2, 3, 4, 5]
     1238            [2, 4, 5, 6]
     1239            [2, 5, 6, 7]
     1240            [2, 8, 6, 7]
     1241        """
     1242        coord_to_int = self.coord_to_int_dict()
     1243        rows = []
     1244        self._starting_rows = [] # indices of the first row for each piece
     1245        for i,p in enumerate(self._pieces):
     1246            self._starting_rows.append(len(rows))
     1247            if self._rotation and self._reflection:
     1248                it = p.translated_orthogonals(self._box, orientation_preserving=False)
     1249            elif self._rotation and not self._reflection:
     1250                it = p.translated_orthogonals(self._box, orientation_preserving=True)
     1251            elif not self._rotation and self._reflection:
     1252                raise NotImplementedError, "Reflection allowed, Rotation not allowed is not implemented"
     1253            else:
     1254                it = p.translated(self._box)
     1255            if self._reusable:
     1256                for q in it:
     1257                    rows.append([coord_to_int[coord] for coord in q])
     1258            else:
     1259                for q in it:
     1260                    rows.append([i] + [coord_to_int[coord] for coord in q])
     1261        self._starting_rows.append(len(rows))
     1262        return rows
     1263
     1264    def nrows_per_piece(self):
     1265        r"""
     1266        Return the number of rows necessary by each piece.
     1267
     1268        OUPUT:
     1269
     1270            list
     1271
     1272        EXAMPLES::
     1273
     1274            sage: from sage.games.quantumino import QuantuminoSolver
     1275            sage: q = QuantuminoSolver(0)
     1276            sage: T = q.tiling_solver()
     1277            sage: T.nrows_per_piece()                           # long time (10s)
     1278            [360, 360, 360, 360, 360, 180, 180, 672, 672, 360, 360, 180, 180, 360, 360, 180]
     1279        """
     1280        if self._starting_rows is None:
     1281            rows = self.rows()
     1282        L = self._starting_rows
     1283        return [L[i+1] - L[i] for i in xrange(len(L)-1)]
     1284
     1285    def dlx_solver(self):
     1286        r"""
     1287        Return the sage DLX solver of that 3D tiling problem.
     1288
     1289        OUTPUT:
     1290
     1291            DLX Solver
     1292
     1293        EXAMPLES::
     1294
     1295            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1296            sage: p = Polyomino([(0,0,0)])
     1297            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1298            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1299            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1300            sage: x = T.dlx_solver()
     1301            sage: x
     1302            <sage.combinat.matrices.dancing_links.dancing_linksWrapper object at ...>
     1303        """
     1304        from sage.combinat.matrices.dancing_links import dlx_solver
     1305        rows = self.rows()
     1306        assert len(rows) != 0, "Number of rows given to the DLX solver must not be zero"
     1307        x = dlx_solver(rows)
     1308        return x
     1309
     1310    def dlx_solutions(self):
     1311        r"""
     1312        Return an iterator over the row indices of the solutions.
     1313
     1314        OUPUT:
     1315
     1316            iterator
     1317
     1318        EXAMPLES::
     1319
     1320            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1321            sage: p = Polyomino([(0,0,0)])
     1322            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1323            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1324            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1325            sage: list(T.dlx_solutions())
     1326            [[0, 7, 14], [0, 12, 10], [6, 13, 5], [6, 14, 2], [11, 9, 5], [11, 10, 3]]
     1327        """
     1328        if len(self.rows()) == 0:
     1329            raise StopIteration
     1330        x = self.dlx_solver()
     1331        while x.search() == 1:
     1332            yield x.get_solution()
     1333
     1334    def dlx_common_prefix_solutions(self):
     1335        r"""
     1336        Return an iterator over the row indices of solutions and of partial
     1337        solutions, i.e. the common prefix of two consecutive solutions.
     1338
     1339        The purpose is to illustrate the backtracking and construct an
     1340        animation of the evolution of solutions.
     1341
     1342        OUPUT:
     1343
     1344            iterator
     1345
     1346        EXAMPLES::
     1347
     1348            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1349            sage: p = Polyomino([(0,0,0)])
     1350            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1351            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1352            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1353            sage: list(T.dlx_solutions())
     1354            [[0, 7, 14], [0, 12, 10], [6, 13, 5], [6, 14, 2], [11, 9, 5], [11, 10, 3]]
     1355            sage: list(T.dlx_common_prefix_solutions())
     1356            [[0, 7, 14], [0], [0, 12, 10], [], [6, 13, 5], [6], [6, 14, 2], [], [11, 9, 5], [11], [11, 10, 3]]
     1357
     1358        ::
     1359
     1360            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1361            sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='yellow')
     1362            sage: T = TilingSolver([y], box=(5,10), reusable=True, reflection=True)
     1363            sage: for a in T.dlx_common_prefix_solutions(): a
     1364            [18, 119, 68, 33, 48, 23, 8, 73, 58, 43]
     1365            [18, 119, 68, 33, 48]
     1366            [18, 119, 68, 33, 48, 133, 8, 73, 168, 43]
     1367            [18, 119]
     1368            [18, 119, 178, 143, 48, 23, 8, 73, 58, 43]
     1369            [18, 119, 178, 143, 48]
     1370            [18, 119, 178, 143, 48, 133, 8, 73, 168, 43]
     1371            [18, 119, 178]
     1372            [18, 119, 178, 164, 152, 73, 8, 133, 159, 147]
     1373            []
     1374            [74, 19, 177, 33, 49, 134, 109, 72, 58, 42]
     1375            [74, 19, 177]
     1376            [74, 19, 177, 54, 151, 72, 109, 134, 160, 37]
     1377            [74, 19, 177, 54, 151]
     1378            [74, 19, 177, 54, 151, 182, 109, 134, 160, 147]
     1379            [74]
     1380            [74, 129, 177, 164, 151, 72, 109, 134, 160, 37]
     1381            [74, 129, 177, 164, 151]
     1382            [74, 129, 177, 164, 151, 182, 109, 134, 160, 147]
     1383        """
     1384        it = self.dlx_solutions()
     1385        B = it.next()
     1386        while True:
     1387            yield B
     1388            A, B = B, it.next()
     1389            common_prefix = []
     1390            for a,b in itertools.izip(A,B):
     1391                if a == b:
     1392                    common_prefix.append(a)
     1393                else:
     1394                    break
     1395            yield common_prefix
     1396
     1397    def dlx_incremental_solutions(self):
     1398        r"""
     1399        Return an iterator over the row indices of the incremental
     1400        solutions.
     1401       
     1402        Between two incremental solution, either one piece is added or one
     1403        piece is removed.
     1404       
     1405        The purpose is to illustrate the backtracking and construct an
     1406        animation of the evolution of solutions.
     1407
     1408        OUPUT:
     1409
     1410            iterator
     1411
     1412        EXAMPLES::
     1413
     1414            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1415            sage: p = Polyomino([(0,0,0)])
     1416            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1417            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1418            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1419            sage: list(T.dlx_solutions())
     1420            [[0, 7, 14], [0, 12, 10], [6, 13, 5], [6, 14, 2], [11, 9, 5], [11, 10, 3]]
     1421            sage: list(T.dlx_incremental_solutions())
     1422            [[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]]
     1423
     1424        ::
     1425
     1426            sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='yellow')
     1427            sage: T = TilingSolver([y], box=(5,10), reusable=True, reflection=True)
     1428            sage: for a in T.dlx_solutions(): a
     1429            [18, 119, 68, 33, 48, 23, 8, 73, 58, 43]
     1430            [18, 119, 68, 33, 48, 133, 8, 73, 168, 43]
     1431            [18, 119, 178, 143, 48, 23, 8, 73, 58, 43]
     1432            [18, 119, 178, 143, 48, 133, 8, 73, 168, 43]
     1433            [18, 119, 178, 164, 152, 73, 8, 133, 159, 147]
     1434            [74, 19, 177, 33, 49, 134, 109, 72, 58, 42]
     1435            [74, 19, 177, 54, 151, 72, 109, 134, 160, 37]
     1436            [74, 19, 177, 54, 151, 182, 109, 134, 160, 147]
     1437            [74, 129, 177, 164, 151, 72, 109, 134, 160, 37]
     1438            [74, 129, 177, 164, 151, 182, 109, 134, 160, 147]
     1439            sage: len(list(T.dlx_incremental_solutions()))
     1440            123
     1441        """
     1442        it = self.dlx_solutions()
     1443        B = it.next()
     1444        while True:
     1445            yield B
     1446            A, B = B, it.next()
     1447            common_prefix = 0
     1448            for a,b in itertools.izip(A,B):
     1449                if a == b:
     1450                    common_prefix += 1
     1451                else:
     1452                    break
     1453            for i in xrange(1,len(A)-common_prefix):
     1454                yield A[:-i]
     1455            for j in xrange(common_prefix, len(B)):
     1456                yield B[:j]
     1457
     1458    def solve(self, partial=None):
     1459        r"""
     1460        Returns an iterator of list of 3D polyominoes that are an exact
     1461        cover of the box.
     1462
     1463        INPUT:
     1464
     1465        - ``partial`` - string (optional, default: ``None``), whether to
     1466          include partial (incomplete) solutions. It can be one of the
     1467          following:
     1468         
     1469          - ``None`` - include only complete solution
     1470          - ``'common_prefix'`` - common prefix between two consecutive solutions
     1471          - ``'incremental'`` - one piece change at a time
     1472       
     1473        OUTPUT:
     1474
     1475            iterator of list of 3D polyominoes
     1476
     1477        EXAMPLES::
     1478
     1479            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1480            sage: p = Polyomino([(0,0,0)])
     1481            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1482            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1483            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1484            sage: it = T.solve()
     1485            sage: for p in it.next(): p
     1486            Polyomino: [(0, 0, 0)], Color: gray
     1487            Polyomino: [(0, 0, 1), (0, 0, 2)], Color: gray
     1488            Polyomino: [(0, 0, 3), (0, 0, 4), (0, 0, 5)], Color: gray
     1489            sage: for p in it.next(): p
     1490            Polyomino: [(0, 0, 0)], Color: gray
     1491            Polyomino: [(0, 0, 1), (0, 0, 2), (0, 0, 3)], Color: gray
     1492            Polyomino: [(0, 0, 4), (0, 0, 5)], Color: gray
     1493            sage: for p in it.next(): p
     1494            Polyomino: [(0, 0, 0), (0, 0, 1)], Color: gray
     1495            Polyomino: [(0, 0, 2), (0, 0, 3), (0, 0, 4)], Color: gray
     1496            Polyomino: [(0, 0, 5)], Color: gray
     1497
     1498        Including the partial solutions::
     1499
     1500            sage: it = T.solve(partial='common_prefix')
     1501            sage: for p in it.next(): p
     1502            Polyomino: [(0, 0, 0)], Color: gray
     1503            Polyomino: [(0, 0, 1), (0, 0, 2)], Color: gray
     1504            Polyomino: [(0, 0, 3), (0, 0, 4), (0, 0, 5)], Color: gray
     1505            sage: for p in it.next(): p
     1506            Polyomino: [(0, 0, 0)], Color: gray
     1507            sage: for p in it.next(): p
     1508            Polyomino: [(0, 0, 0)], Color: gray
     1509            Polyomino: [(0, 0, 1), (0, 0, 2), (0, 0, 3)], Color: gray
     1510            Polyomino: [(0, 0, 4), (0, 0, 5)], Color: gray
     1511            sage: for p in it.next(): p
     1512            sage: for p in it.next(): p
     1513            Polyomino: [(0, 0, 0), (0, 0, 1)], Color: gray
     1514            Polyomino: [(0, 0, 2), (0, 0, 3), (0, 0, 4)], Color: gray
     1515            Polyomino: [(0, 0, 5)], Color: gray
     1516
     1517        Colors are preserved when the polyomino can be reused::
     1518
     1519            sage: p = Polyomino([(0,0,0)], color='yellow')
     1520            sage: q = Polyomino([(0,0,0), (0,0,1)], color='yellow')
     1521            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)], color='yellow')
     1522            sage: T = TilingSolver([p,q,r], box=(1,1,6), reusable=True)
     1523            sage: it = T.solve()
     1524            sage: for p in it.next(): p
     1525            Polyomino: [(0, 0, 0)], Color: yellow
     1526            Polyomino: [(0, 0, 1)], Color: yellow
     1527            Polyomino: [(0, 0, 2)], Color: yellow
     1528            Polyomino: [(0, 0, 3)], Color: yellow
     1529            Polyomino: [(0, 0, 4)], Color: yellow
     1530            Polyomino: [(0, 0, 5)], Color: yellow
     1531
     1532        TESTS::
     1533
     1534            sage: T = TilingSolver([p,q,r], box=(1,1,7))
     1535            sage: T.solve().next()
     1536            Traceback (most recent call last):
     1537            ...
     1538            StopIteration
     1539
     1540        """
     1541        if not self.is_suitable():
     1542            raise StopIteration
     1543        int_to_coord = self.int_to_coord_dict()
     1544        rows = self.rows()
     1545        if partial is None:
     1546            it = self.dlx_solutions()
     1547        elif partial == 'common_prefix':
     1548            it = self.dlx_common_prefix_solutions()
     1549        elif partial == 'incremental':
     1550            it = self.dlx_incremental_solutions()
     1551        else:
     1552            raise ValueError("Unknown value for partial (=%s)" % partial)
     1553        for solution in it:
     1554            pieces = []
     1555            for row_number in solution:
     1556                row = rows[row_number]
     1557                if self._reusable:
     1558                    no = -1
     1559                    while self._starting_rows[no] < row_number:
     1560                        no += 1
     1561                    coords = [int_to_coord[i] for i in row]
     1562                    p = Polyomino(coords, color=self._pieces[no].color())
     1563                else:
     1564                    no = row[0]
     1565                    coords = [int_to_coord[i] for i in row[1:]]
     1566                    p = Polyomino(coords, color=self._pieces[no].color())
     1567                pieces.append(p)
     1568            yield pieces
     1569
     1570    def number_of_solutions(self):
     1571        r"""
     1572        Return the number of distinct solutions.
     1573
     1574        OUPUT:
     1575
     1576            integer
     1577
     1578        EXAMPLES::
     1579
     1580            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1581            sage: p = Polyomino([(0,0)])
     1582            sage: q = Polyomino([(0,0), (0,1)])
     1583            sage: r = Polyomino([(0,0), (0,1), (0,2)])
     1584            sage: T = TilingSolver([p,q,r], box=(1,6))
     1585            sage: T.number_of_solutions()
     1586            6
     1587
     1588        ::
     1589
     1590            sage: T = TilingSolver([p,q,r], box=(1,7))
     1591            sage: T.number_of_solutions()
     1592            0
     1593        """
     1594        if not self.is_suitable():
     1595            return 0
     1596        x = self.dlx_solver()
     1597        N = 0
     1598        while x.search() == 1:
     1599            N += 1
     1600        return N
     1601
     1602    def animate(self, partial=None, stop=None, size=0.75, axes=False):
     1603        r"""
     1604        Return an animation of evolving solutions.
     1605
     1606        INPUT:
     1607
     1608        - ``partial`` - string (optional, default: ``None``), whether to
     1609          include partial (incomplete) solutions. It can be one of the
     1610          following:
     1611         
     1612          - ``None`` - include only complete solutions
     1613          - ``'common_prefix'`` - common prefix between two consecutive solutions
     1614          - ``'incremental'`` - one piece change at a time
     1615
     1616        - ``stop`` - integer (optional, default:``None``), number of frames
     1617
     1618        - ``size`` - number (optional, default: ``0.75``), the size of each
     1619          ``1 \times 1`` square. This does a homothety with respect
     1620          to the center of each polyomino.
     1621
     1622        - ``axes`` - bool (optional, default:``False``), whether the x and
     1623          y axes are shown.
     1624       
     1625        EXAMPLES::
     1626
     1627            sage: from sage.combinat.tiling import Polyomino, TilingSolver
     1628            sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='cyan')
     1629            sage: T = TilingSolver([y], box=(5,10), reusable=True, reflection=True)
     1630            sage: a = T.animate()
     1631            sage: a
     1632            Animation with 10 frames
     1633
     1634        Include partial solutions (common prefix between two consecutive
     1635        solutions)::
     1636
     1637            sage: a = T.animate('common_prefix')
     1638            sage: a
     1639            Animation with 19 frames
     1640
     1641        Incremental solutions (one piece removed or added at a time)::
     1642
     1643            sage: a = T.animate('incremental')      # long time (2s)
     1644            sage: a                                 # long time (2s)
     1645            Animation with 123 frames
     1646
     1647        ::
     1648
     1649            sage: a.show()                 # optional - requires convert command
     1650
     1651        The ``show`` function takes arguments to specify the delay between
     1652        frames (measured in hundredths of a second, default value 20) and
     1653        the number of iterations (default value 0, which means to iterate
     1654        forever). To iterate 4 times with half a second between each frame::
     1655
     1656            sage: a.show(delay=50, iterations=4) # optional
     1657
     1658        Limit the number of frames::
     1659
     1660            sage: a = T.animate('incremental', stop=13)     # not tested
     1661            sage: a                                         # not tested
     1662            Animation with 13 frames
     1663        """
     1664        dimension = len(self._box)
     1665        if dimension == 2:
     1666            it = self.solve(partial=partial)
     1667            it = itertools.islice(it, stop)
     1668            L = [sum([piece.show2d(size) for piece in solution], Graphics()) for solution in it]
     1669            xmax, ymax = self._box
     1670            xmax = xmax-0.5
     1671            ymax = ymax-0.5
     1672            a = Animation(L, xmin=-0.5, ymin=-0.5, xmax=xmax, ymax=ymax, aspect_ratio=1, axes=axes)
     1673            return a
     1674        elif dimension == 3:
     1675            raise NotImplementedError("3d Animation must be implemented in Jmol first")
     1676        else:
     1677            raise NotImplementedError("Dimension must be 2 or 3 in order to make an animation")
     1678
  • new file sage/games/quantumino.py

    diff --git a/sage/games/quantumino.py b/sage/games/quantumino.py
    new file mode 100644
    - +  
     1# coding=utf-8
     2r"""
     3Family Games America's Quantumino solver
     4
     5This module allows to solve the `Quantumino puzzle
     6<http://familygamesamerica.com/mainsite/consumers/productview.php?pro_id=274&search=quantumino>`_
     7made by Family Games America (see also `this video
     8<http://www.youtube.com/watch?v=jX_VKzakZi8>`_ on Youtube). This puzzle was
     9left at the dinner room of the Laboratoire de Combinatoire Informatique
     10Mathematique in Montreal by Franco Saliola during winter 2011.
     11
     12The solution uses the dancing links code which is in Sage and is based on
     13the more general code available in the module :mod:`sage.combinat.tiling`.
     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.
     18
     19This module defines two classes :
     20
     21- :class:`sage.games.quantumino.QuantuminoState` class, to represent a
     22  state of the Quantumino game, i.e. a solution or a partial solution.
     23
     24- :class:`sage.games.quantumino.QuantuminoSolver` class, to find, enumerate
     25  and count the number of solutions of the Quantumino game where one of the
     26  piece is put aside.
     27
     28AUTHOR:
     29
     30    - Sebastien Labbe, April 28th, 2011
     31
     32DESCRIPTION (from [1]):
     33
     34    "
     35    Pentamino games have been taken to a whole different level; a 3-D
     36    level, with this colorful creation! Using the original pentamino
     37    arrangements of 5 connected squares which date from 1907, players are
     38    encouraged to "think inside the box" as they try to fit 16 of the 17
     39    3-D pentamino pieces inside the playing perimeters. Remove a different
     40    piece each time you play for an entirely new challenge! Thousands of
     41    solutions to be found!
     42    Quantumino hands-on educational tool where players learn how shapes
     43    can be transformed or arranged into predefined shapes and spaces.
     44    Includes:
     45    1 wooden frame, 17 wooden blocks, instruction booklet.
     46    Age: 8+
     47    "
     48
     49EXAMPLES:
     50
     51Here are the 17 wooden blocks of the Quantumino puzzle numbered from 0 to 16 in
     52the following 3d picture. They will show up in 3D in your default (=Jmol)
     53viewer::
     54
     55    sage: from sage.games.quantumino import show_pentaminos
     56    sage: show_pentaminos()
     57
     58To solve the puzzle where the pentamino numbered 12 is put aside::
     59
     60    sage: from sage.games.quantumino import QuantuminoSolver
     61    sage: s = QuantuminoSolver(12).solve().next()         # long time (10 s)
     62    sage: s                                               # long time (<1s)
     63    Quantumino state where the following pentamino is put aside :
     64    Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (2, 1, 1)], Color: blue
     65    sage: s.show3d()                                      # long time (<1s)
     66
     67To remove the frame::
     68
     69    sage: s.show3d().show(frame=False)                    # long time (<1s)
     70
     71To solve the puzzle where the pentamino numbered 7 is put aside::
     72
     73    sage: s = QuantuminoSolver(7).solve().next()          # long time (10 s)
     74    sage: s                                               # long time (<1s)
     75    Quantumino state where the following pentamino is put aside :
     76    Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
     77    sage: s.show3d()                                      # long time (<1s)
     78
     79The solution is iterable. This may be used to explicitly list the positions of each
     80pentamino::
     81
     82    sage: for p in s: p                                   # long time (<1s)
     83    Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     84    Polyomino: [(0, 0, 1), (0, 1, 0), (0, 1, 1), (0, 2, 1), (1, 2, 1)], Color: deeppink
     85    Polyomino: [(0, 2, 0), (0, 3, 0), (0, 4, 0), (1, 4, 0), (1, 4, 1)], Color: green
     86    Polyomino: [(0, 3, 1), (1, 3, 1), (2, 2, 0), (2, 2, 1), (2, 3, 1)], Color: green
     87    Polyomino: [(1, 3, 0), (2, 3, 0), (2, 4, 0), (2, 4, 1), (3, 4, 0)], Color: red
     88    Polyomino: [(1, 0, 1), (2, 0, 1), (2, 1, 0), (2, 1, 1), (3, 1, 1)], Color: red
     89    Polyomino: [(2, 0, 0), (3, 0, 0), (3, 0, 1), (3, 1, 0), (4, 0, 0)], Color: gray
     90    Polyomino: [(3, 2, 0), (4, 0, 1), (4, 1, 0), (4, 1, 1), (4, 2, 0)], Color: purple
     91    Polyomino: [(3, 2, 1), (3, 3, 0), (3, 3, 1), (4, 2, 1), (4, 3, 1)], Color: yellow
     92    Polyomino: [(3, 4, 1), (3, 5, 1), (4, 3, 0), (4, 4, 0), (4, 4, 1)], Color: blue
     93    Polyomino: [(0, 4, 1), (0, 5, 0), (0, 5, 1), (0, 6, 1), (1, 5, 0)], Color: midnightblue
     94    Polyomino: [(0, 6, 0), (0, 7, 0), (0, 7, 1), (1, 7, 0), (2, 7, 0)], Color: darkblue
     95    Polyomino: [(1, 7, 1), (2, 6, 0), (2, 6, 1), (2, 7, 1), (3, 6, 0)], Color: blue
     96    Polyomino: [(1, 5, 1), (1, 6, 0), (1, 6, 1), (2, 5, 0), (2, 5, 1)], Color: yellow
     97    Polyomino: [(3, 6, 1), (3, 7, 0), (3, 7, 1), (4, 5, 1), (4, 6, 1)], Color: purple
     98    Polyomino: [(3, 5, 0), (4, 5, 0), (4, 6, 0), (4, 7, 0), (4, 7, 1)], Color: orange
     99
     100To get all the solutions, use the iterator returned by the ``solve``
     101method. Note that finding the first solution is the most time consuming
     102because it needs to create the complete data to describe the problem::
     103
     104    sage: it = QuantuminoSolver(7).solve()
     105    sage: it.next()                                     # not tested (10s)
     106    Quantumino state where the following pentamino is put aside :
     107    Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
     108    sage: it.next()                                     # not tested (0.001s)
     109    Quantumino state where the following pentamino is put aside :
     110    Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
     111    sage: it.next()                                     # not tested (0.001s)
     112    Quantumino state where the following pentamino is put aside :
     113    Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
     114
     115To get the solution inside other boxes::
     116
     117    sage: s = QuantuminoSolver(7, box=(4,4,5)).solve().next()       # not tested (2s)
     118    sage: s.show3d()                                                # not tested (<1s)
     119
     120::
     121
     122    sage: s = QuantuminoSolver(7, box=(2,2,20)).solve().next()      # not tested (1s)
     123    sage: s.show3d()                                                # not tested (<1s)
     124
     125If there are no solution, a StopIteration error is raised::
     126
     127    sage: QuantuminoSolver(7, box=(3,3,3)).solve().next()
     128    Traceback (most recent call last):
     129    ...
     130    StopIteration
     131
     132The implementation allows a lot of introspection. From the
     133:class:`~sage.combinat.tiling.TilingSolver` object,
     134it is possible to retrieve the rows that are passed to the DLX
     135solver and count them. It is also possible to get an instance of the DLX
     136solver to play with it::
     137
     138    sage: q = QuantuminoSolver(0)
     139    sage: T = q.tiling_solver()
     140    sage: T
     141    Tiling solver of 16 pieces into the box (5, 8, 2)
     142    Rotation allowed: True
     143    Reflection allowed: False
     144    Reusing pieces allowed: False
     145    sage: rows = T.rows()                            # not tested (10 s)
     146    sage: len(rows)                                  # not tested (but fast)
     147    5484
     148    sage: x = T.dlx_solver()                         # long time (10 s)
     149    sage: x                                          # long time (fast)
     150    <sage.combinat.matrices.dancing_links.dancing_linksWrapper object at ...>
     151
     152REFERENCES:
     153
     154- [1] `Family Games America's Quantumino
     155  <http://familygamesamerica.com/mainsite/consumers/productview.php?pro_id=274&search=quantumino>`_
     156- [2] `Quantumino - How to Play <http://www.youtube.com/watch?v=jX_VKzakZi8>`_ on Youtube
     157- [3] Knuth, Donald (2000). "Dancing links". `arXiv:cs/0011047
     158  <http://arxiv.org/abs/cs/0011047>`_.
     159
     160"""
     161#*****************************************************************************
     162#       Copyright (C) 2011 Sebastien Labbe <slabqc@gmail.com>
     163#
     164#  Distributed under the terms of the GNU General Public License (GPL)
     165#  as published by the Free Software Foundation; either version 2 of
     166#  the License, or (at your option) any later version. 
     167#                  http://www.gnu.org/licenses/ 
     168#*****************************************************************************
     169from sage.structure.sage_object import SageObject
     170from sage.plot.plot import Graphics
     171from sage.plot.plot3d.platonic import cube
     172from sage.plot.plot3d.shapes2 import text3d
     173from sage.modules.free_module_element import vector
     174from sage.combinat.tiling import Polyomino, TilingSolver
     175
     176################################################
     177# Example:  The family games america: Quantumino
     178################################################
     179pentaminos = []
     180pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,2,0), (1,1,1)], color='deeppink'))
     181pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (-1,0,0), (0,0,1)], color='deeppink'))
     182pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,2,0), (0,0,1)], color='green'))
     183pentaminos.append(Polyomino([(0,0,0), (0,1,0), (0,2,0), (1,0,0), (1,0,1)], color='green'))
     184pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (1,-1,1)], color='red'))
     185pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (2,0,1)], color='red'))
     186pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,2,0), (1,2,1)], color='orange'))
     187pentaminos.append(Polyomino([(0,0,0), (1,0,0), (0,1,0), (0,2,0), (0,2,1)], color='orange'))
     188pentaminos.append(Polyomino([(0,0,0), (1,0,0), (0,1,0), (1,1,0), (0,0,1)], color='yellow'))
     189pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,1,1), (0,0,1)], color='yellow'))
     190pentaminos.append(Polyomino([(0,0,0), (0,1,0), (1,1,0), (0,2,0), (1,1,1)], color='midnightblue'))
     191pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (1,2,0)], color='darkblue'))
     192pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,1,1), (2,1,1)], color='blue'))
     193pentaminos.append(Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1), (1,2,1)], color='blue'))
     194pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (2,1,0), (2,1,1)], color='purple'))
     195pentaminos.append(Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,2,0), (1,2,1)], color='purple'))
     196pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (1,-1,0)], color='gray'))
     197
     198def show_pentaminos(box=(5,8,2)):
     199    r"""
     200    Show the 17 3-D pentaminos included in the game and the `5 \times 8
     201    \times 2` box where 16 of them must fit.
     202
     203    INPUT:
     204
     205    - ``box`` - tuple of size three (optional, default: ``(5,8,2)``),
     206      size of the box
     207
     208    OUTPUT:
     209
     210        3D Graphic object
     211
     212    EXAMPLES::
     213
     214        sage: from sage.games.quantumino import show_pentaminos
     215        sage: show_pentaminos()    # not tested (1s)
     216
     217    To remove the frame do::
     218
     219        sage: show_pentaminos().show(frame=False)  # not tested (1s)
     220    """
     221    G = Graphics()
     222    for i,p in enumerate(pentaminos):
     223        x = 3.5 * (i%4)
     224        y = 3.5 * (i/4)
     225        q = p + (x, y, 0)
     226        G += q.show3d()
     227        G += text3d(str(i), (x,y,2))
     228    G += cube(color='gray',opacity=0.5).scale(box).translate((17,6,0))
     229
     230    # hack to set the aspect ratio to 1
     231    a,b = G.bounding_box()
     232    a,b = map(vector, (a,b))
     233    G.frame_aspect_ratio(tuple(b-a))
     234
     235    return G
     236
     237##############################
     238# Class QuantuminoState
     239##############################
     240class QuantuminoState(SageObject):
     241    r"""
     242    A state of the Quantumino puzzle.
     243
     244    Used to represent an solution or a partial solution of the Quantumino
     245    puzzle.
     246
     247    INPUT:
     248
     249    - ``pentos`` - list of 16 3d pentamino representing the (partial)
     250      solution
     251    - ``aside`` - 3d polyomino, the unused 3D pentamino
     252
     253    EXAMPLES::
     254
     255        sage: from sage.games.quantumino import pentaminos, QuantuminoState
     256        sage: p = pentaminos[0]
     257        sage: q = pentaminos[5]
     258        sage: r = pentaminos[11]
     259        sage: S = QuantuminoState([p,q], r)
     260        sage: S
     261        Quantumino state where the following pentamino is put aside :
     262        Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 2, 0)], Color: darkblue
     263
     264    ::
     265
     266        sage: from sage.games.quantumino import QuantuminoSolver
     267        sage: QuantuminoSolver(3).solve().next()      # not tested (1.5s)
     268        Quantumino state where the following pentamino is put aside :
     269        Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (1, 0, 0), (1, 0, 1)], Color: green
     270    """
     271    def __init__(self, pentos, aside):
     272        r"""
     273        EXAMPLES::
     274
     275            sage: from sage.games.quantumino import pentaminos, QuantuminoState
     276            sage: p = pentaminos[0]
     277            sage: q = pentaminos[5]
     278            sage: QuantuminoState([p], q)
     279            Quantumino state where the following pentamino is put aside :
     280            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (2, 0, 1)], Color: red
     281        """
     282        assert all(isinstance(p, Polyomino) for p in pentos), "pentos must be an iterable of Polyomino"
     283        assert isinstance(aside, Polyomino), "aside must be a Polyomino"
     284        self._pentos = pentos
     285        self._aside = aside
     286
     287    def __repr__(self):
     288        r"""
     289        EXAMPLES::
     290
     291            sage: from sage.games.quantumino import pentaminos, QuantuminoState
     292            sage: p = pentaminos[0]
     293            sage: q = pentaminos[5]
     294            sage: QuantuminoState([p], q)
     295            Quantumino state where the following pentamino is put aside :
     296            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (2, 0, 1)], Color: red
     297        """
     298        return "Quantumino state where the following pentamino is put aside :\n%s" % self._aside
     299
     300    def __iter__(self):
     301        r"""
     302        EXAMPLES::
     303
     304            sage: from sage.games.quantumino import pentaminos, QuantuminoState
     305            sage: p = pentaminos[0]
     306            sage: q = pentaminos[5]
     307            sage: r = pentaminos[11]
     308            sage: S = QuantuminoState([p,q], r)
     309            sage: for a in S: a
     310            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     311            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (2, 0, 1)], Color: red
     312        """
     313        return iter(self._pentos)
     314
     315    def list(self):
     316        r"""
     317        Return the list of 3d polyomino making the solution.
     318
     319        EXAMPLES::
     320
     321            sage: from sage.games.quantumino import pentaminos, QuantuminoState
     322            sage: p = pentaminos[0]
     323            sage: q = pentaminos[5]
     324            sage: r = pentaminos[11]
     325            sage: S = QuantuminoState([p,q], r)
     326            sage: L = S.list()
     327            sage: L[0]
     328            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     329            sage: L[1]
     330            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (2, 0, 1)], Color: red
     331        """
     332        return list(self)
     333
     334    def show3d(self, size=0.85):
     335        r"""
     336        Return the solution as a 3D Graphic object.
     337
     338        OUTPUT:
     339
     340            3D Graphic Object
     341
     342        EXAMPLES::
     343
     344            sage: from sage.games.quantumino import QuantuminoSolver
     345            sage: s = QuantuminoSolver(0).solve().next()    # not tested (1.5s)
     346            sage: G = s.show3d()                            # not tested (<1s)
     347            sage: type(G)                                   # not tested
     348            <class 'sage.plot.plot3d.base.Graphics3dGroup'>
     349
     350        To remove the frame::
     351
     352            sage: G.show(frame=False) # not tested
     353
     354        To see the solution with Tachyon viewer::
     355
     356            sage: G.show(viewer='tachyon', frame=False) # not tested
     357        """
     358        G = Graphics()
     359        for p in self:
     360            G += p.show3d(size=size)
     361        aside_pento = self._aside.canonical() + (2.5*size/0.75,-4*size/0.75,0)
     362        G += aside_pento.show3d(size=size)
     363
     364        # hack to set the aspect ratio to 1
     365        a,b = G.bounding_box()
     366        a,b = map(vector, (a,b))
     367        G.frame_aspect_ratio(tuple(b-a))
     368
     369        return G
     370
     371##############################
     372# Class QuantuminoSolver
     373##############################
     374class QuantuminoSolver(SageObject):
     375    r"""
     376    Return the Quantumino solver for the given box where one of the
     377    pentamino is put aside.
     378
     379    INPUT:
     380
     381    - ``aside`` - integer, from 0 to 16, the aside pentamino
     382    - ``box`` - tuple of size three (optional, default: ``(5,8,2)``),
     383      size of the box
     384
     385    EXAMPLES::
     386
     387        sage: from sage.games.quantumino import QuantuminoSolver
     388        sage: QuantuminoSolver(9)
     389        Quantumino solver for the box (5, 8, 2)
     390        Aside pentamino number: 9
     391        sage: QuantuminoSolver(12, box=(5,4,4))
     392        Quantumino solver for the box (5, 4, 4)
     393        Aside pentamino number: 12
     394    """
     395    def __init__(self, aside, box=(5,8,2)):
     396        r"""
     397        Constructor.
     398
     399        EXAMPLES::
     400
     401            sage: from sage.games.quantumino import QuantuminoSolver
     402            sage: QuantuminoSolver(9)
     403            Quantumino solver for the box (5, 8, 2)
     404            Aside pentamino number: 9
     405        """
     406        if not  0 <= aside < 17:
     407            raise ValueError, "aside (=%s) must be between 0 and 16" % aside
     408        self._aside = aside
     409        self._box = box
     410
     411    def __repr__(self):
     412        r"""
     413        String representation
     414
     415        EXAMPLES::
     416
     417            sage: from sage.games.quantumino import QuantuminoSolver
     418            sage: QuantuminoSolver(0)
     419            Quantumino solver for the box (5, 8, 2)
     420            Aside pentamino number: 0
     421        """
     422        s = "Quantumino solver for the box %s\n" % (self._box, )
     423        s += "Aside pentamino number: %s" % self._aside
     424        return s
     425
     426    def tiling_solver(self):
     427        r"""
     428        Return the Tiling solver of the Quantumino Game where one of the
     429        pentamino is put aside.
     430
     431        EXAMPLES::
     432
     433            sage: from sage.games.quantumino import QuantuminoSolver
     434            sage: QuantuminoSolver(0).tiling_solver()
     435            Tiling solver of 16 pieces into the box (5, 8, 2)
     436            Rotation allowed: True
     437            Reflection allowed: False
     438            Reusing pieces allowed: False
     439            sage: QuantuminoSolver(14).tiling_solver()
     440            Tiling solver of 16 pieces into the box (5, 8, 2)
     441            Rotation allowed: True
     442            Reflection allowed: False
     443            Reusing pieces allowed: False
     444            sage: QuantuminoSolver(14, box=(5,4,4)).tiling_solver()
     445            Tiling solver of 16 pieces into the box (5, 4, 4)
     446            Rotation allowed: True
     447            Reflection allowed: False
     448            Reusing pieces allowed: False
     449        """
     450        pieces = pentaminos[:self._aside] + pentaminos[self._aside+1:]
     451        return TilingSolver(pieces, box=self._box)
     452
     453    def solve(self, partial=None):
     454        r"""
     455        Return an iterator over the solutions where one of the pentamino is
     456        put aside.
     457
     458        INPUT:
     459
     460        - ``partial`` - string (optional, default: ``None``), whether to
     461          include partial (incomplete) solutions. It can be one of the
     462          following:
     463         
     464          - ``None`` - include only complete solution
     465          - ``'common'`` - common part between two consecutive solutions
     466          - ``'incremental'`` - one piece change at a time
     467
     468        OUTPUT:
     469
     470            iterator of QuantuminoState
     471
     472        EXAMPLES:
     473
     474        Get one solution::
     475
     476            sage: from sage.games.quantumino import QuantuminoSolver
     477            sage: s = QuantuminoSolver(8).solve().next()         # long time (9s)
     478            sage: s                                              # long time (fast)
     479            Quantumino state where the following pentamino is put aside :
     480            Polyomino: [(0, 0, 0), (0, 0, 1), (0, 1, 0), (1, 0, 0), (1, 1, 0)], Color: yellow
     481            sage: s.show3d()                                     # long time (< 1s)
     482
     483        The explicit solution::
     484
     485            sage: for p in s: p                                  # long time (fast)
     486            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     487            Polyomino: [(0, 0, 1), (0, 1, 0), (0, 1, 1), (0, 2, 1), (1, 2, 1)], Color: deeppink
     488            Polyomino: [(0, 2, 0), (0, 3, 0), (0, 4, 0), (1, 4, 0), (1, 4, 1)], Color: green
     489            Polyomino: [(0, 3, 1), (1, 3, 1), (2, 2, 0), (2, 2, 1), (2, 3, 1)], Color: green
     490            Polyomino: [(1, 3, 0), (2, 3, 0), (2, 4, 0), (2, 4, 1), (3, 4, 0)], Color: red
     491            Polyomino: [(1, 0, 1), (2, 0, 0), (2, 0, 1), (2, 1, 0), (3, 0, 1)], Color: midnightblue
     492            Polyomino: [(0, 4, 1), (0, 5, 0), (0, 5, 1), (0, 6, 0), (1, 5, 0)], Color: red
     493            Polyomino: [(2, 1, 1), (3, 0, 0), (3, 1, 0), (3, 1, 1), (4, 0, 0)], Color: blue
     494            Polyomino: [(3, 2, 0), (4, 0, 1), (4, 1, 0), (4, 1, 1), (4, 2, 0)], Color: purple
     495            Polyomino: [(3, 2, 1), (3, 3, 0), (4, 2, 1), (4, 3, 0), (4, 3, 1)], Color: yellow
     496            Polyomino: [(3, 3, 1), (3, 4, 1), (4, 4, 0), (4, 4, 1), (4, 5, 0)], Color: blue
     497            Polyomino: [(0, 6, 1), (0, 7, 0), (0, 7, 1), (1, 5, 1), (1, 6, 1)], Color: purple
     498            Polyomino: [(1, 6, 0), (1, 7, 0), (1, 7, 1), (2, 7, 0), (3, 7, 0)], Color: darkblue
     499            Polyomino: [(2, 5, 0), (2, 6, 0), (3, 6, 0), (4, 6, 0), (4, 6, 1)], Color: orange
     500            Polyomino: [(2, 5, 1), (3, 5, 0), (3, 5, 1), (3, 6, 1), (4, 5, 1)], Color: gray
     501            Polyomino: [(2, 6, 1), (2, 7, 1), (3, 7, 1), (4, 7, 0), (4, 7, 1)], Color: orange
     502
     503        Enumerate the solutions::
     504
     505            sage: it = QuantuminoSolver(0).solve()
     506            sage: it.next()                                          # not tested
     507            Quantumino state where the following pentamino is put aside :
     508            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     509            sage: it.next()                                          # not tested
     510            Quantumino state where the following pentamino is put aside :
     511            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     512
     513        With the partial solutions included, one can see the evolution
     514        between consecutive solutions (an animation would be better)::
     515
     516            sage: it = QuantuminoSolver(0).solve(partial='common')
     517            sage: it.next().show3d()               # not tested (2s)
     518            sage: it.next().show3d()               # not tested (< 1s)
     519            sage: it.next().show3d()               # not tested (< 1s)
     520
     521        Generalizations of the game inside different boxes::
     522
     523            sage: QuantuminoSolver(7, (4,4,5)).solve().next()       # long time (2s)
     524            Quantumino state where the following pentamino is put aside :
     525            Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
     526            sage: QuantuminoSolver(7, (2,2,20)).solve().next()      # long time (1s)
     527            Quantumino state where the following pentamino is put aside :
     528            Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
     529            sage: QuantuminoSolver(3, (2,2,20)).solve().next()      # long time (1s)
     530            Quantumino state where the following pentamino is put aside :
     531            Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (1, 0, 0), (1, 0, 1)], Color: green
     532
     533        If the volume of the box is not 80, there is no solution::
     534
     535            sage: QuantuminoSolver(7, box=(3,3,9)).solve().next()
     536            Traceback (most recent call last):
     537            ...
     538            StopIteration
     539
     540        If the box is too small, there is no solution::
     541
     542            sage: QuantuminoSolver(4, box=(40,2,1)).solve().next()
     543            Traceback (most recent call last):
     544            ...
     545            StopIteration
     546        """
     547        T = self.tiling_solver()
     548        aside = pentaminos[self._aside]
     549        for pentos in T.solve(partial=partial):
     550            yield QuantuminoState(pentos, aside)
     551
     552    def number_of_solutions(self):
     553        r"""
     554        Return the number of solutions.
     555
     556        OUTPUT:
     557
     558            integer
     559
     560        EXAMPLES::
     561
     562            sage: from sage.games.quantumino import QuantuminoSolver
     563            sage: QuantuminoSolver(4, box=(3,2,2)).number_of_solutions()
     564            0
     565
     566        This computation takes several days::
     567
     568            sage: QuantuminoSolver(0).number_of_solutions()                # not tested
     569            ??? hundreds of millions ???
     570        """
     571        return self.tiling_solver().number_of_solutions()
     572
     573