Ticket #11379: trac_11379_corrections-sl-v2.patch

File trac_11379_corrections-sl-v2.patch, 104.8 KB (added by rbeezer, 10 years ago)

New version with edited commit string

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

    # HG changeset patch
    # User Sebastien Labbe <slabqc at gmail.com>
    # Date 1308130802 25200
    # Node ID cf455f591acc4183bdb6079db398db3f2301b9b6
    # Parent  cd9903274698f5ea9152c984654a3f06b5f7fd46
    #11379: corrections and add sage.combinat.tiling file
    
    diff --git a/doc/en/reference/combinat/index.rst b/doc/en/reference/combinat/index.rst
    a b Combinatorics 
    3636   ../sage/combinat/skew_partition
    3737   ../sage/combinat/subset
    3838   ../sage/combinat/subword
     39   ../sage/combinat/tiling
    3940   ../sage/combinat/tuple
    4041
    4142   algebra
  • 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
     4Finding a tiling of a box into non-intersecting 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 for any dimension.
     10
     11This module defines two classes:
     12
     13- :class:`sage.games.quantumino.Polyomino` class, to represent polyominoes
     14  in arbitrary dimension. The goal of this class is to return all the
     15  rotated, reflected and/or translated copies of a polyomino that are
     16  contained in a certain box.
     17
     18- :class:`sage.games.quantumino.TilingSolver` class, to solve the general
     19  problem of tiling a rectangular `n`-dimensional box with a set of
     20  `n`-dimensional polyominoes. One can specify if rotations and reflections
     21  are allowed or not and if pieces can be reused or not. This class convert
     22  the tiling data into rows of a matrix that are passed to the DLX solver.
     23  It also allows to compute the number of solutions.
     24
     25AUTHOR:
     26
     27    - Sebastien Labbe, June 2011
     28
     29EXAMPLES:
     30
     312d Easy Example
     32---------------
     33
     34Here is a 2d example. Let's try to fill the `3 \times 2` rectangle with a
     35`1 \times 2` rectangle and a `2 \times 2` square. Obviously, there are two
     36solutions::
     37
     38    sage: from sage.combinat.tiling import TilingSolver, Polyomino
     39    sage: p = Polyomino([(0,0), (0,1)])
     40    sage: q = Polyomino([(0,0), (0,1), (1,0), (1,1)])
     41    sage: T = TilingSolver([p,q], box=[3,2])
     42    sage: it = T.solve()
     43    sage: it.next()
     44    [Polyomino: [(0, 0), (0, 1), (1, 0), (1, 1)], Color: gray, Polyomino: [(2, 0), (2, 1)], Color: gray]
     45    sage: it.next()
     46    [Polyomino: [(1, 0), (1, 1), (2, 0), (2, 1)], Color: gray, Polyomino: [(0, 0), (0, 1)], Color: gray]
     47    sage: it.next()
     48    Traceback (most recent call last):
     49    ...
     50    StopIteration
     51    sage: T.number_of_solutions()
     52    2
     53
     541d Easy Example
     55---------------
     56
     57Here is an easy one dimensional example where we try to tile a stick of
     58length 6 with three sticks of length 1, 2 and 3. There are six solutions::
     59
     60    sage: p = Polyomino([[0]])
     61    sage: q = Polyomino([[0],[1]])
     62    sage: r = Polyomino([[0],[1],[2]])
     63    sage: T = TilingSolver([p,q,r], box=[6])
     64    sage: len(T.rows())
     65    15
     66    sage: it = T.solve()
     67    sage: it.next()
     68    [Polyomino: [(0,)], Color: gray, Polyomino: [(1,), (2,)], Color: gray, Polyomino: [(3,), (4,), (5,)], Color: gray]
     69    sage: it.next()
     70    [Polyomino: [(0,)], Color: gray, Polyomino: [(1,), (2,), (3,)], Color: gray, Polyomino: [(4,), (5,)], Color: gray]
     71    sage: T.number_of_solutions()
     72    6
     73
     742d Puzzle allowing reflections
     75------------------------------
     76
     77The following is a puzzle owned by Florent Hivert::
     78
     79    sage: from sage.combinat.tiling import Polyomino, TilingSolver
     80    sage: L = []
     81    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,2),(1,3)]))
     82    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,2)]))
     83    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,3)]))
     84    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,3)]))
     85    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,1)]))
     86    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,1),(1,2)]))
     87    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,1),(1,3)]))
     88    sage: L.append(Polyomino([(0,1),(0,2),(0,3),(1,0),(1,1),(1,3)]))
     89    sage: L.append(Polyomino([(0,1),(0,2),(0,3),(1,0),(1,1),(1,2)]))
     90    sage: L.append(Polyomino([(0,0),(0,1),(0,2),(1,0),(1,1),(1,2)]))
     91
     92By default, rotations are allowed and reflections are not. In this case, if
     93reflections are not allowed, there are no solution::
     94
     95    sage: T = TilingSolver(L, (8,8))
     96    sage: T.number_of_solutions()                             # long time (2.5 s)
     97    0
     98
     99Allow reflections, solve the puzzle and show one solution::
     100
     101    sage: T = TilingSolver(L, (8,8), reflection=True)
     102    sage: solution = T.solve().next()
     103    sage: G = sum([piece.show2d() for piece in solution], Graphics())
     104    sage: G.show(aspect_ratio=1)
     105
     106Let's compute the number of solutions::
     107
     108    sage: T.number_of_solutions()                              # long time (6s the first time)
     109    328
     110
     1113d Puzzle
     112---------
     113
     114The same thing done in 3d without allowing reflections this time::
     115
     116    sage: from sage.combinat.tiling import Polyomino, TilingSolver
     117    sage: L = []
     118    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)]))
     119    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)]))
     120    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)]))
     121    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,3,0)]))
     122    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0)]))
     123    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,1,0),(1,2,0)]))
     124    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,1,0),(1,3,0)]))
     125    sage: L.append(Polyomino([(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,3,0)]))
     126    sage: L.append(Polyomino([(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,2,0)]))
     127    sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(1,0,0),(1,1,0),(1,2,0)]))
     128
     129Solve the puzzle and show one solution::
     130
     131    sage: T = TilingSolver(L, (8,8,1))
     132    sage: solution = T.solve().next()
     133    sage: G = sum([piece.show3d() for piece in solution], Graphics())
     134    sage: G.show(aspect_ratio=1, viewer='tachyon')
     135
     136Let's compute the number of solutions::
     137
     138    sage: T.number_of_solutions()                              # long time (3s)
     139    328
     140
     141Donald Knuth example : the Y pentamino
     142--------------------------------------
     143
     144Donald Knuth [1] considered the problem of packing 45 Y pentominoes into a
     145`15 \times 15` square::
     146
     147    sage: from sage.combinat.tiling import Polyomino, TilingSolver
     148    sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)])
     149    sage: T = TilingSolver([y], box=(5,10), reusable=True, reflection=True)
     150    sage: T.number_of_solutions()
     151    10
     152    sage: solution = T.solve().next()
     153    sage: G = sum([piece.show2d() for piece in solution], Graphics())
     154    sage: G.show(aspect_ratio=1)
     155
     156::
     157
     158    sage: T = TilingSolver([y], box=(15,15), reusable=True, reflection=True)
     159    sage: T.number_of_solutions()                      #not tested
     160    212
     161
     162REFERENCES:
     163
     164- [1] Knuth, Donald (2000). "Dancing links". `arXiv:cs/0011047
     165  <http://arxiv.org/abs/cs/0011047>`_.
     166
     167"""
     168#*****************************************************************************
     169#       Copyright (C) 2011 Sebastien Labbe <slabqc@gmail.com>
     170#
     171#  Distributed under the terms of the GNU General Public License (GPL)
     172#  as published by the Free Software Foundation; either version 2 of
     173#  the License, or (at your option) any later version. 
     174#                  http://www.gnu.org/licenses/ 
     175#*****************************************************************************
     176import itertools
     177from sage.structure.sage_object import SageObject
     178from sage.misc.cachefunc import cached_method, cached_function
     179from sage.misc.misc import prod
     180from sage.combinat.all import WeylGroup
     181from sage.plot.plot import Graphics
     182from sage.plot.polygon import polygon
     183from sage.modules.free_module_element import vector
     184from sage.plot.plot3d.platonic import cube
     185from sage.misc.mrange import xmrange
     186
     187############################
     188# Orthogonal transformations
     189############################
     190@cached_function
     191def orthogonal_transformation(n, orientation_preserving=True):
     192    r"""
     193    Return the list of orthogonal transformation matrices in the
     194    `n`-dimensional vector space.
     195
     196    INPUT:
     197
     198    - ``n``` - positive integer, dimension of the space
     199    - ``orientation_preserving`` - bool (optional, default: ``True``),
     200      whether the orientation is preserved
     201
     202    OUTPUT:
     203
     204        list of matrices
     205
     206    EXAMPLES::
     207
     208        sage: from sage.combinat.tiling import orthogonal_transformation
     209        sage: orthogonal_transformation(2)
     210        [
     211        [1 0]  [ 0 -1]  [ 0  1]  [-1  0]
     212        [0 1], [ 1  0], [-1  0], [ 0 -1]
     213        ]
     214        sage: orthogonal_transformation(2, orientation_preserving=False)
     215        [
     216        [1 0]  [0 1]  [ 0 -1]  [-1  0]  [ 1  0]  [ 0  1]  [ 0 -1]  [-1  0]
     217        [0 1], [1 0], [ 1  0], [ 0  1], [ 0 -1], [-1  0], [-1  0], [ 0 -1]
     218        ]
     219        sage: orthogonal_transformation(3)
     220        [
     221        [1 0 0]  [0 0 1]  [ 0  0 -1]  [-1  0  0]  [ 0 -1  0]  [0 1 0]
     222        [0 1 0]  [1 0 0]  [ 0  1  0]  [ 0  0  1]  [ 1  0  0]  [0 0 1]
     223        [0 0 1], [0 1 0], [ 1  0  0], [ 0  1  0], [ 0  0  1], [1 0 0],
     224        <BLANKLINE>
     225        [ 1  0  0]  [ 0  0  1]  [ 0  0 -1]  [-1  0  0]  [ 0 -1  0]  [ 0  1  0]
     226        [ 0  0 -1]  [ 0 -1  0]  [-1  0  0]  [ 0 -1  0]  [ 0  0 -1]  [-1  0  0]
     227        [ 0  1  0], [ 1  0  0], [ 0  1  0], [ 0  0  1], [ 1  0  0], [ 0  0  1],
     228        <BLANKLINE>
     229        [ 0  1  0]  [ 0  0  1]  [ 0  0 -1]  [ 0 -1  0]  [-1  0  0]  [ 1  0  0]
     230        [ 1  0  0]  [ 0  1  0]  [ 1  0  0]  [ 0  0  1]  [ 0  1  0]  [ 0  0  1]
     231        [ 0  0 -1], [-1  0  0], [ 0 -1  0], [-1  0  0], [ 0  0 -1], [ 0 -1  0],
     232        <BLANKLINE>
     233        [ 0  1  0]  [ 0  0  1]  [ 0  0 -1]  [ 0 -1  0]  [-1  0  0]  [ 1  0  0]
     234        [ 0  0 -1]  [-1  0  0]  [ 0 -1  0]  [-1  0  0]  [ 0  0 -1]  [ 0 -1  0]
     235        [-1  0  0], [ 0 -1  0], [-1  0  0], [ 0  0 -1], [ 0 -1  0], [ 0  0 -1]
     236        ]
     237
     238    TESTS::
     239
     240        sage: orthogonal_transformation(1)
     241        [[1]]
     242        sage: orthogonal_transformation(0)
     243        Traceback (most recent call last):
     244        ...
     245        ValueError: ['B', 0] is not a valid cartan type
     246    """
     247    if orientation_preserving:
     248        return [w.matrix() for w in WeylGroup(['B', n]) if w.matrix().det() == 1]
     249    else:
     250        return [w.matrix() for w in WeylGroup(['B', n])]
     251
     252##############################
     253# Class Polyomino
     254##############################
     255class Polyomino(SageObject):
     256    r"""
     257    Return the polyomino defined by a set of coordinates.
     258
     259    The polyomino is the union of the unit square (or cube, or n-cube)
     260    centered at those coordinates. Such an object should be connected, but
     261    the code do not make this assumption.
     262
     263    INPUT:
     264
     265    - ``coords`` - iterable of tuple
     266    - ``color`` - string (optional, default: ``'gray'``), the color
     267
     268    EXAMPLES::
     269       
     270        sage: from sage.combinat.tiling import Polyomino
     271        sage: Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     272        Polyomino: [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)], Color: blue
     273    """
     274    def __init__(self, coords, color='gray'):
     275        r"""
     276        INPUT:
     277
     278        - ``coords`` - iterable of tuple
     279        - ``color`` - string (optional, default: ``'gray'``), the color
     280
     281        EXAMPLES::
     282           
     283            sage: from sage.combinat.tiling import Polyomino
     284            sage: Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     285            Polyomino: [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)], Color: blue
     286
     287        ::
     288
     289            sage: from sage.combinat.tiling import Polyomino
     290            sage: Polyomino([(0,0), (1,0), (2,0)])
     291            Polyomino: [(0, 0), (1, 0), (2, 0)], Color: gray
     292        """
     293        assert isinstance(color, str)
     294        self._color = color
     295        self._blocs = frozenset(tuple(c) for c in coords)
     296        dimension_set = set(len(a) for a in self._blocs)
     297        assert len(dimension_set) <= 1, "coord must be all of the same dimension"
     298        self._dimension = dimension_set.pop()
     299
     300    def __repr__(self):
     301        r"""
     302        String representation.
     303
     304        EXAMPLES::
     305
     306            sage: from sage.combinat.tiling import Polyomino
     307            sage: Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='red')
     308            Polyomino: [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)], Color: red
     309        """
     310        s = "Polyomino: %s, " % sorted(self._blocs)
     311        s += "Color: %s" % self._color
     312        return s
     313
     314    def __hash__(self):
     315        r"""
     316        EXAMPLES::
     317
     318            sage: from sage.combinat.tiling import Polyomino
     319            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     320            sage: hash(p)     # random
     321            2059134902
     322        """
     323        return hash(self._blocs)
     324
     325    def __eq__(self, other):
     326        r"""
     327        Return whether self is equal to other.
     328
     329        INPUT:
     330
     331        - ``other`` - a polyomino
     332
     333        OUTPUT:
     334
     335            boolean
     336
     337        EXAMPLES::
     338
     339            sage: from sage.combinat.tiling import Polyomino
     340            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     341            sage: q = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='red')
     342            sage: p == q
     343            True
     344            sage: r = Polyomino([(0,0,0), (0,1,0), (1,1,0)], color='blue')
     345            sage: p == r
     346            False
     347        """
     348        return self._blocs == other._blocs
     349
     350    def __ne__(self, other):
     351        r"""
     352        Return whether self is not equal to other.
     353
     354        INPUT:
     355
     356        - ``other`` - a polyomino
     357
     358        OUTPUT:
     359
     360            boolean
     361
     362        EXAMPLES::
     363
     364            sage: from sage.combinat.tiling import Polyomino
     365            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     366            sage: q = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='red')
     367            sage: p != q
     368            False
     369            sage: r = Polyomino([(0,0,0), (0,1,0), (1,1,0)], color='blue')
     370            sage: p != r
     371            True
     372        """
     373        return self._blocs != other._blocs
     374
     375    def __iter__(self):
     376        r"""
     377        EXAMPLES::
     378
     379            sage: from sage.combinat.tiling import Polyomino
     380            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     381            sage: it = iter(p)
     382            sage: it.next()
     383            (1, 1, 0)
     384        """
     385        return iter(self._blocs)
     386
     387    def color(self):
     388        r"""
     389        Return the color of the polyomino.
     390
     391        EXAMPLES::
     392
     393            sage: from sage.combinat.tiling import Polyomino
     394            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     395            sage: p.color()
     396            'blue'
     397        """
     398        return self._color
     399
     400    def __len__(self):
     401        r"""
     402        Return the size of the polyomino, i.e. the number of n-dimensional
     403        unit cubes.
     404
     405        EXAMPLES::
     406
     407            sage: from sage.combinat.tiling import Polyomino
     408            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     409            sage: len(p)
     410            4
     411        """
     412        return len(self._blocs)
     413
     414    def orthogonals(self, orientation_preserving=True):
     415        r"""
     416        Iterator over the images of self under orthogonal transformations.
     417
     418        .. NOTE::
     419
     420            No guarentee of unicity.
     421
     422        INPUT:
     423
     424        - ``orientation_preserving`` - bool (optional, default: ``True``),
     425          whether the orientation is preserved
     426
     427        OUTPUT:
     428
     429            iterator of Polyomino
     430
     431        EXAMPLES::
     432
     433            sage: from sage.combinat.tiling import Polyomino
     434            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     435            sage: L = list(p.orthogonals())
     436            sage: len(L)
     437            24
     438            sage: L = list(p.orthogonals(False))
     439            sage: len(L)
     440            48
     441        """
     442        return (m * self for m in orthogonal_transformation(self._dimension, orientation_preserving))
     443
     444    def canonical_orthogonals(self, orientation_preserving=True):
     445        r"""
     446        Iterator over the image of self under orthogonal transformations
     447        where the coordinates are all positive and minimal.
     448
     449        .. NOTE::
     450
     451            No guarentee of unicity.
     452
     453        INPUT:
     454
     455        - ``orientation_preserving`` - bool (optional, default: ``True``),
     456          whether the orientation is preserved
     457
     458        OUTPUT:
     459
     460            iterator of Polyomino
     461
     462        EXAMPLES::
     463
     464            sage: from sage.combinat.tiling import Polyomino
     465            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     466            sage: L = list(p.canonical_orthogonals())
     467            sage: len(L)
     468            24
     469
     470        They might not be all different::
     471
     472            sage: s = set(p.canonical_orthogonals())
     473            sage: len(s)
     474            12
     475
     476        With the non orientation-preserving::
     477
     478            sage: s = set(p.canonical_orthogonals(False))
     479            sage: len(s)
     480            24
     481        """
     482        for q in self.orthogonals(orientation_preserving):
     483            yield q.canonical()
     484
     485    def canonical(self):
     486        r"""
     487        Returns the translated copy of self having minimal and positive
     488        coordinates
     489
     490        EXAMPLES::
     491
     492            sage: from sage.combinat.tiling import Polyomino
     493            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     494            sage: p
     495            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     496            sage: p.canonical()
     497            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     498
     499        TESTS::
     500           
     501            sage: p
     502            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     503            sage: p + (3,4,5)
     504            Polyomino: [(3, 4, 5), (4, 4, 5), (4, 5, 5), (4, 5, 6), (4, 6, 5)], Color: deeppink
     505            sage: (p + (3,4,5)).canonical()
     506            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     507        """
     508        minxyz,maxxyz = self.bounding_box()
     509        return self - minxyz
     510
     511    def __sub__(self, v):
     512        r"""
     513        Return a translated copy of self by the opposite of the
     514        vector v.
     515
     516        INPUT:
     517
     518        - ``v`` - tuple
     519
     520        OUPUT:
     521
     522            polyomino
     523
     524        EXAMPLES::
     525
     526            sage: from sage.combinat.tiling import Polyomino
     527            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     528            sage: p - (2,2,2)
     529            Polyomino: [(-2, -2, -2), (-1, -2, -2), (-1, -1, -2), (-1, -1, -1), (-1, 0, -2)], Color: deeppink
     530        """
     531        if not len(v) == self._dimension:
     532            raise ValueError, "Dimension of input vector must match the dimension of the polyomino"
     533        v = vector(v)
     534        return Polyomino([vector(p)-v for p in self], color=self._color)
     535
     536    def __add__(self, v):
     537        r"""
     538        Return a translated copy of self by the vector v.
     539
     540        INPUT:
     541
     542        - ``v`` - tuple
     543
     544        OUPUT:
     545
     546            polyomino
     547
     548        EXAMPLES::
     549
     550            sage: from sage.combinat.tiling import Polyomino
     551            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     552            sage: p + (2,2,2)
     553            Polyomino: [(2, 2, 2), (3, 2, 2), (3, 3, 2), (3, 3, 3), (3, 4, 2)], Color: deeppink
     554        """
     555        if not len(v) == self._dimension:
     556            raise ValueError, "Dimension of input vector must match the dimension of the polyomino"
     557        v = vector(v)
     558        return Polyomino([vector(p)+v for p in self], color=self._color)
     559
     560    def __rmul__(self, m):
     561        r"""
     562        Return the image of the polyomino under the application of the
     563        matrix m.
     564
     565        INPUT:
     566
     567        - ``m`` - square matrix, matching the dimension of self.
     568
     569        OUPUT:
     570
     571            Polyomino
     572
     573        EXAMPLES::
     574
     575            sage: from sage.combinat.tiling import Polyomino
     576            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     577            sage: m = matrix(3, [1,0,0,0,1,0,0,0,1])
     578            sage: m * p
     579            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     580            sage: m = matrix(3, [1,0,0,0,0,-1,0,1,0])
     581            sage: m * p
     582            Polyomino: [(0, 0, 0), (1, -1, 1), (1, 0, 0), (1, 0, 1), (1, 0, 2)], Color: deeppink
     583
     584        TESTS::
     585
     586            sage: m = matrix(2, [1,0,0,1])
     587            sage: m * p
     588            Traceback (most recent call last):
     589            ...
     590            ValueError: Dimension of input matrix must match the dimension of the polyomino
     591        """
     592        if not m.nrows() == m.ncols() == self._dimension:
     593            raise ValueError, "Dimension of input matrix must match the dimension of the polyomino"
     594        return Polyomino([m * vector(p) for p in self], color=self._color)
     595
     596    def bounding_box(self):
     597        r"""
     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.bounding_box()
     603            [[0, 0, 0], [1, 2, 1]]
     604        """
     605        zipped_coords = zip(*self)
     606        return [map(min, zipped_coords), map(max, zipped_coords)]
     607
     608    def translated(self, box):
     609        r"""
     610        Returns an iterator over the translated images of self inside a
     611        box.
     612
     613        INPUT:
     614
     615        - ``box`` - tuple, size of the box
     616
     617        OUTPUT:
     618           
     619            iterator of 3D polyominoes
     620
     621        EXAMPLES::
     622           
     623            sage: from sage.combinat.tiling import Polyomino
     624            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     625            sage: for t in p.translated(box=(5,8,2)): t
     626            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     627            Polyomino: [(0, 1, 0), (1, 1, 0), (1, 2, 0), (1, 2, 1), (1, 3, 0)], Color: deeppink
     628            Polyomino: [(0, 2, 0), (1, 2, 0), (1, 3, 0), (1, 3, 1), (1, 4, 0)], Color: deeppink
     629            Polyomino: [(0, 3, 0), (1, 3, 0), (1, 4, 0), (1, 4, 1), (1, 5, 0)], Color: deeppink
     630            Polyomino: [(0, 4, 0), (1, 4, 0), (1, 5, 0), (1, 5, 1), (1, 6, 0)], Color: deeppink
     631            Polyomino: [(0, 5, 0), (1, 5, 0), (1, 6, 0), (1, 6, 1), (1, 7, 0)], Color: deeppink
     632            Polyomino: [(1, 0, 0), (2, 0, 0), (2, 1, 0), (2, 1, 1), (2, 2, 0)], Color: deeppink
     633            Polyomino: [(1, 1, 0), (2, 1, 0), (2, 2, 0), (2, 2, 1), (2, 3, 0)], Color: deeppink
     634            Polyomino: [(1, 2, 0), (2, 2, 0), (2, 3, 0), (2, 3, 1), (2, 4, 0)], Color: deeppink
     635            Polyomino: [(1, 3, 0), (2, 3, 0), (2, 4, 0), (2, 4, 1), (2, 5, 0)], Color: deeppink
     636            Polyomino: [(1, 4, 0), (2, 4, 0), (2, 5, 0), (2, 5, 1), (2, 6, 0)], Color: deeppink
     637            Polyomino: [(1, 5, 0), (2, 5, 0), (2, 6, 0), (2, 6, 1), (2, 7, 0)], Color: deeppink
     638            Polyomino: [(2, 0, 0), (3, 0, 0), (3, 1, 0), (3, 1, 1), (3, 2, 0)], Color: deeppink
     639            Polyomino: [(2, 1, 0), (3, 1, 0), (3, 2, 0), (3, 2, 1), (3, 3, 0)], Color: deeppink
     640            Polyomino: [(2, 2, 0), (3, 2, 0), (3, 3, 0), (3, 3, 1), (3, 4, 0)], Color: deeppink
     641            Polyomino: [(2, 3, 0), (3, 3, 0), (3, 4, 0), (3, 4, 1), (3, 5, 0)], Color: deeppink
     642            Polyomino: [(2, 4, 0), (3, 4, 0), (3, 5, 0), (3, 5, 1), (3, 6, 0)], Color: deeppink
     643            Polyomino: [(2, 5, 0), (3, 5, 0), (3, 6, 0), (3, 6, 1), (3, 7, 0)], Color: deeppink
     644            Polyomino: [(3, 0, 0), (4, 0, 0), (4, 1, 0), (4, 1, 1), (4, 2, 0)], Color: deeppink
     645            Polyomino: [(3, 1, 0), (4, 1, 0), (4, 2, 0), (4, 2, 1), (4, 3, 0)], Color: deeppink
     646            Polyomino: [(3, 2, 0), (4, 2, 0), (4, 3, 0), (4, 3, 1), (4, 4, 0)], Color: deeppink
     647            Polyomino: [(3, 3, 0), (4, 3, 0), (4, 4, 0), (4, 4, 1), (4, 5, 0)], Color: deeppink
     648            Polyomino: [(3, 4, 0), (4, 4, 0), (4, 5, 0), (4, 5, 1), (4, 6, 0)], Color: deeppink
     649            Polyomino: [(3, 5, 0), (4, 5, 0), (4, 6, 0), (4, 6, 1), (4, 7, 0)], Color: deeppink
     650
     651        This method is independant of the translation of the polyomino::
     652
     653            sage: q = Polyomino([(0,0,0), (1,0,0)])
     654            sage: list(q.translated((2,2,1)))
     655            [Polyomino: [(0, 0, 0), (1, 0, 0)], Color: gray, Polyomino: [(0, 1, 0), (1, 1, 0)], Color: gray]
     656            sage: q = Polyomino([(34,7,-9), (35,7,-9)])
     657            sage: list(q.translated((2,2,1)))
     658            [Polyomino: [(0, 0, 0), (1, 0, 0)], Color: gray, Polyomino: [(0, 1, 0), (1, 1, 0)], Color: gray]
     659
     660        Inside smaller boxes::
     661           
     662            sage: list(p.translated(box=(2,2,3)))
     663            []
     664            sage: list(p.translated(box=(2,3,2)))
     665            [Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink]
     666            sage: list(p.translated(box=(3,2,2)))
     667            []
     668            sage: list(p.translated(box=(1,1,1)))
     669            []
     670            sage: list(p.translated(box=(1,1,-1)))
     671            []
     672        """
     673        if not len(box) == self._dimension:
     674            raise ValueError, "Dimension of input box must match the dimension of the polyomino"
     675        minxyz,maxxyz = map(vector, self.bounding_box())
     676        size = maxxyz - minxyz
     677        cano = self.canonical()
     678        for v in xmrange(vector(box) - vector(size), tuple):
     679            yield cano + v
     680
     681    def translated_orthogonals(self, box, orientation_preserving=True):
     682        r"""
     683        Return the translated and rotated of self that lies in the box.
     684
     685        INPUT:
     686
     687        - ``box`` - tuple of size three, size of the box
     688        - ``orientation_preserving`` - bool (optional, default: ``True``),
     689          whether the orientation is preserved
     690
     691        EXAMPLES::
     692
     693            sage: from sage.combinat.tiling import Polyomino
     694            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     695            sage: L = list(p.translated_orthogonals(box=(5,8,2)))
     696            sage: len(L)
     697            360
     698
     699        ::
     700
     701            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,2,0),(1,2,1)], color='orange')
     702            sage: L = list(p.translated_orthogonals(box=(5,8,2)))
     703            sage: len(L)
     704            180
     705
     706        ::
     707
     708            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,2,0),(1,2,1)], color='orange')
     709            sage: L = list(p.translated_orthogonals((5,8,2), False))
     710            sage: len(L)
     711            360
     712        """
     713        if not len(box) == self._dimension:
     714            raise ValueError, "Dimension of input box must match the dimension of the polyomino"
     715        all_distinct_cano = set(self.canonical_orthogonals(orientation_preserving))
     716        for cano in all_distinct_cano:
     717            for t in cano.translated(box=box):
     718                yield t
     719
     720
     721    def middle_of_neighbor_coords(self):
     722        r"""
     723        Return the list of middle of neighbor coords.
     724
     725        This is use to draw cube in between two neighbor cubes.
     726
     727        EXAMPLES::
     728
     729            sage: from sage.combinat.tiling import Polyomino
     730            sage: p = Polyomino([(0,0,0),(0,0,1)])
     731            sage: list(p.middle_of_neighbor_coords())
     732            [(0.0, 0.0, 0.5)]
     733
     734        In 3d::
     735
     736            sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
     737            sage: L = sorted(p.middle_of_neighbor_coords())
     738            sage: for a in L: a
     739            (0.5, 0.0, 0.0)
     740            (1.0, 0.5, 0.0)
     741            (1.0, 1.0, 0.5)
     742            (1.0, 1.5, 0.0)
     743
     744        In 2d::
     745
     746            sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)])
     747            sage: L = sorted(p.middle_of_neighbor_coords())
     748            sage: for a in L: a
     749            (0.5, 0.0)
     750            (1.0, 0.5)
     751            (1.0, 1.5)
     752        """
     753        for P, Q in itertools.combinations(self, 2):
     754            P, Q = vector(P), vector(Q)
     755            s = sorted(map(abs, Q-P))
     756            firsts = s[:-1]
     757            last = s[-1]
     758            if last == 1 and all(f == 0 for f in firsts):
     759                yield (P + Q) / 2.0
     760
     761    def show3d(self, size=0.75):
     762        r"""
     763        Returns a 3d Graphic object representing the polyomino.
     764
     765        .. NOTE::
     766
     767            For now this is simply a bunch of intersecting cubes. It could
     768            be improved to give better results.
     769
     770        INPUT:
     771
     772        - ``self`` - a polyomino of dimension 3
     773        - ``size`` - number (optional, default: ``0.75``), the size of the
     774          ``1 \times 1 \times 1`` cubes
     775
     776        EXAMPLES::
     777
     778            sage: from sage.combinat.tiling import Polyomino
     779            sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
     780            sage: p.show3d()
     781        """
     782        assert self._dimension == 3, "To show a polyomino in 3d, its dimension must be 3."
     783        G = Graphics()
     784        for p in self:
     785            G += cube(p, color=self._color, size=size)
     786        for m in self.middle_of_neighbor_coords():
     787            G += cube(m, color=self._color, size=size)
     788        return G
     789
     790    def show2d(self, size=0.75):
     791        r"""
     792        Returns a 2d Graphic object representing the polyomino.
     793
     794        .. NOTE::
     795
     796            For now this is simply a bunch of intersecting cubes. It could
     797            be improved to give better results.
     798
     799        INPUT:
     800
     801        - ``self`` - a polyomino of dimension 2
     802        - ``size`` - number (optional, default: ``0.75``), the size of the
     803          ``1 \times 1 \times 1`` cubes
     804
     805        EXAMPLES::
     806
     807            sage: from sage.combinat.tiling import Polyomino
     808            sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)], color='deeppink')
     809            sage: p.show2d()              # long time (0.5s)
     810        """
     811        assert self._dimension == 2, "To show a polyomino in 2d, its dimension must be 2."
     812        h = size / 2.0
     813        G = Graphics()
     814        for a,b in self:
     815            G += polygon([(a-h,b-h), (a+h,b-h), (a+h,b+h), (a-h,b+h), (a-h,b-h)], color=self._color)
     816        for a,b in self.middle_of_neighbor_coords():
     817            G += polygon([(a-h,b-h), (a+h,b-h), (a+h,b+h), (a-h,b+h), (a-h,b-h)], color=self._color)
     818        return G
     819
     820#######################
     821# General tiling solver
     822#######################
     823class TilingSolver(SageObject):
     824    r"""
     825    Tiling solver
     826
     827    Solve the problem of tiling a rectangular box with a certain number
     828    of pieces, called polyominoes, where each polyomino must be used
     829    exactly once.
     830
     831    INPUT:
     832
     833    - ``pieces`` - iterable of Polyominoes
     834    - ``box`` - tuple, size of the box
     835    - ``rotation`` - bool (optional, default: ``True``), whether to allow
     836      rotations
     837    - ``reflection`` - bool (optional, default: ``False``), whether to allow
     838      reflections
     839    - ``reusable`` - bool (optional, default: ``False``), whether to allow
     840      the pieces to be reused
     841
     842    EXAMPLES:
     843
     844    By default, rotations are allowed and reflections are not allowed::
     845
     846        sage: from sage.combinat.tiling import TilingSolver, Polyomino
     847        sage: p = Polyomino([(0,0,0)])
     848        sage: q = Polyomino([(0,0,0), (0,0,1)])
     849        sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     850        sage: T = TilingSolver([p,q,r], box=(1,1,6))
     851        sage: T
     852        Tiling solver of 3 pieces into the box (1, 1, 6)
     853        Rotation allowed: True
     854        Reflection allowed: False
     855        Reusing pieces allowed: False
     856
     857    Solutions are given by an iterator::
     858
     859        sage: it = T.solve()
     860        sage: for p in it.next(): p
     861        Polyomino: [(0, 0, 0)], Color: gray
     862        Polyomino: [(0, 0, 1), (0, 0, 2)], Color: gray
     863        Polyomino: [(0, 0, 3), (0, 0, 4), (0, 0, 5)], Color: gray
     864
     865    Another solution::
     866
     867        sage: for p in it.next(): p
     868        Polyomino: [(0, 0, 0)], Color: gray
     869        Polyomino: [(0, 0, 1), (0, 0, 2), (0, 0, 3)], Color: gray
     870        Polyomino: [(0, 0, 4), (0, 0, 5)], Color: gray
     871
     872    TESTS::
     873
     874        sage: T = TilingSolver([p,q,r], box=(1,1,6), rotation=False, reflection=True)
     875        Traceback (most recent call last):
     876        ...
     877        NotImplementedError: When reflection is allowed and rotation is not allowed
     878    """
     879    def __init__(self, pieces, box, rotation=True, reflection=False, reusable=False):
     880        r"""
     881        Constructor.
     882
     883        EXAMPLES::
     884
     885            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     886            sage: p = Polyomino([(0,0,0)])
     887            sage: q = Polyomino([(0,0,0), (0,0,1)])
     888            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     889            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     890            sage: T
     891            Tiling solver of 3 pieces into the box (1, 1, 6)
     892            Rotation allowed: True
     893            Reflection allowed: False
     894            Reusing pieces allowed: False
     895        """
     896        self._pieces = pieces
     897        self._box = box
     898        self._rotation = rotation
     899        self._reflection = reflection
     900        if not self._rotation and self._reflection:
     901            raise NotImplementedError, "When reflection is allowed and rotation is not allowed"
     902        self._reusable = reusable
     903
     904    def __repr__(self):
     905        r"""
     906        String representation
     907
     908        EXAMPLES::
     909
     910            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     911            sage: p = Polyomino([(0,0,0)])
     912            sage: q = Polyomino([(0,0,0), (0,0,1)])
     913            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     914            sage: TilingSolver([p,q,r], box=(1,1,6))
     915            Tiling solver of 3 pieces into the box (1, 1, 6)
     916            Rotation allowed: True
     917            Reflection allowed: False
     918            Reusing pieces allowed: False
     919
     920        """
     921        N = len(self._pieces)
     922        s = "Tiling solver of %s pieces into the box %s\n" % (N, self._box)
     923        s += "Rotation allowed: %s\n" % self._rotation
     924        s += "Reflection allowed: %s\n" % self._reflection
     925        s += "Reusing pieces allowed: %s" % self._reusable
     926        return s
     927
     928    def is_suitable(self):
     929        r"""
     930        Return whether the volume of the box is equal to sum of the volume
     931        of the polyominoes and the number of rows sent to the DLX solver is
     932        larger than zero.
     933
     934        If these conditions are not verified, then the problem is not suitable
     935        in the sense that there are no solution.
     936
     937        .. NOTE::
     938
     939            The DLX solver throws a Segmentation Fault when the
     940            number of rows is zero::
     941               
     942                sage: from sage.combinat.matrices.dancing_links import dlx_solver
     943                sage: rows = []
     944                sage: x = dlx_solver(rows)
     945                sage: x.search()        # not tested
     946                BOOM !!!
     947
     948        EXAMPLES::
     949
     950            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     951            sage: p = Polyomino([(0,0,0)])
     952            sage: q = Polyomino([(0,0,0), (0,0,1)])
     953            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     954            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     955            sage: T.is_suitable()
     956            True
     957            sage: T = TilingSolver([p,q,r], box=(1,1,7))
     958            sage: T.is_suitable()
     959            False
     960        """
     961        if self._reusable:
     962            return len(self.rows()) != 0
     963        else:
     964            return (sum(len(p) for p in self.pieces()) == prod(self._box)
     965                    and len(self.rows()) != 0)
     966
     967    def pieces(self):
     968        r"""
     969        Return the list of pieces.
     970
     971        OUTPUT:
     972           
     973            list of 3D polyominoes
     974       
     975        EXAMPLES::
     976
     977            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     978            sage: p = Polyomino([(0,0,0)])
     979            sage: q = Polyomino([(0,0,0), (0,0,1)])
     980            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     981            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     982            sage: for p in T._pieces: p
     983            Polyomino: [(0, 0, 0)], Color: gray
     984            Polyomino: [(0, 0, 0), (0, 0, 1)], Color: gray
     985            Polyomino: [(0, 0, 0), (0, 0, 1), (0, 0, 2)], Color: gray
     986        """
     987        return self._pieces
     988
     989    def space(self):
     990        r"""
     991        Returns an iterator over all the non negative integer coordinates
     992        contained in the box.
     993
     994        EXAMPLES::
     995
     996            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     997            sage: p = Polyomino([(0,0,0)])
     998            sage: q = Polyomino([(0,0,0), (0,0,1)])
     999            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1000            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1001            sage: list(T.space())
     1002            [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 0, 4), (0, 0, 5)]
     1003        """
     1004        return xmrange(self._box, tuple)
     1005
     1006    @cached_method
     1007    def coord_to_int_dict(self):
     1008        r"""
     1009        Returns a dictionary mapping coordinates to integers.
     1010
     1011        OUTPUT:
     1012
     1013            dict
     1014
     1015        EXAMPLES::
     1016           
     1017            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1018            sage: p = Polyomino([(0,0,0)])
     1019            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1020            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1021            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1022            sage: A = T.coord_to_int_dict()
     1023            sage: sorted(A.iteritems())
     1024            [((0, 0, 0), 3), ((0, 0, 1), 4), ((0, 0, 2), 5), ((0, 0, 3), 6), ((0, 0, 4), 7), ((0, 0, 5), 8)]
     1025
     1026        Reusable pieces::
     1027
     1028            sage: p = Polyomino([(0,0), (0,1)])
     1029            sage: q = Polyomino([(0,0), (0,1), (1,0), (1,1)])
     1030            sage: T = TilingSolver([p,q], box=[3,2], reusable=True)
     1031            sage: B = T.coord_to_int_dict()
     1032            sage: sorted(B.iteritems())
     1033            [((0, 0), 0), ((0, 1), 1), ((1, 0), 2), ((1, 1), 3), ((2, 0), 4), ((2, 1), 5)]
     1034        """
     1035        if self._reusable:
     1036            return dict( (c,i) for i,c in enumerate(self.space()) )
     1037        else:
     1038            number_of_pieces = len(self._pieces)
     1039            return dict( (c,i+number_of_pieces) for i,c in enumerate(self.space()) )
     1040
     1041    @cached_method
     1042    def int_to_coord_dict(self):
     1043        r"""
     1044        Returns a dictionary mapping integers to coordinates.
     1045
     1046        EXAMPLES::
     1047           
     1048            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1049            sage: p = Polyomino([(0,0,0)])
     1050            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1051            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1052            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1053            sage: B = T.int_to_coord_dict()
     1054            sage: sorted(B.iteritems())
     1055            [(3, (0, 0, 0)), (4, (0, 0, 1)), (5, (0, 0, 2)), (6, (0, 0, 3)), (7, (0, 0, 4)), (8, (0, 0, 5))]
     1056
     1057        Reusable pieces::
     1058
     1059            sage: from sage.combinat.tiling import Polyomino, TilingSolver
     1060            sage: p = Polyomino([(0,0), (0,1)])
     1061            sage: q = Polyomino([(0,0), (0,1), (1,0), (1,1)])
     1062            sage: T = TilingSolver([p,q], box=[3,2], reusable=True)
     1063            sage: B = T.int_to_coord_dict()
     1064            sage: sorted(B.iteritems())
     1065            [(0, (0, 0)), (1, (0, 1)), (2, (1, 0)), (3, (1, 1)), (4, (2, 0)), (5, (2, 1))]
     1066
     1067        TESTS:
     1068
     1069        The methods ``int_to_coord_dict`` and ``coord_to_int_dict`` returns
     1070        dictionary that are inverse of each other::
     1071
     1072            sage: A = T.coord_to_int_dict()
     1073            sage: B = T.int_to_coord_dict()
     1074            sage: all(A[B[i]] == i for i in B)
     1075            True
     1076            sage: all(B[A[i]] == i for i in A)
     1077            True
     1078
     1079        """
     1080        if self._reusable:
     1081            return dict( (i,c) for i,c in enumerate(self.space()) )
     1082        else:
     1083            number_of_pieces = len(self._pieces)
     1084            return dict( (i+number_of_pieces,c) for i,c in enumerate(self.space()) )
     1085
     1086    @cached_method
     1087    def rows(self, verbose=False):
     1088        r"""
     1089        Creation of the rows
     1090
     1091        EXAMPLES::
     1092           
     1093            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1094            sage: p = Polyomino([(0,0,0)])
     1095            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1096            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1097            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1098            sage: rows = T.rows()
     1099            sage: for row in rows: row
     1100            [0, 3]
     1101            [0, 4]
     1102            [0, 5]
     1103            [0, 6]
     1104            [0, 7]
     1105            [0, 8]
     1106            [1, 3, 4]
     1107            [1, 4, 5]
     1108            [1, 5, 6]
     1109            [1, 6, 7]
     1110            [1, 8, 7]
     1111            [2, 3, 4, 5]
     1112            [2, 4, 5, 6]
     1113            [2, 5, 6, 7]
     1114            [2, 8, 6, 7]
     1115        """
     1116        coord_to_int = self.coord_to_int_dict()
     1117        rows = []
     1118        for i,p in enumerate(self._pieces):
     1119            if self._rotation and self._reflection:
     1120                it = p.translated_orthogonals(self._box, orientation_preserving=False)
     1121            elif self._rotation and not self._reflection:
     1122                it = p.translated_orthogonals(self._box, orientation_preserving=True)
     1123            elif not self._rotation and self._reflection:
     1124                raise NotImplementedError, "Reflection allowed, Rotation not allowed is not implemented"
     1125            else:
     1126                it = p.translated(self._box)
     1127            if self._reusable:
     1128                for q in it:
     1129                    rows.append([coord_to_int[coord] for coord in q])
     1130            else:
     1131                for q in it:
     1132                    rows.append([i] + [coord_to_int[coord] for coord in q])
     1133        if verbose:
     1134            print "Number of rows : %s" % len(rows)
     1135            print "Number of distinct rows : %s" % len(set(tuple(sorted(row)) for row in rows))
     1136        return rows
     1137
     1138    def dlx_solver(self):
     1139        r"""
     1140        Return the sage DLX solver of that 3D tiling problem.
     1141
     1142        OUTPUT:
     1143
     1144            DLX Solver
     1145
     1146        EXAMPLES::
     1147
     1148            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1149            sage: p = Polyomino([(0,0,0)])
     1150            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1151            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1152            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1153            sage: x = T.dlx_solver()
     1154            sage: x
     1155            <sage.combinat.matrices.dancing_links.dancing_linksWrapper object at ...>
     1156        """
     1157        from sage.combinat.matrices.dancing_links import dlx_solver
     1158        rows = self.rows()
     1159        assert len(rows) != 0, "Number of rows given to the DLX solver must not be zero"
     1160        x = dlx_solver(rows)
     1161        return x
     1162
     1163    def dlx_solutions(self):
     1164        r"""
     1165        Return an iterator over the row indices of the solutions.
     1166
     1167        OUPUT:
     1168
     1169            iterator
     1170
     1171        EXAMPLES::
     1172
     1173            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1174            sage: p = Polyomino([(0,0,0)])
     1175            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1176            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1177            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1178            sage: list(T.dlx_solutions())
     1179            [[0, 7, 14], [0, 12, 10], [6, 13, 5], [6, 14, 2], [11, 9, 5], [11, 10, 3]]
     1180        """
     1181        if len(self.rows()) == 0:
     1182            raise StopIteration
     1183        x = self.dlx_solver()
     1184        while x.search() == 1:
     1185            yield x.get_solution()
     1186
     1187    def dlx_partial_solutions(self):
     1188        r"""
     1189        Return an iterator over the row indices of solutions and of partial
     1190        solutions, i.e. the common part of two consecutive solutions.
     1191
     1192        The purpose is to illustrate the backtracking and construct an
     1193        animation of the evolution of solutions.
     1194
     1195        OUPUT:
     1196
     1197            iterator
     1198
     1199        EXAMPLES::
     1200
     1201            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1202            sage: p = Polyomino([(0,0,0)])
     1203            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1204            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1205            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1206            sage: list(T.dlx_solutions())
     1207            [[0, 7, 14], [0, 12, 10], [6, 13, 5], [6, 14, 2], [11, 9, 5], [11, 10, 3]]
     1208            sage: list(T.dlx_partial_solutions())
     1209            [[0, 7, 14], [0], [0, 12, 10], [], [6, 13, 5], [6], [6, 14, 2], [], [11, 9, 5], [11], [11, 10, 3]]
     1210        """
     1211        it = self.dlx_solutions()
     1212        B = it.next()
     1213        while True:
     1214            yield B
     1215            A, B = B, it.next()
     1216            common = []
     1217            for a,b in itertools.izip(A,B):
     1218                if a == b:
     1219                    common.append(a)
     1220            yield common
     1221
     1222    def solve(self, include_partial=False):
     1223        r"""
     1224        Returns an iterator of list of 3D polyominoes that are an exact
     1225        cover of the box.
     1226
     1227        INPUT:
     1228
     1229        - ``include_partial`` - boolean (optional, default: ``False``),
     1230          whether to include partial (incomplete) solutions, i.e. the
     1231          common part between two consecutive solutions.
     1232       
     1233        OUTPUT:
     1234
     1235            iterator of list of 3D polyominoes
     1236
     1237        EXAMPLES::
     1238
     1239            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1240            sage: p = Polyomino([(0,0,0)])
     1241            sage: q = Polyomino([(0,0,0), (0,0,1)])
     1242            sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
     1243            sage: T = TilingSolver([p,q,r], box=(1,1,6))
     1244            sage: it = T.solve()
     1245            sage: for p in it.next(): p
     1246            Polyomino: [(0, 0, 0)], Color: gray
     1247            Polyomino: [(0, 0, 1), (0, 0, 2)], Color: gray
     1248            Polyomino: [(0, 0, 3), (0, 0, 4), (0, 0, 5)], Color: gray
     1249            sage: for p in it.next(): p
     1250            Polyomino: [(0, 0, 0)], Color: gray
     1251            Polyomino: [(0, 0, 1), (0, 0, 2), (0, 0, 3)], Color: gray
     1252            Polyomino: [(0, 0, 4), (0, 0, 5)], Color: gray
     1253            sage: for p in it.next(): p
     1254            Polyomino: [(0, 0, 0), (0, 0, 1)], Color: gray
     1255            Polyomino: [(0, 0, 2), (0, 0, 3), (0, 0, 4)], Color: gray
     1256            Polyomino: [(0, 0, 5)], Color: gray
     1257
     1258        Including the partial solutions::
     1259
     1260            sage: it = T.solve(include_partial=True)
     1261            sage: for p in it.next(): p
     1262            Polyomino: [(0, 0, 0)], Color: gray
     1263            Polyomino: [(0, 0, 1), (0, 0, 2)], Color: gray
     1264            Polyomino: [(0, 0, 3), (0, 0, 4), (0, 0, 5)], Color: gray
     1265            sage: for p in it.next(): p
     1266            Polyomino: [(0, 0, 0)], Color: gray
     1267            sage: for p in it.next(): p
     1268            Polyomino: [(0, 0, 0)], Color: gray
     1269            Polyomino: [(0, 0, 1), (0, 0, 2), (0, 0, 3)], Color: gray
     1270            Polyomino: [(0, 0, 4), (0, 0, 5)], Color: gray
     1271            sage: for p in it.next(): p
     1272            sage: for p in it.next(): p
     1273            Polyomino: [(0, 0, 0), (0, 0, 1)], Color: gray
     1274            Polyomino: [(0, 0, 2), (0, 0, 3), (0, 0, 4)], Color: gray
     1275            Polyomino: [(0, 0, 5)], Color: gray
     1276
     1277        TESTS::
     1278
     1279            sage: T = TilingSolver([p,q,r], box=(1,1,7))
     1280            sage: T.solve().next()
     1281            Traceback (most recent call last):
     1282            ...
     1283            StopIteration
     1284
     1285        """
     1286        if not self.is_suitable():
     1287            raise StopIteration
     1288        int_to_coord = self.int_to_coord_dict()
     1289        rows = self.rows()
     1290        if include_partial:
     1291            it = self.dlx_partial_solutions()
     1292        else:
     1293            it = self.dlx_solutions()
     1294        for solution in it:
     1295            pentos = []
     1296            for row_number in solution:
     1297                row = rows[row_number]
     1298                if self._reusable:
     1299                    coords = [int_to_coord[i] for i in row]
     1300                    p = Polyomino(coords)
     1301                else:
     1302                    no = row[0]
     1303                    coords = [int_to_coord[i] for i in row[1:]]
     1304                    p = Polyomino(coords, color=self._pieces[no].color())
     1305                pentos.append(p)
     1306            yield pentos
     1307
     1308    def number_of_solutions(self):
     1309        r"""
     1310        Return the number of distinct solutions.
     1311
     1312        OUPUT:
     1313
     1314            integer
     1315
     1316        EXAMPLES::
     1317
     1318            sage: from sage.combinat.tiling import TilingSolver, Polyomino
     1319            sage: p = Polyomino([(0,0)])
     1320            sage: q = Polyomino([(0,0), (0,1)])
     1321            sage: r = Polyomino([(0,0), (0,1), (0,2)])
     1322            sage: T = TilingSolver([p,q,r], box=(1,6))
     1323            sage: T.number_of_solutions()
     1324            6
     1325
     1326        ::
     1327
     1328            sage: T = TilingSolver([p,q,r], box=(1,7))
     1329            sage: T.number_of_solutions()
     1330            0
     1331        """
     1332        if not self.is_suitable():
     1333            return 0
     1334        x = self.dlx_solver()
     1335        N = 0
     1336        while x.search() == 1:
     1337            N += 1
     1338        return N
     1339
  • sage/games/quantumino.py

    diff --git a/sage/games/quantumino.py b/sage/games/quantumino.py
    a b  
    22r"""
    33Family Games America's Quantumino solver
    44
    5 This module allows to solve the Quantumino puzzle made by Family Games
    6 America (see [1] and [2]). This puzzle was left at the dinner room of the
    7 Laboratoire de Combinatoire Informatique Mathématique in Montréal by Franco
    8 Saliola during winter 2011.
     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.
    911
    10 The solution uses the dancing links code which is in Sage. Dancing links
    11 were originally introduced by Donald Knuth in 2000 [3]. In particular,
    12 Knuth used dancing links to solve tilings of a region by 2D pentaminos.
    13 Here we extend the method for 3D pentaminos.
     12The solution uses the dancing links code which is in Sage and is based on
     13the more general code available in the module ``sage.combinat.tiling``.
     14Dancing links were originally introduced by Donald Knuth in 2000 [3]. In
     15particular, Knuth used dancing links to solve tilings of a region by 2D
     16pentaminos.  Here we extend the method for 3D pentaminos.
     17
     18This module defines two classes :
     19
     20- :class:`sage.games.quantumino.QuantuminoState` class, to represent a
     21  state of the Quantumino game, i.e. a solution or a partial solution.
     22
     23- :class:`sage.games.quantumino.QuantuminoSolver` class, to find, enumerate
     24  and count the number of solutions of the Quantumino game where one of the
     25  piece is put aside.
    1426
    1527AUTHOR:
    1628
    17     - Sébastien Labbé, April 28th, 2011
     29    - Sebastien Labbe, April 28th, 2011
    1830
    1931DESCRIPTION (from [1]):
    2032
    DESCRIPTION (from [1]): 
    2234    Pentamino games have been taken to a whole different level; a 3-D
    2335    level, with this colorful creation! Using the original pentamino
    2436    arrangements of 5 connected squares which date from 1907, players are
    25     encouraged to «think inside the box» as they try to fit 16 of the 17
     37    encouraged to "think inside the box" as they try to fit 16 of the 17
    2638    3-D pentamino pieces inside the playing perimeters. Remove a different
    2739    piece each time you play for an entirely new challenge! Thousands of
    2840    solutions to be found!
    29     Quantumino hands-on educational tool where players learn how shapes
     41    Quantumino hands-on educational tool where players learn how shapes
    3042    can be transformed or arranged into predefined shapes and spaces.
    3143    Includes:
    3244    1 wooden frame, 17 wooden blocks, instruction booklet.
    DESCRIPTION (from [1]): 
    3547
    3648EXAMPLES:
    3749
    38 Here are the 17 wooden blocks of the Quantumino puzzle numbered from 0 to
    39 16 in the following 3d picture::
     50Here are the 17 wooden blocks of the Quantumino puzzle numbered from 0 to 16 in
     51the following 3d picture. They will show up in 3D in your default (=Jmol)
     52viewer::
    4053
    41     sage: from sage.games.quantumino import quantumino_solver
    42     sage: quantumino_solver.show_pentaminos()
     54    sage: from sage.games.quantumino import show_pentaminos
     55    sage: show_pentaminos()
    4356
    44 To solve the puzzle without using the block numbered 12::
     57To solve the puzzle where the pentamino numbered 12 is put aside::
    4558
    46     sage: s = quantumino_solver.get_solution(12)
    47     sage: s
    48     Quantumino solution without the following pentamino :
    49     3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (2, 1, 1)], Color: blue
    50     sage: s.show3d()      # long time (<1s)
     59    sage: from sage.games.quantumino import QuantuminoSolver
     60    sage: s = QuantuminoSolver(12).solve().next()         # long time (10 s)
     61    sage: s                                               # long time (<1s)
     62    Quantumino state where the following pentamino is put aside :
     63    Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (2, 1, 1)], Color: blue
     64    sage: s.show3d()                                      # long time (<1s)
    5165
    52 To solve the puzzle without using the block numbered 7::
     66To remove the frame::
    5367
    54     sage: s = quantumino_solver.get_solution(7)
    55     sage: s
    56     Quantumino solution without the following pentamino :
    57     3D Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
    58     sage: s.show3d()      # long time (<1s)
     68    sage: s.show3d().show(frame=False)                    # long time (<1s)
    5969
    60 To get all the solutions, use the iterator. Note that finding the first
    61 solution is the most time consuming because it needs to creates the
    62 complete data to describe the problem::
     70To solve the puzzle where the pentamino numbered 7 is put aside::
    6371
    64     sage: it = quantumino_solver.solutions_iterator(7)
    65     sage: it.next()                                     # (1s)
    66     Quantumino solution without the following pentamino :
    67     3D Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
    68     sage: it.next()                                     # (0.001s)
    69     Quantumino solution without the following pentamino :
    70     3D Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
    71     sage: it.next()                                     # (0.001s)
    72     Quantumino solution without the following pentamino :
    73     3D Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
     72    sage: s = QuantuminoSolver(7).solve().next()          # long time (10 s)
     73    sage: s                                               # long time (<1s)
     74    Quantumino state where the following pentamino is put aside :
     75    Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
     76    sage: s.show3d()                                      # long time (<1s)
     77
     78The solution is iterable. This may be used to explicit the positions of each
     79pentamino::
     80
     81    sage: for p in s: p                                   # long time (<1s)
     82    Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     83    Polyomino: [(0, 0, 1), (0, 1, 0), (0, 1, 1), (0, 2, 1), (1, 2, 1)], Color: deeppink
     84    Polyomino: [(0, 2, 0), (0, 3, 0), (0, 4, 0), (1, 4, 0), (1, 4, 1)], Color: green
     85    Polyomino: [(0, 3, 1), (1, 3, 1), (2, 2, 0), (2, 2, 1), (2, 3, 1)], Color: green
     86    Polyomino: [(1, 3, 0), (2, 3, 0), (2, 4, 0), (2, 4, 1), (3, 4, 0)], Color: red
     87    Polyomino: [(1, 0, 1), (2, 0, 1), (2, 1, 0), (2, 1, 1), (3, 1, 1)], Color: red
     88    Polyomino: [(2, 0, 0), (3, 0, 0), (3, 0, 1), (3, 1, 0), (4, 0, 0)], Color: gray
     89    Polyomino: [(3, 2, 0), (4, 0, 1), (4, 1, 0), (4, 1, 1), (4, 2, 0)], Color: purple
     90    Polyomino: [(3, 2, 1), (3, 3, 0), (3, 3, 1), (4, 2, 1), (4, 3, 1)], Color: yellow
     91    Polyomino: [(3, 4, 1), (3, 5, 1), (4, 3, 0), (4, 4, 0), (4, 4, 1)], Color: blue
     92    Polyomino: [(0, 4, 1), (0, 5, 0), (0, 5, 1), (0, 6, 1), (1, 5, 0)], Color: midnightblue
     93    Polyomino: [(0, 6, 0), (0, 7, 0), (0, 7, 1), (1, 7, 0), (2, 7, 0)], Color: darkblue
     94    Polyomino: [(1, 7, 1), (2, 6, 0), (2, 6, 1), (2, 7, 1), (3, 6, 0)], Color: blue
     95    Polyomino: [(1, 5, 1), (1, 6, 0), (1, 6, 1), (2, 5, 0), (2, 5, 1)], Color: yellow
     96    Polyomino: [(3, 6, 1), (3, 7, 0), (3, 7, 1), (4, 5, 1), (4, 6, 1)], Color: purple
     97    Polyomino: [(3, 5, 0), (4, 5, 0), (4, 6, 0), (4, 7, 0), (4, 7, 1)], Color: orange
     98
     99To get all the solutions, use the iterator returned by the ``solve``
     100method. Note that finding the first solution is the most time consuming
     101because it needs to create the complete data to describe the problem::
     102
     103    sage: it = QuantuminoSolver(7).solve()
     104    sage: it.next()                                     # not tested (10s)
     105    Quantumino state where the following pentamino is put aside :
     106    Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
     107    sage: it.next()                                     # not tested (0.001s)
     108    Quantumino state where the following pentamino is put aside :
     109    Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
     110    sage: it.next()                                     # not tested (0.001s)
     111    Quantumino state where the following pentamino is put aside :
     112    Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
    74113
    75114To get the solution inside other boxes::
    76115
    77     sage: s = quantumino_solver.get_solution(7, box=(4,4,5))        # long time (2s)
    78     sage: s.show3d()                                                # long time (<1s)
     116    sage: s = QuantuminoSolver(7, box=(4,4,5)).solve().next()       # not tested (2s)
     117    sage: s.show3d()                                                # not tested (<1s)
    79118
    80119::
    81120
    82     sage: s = quantumino_solver.get_solution(7, box=(2,2,20))       # long time (1s)
    83     sage: s.show3d()                                                # long time (<1s)
     121    sage: s = QuantuminoSolver(7, box=(2,2,20)).solve().next()      # not tested (1s)
     122    sage: s.show3d()                                                # not tested (<1s)
    84123
    85 EXAMPLES:
     124If there are no solution, a StopIteration error is raised::
    86125
    87 One can also solve similar problems by defining 3D polyominos directly. The
    88 following is a 2d puzzle owned by Florent Hivert::
     126    sage: QuantuminoSolver(7, box=(3,3,3)).solve().next()
     127    Traceback (most recent call last):
     128    ...
     129    StopIteration
    89130
    90     sage: from sage.games.quantumino import Polyomino3d, solve_3d_puzzle
    91     sage: L = []
    92     sage: L.append(Polyomino3d([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,2,0),(1,3,0)]))
    93     sage: L.append(Polyomino3d([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,2,0)]))
    94     sage: L.append(Polyomino3d([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,3,0)]))
    95     sage: L.append(Polyomino3d([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,3,0)]))
    96     sage: L.append(Polyomino3d([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0)]))
    97     sage: L.append(Polyomino3d([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,1,0),(1,2,0)]))
    98     sage: L.append(Polyomino3d([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,1,0),(1,3,0)]))
    99     sage: L.append(Polyomino3d([(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,3,0)]))
    100     sage: L.append(Polyomino3d([(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,2,0)]))
    101     sage: L.append(Polyomino3d([(0,0,0),(0,1,0),(0,2,0),(1,0,0),(1,1,0),(1,2,0)]))
     131The implementation allows a lot of introspection. From the TilingSolver
     132object, it is possible to retrieve the rows that are passed to the DLX
     133solver and count them. It is also possible to get an instance of the DLX
     134solver to play with it::
    102135
    103 Solve the puzzle and show the solution::
    104 
    105     sage: it = solve_3d_puzzle(L, (8,8,1))
    106     sage: solution = it.next()
    107     sage: G = sum([piece.show3d() for piece in solution], Graphics())
    108     sage: G.show(aspect_ratio=1, viewer='tachyon')
     136    sage: q = QuantuminoSolver(0)
     137    sage: T = q.tiling_solver()
     138    sage: T
     139    Tiling solver of 16 pieces into the box (5, 8, 2)
     140    Rotation allowed: True
     141    Reflection allowed: False
     142    Reusing pieces allowed: False
     143    sage: rows = T.rows()                            # not tested (10 s)
     144    sage: len(rows)                                  # not tested (but fast)
     145    5484
     146    sage: x = T.dlx_solver()                         # long time (10 s)
     147    sage: x                                          # long time (fast)
     148    <sage.combinat.matrices.dancing_links.dancing_linksWrapper object at ...>
    109149
    110150REFERENCES:
    111151
    112     - [1] http://familygamesamerica.com/mainsite/consumers/productview.php?pro_id=274&search=quantumino
    113     - [2] Quantumino - How to Play, http://www.youtube.com/watch?v=jX_VKzakZi8
    114     - [3] Knuth, Donald (2000). "Dancing links". arXiv:cs/0011047.
     152- [1] `Family Games America's Quantumino
     153  <http://familygamesamerica.com/mainsite/consumers/productview.php?pro_id=274&search=quantumino>`_
     154- [2] `Quantumino - How to Play <http://www.youtube.com/watch?v=jX_VKzakZi8>`_ on Youtube
     155- [3] Knuth, Donald (2000). "Dancing links". `arXiv:cs/0011047
     156  <http://arxiv.org/abs/cs/0011047>`_.
    115157
    116158"""
    117159#*****************************************************************************
    REFERENCES: 
    122164#  the License, or (at your option) any later version. 
    123165#                  http://www.gnu.org/licenses/ 
    124166#*****************************************************************************
    125 import itertools
    126 from sage.combinat.all import WeylGroup
     167from sage.structure.sage_object import SageObject
    127168from sage.plot.plot import Graphics
    128 from sage.modules.free_module_element import vector
    129169from sage.plot.plot3d.platonic import cube
    130170from sage.plot.plot3d.shapes2 import text3d
    131 from sage.structure.sage_object import SageObject
    132 
    133 ##############################
    134 # Orthogonal Rotation matrices
    135 ##############################
    136 rotation_matrices = [w.matrix() for w in WeylGroup(['B',3]) if w.matrix().det() == 1]
    137 
    138 ##############################
    139 # Class Polyomino3d
    140 ##############################
    141 class Polyomino3d(SageObject):
    142     r"""
    143     Return the 3D polyomino defined by a set of 3d coordinates.
    144 
    145     INPUT:
    146 
    147     - ``coords`` - iterable of 3d tuple
    148     - ``color`` - string (optional, default: ``'gray'``), the color
    149 
    150     EXAMPLES::
    151        
    152         sage: from sage.games.quantumino import Polyomino3d
    153         sage: Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
    154         3D Polyomino: [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)], Color: blue
    155     """
    156     def __init__(self, coords, color='gray'):
    157         r"""
    158         INPUT:
    159 
    160         - ``coords`` - iterable of 3d tuple
    161         - ``color`` - string (optional, default: ``'gray'``), the color
    162 
    163         EXAMPLES::
    164            
    165             sage: from sage.games.quantumino import Polyomino3d
    166             sage: Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
    167             3D Polyomino: [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)], Color: blue
    168         """
    169         assert all(len(a) == 3 for a in coords), "coord must be in dimension 3"
    170         assert isinstance(color, str)
    171         self._blocs = frozenset(tuple(c) for c in coords)
    172         self._color = color
    173 
    174     def __repr__(self):
    175         r"""
    176         EXAMPLES::
    177 
    178             sage: from sage.games.quantumino import Pentamino3D
    179             sage: p = Pentamino3D(0)
    180             sage: p
    181             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
    182         """
    183         s = "3D Polyomino: %s, " % sorted(self._blocs)
    184         s += "Color: %s" % self._color
    185         return s
    186 
    187     def __hash__(self):
    188         r"""
    189         EXAMPLES::
    190 
    191             sage: from sage.games.quantumino import Polyomino3d
    192             sage: p = Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
    193             sage: hash(p)
    194             2059134902
    195         """
    196         return hash(self._blocs)
    197    
    198     def __eq__(self, other):
    199         r"""
    200         Return whether self is equal to other.
    201 
    202         INPUT:
    203 
    204         - ``other`` - a polyomino
    205 
    206         EXAMPLES::
    207 
    208             sage: from sage.games.quantumino import Polyomino3d
    209             sage: p = Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
    210             sage: q = Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='red')
    211             sage: p == q
    212             True
    213             sage: r = Polyomino3d([(0,0,0), (0,1,0), (1,1,0)], color='blue')
    214             sage: p == r
    215             False
    216         """
    217         return self._blocs == other._blocs
    218 
    219     def __ne__(self, other):
    220         r"""
    221         Return whether self is not equal to other.
    222 
    223         INPUT:
    224 
    225         - ``other`` - a polyomino
    226 
    227         EXAMPLES::
    228 
    229             sage: from sage.games.quantumino import Polyomino3d
    230             sage: p = Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
    231             sage: q = Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='red')
    232             sage: p != q
    233             False
    234             sage: r = Polyomino3d([(0,0,0), (0,1,0), (1,1,0)], color='blue')
    235             sage: p != r
    236             True
    237         """
    238         return self._blocs != other._blocs
    239 
    240     def color(self):
    241         r"""
    242         Return the color of the polyomino.
    243 
    244         EXAMPLES::
    245 
    246             sage: from sage.games.quantumino import Polyomino3d
    247             sage: p = Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
    248             sage: p.color()
    249             'blue'
    250         """
    251         return self._color
    252 
    253     def __iter__(self):
    254         r"""
    255         EXAMPLES::
    256 
    257             sage: from sage.games.quantumino import Polyomino3d
    258             sage: p = Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
    259             sage: it = iter(p)
    260             sage: it.next()
    261             (1, 1, 0)
    262         """
    263         return iter(self._blocs)
    264 
    265     def rotated(self):
    266         r"""
    267         Iterator over the 24 orthogonal rotated image of self.
    268 
    269         EXAMPLES::
    270 
    271             sage: from sage.games.quantumino import Polyomino3d
    272             sage: p = Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
    273             sage: L = list(p.rotated())
    274             sage: len(L)
    275             24
    276         """
    277         for m in rotation_matrices:
    278             L = [m * vector(p) for p in self]
    279             yield Polyomino3d(L, color=self._color)
    280 
    281     def rotated_canonical(self):
    282         r"""
    283         Iterator over the 24 orthogonal rotated image of self where the
    284         coordinates are all positive and minimal.
    285 
    286         EXAMPLES::
    287 
    288             sage: from sage.games.quantumino import Polyomino3d
    289             sage: p = Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
    290             sage: L = list(p.rotated_canonical())
    291             sage: len(L)
    292             24
    293 
    294         They might not be all different::
    295 
    296             sage: s = set(p.rotated_canonical())
    297             sage: len(s)
    298             12
    299         """
    300         for q in self.rotated():
    301             yield q.canonical()
    302 
    303     def canonical(self):
    304         r"""
    305         Returns the translated copy of self having minimal and positive
    306         coordinates
    307 
    308         EXAMPLES::
    309 
    310             sage: from sage.games.quantumino import Pentamino3D
    311             sage: p = Pentamino3D(0)
    312             sage: p
    313             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
    314             sage: p.canonical()
    315             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
    316 
    317         TESTS::
    318            
    319             sage: p
    320             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
    321             sage: p + (3,4,5)
    322             3D Polyomino: [(3, 4, 5), (4, 4, 5), (4, 5, 5), (4, 5, 6), (4, 6, 5)], Color: deeppink
    323             sage: (p + (3,4,5)).canonical()
    324             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
    325         """
    326         minxyz,maxxyz = self.bounding_box()
    327         mx, my, mz = minxyz
    328         return self - (mx,my,mz)
    329 
    330     def is_higher_than(self, k):
    331         r"""
    332         EXAMPLES::
    333 
    334             sage: from sage.games.quantumino import Pentamino3D
    335             sage: p = Pentamino3D(15)
    336             sage: p.is_higher_than(2)
    337             False
    338         """
    339         [(a,b,c), (aa,bb,cc)] = self.bounding_box()
    340         return cc - c > k-1
    341 
    342     def __sub__(self, arg):
    343         r"""
    344         EXAMPLES::
    345 
    346             sage: from sage.games.quantumino import Pentamino3D
    347             sage: p = Pentamino3D(0)
    348             sage: p - (2,2,2)
    349             3D Polyomino: [(-2, -2, -2), (-1, -2, -2), (-1, -1, -2), (-1, -1, -1), (-1, 0, -2)], Color: deeppink
    350 
    351         """
    352         x,y,z = arg
    353         return Polyomino3d([(a-x, b-y, c-z) for a,b,c in self], color=self._color)
    354 
    355     def __add__(self, arg):
    356         r"""
    357         EXAMPLES::
    358 
    359             sage: from sage.games.quantumino import Pentamino3D
    360             sage: p = Pentamino3D(0)
    361             sage: p + (2,2,2)
    362             3D Polyomino: [(2, 2, 2), (3, 2, 2), (3, 3, 2), (3, 3, 3), (3, 4, 2)], Color: deeppink
    363         """
    364         x,y,z = arg
    365         return Polyomino3d([(a+x, b+y, c+z) for a,b,c in self], color=self._color)
    366 
    367     def bounding_box(self):
    368         r"""
    369         EXAMPLES::
    370 
    371             sage: from sage.games.quantumino import Pentamino3D
    372             sage: p = Pentamino3D(15)
    373             sage: p.bounding_box()
    374             [(0, 0, 0), (1, 2, 1)]
    375         """
    376         xx,yy,zz = zip(*self)
    377         minx = min(xx)
    378         miny = min(yy)
    379         minz = min(zz)
    380         maxx = max(xx)
    381         maxy = max(yy)
    382         maxz = max(zz)
    383         return [(minx,miny,minz), (maxx,maxy,maxz)]
    384 
    385     def translated(self, box):
    386         r"""
    387         Returns an iterator over the translated images of self inside a
    388         box.
    389 
    390         INPUT:
    391 
    392         - ``box`` - tuple of size three, size of the box
    393 
    394         EXAMPLES::
    395            
    396             sage: from sage.games.quantumino import Pentamino3D
    397             sage: p = Pentamino3D(0)
    398             sage: for t in p.translated(box=(5,8,2)): t
    399             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
    400             3D Polyomino: [(0, 1, 0), (1, 1, 0), (1, 2, 0), (1, 2, 1), (1, 3, 0)], Color: deeppink
    401             3D Polyomino: [(0, 2, 0), (1, 2, 0), (1, 3, 0), (1, 3, 1), (1, 4, 0)], Color: deeppink
    402             3D Polyomino: [(0, 3, 0), (1, 3, 0), (1, 4, 0), (1, 4, 1), (1, 5, 0)], Color: deeppink
    403             3D Polyomino: [(0, 4, 0), (1, 4, 0), (1, 5, 0), (1, 5, 1), (1, 6, 0)], Color: deeppink
    404             3D Polyomino: [(0, 5, 0), (1, 5, 0), (1, 6, 0), (1, 6, 1), (1, 7, 0)], Color: deeppink
    405             3D Polyomino: [(1, 0, 0), (2, 0, 0), (2, 1, 0), (2, 1, 1), (2, 2, 0)], Color: deeppink
    406             3D Polyomino: [(1, 1, 0), (2, 1, 0), (2, 2, 0), (2, 2, 1), (2, 3, 0)], Color: deeppink
    407             3D Polyomino: [(1, 2, 0), (2, 2, 0), (2, 3, 0), (2, 3, 1), (2, 4, 0)], Color: deeppink
    408             3D Polyomino: [(1, 3, 0), (2, 3, 0), (2, 4, 0), (2, 4, 1), (2, 5, 0)], Color: deeppink
    409             3D Polyomino: [(1, 4, 0), (2, 4, 0), (2, 5, 0), (2, 5, 1), (2, 6, 0)], Color: deeppink
    410             3D Polyomino: [(1, 5, 0), (2, 5, 0), (2, 6, 0), (2, 6, 1), (2, 7, 0)], Color: deeppink
    411             3D Polyomino: [(2, 0, 0), (3, 0, 0), (3, 1, 0), (3, 1, 1), (3, 2, 0)], Color: deeppink
    412             3D Polyomino: [(2, 1, 0), (3, 1, 0), (3, 2, 0), (3, 2, 1), (3, 3, 0)], Color: deeppink
    413             3D Polyomino: [(2, 2, 0), (3, 2, 0), (3, 3, 0), (3, 3, 1), (3, 4, 0)], Color: deeppink
    414             3D Polyomino: [(2, 3, 0), (3, 3, 0), (3, 4, 0), (3, 4, 1), (3, 5, 0)], Color: deeppink
    415             3D Polyomino: [(2, 4, 0), (3, 4, 0), (3, 5, 0), (3, 5, 1), (3, 6, 0)], Color: deeppink
    416             3D Polyomino: [(2, 5, 0), (3, 5, 0), (3, 6, 0), (3, 6, 1), (3, 7, 0)], Color: deeppink
    417             3D Polyomino: [(3, 0, 0), (4, 0, 0), (4, 1, 0), (4, 1, 1), (4, 2, 0)], Color: deeppink
    418             3D Polyomino: [(3, 1, 0), (4, 1, 0), (4, 2, 0), (4, 2, 1), (4, 3, 0)], Color: deeppink
    419             3D Polyomino: [(3, 2, 0), (4, 2, 0), (4, 3, 0), (4, 3, 1), (4, 4, 0)], Color: deeppink
    420             3D Polyomino: [(3, 3, 0), (4, 3, 0), (4, 4, 0), (4, 4, 1), (4, 5, 0)], Color: deeppink
    421             3D Polyomino: [(3, 4, 0), (4, 4, 0), (4, 5, 0), (4, 5, 1), (4, 6, 0)], Color: deeppink
    422             3D Polyomino: [(3, 5, 0), (4, 5, 0), (4, 6, 0), (4, 6, 1), (4, 7, 0)], Color: deeppink
    423         """
    424         box_x, box_y, box_z = box
    425         cano = self.canonical()
    426         x,y,z = zip(*cano)
    427         mx = max(x)
    428         my = max(y)
    429         mz = max(z)
    430         for i in range(box_x-mx):
    431             for j in range(box_y-my):
    432                 for k in range(box_z-mz):
    433                     yield self + (i,j,k)
    434 
    435     def translated_rotated(self, box):
    436         r"""
    437         Return the translated and rotated of self that lies in the box.
    438 
    439         INPUT:
    440 
    441         - ``box`` - tuple of size three, size of the box
    442 
    443         EXAMPLES::
    444 
    445             sage: from sage.games.quantumino import Pentamino3D
    446             sage: p = Pentamino3D(0)
    447             sage: L = list(p.translated_rotated(box=(5,8,2)))
    448             sage: len(L)
    449             360
    450 
    451         ::
    452 
    453             sage: p = Pentamino3D(6)
    454             sage: L = list(p.translated_rotated(box=(5,8,2)))
    455             sage: len(L)
    456             180
    457         """
    458         height = box[2]
    459         all_distinct_cano = set(self.rotated_canonical())
    460         for cano in all_distinct_cano:
    461             if cano.is_higher_than(height):
    462                 continue
    463             for t in cano.translated(box=box):
    464                 yield t
    465 
    466     def middle_of_neighbor_coords(self):
    467         r"""
    468         Return the list of middle of neighbor coords.
    469 
    470         This is use to draw cube in between two neighbor cubes.
    471 
    472         EXAMPLES::
    473 
    474             sage: from sage.games.quantumino import Polyomino3d
    475             sage: p = Polyomino3d([(0,0,0),(0,0,1)])
    476             sage: list(p.middle_of_neighbor_coords())
    477             [(0.0, 0.0, 0.5)]
    478 
    479         ::
    480 
    481             sage: from sage.games.quantumino import Pentamino3D
    482             sage: L = sorted(Pentamino3D(0).middle_of_neighbor_coords())
    483             sage: for a in L: a
    484             (0.5, 0.0, 0.0)
    485             (1.0, 0.5, 0.0)
    486             (1.0, 1.0, 0.5)
    487             (1.0, 1.5, 0.0)
    488         """
    489         for (a,b,c), (p,q,r) in itertools.combinations(self, 2):
    490             if [0,0,1] == sorted(map(abs, (p-a, q-b, r-c))):
    491                 yield ( (a+p)/2.0, (b+q)/2.0, (c+r)/2.0 )
    492 
    493     def show3d(self, size=0.75):
    494         r"""
    495         INPUT:
    496 
    497         - ``size`` - number (optional, default: ``0.75``), the size of the
    498           ``1 \times 1 \times 1`` cubes
    499 
    500         EXAMPLES::
    501 
    502             sage: from sage.games.quantumino import Polyomino3d
    503             sage: p = Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
    504             sage: p.show3d()
    505         """
    506         #bug trac #11272
    507         G = Graphics()
    508         for p in self:
    509             G += cube(color=self._color, size=size).translate(p)
    510         for m in self.middle_of_neighbor_coords():
    511             G += cube(color=self._color, size=size).translate(m)
    512         return G
    513 
    514 #######################
    515 # General puzzle solver
    516 #######################
    517 def solve_3d_puzzle(pieces, box=(5,8,2), verbose=False):
    518     r"""
    519     Solve the 3d puzzle, that is find the partition of the box into translated
    520     and rotated pieces where each pieces is used exactly once.
    521 
    522     INPUT:
    523 
    524     - ``pieces`` - iterable of Polyominos3d
    525     - ``box`` - tuple of size three (optional, default: ``(5,8,2)``),
    526       size of the box
    527     - ``verbose`` - bool
    528 
    529     OUTPUT:
    530 
    531     iterator of list of translated and rotated polyominos 3d that partition the box
    532 
    533     EXAMPLES::
    534 
    535         sage: from sage.games.quantumino import Pentamino3D, solve_3d_puzzle
    536         sage: L = map(Pentamino3D, range(17))
    537         sage: all_solutions = solve_3d_puzzle(L[:16], verbose=True)
    538         sage: K = all_solutions.next()
    539         Number of rows : 5664
    540         Number of distinct rows : 5664
    541         Row indices of the solution : [24, 696, 815, 1172, 1699, 1969, 5318, 5277, 4024, 3271, 4851, 2484, 4748, 4555, 2248, 2659]
    542         sage: for k in K: k
    543         3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
    544         3D Polyomino: [(0, 0, 1), (0, 1, 0), (0, 1, 1), (0, 2, 1), (1, 2, 1)], Color: deeppink
    545         3D Polyomino: [(0, 2, 0), (0, 3, 0), (0, 4, 0), (1, 4, 0), (1, 4, 1)], Color: green
    546         3D Polyomino: [(0, 3, 1), (1, 3, 1), (2, 2, 0), (2, 2, 1), (2, 3, 1)], Color: green
    547         3D Polyomino: [(1, 3, 0), (2, 3, 0), (2, 4, 0), (2, 4, 1), (3, 4, 0)], Color: red
    548         3D Polyomino: [(1, 0, 1), (2, 0, 1), (2, 1, 0), (2, 1, 1), (3, 1, 1)], Color: red
    549         3D Polyomino: [(2, 0, 0), (3, 0, 0), (3, 0, 1), (4, 0, 1), (4, 1, 1)], Color: purple
    550         3D Polyomino: [(3, 1, 0), (3, 2, 0), (3, 2, 1), (4, 0, 0), (4, 1, 0)], Color: purple
    551         3D Polyomino: [(0, 4, 1), (0, 5, 0), (0, 5, 1), (0, 6, 1), (1, 5, 0)], Color: midnightblue
    552         3D Polyomino: [(3, 3, 0), (3, 3, 1), (4, 2, 0), (4, 2, 1), (4, 3, 1)], Color: yellow
    553         3D Polyomino: [(3, 4, 1), (3, 5, 1), (4, 3, 0), (4, 4, 0), (4, 4, 1)], Color: blue
    554         3D Polyomino: [(0, 7, 0), (0, 7, 1), (1, 7, 1), (2, 6, 1), (2, 7, 1)], Color: orange
    555         3D Polyomino: [(0, 6, 0), (1, 5, 1), (1, 6, 0), (1, 6, 1), (2, 5, 1)], Color: blue
    556         3D Polyomino: [(1, 7, 0), (2, 7, 0), (3, 6, 0), (3, 7, 0), (3, 7, 1)], Color: darkblue
    557         3D Polyomino: [(2, 5, 0), (2, 6, 0), (3, 5, 0), (4, 5, 0), (4, 5, 1)], Color: orange
    558         3D Polyomino: [(3, 6, 1), (4, 6, 0), (4, 6, 1), (4, 7, 0), (4, 7, 1)], Color: yellow
    559     """
    560     box_x, box_y, box_z = box
    561     number_of_pieces = len(pieces)
    562 
    563     # Bijection column indices <-> coordinates
    564     SPACE = list(itertools.product(range(box_x), range(box_y), range(box_z)))
    565     COORD_TO_INT = dict( (c,i+number_of_pieces) for i,c in enumerate(SPACE) )
    566     INT_TO_COORD = dict( (i+number_of_pieces,c) for i,c in enumerate(SPACE) )
    567 
    568     # Creation of the rows
    569     rows = []
    570     for i,p in enumerate(pieces):
    571         for q in p.translated_rotated(box):
    572             rows += [[i] + [COORD_TO_INT[coord] for coord in q]]
    573     if verbose:
    574         print "Number of rows : %s" % len(rows)
    575         print "Number of distinct rows : %s" % len(set(map(tuple,rows)))
    576 
    577     # Solve using dancing links
    578     from sage.combinat.matrices.dancing_links import dlx_solver
    579     x = dlx_solver(rows)
    580 
    581     while True:
    582         a = x.search()
    583         solution = x.get_solution()
    584         if verbose:
    585             print "Row indices of the solution : %s" % solution
    586 
    587         #Recover the pentos from the solution
    588         pentos = []
    589         for row_number in solution:
    590             row = rows[row_number]
    591             no = row[0]
    592             coords = [INT_TO_COORD[i] for i in row[1:]]
    593             p = Polyomino3d(coords, color=pieces[no].color())
    594             pentos.append(p)
    595         yield pentos
     171from sage.modules.free_module_element import vector
     172from sage.combinat.tiling import Polyomino, TilingSolver
    596173
    597174################################################
    598175# Example:  The family games america: Quantumino
    599176################################################
    600 _list_pentaminos = []
    601 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (1,1,0), (1,2,0), (1,1,1)], color='deeppink'))
    602 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (1,1,0), (-1,0,0), (0,0,1)], color='deeppink'))
    603 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (1,1,0), (1,2,0), (0,0,1)], color='green'))
    604 _list_pentaminos.append(Polyomino3d([(0,0,0), (0,1,0), (0,2,0), (1,0,0), (1,0,1)], color='green'))
    605 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (1,-1,1)], color='red'))
    606 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (2,0,1)], color='red'))
    607 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (1,1,0), (1,2,0), (1,2,1)], color='orange'))
    608 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (0,1,0), (0,2,0), (0,2,1)], color='orange'))
    609 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (0,1,0), (1,1,0), (0,0,1)], color='yellow'))
    610 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (1,1,0), (1,1,1), (0,0,1)], color='yellow'))
    611 _list_pentaminos.append(Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (0,2,0), (1,1,1)], color='midnightblue'))
    612 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (1,2,0)], color='darkblue'))
    613 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (1,1,0), (1,1,1), (2,1,1)], color='blue'))
    614 _list_pentaminos.append(Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,1,1), (1,2,1)], color='blue'))
    615 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (1,1,0), (2,1,0), (2,1,1)], color='purple'))
    616 _list_pentaminos.append(Polyomino3d([(0,0,0), (0,1,0), (1,1,0), (1,2,0), (1,2,1)], color='purple'))
    617 _list_pentaminos.append(Polyomino3d([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (1,-1,0)], color='gray'))
     177pentaminos = []
     178pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,2,0), (1,1,1)], color='deeppink'))
     179pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (-1,0,0), (0,0,1)], color='deeppink'))
     180pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,2,0), (0,0,1)], color='green'))
     181pentaminos.append(Polyomino([(0,0,0), (0,1,0), (0,2,0), (1,0,0), (1,0,1)], color='green'))
     182pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (1,-1,1)], color='red'))
     183pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (2,0,1)], color='red'))
     184pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,2,0), (1,2,1)], color='orange'))
     185pentaminos.append(Polyomino([(0,0,0), (1,0,0), (0,1,0), (0,2,0), (0,2,1)], color='orange'))
     186pentaminos.append(Polyomino([(0,0,0), (1,0,0), (0,1,0), (1,1,0), (0,0,1)], color='yellow'))
     187pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,1,1), (0,0,1)], color='yellow'))
     188pentaminos.append(Polyomino([(0,0,0), (0,1,0), (1,1,0), (0,2,0), (1,1,1)], color='midnightblue'))
     189pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (1,2,0)], color='darkblue'))
     190pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,1,1), (2,1,1)], color='blue'))
     191pentaminos.append(Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1), (1,2,1)], color='blue'))
     192pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (2,1,0), (2,1,1)], color='purple'))
     193pentaminos.append(Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,2,0), (1,2,1)], color='purple'))
     194pentaminos.append(Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (1,-1,0)], color='gray'))
    618195
    619 def Pentamino3D(i):
     196def show_pentaminos(box=(5,8,2)):
    620197    r"""
    621     Return one of the seventeen 3D Pentamino pieces included in the game.
     198    Show the 17 3-D pentaminos included in the game and the `5 \times 8
     199    \times 2` box where 16 of them must fit.
    622200
    623201    INPUT:
    624202
    625     - ``i`` - integer, from 0 to 16.
     203    - ``box`` - tuple of size three (optional, default: ``(5,8,2)``),
     204      size of the box
     205
     206    OUTPUT:
     207
     208        3D Graphic object
    626209
    627210    EXAMPLES::
    628211
    629         sage: from sage.games.quantumino import Pentamino3D
    630         sage: Pentamino3D(3)
    631         3D Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (1, 0, 0), (1, 0, 1)], Color: green
     212        sage: from sage.games.quantumino import show_pentaminos
     213        sage: show_pentaminos()    # not tested (1s)
     214
     215    To remove the frame do::
     216
     217        sage: show_pentaminos().show(frame=False)  # not tested (1s)
    632218    """
    633     return _list_pentaminos[i]
     219    G = Graphics()
     220    for i,p in enumerate(pentaminos):
     221        x = 3.5 * (i%4)
     222        y = 3.5 * (i/4)
     223        q = p + (x, y, 0)
     224        G += q.show3d()
     225        G += text3d(str(i), (x,y,2))
     226    G += cube(color='gray',opacity=0.5).scale(box).translate((17,6,0))
    634227
     228    # hack to set the aspect ratio to 1
     229    a,b = G.bounding_box()
     230    a,b = map(vector, (a,b))
     231    G.frame_aspect_ratio(tuple(b-a))
     232
     233    return G
    635234
    636235##############################
    637 # Class QuantuminoSolultion
     236# Class QuantuminoState
    638237##############################
    639 class QuantuminoSolution(SageObject):
     238class QuantuminoState(SageObject):
    640239    r"""
    641     A solution of the Quantumino puzzle.
     240    A state of the Quantumino puzzle.
     241
     242    Used to represent an solution or a partial solution of the Quantumino
     243    puzzle.
    642244
    643245    INPUT:
    644246
    645     - ``pentos`` - list of 16 3D pentamino representing the solution
    646     - ``aside`` - the unused 3D pentamino
     247    - ``pentos`` - list of 16 3d pentamino representing the (partial)
     248      solution
     249    - ``aside`` - 3d polyomino, the unused 3D pentamino
    647250
    648251    EXAMPLES::
    649252
    650         sage: from sage.games.quantumino import Pentamino3D, QuantuminoSolution
    651         sage: p = Pentamino3D(0)
    652         sage: q = Pentamino3D(5)
    653         sage: r = Pentamino3D(11)
    654         sage: S = QuantuminoSolution([p,q], r)
     253        sage: from sage.games.quantumino import pentaminos, QuantuminoState
     254        sage: p = pentaminos[0]
     255        sage: q = pentaminos[5]
     256        sage: r = pentaminos[11]
     257        sage: S = QuantuminoState([p,q], r)
    655258        sage: S
    656         Quantumino solution without the following pentamino :
    657         3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 2, 0)], Color: darkblue
     259        Quantumino state where the following pentamino is put aside :
     260        Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 2, 0)], Color: darkblue
    658261
    659262    ::
    660263
    661         sage: from sage.games.quantumino import quantumino_solver
    662         sage: quantumino_solver.get_solution(3)      # long time (1.5s)
    663         Quantumino solution without the following pentamino :
    664         3D Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (1, 0, 0), (1, 0, 1)], Color: green
     264        sage: from sage.games.quantumino import QuantuminoSolver
     265        sage: QuantuminoSolver(3).solve().next()      # not tested (1.5s)
     266        Quantumino state where the following pentamino is put aside :
     267        Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (1, 0, 0), (1, 0, 1)], Color: green
    665268    """
    666269    def __init__(self, pentos, aside):
    667270        r"""
    668271        EXAMPLES::
    669272
    670             sage: from sage.games.quantumino import Pentamino3D, QuantuminoSolution
    671             sage: p = Pentamino3D(0)
    672             sage: q = Pentamino3D(5)
    673             sage: QuantuminoSolution([p], q)
    674             Quantumino solution without the following pentamino :
    675             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (2, 0, 1)], Color: red
     273            sage: from sage.games.quantumino import pentaminos, QuantuminoState
     274            sage: p = pentaminos[0]
     275            sage: q = pentaminos[5]
     276            sage: QuantuminoState([p], q)
     277            Quantumino state where the following pentamino is put aside :
     278            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (2, 0, 1)], Color: red
    676279        """
     280        assert all(isinstance(p, Polyomino) for p in pentos), "pentos must be an iterable of Polyomino"
     281        assert isinstance(aside, Polyomino), "aside must be a Polyomino"
    677282        self._pentos = pentos
    678283        self._aside = aside
    679284
    class QuantuminoSolution(SageObject): 
    681286        r"""
    682287        EXAMPLES::
    683288
    684             sage: from sage.games.quantumino import Pentamino3D, QuantuminoSolution
    685             sage: p = Pentamino3D(0)
    686             sage: q = Pentamino3D(5)
    687             sage: QuantuminoSolution([p], q)
    688             Quantumino solution without the following pentamino :
    689             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (2, 0, 1)], Color: red
     289            sage: from sage.games.quantumino import pentaminos, QuantuminoState
     290            sage: p = pentaminos[0]
     291            sage: q = pentaminos[5]
     292            sage: QuantuminoState([p], q)
     293            Quantumino state where the following pentamino is put aside :
     294            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (2, 0, 1)], Color: red
    690295        """
    691         return "Quantumino solution without the following pentamino :\n%s" % self._aside
     296        return "Quantumino state where the following pentamino is put aside :\n%s" % self._aside
    692297
    693298    def __iter__(self):
    694299        r"""
    695300        EXAMPLES::
    696301
    697             sage: from sage.games.quantumino import Pentamino3D, QuantuminoSolution
    698             sage: p = Pentamino3D(0)
    699             sage: q = Pentamino3D(5)
    700             sage: r = Pentamino3D(11)
    701             sage: S = QuantuminoSolution([p,q], r)
     302            sage: from sage.games.quantumino import pentaminos, QuantuminoState
     303            sage: p = pentaminos[0]
     304            sage: q = pentaminos[5]
     305            sage: r = pentaminos[11]
     306            sage: S = QuantuminoState([p,q], r)
    702307            sage: for a in S: a
    703             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
    704             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (2, 0, 1)], Color: red
     308            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     309            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (2, 0, 1)], Color: red
    705310        """
    706311        return iter(self._pentos)
    707312
    708     def show3d(self):
     313    def list(self):
    709314        r"""
    710         Show the solution in 3D.
     315        Return the list of 3d polyomino making the solution.
    711316
    712317        EXAMPLES::
    713318
    714             sage: from sage.games.quantumino import quantumino_solver
    715             sage: s = quantumino_solver.get_solution(0)     # long time (1.5s)
    716             sage: s.show3d()      # long time (<1s)
     319            sage: from sage.games.quantumino import pentaminos, QuantuminoState
     320            sage: p = pentaminos[0]
     321            sage: q = pentaminos[5]
     322            sage: r = pentaminos[11]
     323            sage: S = QuantuminoState([p,q], r)
     324            sage: L = S.list()
     325            sage: L[0]
     326            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     327            sage: L[1]
     328            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 0, 1), (1, 1, 0), (2, 0, 1)], Color: red
     329        """
     330        return list(self)
    717331
     332    def show3d(self, size=0.75):
     333        r"""
     334        Return the solution as a 3D Graphic object.
     335
     336        OUTPUT:
     337
     338            3D Graphic Object
     339
     340        EXAMPLES::
     341
     342            sage: from sage.games.quantumino import QuantuminoSolver
     343            sage: s = QuantuminoSolver(0).solve().next()    # not tested (1.5s)
     344            sage: G = s.show3d()                            # not tested (<1s)
     345            sage: type(G)                                   # not tested
     346            <class 'sage.plot.plot3d.base.Graphics3dGroup'>
     347
     348        To remove the frame::
     349
     350            sage: G.show(frame=False) # not tested
     351
     352        To see the solution with Tachyon viewer::
     353
     354            sage: G.show(viewer='tachyon', frame=False) # not tested
    718355        """
    719356        G = Graphics()
    720357        for p in self:
    721             G += p.show3d()
    722         aside_pento = self._aside.canonical() + (2.5,-4,0)
    723         G += aside_pento.show3d()
    724         G.show(aspect_ratio=1, frame=False)
    725         #return G
     358            G += p.show3d(size=size)
     359        aside_pento = self._aside.canonical() + (2.5*size/0.75,-4*size/0.75,0)
     360        G += aside_pento.show3d(size=size)
    726361
     362        # hack to set the aspect ratio to 1
     363        a,b = G.bounding_box()
     364        a,b = map(vector, (a,b))
     365        G.frame_aspect_ratio(tuple(b-a))
     366
     367        return G
    727368
    728369##############################
    729370# Class QuantuminoSolver
    730371##############################
    731372class QuantuminoSolver(SageObject):
    732373    r"""
     374    Return the Quantumino solver for the giving box where one of the
     375    pentamino is put aside.
     376
     377    INPUT:
     378
     379    - ``aside`` - integer, from 0 to 16, the aside pentamino
     380    - ``box`` - tuple of size three (optional, default: ``(5,8,2)``),
     381      size of the box
     382
     383    EXAMPLES::
     384
     385        sage: from sage.games.quantumino import QuantuminoSolver
     386        sage: QuantuminoSolver(9)
     387        Quantumino solver for the box (5, 8, 2)
     388        Aside pentamino number: 9
     389        sage: QuantuminoSolver(12, box=(5,4,4))
     390        Quantumino solver for the box (5, 4, 4)
     391        Aside pentamino number: 12
    733392    """
    734     def list_pentaminos(self):
     393    def __init__(self, aside, box=(5,8,2)):
    735394        r"""
    736         Return the list of the 17 3-D pentaminos included in the game.
     395        Constructor.
     396
     397        EXAMPLES::
     398
     399            sage: from sage.games.quantumino import QuantuminoSolver
     400            sage: QuantuminoSolver(9)
     401            Quantumino solver for the box (5, 8, 2)
     402            Aside pentamino number: 9
     403        """
     404        if not  0 <= aside < 17:
     405            raise ValueError, "aside (=%s) must be between 0 and 16" % aside
     406        self._aside = aside
     407        self._box = box
     408
     409    def __repr__(self):
     410        r"""
     411        String representation
     412
     413        EXAMPLES::
     414
     415            sage: from sage.games.quantumino import QuantuminoSolver
     416            sage: QuantuminoSolver(0)
     417            Quantumino solver for the box (5, 8, 2)
     418            Aside pentamino number: 0
     419        """
     420        s = "Quantumino solver for the box %s\n" % (self._box, )
     421        s += "Aside pentamino number: %s" % self._aside
     422        return s
     423
     424    def tiling_solver(self):
     425        r"""
     426        Return the Tiling solver of the Quantumino Game where one of the
     427        pentamino is put aside.
     428
     429        EXAMPLES::
     430
     431            sage: from sage.games.quantumino import QuantuminoSolver
     432            sage: QuantuminoSolver(0).tiling_solver()
     433            Tiling solver of 16 pieces into the box (5, 8, 2)
     434            Rotation allowed: True
     435            Reflection allowed: False
     436            Reusing pieces allowed: False
     437            sage: QuantuminoSolver(14).tiling_solver()
     438            Tiling solver of 16 pieces into the box (5, 8, 2)
     439            Rotation allowed: True
     440            Reflection allowed: False
     441            Reusing pieces allowed: False
     442            sage: QuantuminoSolver(14, box=(5,4,4)).tiling_solver()
     443            Tiling solver of 16 pieces into the box (5, 4, 4)
     444            Rotation allowed: True
     445            Reflection allowed: False
     446            Reusing pieces allowed: False
     447        """
     448        pieces = pentaminos[:self._aside] + pentaminos[self._aside+1:]
     449        return TilingSolver(pieces, box=self._box)
     450
     451    def solve(self, include_partial=False):
     452        r"""
     453        Return an iterator over the solutions where one of the pentamino is
     454        put aside.
     455
     456        INPUT:
     457
     458        - ``include_partial`` - boolean (optional, default: ``False``),
     459          whether to include partial (incomplete) solutions, i.e. the
     460          common part between two consecutive solutions.
    737461
    738462        OUTPUT:
    739463
    740         list
    741        
    742         EXAMPLES::
     464            iterator of QuantuminoState
    743465
    744             sage: from sage.games.quantumino import quantumino_solver
    745             sage: L = quantumino_solver.list_pentaminos()
    746             sage: len(L)
    747             17
     466        EXAMPLES:
     467
     468        Get one solution::
     469
     470            sage: from sage.games.quantumino import QuantuminoSolver
     471            sage: s = QuantuminoSolver(8).solve().next()         # long time (9s)
     472            sage: s                                              # long time (fast)
     473            Quantumino state where the following pentamino is put aside :
     474            Polyomino: [(0, 0, 0), (0, 0, 1), (0, 1, 0), (1, 0, 0), (1, 1, 0)], Color: yellow
     475            sage: s.show3d()                                     # long time (< 1s)
     476
     477        The explicit solution::
     478
     479            sage: for p in s: p                                  # long time (fast)
     480            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     481            Polyomino: [(0, 0, 1), (0, 1, 0), (0, 1, 1), (0, 2, 1), (1, 2, 1)], Color: deeppink
     482            Polyomino: [(0, 2, 0), (0, 3, 0), (0, 4, 0), (1, 4, 0), (1, 4, 1)], Color: green
     483            Polyomino: [(0, 3, 1), (1, 3, 1), (2, 2, 0), (2, 2, 1), (2, 3, 1)], Color: green
     484            Polyomino: [(1, 3, 0), (2, 3, 0), (2, 4, 0), (2, 4, 1), (3, 4, 0)], Color: red
     485            Polyomino: [(1, 0, 1), (2, 0, 0), (2, 0, 1), (2, 1, 0), (3, 0, 1)], Color: midnightblue
     486            Polyomino: [(0, 4, 1), (0, 5, 0), (0, 5, 1), (0, 6, 0), (1, 5, 0)], Color: red
     487            Polyomino: [(2, 1, 1), (3, 0, 0), (3, 1, 0), (3, 1, 1), (4, 0, 0)], Color: blue
     488            Polyomino: [(3, 2, 0), (4, 0, 1), (4, 1, 0), (4, 1, 1), (4, 2, 0)], Color: purple
     489            Polyomino: [(3, 2, 1), (3, 3, 0), (4, 2, 1), (4, 3, 0), (4, 3, 1)], Color: yellow
     490            Polyomino: [(3, 3, 1), (3, 4, 1), (4, 4, 0), (4, 4, 1), (4, 5, 0)], Color: blue
     491            Polyomino: [(0, 6, 1), (0, 7, 0), (0, 7, 1), (1, 5, 1), (1, 6, 1)], Color: purple
     492            Polyomino: [(1, 6, 0), (1, 7, 0), (1, 7, 1), (2, 7, 0), (3, 7, 0)], Color: darkblue
     493            Polyomino: [(2, 5, 0), (2, 6, 0), (3, 6, 0), (4, 6, 0), (4, 6, 1)], Color: orange
     494            Polyomino: [(2, 5, 1), (3, 5, 0), (3, 5, 1), (3, 6, 1), (4, 5, 1)], Color: gray
     495            Polyomino: [(2, 6, 1), (2, 7, 1), (3, 7, 1), (4, 7, 0), (4, 7, 1)], Color: orange
     496
     497        Enumerate the solutions::
     498
     499            sage: it = QuantuminoSolver(0).solve()
     500            sage: it.next()                                          # not tested
     501            Quantumino state where the following pentamino is put aside :
     502            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     503            sage: it.next()                                          # not tested
     504            Quantumino state where the following pentamino is put aside :
     505            Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
     506
     507        With the partial solutions included, one can see the evolution
     508        between consecutive solutions (an animation would be better)::
     509
     510            sage: it = QuantuminoSolver(0).solve(include_partial=True)
     511            sage: it.next().show3d()               # not tested (2s)
     512            sage: it.next().show3d()               # not tested (< 1s)
     513            sage: it.next().show3d()               # not tested (< 1s)
     514
     515        Generalizations of the game inside different boxes::
     516
     517            sage: QuantuminoSolver(7, (4,4,5)).solve().next()       # long time (2s)
     518            Quantumino state where the following pentamino is put aside :
     519            Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
     520            sage: QuantuminoSolver(7, (2,2,20)).solve().next()      # long time (1s)
     521            Quantumino state where the following pentamino is put aside :
     522            Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
     523            sage: QuantuminoSolver(3, (2,2,20)).solve().next()      # long time (1s)
     524            Quantumino state where the following pentamino is put aside :
     525            Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (1, 0, 0), (1, 0, 1)], Color: green
     526
     527        If the volume of the box is not 80, there is no solution::
     528
     529            sage: QuantuminoSolver(7, box=(3,3,9)).solve().next()
     530            Traceback (most recent call last):
     531            ...
     532            StopIteration
     533
     534        If the box is too small, there is no solution::
     535
     536            sage: QuantuminoSolver(4, box=(40,2,1)).solve().next()
     537            Traceback (most recent call last):
     538            ...
     539            StopIteration
    748540        """
    749         return _list_pentaminos
     541        T = self.tiling_solver()
     542        aside = pentaminos[self._aside]
     543        for pentos in T.solve(include_partial=include_partial):
     544            yield QuantuminoState(pentos, aside)
    750545
    751     def show_pentaminos(self, box=(5,8,2)):
     546    def number_of_solutions(self):
    752547        r"""
    753         Show the 17 3-D pentaminos included in the game and the `5 \times 8
    754         \times 2` box where 16 of them must fit.
    755 
    756         INPUT:
    757 
    758         - ``box`` - tuple of size three (optional, default: ``(5,8,2)``),
    759           size of the box
     548        Return the number of solutions.
    760549
    761550        OUTPUT:
    762551
    763         3D Graphic object
     552            integer
    764553
    765554        EXAMPLES::
    766555
    767             sage: from sage.games.quantumino import quantumino_solver
    768             sage: quantumino_solver.show_pentaminos()    # long time (1s)
     556            sage: from sage.games.quantumino import QuantuminoSolver
     557            sage: QuantuminoSolver(4, box=(3,2,2)).number_of_solutions()
     558            0
     559
     560        ::
     561
     562            sage: QuantuminoSolver(0).number_of_solutions()                # not tested
     563            ??? hundreds of millions ???
    769564        """
    770         G = Graphics()
    771         for i,p in enumerate(self.list_pentaminos()):
    772             x = 3.5 * (i%4)
    773             y = 3.5 * (i/4)
    774             q = p + (x, y, 0)
    775             G += q.show3d()
    776             G += text3d(str(i), (x,y,2))
    777         G += cube(color='gray',opacity=0.5).scale(box).translate((17,6,0))
    778         a,b = G.bounding_box()
    779         a,b = map(vector, (a,b))
    780         G.frame_aspect_ratio(tuple(b-a))
    781         G.show(frame=False)
    782         #return G
     565        return self.tiling_solver().number_of_solutions()
    783566
    784     def get_solution(self, without, box=(5,8,2), verbose=False):
    785         r"""
    786         Return a solution without one of the block.
    787567
    788         INPUT:
    789 
    790         - ``without`` - integer, from 0 to 16.
    791         - ``box`` - tuple of size three (optional, default: ``(5,8,2)``),
    792           size of the box
    793         - ``verbose`` - bool (optional, default: ``False``)
    794 
    795         OUTPUT:
    796 
    797         QuantuminoSolution
    798 
    799         EXAMPLES::
    800 
    801             sage: from sage.games.quantumino import quantumino_solver
    802             sage: s = quantumino_solver.get_solution(8)
    803             sage: s
    804             Quantumino solution without the following pentamino :
    805             3D Polyomino: [(0, 0, 0), (0, 0, 1), (0, 1, 0), (1, 0, 0), (1, 1, 0)], Color: yellow
    806             sage: for p in s: p
    807             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
    808             3D Polyomino: [(0, 0, 1), (0, 1, 0), (0, 1, 1), (0, 2, 1), (1, 2, 1)], Color: deeppink
    809             3D Polyomino: [(0, 2, 0), (0, 3, 0), (0, 4, 0), (1, 4, 0), (1, 4, 1)], Color: green
    810             3D Polyomino: [(0, 3, 1), (1, 3, 1), (2, 2, 0), (2, 2, 1), (2, 3, 1)], Color: green
    811             3D Polyomino: [(1, 3, 0), (2, 3, 0), (2, 4, 0), (2, 4, 1), (3, 4, 0)], Color: red
    812             3D Polyomino: [(1, 0, 1), (2, 0, 0), (2, 0, 1), (2, 1, 0), (3, 0, 1)], Color: midnightblue
    813             3D Polyomino: [(0, 4, 1), (0, 5, 0), (0, 5, 1), (0, 6, 0), (1, 5, 0)], Color: red
    814             3D Polyomino: [(2, 1, 1), (3, 0, 0), (3, 1, 0), (3, 1, 1), (4, 0, 0)], Color: blue
    815             3D Polyomino: [(3, 2, 0), (4, 0, 1), (4, 1, 0), (4, 1, 1), (4, 2, 0)], Color: purple
    816             3D Polyomino: [(3, 2, 1), (3, 3, 0), (4, 2, 1), (4, 3, 0), (4, 3, 1)], Color: yellow
    817             3D Polyomino: [(3, 3, 1), (3, 4, 1), (4, 4, 0), (4, 4, 1), (4, 5, 0)], Color: blue
    818             3D Polyomino: [(0, 6, 1), (0, 7, 0), (0, 7, 1), (1, 5, 1), (1, 6, 1)], Color: purple
    819             3D Polyomino: [(1, 6, 0), (1, 7, 0), (1, 7, 1), (2, 7, 0), (3, 7, 0)], Color: darkblue
    820             3D Polyomino: [(2, 5, 0), (2, 6, 0), (3, 6, 0), (4, 6, 0), (4, 6, 1)], Color: orange
    821             3D Polyomino: [(2, 5, 1), (3, 5, 0), (3, 5, 1), (3, 6, 1), (4, 5, 1)], Color: gray
    822             3D Polyomino: [(2, 6, 1), (2, 7, 1), (3, 7, 1), (4, 7, 0), (4, 7, 1)], Color: orange
    823 
    824         Generalizations of the game inside different boxes::
    825 
    826             sage: quantumino_solver.get_solution(7, (4,4,5))       # long time (2s)
    827             Quantumino solution without the following pentamino :
    828             3D Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
    829             sage: quantumino_solver.get_solution(7, (2,2,20))      # long time (1s)
    830             Quantumino solution without the following pentamino :
    831             3D Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 2, 1), (1, 0, 0)], Color: orange
    832             sage: quantumino_solver.get_solution(3, (2,2,20))      # long time (1s)
    833             Quantumino solution without the following pentamino :
    834             3D Polyomino: [(0, 0, 0), (0, 1, 0), (0, 2, 0), (1, 0, 0), (1, 0, 1)], Color: green
    835         """
    836         return next(self.solutions_iterator(without, box, verbose))
    837 
    838     def solutions_iterator(self, without, box=(5,8,2), verbose=False):
    839         r"""
    840         Return an iterator over the solutions without the block.
    841 
    842         INPUT:
    843 
    844         - ``without`` - integer, from 0 to 16.
    845         - ``box`` - tuple of size three (optional, default: ``(5,8,2)``),
    846           size of the box
    847         - ``verbose`` - bool (optional, default: ``False``)
    848 
    849         OUTPUT:
    850 
    851         iterator of QuantuminoSolution
    852 
    853         EXAMPLES::
    854 
    855             sage: from sage.games.quantumino import quantumino_solver
    856             sage: it = quantumino_solver.solutions_iterator(0)
    857             sage: it.next()
    858             Quantumino solution without the following pentamino :
    859             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
    860             sage: it.next()
    861             Quantumino solution without the following pentamino :
    862             3D Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
    863         """
    864         if not  0 <= without < 17:
    865             raise ValueError, "without (=%s) must be between 0 and 16" % without
    866         all_pieces = self.list_pentaminos()
    867         pieces = all_pieces[:without] + all_pieces[without+1:]
    868         aside = all_pieces[without]
    869         for pentos in solve_3d_puzzle(pieces, box=box, verbose=verbose):
    870             yield QuantuminoSolution(pentos, aside)
    871 
    872 
    873 
    874 quantumino_solver = QuantuminoSolver()
    875 
    876