Ticket #11379: trac_11379_quantamino-sl.patch

File trac_11379_quantamino-sl.patch, 35.2 KB (added by slabbe, 7 years ago)
  • doc/en/reference/games.rst

    # HG changeset patch
    # User Sebastien Labbe <slabqc at gmail.com>
    # Date 1306440157 14400
    # Node ID 465633f52bf99814657d840b5d0a92e9208f2ad9
    # Parent  c0818dc34ac62f428db2d8bd8add7aa6f7d548a2
    #11379: Family Games America's Quantumino solver
    
    diff --git a/doc/en/reference/games.rst b/doc/en/reference/games.rst
    a b Rubik's cube solver (see :ref:`Rubik's C 
    77.. toctree::
    88   :maxdepth: 2
    99
    10    sage/games/sudoku
    11  No newline at end of file
     10   sage/games/sudoku
     11   sage/games/quantumino
  • new file sage/games/quantumino.py

    diff --git a/sage/games/quantumino.py b/sage/games/quantumino.py
    new file mode 100644
    - +  
     1# coding=utf-8
     2r"""
     3Family Games America's Quantumino solver
     4
     5This module allows to solve the Quantumino puzzle made by Family Games
     6America (see [1] and [2]). This puzzle was left at the dinner room of the
     7Laboratoire de Combinatoire Informatique Mathématique in Montréal by Franco
     8Saliola during winter 2011.
     9
     10The solution uses the dancing links code which is in Sage. Dancing links
     11were originally introduced by Donald Knuth in 2000 [3]. In particular,
     12Knuth used dancing links to solve tilings of a region by 2D pentaminos.
     13Here we extend the method for 3D pentaminos.
     14
     15AUTHOR:
     16
     17    - Sébastien Labbé, April 28th, 2011
     18
     19DESCRIPTION (from [1]):
     20
     21    "
     22    Pentamino games have been taken to a whole different level; a 3-D
     23    level, with this colorful creation! Using the original pentamino
     24    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
     26    3-D pentamino pieces inside the playing perimeters. Remove a different
     27    piece each time you play for an entirely new challenge! Thousands of
     28    solutions to be found!
     29    Quantumino™ hands-on educational tool where players learn how shapes
     30    can be transformed or arranged into predefined shapes and spaces.
     31    Includes:
     32    1 wooden frame, 17 wooden blocks, instruction booklet.
     33    Age: 8+
     34    "
     35
     36EXAMPLES:
     37
     38Here are the 17 wooden blocks of the Quantumino puzzle numbered from 0 to
     3916 in the following 3d picture::
     40
     41    sage: from sage.games.quantumino import quantumino_solver
     42    sage: quantumino_solver.show_pentaminos()
     43
     44To solve the puzzle without using the block numbered 12::
     45
     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)
     51
     52To solve the puzzle without using the block numbered 7::
     53
     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)
     59
     60To get all the solutions, use the iterator. Note that finding the first
     61solution is the most time consuming because it needs to creates the
     62complete data to describe the problem::
     63
     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
     74
     75To get the solution inside other boxes::
     76
     77    sage: s = quantumino_solver.get_solution(7, box=(4,4,5))        # long time (2s)
     78    sage: s.show3d()                                                # long time (<1s)
     79
     80::
     81
     82    sage: s = quantumino_solver.get_solution(7, box=(2,2,20))       # long time (1s)
     83    sage: s.show3d()                                                # long time (<1s)
     84
     85EXAMPLES:
     86
     87One can also solve similar problems by defining 3D polyominos directly. The
     88following is a 2d puzzle owned by Florent Hivert::
     89
     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)]))
     102
     103Solve 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')
     109
     110REFERENCES:
     111
     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.
     115
     116"""
     117#*****************************************************************************
     118#       Copyright (C) 2011 Sebastien Labbe <slabqc@gmail.com>
     119#
     120#  Distributed under the terms of the GNU General Public License (GPL)
     121#  as published by the Free Software Foundation; either version 2 of
     122#  the License, or (at your option) any later version. 
     123#                  http://www.gnu.org/licenses/ 
     124#*****************************************************************************
     125import itertools
     126from sage.combinat.all import WeylGroup
     127from sage.plot.plot import Graphics
     128from sage.modules.free_module_element import vector
     129from sage.plot.plot3d.platonic import cube
     130from sage.plot.plot3d.shapes2 import text3d
     131from sage.structure.sage_object import SageObject
     132
     133##############################
     134# Orthogonal Rotation matrices
     135##############################
     136rotation_matrices = [w.matrix() for w in WeylGroup(['B',3]) if w.matrix().det() == 1]
     137
     138##############################
     139# Class Polyomino3d
     140##############################
     141class 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#######################
     517def 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
     596
     597################################################
     598# Example:  The family games america: Quantumino
     599################################################
     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'))
     618
     619def Pentamino3D(i):
     620    r"""
     621    Return one of the seventeen 3D Pentamino pieces included in the game.
     622
     623    INPUT:
     624
     625    - ``i`` - integer, from 0 to 16.
     626
     627    EXAMPLES::
     628
     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
     632    """
     633    return _list_pentaminos[i]
     634
     635
     636##############################
     637# Class QuantuminoSolultion
     638##############################
     639class QuantuminoSolution(SageObject):
     640    r"""
     641    A solution of the Quantumino puzzle.
     642
     643    INPUT:
     644
     645    - ``pentos`` - list of 16 3D pentamino representing the solution
     646    - ``aside`` - the unused 3D pentamino
     647
     648    EXAMPLES::
     649
     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)
     655        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
     658
     659    ::
     660
     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
     665    """
     666    def __init__(self, pentos, aside):
     667        r"""
     668        EXAMPLES::
     669
     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
     676        """
     677        self._pentos = pentos
     678        self._aside = aside
     679
     680    def __repr__(self):
     681        r"""
     682        EXAMPLES::
     683
     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
     690        """
     691        return "Quantumino solution without the following pentamino :\n%s" % self._aside
     692
     693    def __iter__(self):
     694        r"""
     695        EXAMPLES::
     696
     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)
     702            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
     705        """
     706        return iter(self._pentos)
     707
     708    def show3d(self):
     709        r"""
     710        Show the solution in 3D.
     711
     712        EXAMPLES::
     713
     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)
     717
     718        """
     719        G = Graphics()
     720        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
     726
     727
     728##############################
     729# Class QuantuminoSolver
     730##############################
     731class QuantuminoSolver(SageObject):
     732    r"""
     733    """
     734    def list_pentaminos(self):
     735        r"""
     736        Return the list of the 17 3-D pentaminos included in the game.
     737
     738        OUTPUT:
     739
     740        list
     741       
     742        EXAMPLES::
     743
     744            sage: from sage.games.quantumino import quantumino_solver
     745            sage: L = quantumino_solver.list_pentaminos()
     746            sage: len(L)
     747            17
     748        """
     749        return _list_pentaminos
     750
     751    def show_pentaminos(self, box=(5,8,2)):
     752        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
     760
     761        OUTPUT:
     762
     763        3D Graphic object
     764
     765        EXAMPLES::
     766
     767            sage: from sage.games.quantumino import quantumino_solver
     768            sage: quantumino_solver.show_pentaminos()    # long time (1s)
     769        """
     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
     783
     784    def get_solution(self, without, box=(5,8,2), verbose=False):
     785        r"""
     786        Return a solution without one of the block.
     787
     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
     874quantumino_solver = QuantuminoSolver()
     875
     876