Ticket #14498: trac_14498_trees_latex_output_EliX-jbp.patch

File trac_14498_trees_latex_output_EliX-jbp.patch, 21.6 KB (added by elixyre, 7 years ago)
  • sage/combinat/abstract_tree.py

    # HG changeset patch
    # User Jean-Baptiste Priez <jbp@kerios.fr>
    # Date 1367061665 -7200
    # Node ID 2567f2c80dff342d2830df5b8b5a7e4ec4a066e5
    # Parent  3e91cc3ca6facc277fea4dad9eb017c7ac7dddd9
    latex output for trees
    
    diff --git a/sage/combinat/abstract_tree.py b/sage/combinat/abstract_tree.py
    a b incoherent with the data structure. 
    6767from sage.structure.list_clone import ClonableArray
    6868from sage.rings.integer import Integer
    6969from sage.misc.misc_c import prod
     70################################################################################
     71## use to load tikz in the preamble (one for *view* and one for *notebook*)
     72from sage.misc.latex import latex
     73latex.add_package_to_preamble_if_available("tikz")
     74latex.add_to_mathjax_avoid_list("tikz")
     75################################################################################
    7076
    7177# Unfortunately Cython forbids multiple inheritance. Therefore, we do not
    7278# inherits from SageObject to be able to inherits from Element or a subclass
    class AbstractTree(object): 
    493499        else:
    494500            return nb*prod(s.tree_factorial() for s in self)
    495501
    496     latex_unit_length   = "4mm"
    497     latex_node_diameter = "0.5"
    498     latex_root_diameter = "0.7"
    499 
    500502    def _latex_(self):
    501         """
    502         Returns a LaTeX version of ``self``
    503 
    504         EXAMPLES::
    505 
    506             sage: print(OrderedTree([[],[]])._latex_())
    507             \vcenter{\hbox{{\setlength\unitlength{4mm}
    508             \begin{picture}(4,3)
    509             \put(1,1){\circle*{0.5}}
    510             \put(2,2){\circle*{0.5}}
    511             \put(3,1){\circle*{0.5}}
    512             \put(2,2){\line(-1,-1){1}}
    513             \put(2,2){\line(1,-1){1}}
    514             \put(2,2){\circle*{0.7}}
    515             \end{picture}}}}
    516 
     503        '''
     504        Nice output which can be easily modified
     505       
    517506        TESTS::
    518 
    519             sage: OrderedTree([])._latex_()
    520             '\\vcenter{\\hbox{{\\setlength\\unitlength{4mm}\n\\begin{picture}(2,2)\n\\put(1,1){\\circle*{0.5}}\n\\put(1,1){\\circle*{0.7}}\n\\end{picture}}}}'
    521             sage: OrderedTree([[]])._latex_()
    522             '\\vcenter{\\hbox{{\\setlength\\unitlength{4mm}\n\\begin{picture}(2,3)\n\\put(1,1){\\circle*{0.5}}\n\\put(1,2){\\circle*{0.5}}\n\\put(1,2){\\line(0,-1){1}}\n\\put(1,2){\\circle*{0.7}}\n\\end{picture}}}}'
    523             sage: OrderedTree([[], [[], [[], []], [[], []]], [[], []]])._latex_()
    524             '\\vcenter{\\hbox{{\\setlength\\unitlength{4mm}\n\\begin{picture}(12,5)\n\\put(1,3){\\circle*{0.5}}\n\\put(2,2){\\circle*{0.5}}\n\\put(3,1){\\circle*{0.5}}\n\\put(4,2){\\circle*{0.5}}\n\\put(5,1){\\circle*{0.5}}\n\\put(4,2){\\line(-1,-1){1}}\n\\put(4,2){\\line(1,-1){1}}\n\\put(4,3){\\circle*{0.5}}\n\\put(6,1){\\circle*{0.5}}\n\\put(7,2){\\circle*{0.5}}\n\\put(8,1){\\circle*{0.5}}\n\\put(7,2){\\line(-1,-1){1}}\n\\put(7,2){\\line(1,-1){1}}\n\\put(4,3){\\line(-2,-1){2}}\n\\put(4,3){\\line(0,-1){1}}\n\\put(4,3){\\line(3,-1){3}}\n\\put(4,4){\\circle*{0.5}}\n\\put(9,2){\\circle*{0.5}}\n\\put(10,3){\\circle*{0.5}}\n\\put(11,2){\\circle*{0.5}}\n\\put(10,3){\\line(-1,-1){1}}\n\\put(10,3){\\line(1,-1){1}}\n\\put(4,4){\\line(-3,-1){3}}\n\\put(4,4){\\line(0,-1){1}}\n\\put(4,4){\\line(6,-1){6}}\n\\put(4,4){\\circle*{0.7}}\n\\end{picture}}}}'
    525         """
    526         from sage.misc.latex import latex
    527         drawing = [""] # allows modification of  in rec...
    528         x = [1]        # allows modification of x[0] in rec...
    529         max_label_width = [0] # allows modification of x[0] in rec...
    530         maxy = self.depth()
    531 
    532         def rec(t, y):
    533             """
    534             Draw the subtree t on the drawing, below y (included) and to
    535             the right of x[0] (included). Update x[0]. Returns the horizontal
    536             position of the root
    537             """
    538             if t.node_number() == 0 : return -1
    539             n = len(t)
    540             posChild = [rec(t[i], y+1) for i in range(n // 2)]
    541             i = n // 2
    542             if n % 2 == 1:
    543                 xc = rec(t[i], y+1)
    544                 posChild.append(xc)
    545                 i += 1
    546             else:
    547                 xc = x[0]
    548                 x[0] +=1
    549             drawing[0] = drawing[0] + "\\put(%s,%s){\\circle*{%s}}\n"%(
    550                 xc, maxy-y, self.latex_node_diameter)
    551             try:
    552                 lbl = t.label()
    553             except AttributeError:
    554                 pass
    555             else:
    556                 max_label_width[0] = 1 # TODO find a better heuristic
    557                 drawing[0] = drawing[0] + "\\put(%s,%s){$\scriptstyle %s$}"%(
    558                     xc+0.3, maxy-y-0.3, latex(lbl))
    559             posChild += [rec(t[j], y+1) for j in range(i, n)]
    560             for i in range(n):
    561                 if posChild[i] != -1:
    562                     drawing[0] = (drawing[0] +
    563                         "\\put(%s,%s){\\line(%s,-1){%s}}\n"%(
    564                             xc, maxy-y, posChild[i]-xc,
    565                             max(abs(posChild[i]-xc), 1)))
    566             return xc
    567 
    568         res = rec(self, 0)
    569         drawing[0] = drawing[0] + "\\put(%s,%s){\\circle*{%s}}\n"%(
    570             res, maxy, self.latex_root_diameter)
    571         return "\\vcenter{\hbox{{\setlength\unitlength{%s}\n\\begin{picture}(%s,%s)\n%s\\end{picture}}}}"%(
    572             self.latex_unit_length,
    573             x[0] + max_label_width[0],
    574             maxy + 1,
    575             drawing[0])
     507       
     508            sage: latex(BinaryTree([[[],[]],[[],None]]))
     509            { \newcommand{\nodea}{\node[draw,circle] (a) {$$}
     510            ;}\newcommand{\nodeb}{\node[draw,circle] (b) {$$}
     511            ;}\newcommand{\nodec}{\node[draw,circle] (c) {$$}
     512            ;}\newcommand{\noded}{\node[draw,circle] (d) {$$}
     513            ;}\newcommand{\nodee}{\node[draw,circle] (e) {$$}
     514            ;}\newcommand{\nodef}{\node[draw,circle] (f) {$$}
     515            ;}\begin{tikzpicture}[auto]
     516            \matrix[column sep=.3cm, row sep=.3cm,ampersand replacement=\&]{
     517                     \&         \&         \& \nodea  \&         \&         \&         \\
     518                     \& \nodeb  \&         \&         \&         \& \nodee  \&         \\
     519             \nodec  \&         \& \noded  \&         \& \nodef  \&         \&         \\
     520            };
     521           
     522            \path[ultra thick, red] (b) edge (c) edge (d)
     523                (e) edge (f)
     524                (a) edge (b) edge (e);
     525            \end{tikzpicture}}
     526        '''
     527        # latex environnement : TikZ
     528        begin_env = "\\begin{tikzpicture}[auto]\n"
     529        end_env = "\\end{tikzpicture}"
     530        # it uses matrix trick to place each node
     531        matrix_begin = "\\matrix[column sep=.3cm, row sep=.3cm,ampersand replacement=\&]{\n"
     532        matrix_end = "\\\\\n};\n"
     533        # a basic path to each edges
     534        path_begin = "\\path[ultra thick, red] "
     535        path_end = ";\n"
     536        # to make a pretty output, it creates one LaTeX command for
     537        # each node
     538        cmd = "\\node"
     539        new_cmd1 = "\\newcommand{" + cmd
     540        new_cmd2 = "}{\\node[draw,circle] ("
     541        new_cmd3 = ") {$"
     542        new_cmd4 = "$}\n;}"
     543        # some variables to simplify code
     544        sep = "\\&"
     545        space = " "*9
     546        sepspace = sep + space
     547        spacesep = space + sep
     548        node_to_str = lambda node : " " + node + " "*(len(space)-1-len(node))
     549        ## TODO:: modify how to create nodes --> new_cmd : \\node[...] in create_node
     550        num = [0]
     551        def resolve( self ):
     552            nodes = []; matrix = []; edges = []
     553           
     554            def create_node( self ):
     555                '''
     556                create a name (infixe reading)
     557                 -> ex: b
     558                create a new command:
     559                 -> ex: \newcommand{\nodeb}{\node[draw,circle] (b) {$$};
     560                return the name and the command to build:
     561                  . the matrix
     562                  . and the edges
     563                '''
     564                name = reduce(
     565                    lambda x,y: x+y,
     566                    map(
     567                        lambda x: chr(ord(x)+49),
     568                        list( str( num[0] ) ) ),
     569                    "")
     570                node = cmd + name
     571                nodes.append( (name,
     572                    (str( self.label() ) if hasattr( self, "label" ) else "") )
     573                )
     574                num[0] += 1
     575                return node, name
     576           
     577            def empty_tree():
     578                '''
     579                TESTS::
     580                   
     581                    sage: t = BinaryTree()
     582                    sage: print latex(t)
     583                    {\begin{tikzpicture}[auto]
     584                    \end{tikzpicture}}
     585                '''
     586                matrix.append( space )
     587            def one_node_tree( self ):
     588                '''
     589                TESTS::
     590               
     591                    sage: t = BinaryTree([]); print latex(t)
     592                    { \newcommand{\nodea}{\node[draw,circle] (a) {$$}
     593                    ;}\begin{tikzpicture}[auto]
     594                    \matrix[column sep=.3cm, row sep=.3cm,ampersand replacement=\&]{
     595                     \nodea  \\
     596                    };
     597                    \end{tikzpicture}}
     598                    sage: t = OrderedTree([]); print latex(t)
     599                    {\newcommand{\nodeb}{\node[draw,circle] (b) {$$}
     600                    ;}\begin{tikzpicture}[auto]
     601                    \matrix[column sep=.3cm, row sep=.3cm,ampersand replacement=\&]{
     602                    \nodeb\\
     603                    };
     604                    \end{tikzpicture}}
     605                '''
     606                node, _ = create_node( self )
     607                matrix.append( node_to_str ( node ))
     608            def concat_matrix( mat, mat2 ):
     609                lmat = len( mat ); lmat2 = len( mat2 )
     610                for i in range( max( lmat, lmat2 ) ):
     611                    # mat[i] --> n & n & ...
     612                    # mat2[i] -> n' & n' & ...
     613                    # ==> n & n & ... & n' & n' & ...
     614                    try: mat[i] += sep + mat2[i]
     615                    except:
     616                        if i >= lmat:
     617                            if i != 0:
     618                                # mat[i] doesn't exist but
     619                                # mat[0] has k "&"
     620                                # mat2[i] -> n' & n' & ...
     621                                # ==> (_ &)*k+1 n' & n' & ...
     622                                nb_of_and = mat[0].count( sep ) - mat2[0].count( sep )
     623                                mat.append( spacesep*(nb_of_and) + mat2[i] )
     624                            else:
     625                                # mat is empty
     626                                # mat2[i] -> n' & n' & ...
     627                                # ==> mat2
     628                                mat.extend( mat2 )
     629                                return
     630                        else:
     631                            # mat[i] -> n & n & ...
     632                            # mat2[i] doesn't exist but mat2[0] exists
     633                            ## and has k "&"
     634                            # NOTE:: i != 0 because that is a no-empty subtree.
     635                            # ==> n & n & ... (& _)*k+1
     636                            nb_of_and = mat2[0].count( sep )
     637                            mat[i] += sepspace*(nb_of_and+1)
     638            def tmp( subtree, edge, nodes, edges, matrix ):
     639                if not subtree.is_empty():
     640                    ## create representation of the subtree
     641                    nodes_st, matrix_st, edges_st = resolve( subtree )
     642                    ## add its nodes to the "global" nodes set
     643                    nodes.extend( nodes_st )
     644                    ## create a new edge between the root and the subtree
     645                    edge.append( nodes_st[0][0] )
     646                    ## add the subtree edges to the "global" edges set
     647                    edges.extend( edges_st )
     648                    ## build a new matrix by concatenation
     649                    concat_matrix(matrix, matrix_st)
     650                else: concat_matrix(matrix, [ space ])   
     651            def pair_nodes_tree( self, nodes, edges, matrix ):
     652                """
     653                TESTS::
     654               
     655                    sage: t = OrderedTree([[[],[]],[[],[]]]).canonical_labelling(); print latex(t)
     656                    { \newcommand{\nodea}{\node[draw,circle] (a) {$1$}
     657                    ;}\newcommand{\nodeb}{\node[draw,circle] (b) {$2$}
     658                    ;}\newcommand{\nodec}{\node[draw,circle] (c) {$3$}
     659                    ;}\newcommand{\noded}{\node[draw,circle] (d) {$4$}
     660                    ;}\newcommand{\nodee}{\node[draw,circle] (e) {$5$}
     661                    ;}\newcommand{\nodef}{\node[draw,circle] (f) {$6$}
     662                    ;}\newcommand{\nodeg}{\node[draw,circle] (g) {$7$}
     663                    ;}\begin{tikzpicture}[auto]
     664                    \matrix[column sep=.3cm, row sep=.3cm,ampersand replacement=\&]{
     665                             \&         \&         \& \nodea  \&         \&         \&         \\
     666                             \& \nodeb  \&         \&         \&         \& \nodee  \&         \\
     667                     \nodec  \&         \& \noded  \&         \& \nodef  \&         \& \nodeg  \\
     668                    };
     669                    <BLANKLINE>
     670                    \path[ultra thick, red] (b) edge (c) edge (d)
     671                        (e) edge (f) edge (g)
     672                        (a) edge (b) edge (e);
     673                    \end{tikzpicture}}
     674                    sage: t = BinaryTree([[],[[],[]]]); print latex(t)
     675                    { \newcommand{\nodea}{\node[draw,circle] (a) {$$}
     676                    ;}\newcommand{\nodeb}{\node[draw,circle] (b) {$$}
     677                    ;}\newcommand{\nodec}{\node[draw,circle] (c) {$$}
     678                    ;}\newcommand{\noded}{\node[draw,circle] (d) {$$}
     679                    ;}\newcommand{\nodee}{\node[draw,circle] (e) {$$}
     680                    ;}\begin{tikzpicture}[auto]
     681                    \matrix[column sep=.3cm, row sep=.3cm,ampersand replacement=\&]{
     682                             \& \nodea  \&         \&         \&         \\
     683                     \nodeb  \&         \&         \& \nodec  \&         \\
     684                             \&         \& \noded  \&         \& \nodee  \\
     685                    };
     686                    <BLANKLINE>
     687                    \path[ultra thick, red] (c) edge (d) edge (e)
     688                        (a) edge (b) edge (c);
     689                    \end{tikzpicture}}
     690                """
     691                # build all subtree matrices.
     692                node, name = create_node( self )
     693                edge = [name]
     694                split = int(len( self )/2)
     695                # the left part
     696                for i in range( split ):
     697                    tmp( self[i], edge, nodes, edges, matrix )
     698                ## prepare the root line
     699                nb_of_and = matrix[0].count( sep )
     700                # the middle
     701                for i in range( len( matrix ) ): matrix[i] += sepspace
     702                # the right part   
     703                for i in range( split, len( self ) ):
     704                    tmp( self[i], edge, nodes, edges, matrix )
     705               
     706                ## create the root line
     707                root_line = ( spacesep*(nb_of_and+1) + node_to_str(node) +
     708                    sepspace*(matrix[0].count(sep) - nb_of_and - 1) )
     709                matrix.insert(0, root_line)
     710                # add edges from the root
     711                edges.append( edge )
     712            def odd_nodes_tree( self, nodes, edges, matrix):
     713                '''
     714                TESTS::
     715               
     716                    sage: t = OrderedTree([[]]).canonical_labelling(); print latex(t)
     717                    { \newcommand{\nodea}{\node[draw,circle] (a) {$1$}
     718                    ;}\newcommand{\nodeb}{\node[draw,circle] (b) {$2$}
     719                    ;}\begin{tikzpicture}[auto]
     720                    \matrix[column sep=.3cm, row sep=.3cm,ampersand replacement=\&]{
     721                     \nodea  \\
     722                     \nodeb  \\
     723                    };
     724                    <BLANKLINE>
     725                    \path[ultra thick, red] (a) edge (b);
     726                    \end{tikzpicture}}
     727                    sage: t = OrderedTree([[[],[]]]).canonical_labelling(); print latex(t)
     728                    { \newcommand{\nodea}{\node[draw,circle] (a) {$1$}
     729                    ;}\newcommand{\nodeb}{\node[draw,circle] (b) {$2$}
     730                    ;}\newcommand{\nodec}{\node[draw,circle] (c) {$3$}
     731                    ;}\newcommand{\noded}{\node[draw,circle] (d) {$4$}
     732                    ;}\begin{tikzpicture}[auto]
     733                    \matrix[column sep=.3cm, row sep=.3cm,ampersand replacement=\&]{
     734                             \& \nodea  \&         \\
     735                             \& \nodeb  \&         \\
     736                     \nodec  \&         \& \noded  \\
     737                    };
     738                   
     739                    \path[ultra thick, red] (b) edge (c) edge (d)
     740                        (a) edge (b);
     741                    \end{tikzpicture}}
     742                    sage: t = OrderedTree([[[],[],[]]]).canonical_labelling(); print latex(t)
     743                    { \newcommand{\nodea}{\node[draw,circle] (a) {$1$}
     744                    ;}\newcommand{\nodeb}{\node[draw,circle] (b) {$2$}
     745                    ;}\newcommand{\nodec}{\node[draw,circle] (c) {$3$}
     746                    ;}\newcommand{\noded}{\node[draw,circle] (d) {$4$}
     747                    ;}\newcommand{\nodee}{\node[draw,circle] (e) {$5$}
     748                    ;}\begin{tikzpicture}[auto]
     749                    \matrix[column sep=.3cm, row sep=.3cm,ampersand replacement=\&]{
     750                             \& \nodea  \&         \\
     751                             \& \nodeb  \&         \\
     752                     \nodec  \& \noded  \& \nodee  \\
     753                    };
     754                    <BLANKLINE>
     755                    \path[ultra thick, red] (b) edge (c) edge (d) edge (e)
     756                        (a) edge (b);
     757                    \end{tikzpicture}}
     758                    sage: t = OrderedTree([[[],[],[]],[],[]]).canonical_labelling(); print latex(t)
     759                    { \newcommand{\nodea}{\node[draw,circle] (a) {$1$}
     760                    ;}\newcommand{\nodeb}{\node[draw,circle] (b) {$2$}
     761                    ;}\newcommand{\nodec}{\node[draw,circle] (c) {$3$}
     762                    ;}\newcommand{\noded}{\node[draw,circle] (d) {$4$}
     763                    ;}\newcommand{\nodee}{\node[draw,circle] (e) {$5$}
     764                    ;}\newcommand{\nodef}{\node[draw,circle] (f) {$6$}
     765                    ;}\newcommand{\nodeg}{\node[draw,circle] (g) {$7$}
     766                    ;}\begin{tikzpicture}[auto]
     767                    \matrix[column sep=.3cm, row sep=.3cm,ampersand replacement=\&]{
     768                             \&         \&         \& \nodea  \&         \\
     769                             \& \nodeb  \&         \& \nodef  \& \nodeg  \\
     770                     \nodec  \& \noded  \& \nodee  \&         \&         \\
     771                    };
     772                    <BLANKLINE>
     773                    \path[ultra thick, red] (b) edge (c) edge (d) edge (e)
     774                        (a) edge (b) edge (f) edge (g);
     775                    \end{tikzpicture}}
     776                '''
     777                # build all subtree matrices.
     778                node, name = create_node( self )
     779                edge = [name]
     780                split = int(len( self )/2)
     781                # the left part
     782                for i in range( split ):
     783                    tmp( self[i], edge, nodes, edges, matrix )
     784                ## prepare the root line
     785                if len( matrix ) != 0:
     786                    nb_of_and = matrix[0].count( sep )
     787                    sizetmp = len( matrix[0] )
     788                else:
     789                    nb_of_and = 0
     790                    sizetmp = 0
     791                # the middle
     792                tmp( self[split], edge, nodes, edges, matrix )
     793                nb_of_and += matrix[0][sizetmp:].split("node")[0].count( sep )
     794               
     795                # the right part   
     796                for i in range( split+1, len( self ) ):
     797                    tmp( self[i], edge, nodes, edges, matrix )
     798               
     799                ## create the root line
     800                root_line = ( spacesep*(nb_of_and) + node_to_str(node) +
     801                    sepspace*(matrix[0].count(sep) - nb_of_and) )
     802                matrix.insert(0, root_line)
     803                # add edges from the root
     804                edges.append( edge )
     805            if self.is_empty():
     806                empty_tree()
     807            elif len( self ) == 0 or all(subtree.is_empty() for subtree in self):
     808                one_node_tree( self )
     809            elif len( self ) % 2 == 0:
     810                pair_nodes_tree( self, nodes, edges, matrix )
     811            else:
     812                odd_nodes_tree( self, nodes, edges, matrix )
     813            return nodes, matrix, edges
     814       
     815        nodes, matrix, edges = resolve( self )
     816        def make_cmd(nodes):
     817            cmds = []
     818            for name, label in nodes:
     819                cmds.append(new_cmd1 + name + new_cmd2 +
     820                    name + new_cmd3 +
     821                    label + new_cmd4)
     822            return cmds
     823        def make_edges(edges):
     824            all_paths = []
     825            for edge in edges:
     826                path = "(" + edge[0] + ")"
     827                for i in range(1,len(edge)):
     828                    path += " edge (%s)"%edge[i]
     829                all_paths.append(path)
     830            return all_paths
     831        return ("{ " +
     832            "".join(make_cmd( nodes )) +
     833            begin_env +
     834                (matrix_begin +
     835                    "\\\\ \n".join( matrix ) +
     836                matrix_end +
     837                ("\n" +
     838                path_begin +
     839                    "\n\t".join( make_edges( edges ) ) +
     840                path_end if len(edges)>0 else "")
     841                if len(matrix) > 0 else "") +
     842            end_env +
     843            "}")
    576844
    577845class AbstractClonableTree(AbstractTree):
    578846    """